@tool class_name BeatMeasurer extends Node @export var path: Path3D @export var track_duration: float = 1.0 @export var track_bpm: int = 120 @export var track_bars: int = 4 @export_tool_button("Force Update") var editor_force_update: Callable = _update_measurement # Called when the node enters the scene tree for the first time. func _ready() -> void: path.curve_changed.connect(_update_measurement) _update_measurement.call_deferred() func _update_measurement() -> void: for child: Node in get_children(): if child.owner == null: child.queue_free() var path_length: float = path.curve.get_baked_length() var beat_time: float = track_bpm / 60.0 var beats := int(path_length / beat_time) for beat: int in range(beats): var progress: float = beat_time * beat var position: Vector3 = path.global_position + path.curve.sample_baked(progress) DebugDraw3D.draw_sphere(position, 0.15, Color.WHITE, 5.0 if Engine.is_editor_hint() else 500.0) var label := Label3D.new() label.text = str(beat) label.billboard = BaseMaterial3D.BILLBOARD_ENABLED add_child(label) label.global_position = position + Vector3.UP