extends Node signal camera_move_requested(destination: Transform3D) signal back_requested var _hovered: Clickable = null func get_camera() -> Camera3D: return get_viewport().get_camera_3d() func _ready() -> void: back_requested.connect(_on_back_requested) func _process(_delta: float) -> void: _update_hover(get_viewport().get_mouse_position()) func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: _raycast_click(get_viewport().get_mouse_position()) if event is InputEventKey and event.pressed and not event.echo: if event.keycode == KEY_ESCAPE or event.keycode == KEY_BACKSPACE: request_back() func _on_back_requested() -> void: var cam = get_camera() if cam and cam.has_method("go_back"): cam.go_back() func request_back() -> void: back_requested.emit() func _update_hover(screen_pos: Vector2) -> void: var clickable = _raycast_find_clickable(screen_pos) if clickable != _hovered: _hovered = clickable if _hovered: CursorManager.set_cursor(_hovered.cursor_hint) else: CursorManager.reset() func _raycast_click(screen_pos: Vector2) -> void: var clickable = _raycast_find_clickable(screen_pos) if clickable: clickable.on_clicked(_raycast_position(screen_pos)) func _raycast_find_clickable(screen_pos: Vector2) -> Clickable: var result = _raycast(screen_pos) if result.is_empty(): return null return _find_clickable_in(result.collider) func _raycast_position(screen_pos: Vector2) -> Vector3: var result = _raycast(screen_pos) return result.get("position", Vector3.ZERO) func _raycast(screen_pos: Vector2) -> Dictionary: var camera = get_camera() if not camera: return {} var space = camera.get_world_3d().direct_space_state var origin = camera.project_ray_origin(screen_pos) var end = origin + camera.project_ray_normal(screen_pos) * 100.0 var query = PhysicsRayQueryParameters3D.create(origin, end) return space.intersect_ray(query) func _find_clickable_in(node: Node) -> Clickable: while node: var c = node.find_child("Clickable", false, false) if c: return c as Clickable node = node.get_parent() return null func request_camera_move(destination: Transform3D) -> void: camera_move_requested.emit(destination)