ajhahn.de
← Theria
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)