Files
DartGame/Scripts/DropCatcher.gd
T

157 lines
6.3 KiB
GDScript

extends Control
# DropCatcher.gd (manual-drag version)
# No longer uses Godot's built-in _drop_data. Instead it exposes
# paint_at_screen_pos(), which the trapezoid calls on release.
# This Control no longer needs to catch mouse events at all, so it can be
# tiny/invisible — but keep it in the tree so it can reach the camera & board.
@export var camera: Camera3D
@export var main: Node # the ROOT node (DartboardProto)
const RAY_LENGTH := 1000.0
var dartboard: MeshInstance3D
# Standard dartboard number sequence, clockwise starting from the TOP (12 o'clock).
const DART_NUMBERS := [20, 1, 18, 4, 13, 6, 10, 15, 2, 17,
3, 19, 7, 16, 8, 11, 14, 9, 12, 5]
# Calibration: rotate which sector counts as "top". If the wrong number gets
# hit, bump this by 1 until it lines up. Range 0-19.
@export var sector_offset := 0
# If your board's numbers go counter-clockwise in 3D, flip this.
@export var reverse_direction := false
# --- Radial band boundaries (fractions of outer radius, 0=center 1=rim) ---
# Tune these in the Inspector by reading the norm= prints for each ring.
@export var bull_inner_max := 0.04 # <= this = inner bull (50)
@export var bull_outer_max := 0.09 # <= this = outer bull (25)
@export var inner_max := 0.45 # <= this = inner single
@export var triple_max := 0.55 # <= this = triple ring
@export var outer_max := 0.80 # <= this = outer single; above = double
# Stores which modifier is applied to each surface index: { surface_index: Modifier }
var applied_modifiers: Dictionary = {}
func _ready() -> void:
# Don't block mouse input; the trapezoids handle their own dragging.
mouse_filter = Control.MOUSE_FILTER_IGNORE
print("DropCatcher ready. camera=", camera, " main=", main)
func _get_board() -> MeshInstance3D:
if dartboard == null and main != null and "dartboard" in main:
dartboard = main.dartboard
return dartboard
# Public: try to apply a modifier (paint its texture + store its rule) at a
# screen position. Returns true if it hit the board, false if it missed.
func apply_modifier_at_screen_pos(screen_pos: Vector2, modifier: Resource) -> bool:
var board := _get_board()
if board == null:
push_error("DropCatcher: no dartboard mesh (is 'main' set?).")
return false
if camera == null:
push_error("DropCatcher: camera export not set.")
return false
if modifier == null:
return false
var ray_origin := camera.project_ray_origin(screen_pos)
var ray_dir := camera.project_ray_normal(screen_pos)
var ray_end := ray_origin + ray_dir * RAY_LENGTH
var space_state := get_viewport().world_3d.direct_space_state
var query := PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
query.collide_with_areas = false
query.collide_with_bodies = true
var result := space_state.intersect_ray(query)
if result.is_empty():
print(" drop missed the board")
return false
var surface_index := _surface_from_hit(result, board)
if surface_index < 0:
return false
_paint_surface(board, surface_index, modifier.texture)
applied_modifiers[surface_index] = modifier # remember the scoring rule
print(" applied '", modifier.id, "' to surface ", surface_index)
return true
# Call this when a dart lands on a surface to get the modified score.
func score_for_surface(surface_index: int, base_value: int) -> int:
if applied_modifiers.has(surface_index):
return applied_modifiers[surface_index].apply_score(base_value)
return base_value
func _surface_from_hit(result: Dictionary, board: MeshInstance3D) -> int:
var hit_point: Vector3 = result.get("position", Vector3.ZERO)
var local := board.to_local(hit_point)
var aabb := board.get_aabb()
var center := aabb.position + aabb.size * 0.5
# Board disc lies in the X/Y plane (thin on Z). Measure radius/angle in X/Y.
var dx := local.x - center.x
var dy := local.y - center.y
var radius := Vector2(dx, dy).length()
var outer_radius : float = max(aabb.size.x, aabb.size.y) * 0.5
var norm := radius / outer_radius if outer_radius > 0.0 else 0.0
print(" local=", local, " dx=", "%.3f" % dx, " dy=", "%.3f" % dy, " norm=", "%.3f" % norm)
# --- RADIAL BAND ---
# These thresholds are fractions of the outer radius. TUNE to match your art:
# the real proportions of a dartboard, from center outward.
var seg_name := ""
if norm <= bull_inner_max:
seg_name = "bull_inner" # inner bull (50)
elif norm <= bull_outer_max:
seg_name = "bull_outer" # outer bull (25)
elif norm > 1.0:
print(" outside board")
return -1
else:
# --- ANGULAR SECTOR ---
# atan2 gives angle of the hit point. We map it to one of 20 sectors,
# then index into the dartboard number sequence.
var angle := atan2(dx, dy) # -PI..PI, 0 = straight up (+y = top)
# Convert to 0..1 going clockwise from top. Adjust the rotation so 0 = top.
var t := angle / (2.0 * PI)
t = fposmod(t, 1.0)
if reverse_direction:
t = 1.0 - t
var sector_index := int(floor(t * 20.0 + 0.5)) % 20
sector_index = (sector_index + sector_offset) % 20
var number : int = DART_NUMBERS[sector_index]
# Which ring band within the sector.
var band := ""
if norm <= inner_max:
band = "inner" # inner single
elif norm <= triple_max:
band = "triple" # triple ring
elif norm <= outer_max:
band = "outer" # outer single
else:
band = "double" # double ring
seg_name = "seg_%d_%s" % [number, band]
print(" norm=", "%.3f" % norm, " -> ", seg_name)
return _index_of_surface_named(board, seg_name)
func _index_of_surface_named(board: MeshInstance3D, target_name: String) -> int:
var mesh := board.mesh
for i in mesh.get_surface_count():
var mat := mesh.surface_get_material(i)
if mat and mat.resource_name == target_name:
return i
return -1
func _paint_surface(board: MeshInstance3D, surface_index: int, tex: Texture2D) -> void:
# Always build a fresh StandardMaterial3D so the texture shows as an IMAGE,
# not tinted by the old base color. If the segment looks like a flat color
# instead of the pattern, the mesh is missing proper UVs on that face.
var new_mat := StandardMaterial3D.new()
new_mat.albedo_color = Color.WHITE # white so texture isn't tinted
new_mat.albedo_texture = tex
new_mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR
new_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
board.set_surface_override_material(surface_index, new_mat)