GDScript 53 lines
class_name MoveMarker
extends Node3D
## The click-to-move destination marker (LoL-style): a flat ring laid on the ground at the
## point the player last right-clicked, shown while the hero walks toward it and hidden once
## it arrives. It pulses so the destination reads at a glance. Pure presentation — driven by
## the client's move target each tick, never by the simulation or the wire.
## Ring footprint and how far it floats above the ground (a hair, to dodge z-fighting with
## the ground plane), then the pulse rate and how far the radius breathes.
const RADIUS := 48.0
const THICKNESS := 8.0
const LIFT := 4.0
const COLOR := Color(0.45, 1.0, 0.65)
const PULSE_HZ := 2.0
const PULSE_AMOUNT := 0.16
var _ring: MeshInstance3D = null
var _phase := 0.0
func _ready() -> void:
var torus := TorusMesh.new()
torus.inner_radius = RADIUS - THICKNESS
torus.outer_radius = RADIUS
_ring = MeshInstance3D.new()
_ring.mesh = torus
var mat := StandardMaterial3D.new()
mat.albedo_color = COLOR
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
_ring.material_override = mat
add_child(_ring)
visible = false
## Shows the marker at a field point (a sim `Vector2`, placed on the ground). Idempotent —
## the pulse runs on its own clock, so re-pointing each tick just tracks the live target.
func point_at(field_point: Vector2) -> void:
position = Vector3(field_point.x, LIFT, field_point.y)
visible = true
## Hides the marker — the hero has arrived (or has no destination).
func clear() -> void:
visible = false
func _process(delta: float) -> void:
if not visible:
return
_phase += delta * PULSE_HZ * TAU
var s := 1.0 + sin(_phase) * PULSE_AMOUNT
_ring.scale = Vector3(s, 1.0, s)