extends CanvasLayer @export var player_path: NodePath @export var camera_path: NodePath @export var vector_scale: float = 2.5 @export var max_radius: float = 140.0 @export var gizmo_margin: Vector2 = Vector2(140, 140) @export var gizmo_axis_length: float = 55.0 @export var gizmo_axis_width: float = 2.0 @export var velocity_line_width: float = 3.0 @export var show_axis_debug: bool = true var _velocity_line: Line2D var _axis_x: Line2D var _axis_y: Line2D var _axis_z: Line2D var _vector_label: Label var _axis_label: Label var _player: RigidBody3D var _camera: Camera3D func _ready() -> void: _velocity_line = _get_or_create_velocity_line() _axis_x = _get_or_create_axis_line("AxisX", Color(0.95, 0.2, 0.2, 0.9)) _axis_y = _get_or_create_axis_line("AxisY", Color(0.2, 0.9, 0.4, 0.9)) _axis_z = _get_or_create_axis_line("AxisZ", Color(0.2, 0.5, 1.0, 0.9)) _vector_label = _get_or_create_vector_label() _axis_label = _get_or_create_axis_label() _player = get_node_or_null(player_path) _camera = get_node_or_null(camera_path) _axis_label.visible = show_axis_debug func _process(_delta: float) -> void: if _player == null or _camera == null: return var velocity = _player.linear_velocity var cam_basis = _camera.global_transform.basis var local_velocity = cam_basis.inverse() * velocity var arrow = Vector2(local_velocity.x, -local_velocity.y) * vector_scale if arrow.length() > max_radius: arrow = arrow.normalized() * max_radius var viewport_size = get_viewport().get_visible_rect().size var center = Vector2( viewport_size.x - gizmo_margin.x, viewport_size.y - gizmo_margin.y ) _velocity_line.points = PackedVector2Array([center, center + arrow]) var player_basis = _player.global_transform.basis var rel_basis = cam_basis.inverse() * player_basis _axis_x.points = PackedVector2Array([ center, center + Vector2(rel_basis.x.x, -rel_basis.x.y) * gizmo_axis_length ]) _axis_y.points = PackedVector2Array([ center, center + Vector2(rel_basis.y.x, -rel_basis.y.y) * gizmo_axis_length ]) _axis_z.points = PackedVector2Array([ center, center + Vector2(rel_basis.z.x, -rel_basis.z.y) * gizmo_axis_length ]) _vector_label.text = "Velocity: (%.1f, %.1f, %.1f) m/s | Speed: %.1f" % [ velocity.x, velocity.y, velocity.z, velocity.length(), ] if show_axis_debug and _axis_label != null: if _player.has_method("get_axis_debug_text"): _axis_label.text = _player.get_axis_debug_text() func _get_or_create_velocity_line() -> Line2D: var existing: Line2D = get_node_or_null("VelocityLine") if existing != null: return existing var line := Line2D.new() line.name = "VelocityLine" line.width = velocity_line_width line.default_color = Color(1, 0.7, 0.2, 0.9) line.antialiased = true add_child(line) return line func _get_or_create_axis_line(name: String, color: Color) -> Line2D: var existing: Line2D = get_node_or_null(name) if existing != null: return existing var line := Line2D.new() line.name = name line.width = gizmo_axis_width line.default_color = color line.antialiased = true add_child(line) return line func _get_or_create_vector_label() -> Label: var existing: Label = get_node_or_null("VectorLabel") if existing != null: return existing var label := Label.new() label.name = "VectorLabel" label.text = "Velocity:" label.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT) label.offset_left = 12.0 label.offset_top = 12.0 label.offset_right = 520.0 label.offset_bottom = 40.0 add_child(label) return label func _get_or_create_axis_label() -> Label: var existing: Label = get_node_or_null("AxisLabel") if existing != null: return existing var label := Label.new() label.name = "AxisLabel" label.text = "Axis:" label.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_LEFT) label.offset_left = 12.0 label.offset_top = -60.0 label.offset_right = 520.0 label.offset_bottom = -12.0 add_child(label) return label