157 lines
6.3 KiB
GDScript
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)
|