extends Node3D @export_group("Speed") @export var pan_speed: float = 5.0 @export var rotation_speed: float = 0.003 @export_group("Bounds") @export var movement_bounds: AABB @export_group("Ref") @export var rotation_target: Node3D @onready var camera: Camera3D = $%Camera3D var total_yaw: float = 0.0 var total_pitch: float = 0.0 var orbit_distance: float = 0.0 var current_pan: Vector2 = Vector2.ZERO var is_active: bool = false func _ready() -> void: total_yaw = global_rotation.y total_pitch = global_rotation.x if rotation_target: orbit_distance = global_position.distance_to(rotation_target.global_position) func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("toggle_photo_mode"): is_active = !is_active get_tree().paused = is_active if !is_active: Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) else: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) if event is InputEventMouseMotion and is_active: total_yaw -= event.relative.x * rotation_speed if event.is_action_pressed("take_photo"): take_photo_async() func _process(delta: float) -> void: if not rotation_target: return var pan_input = Input.get_vector("photo_pan_left", "photo_pan_right", "photo_pan_up", "photo_pan_down") var rot_basis = Basis.from_euler(Vector3(total_pitch, total_yaw, 0)) if pan_input != Vector2.ZERO and is_active: current_pan.x += pan_input.x * pan_speed * delta current_pan.y += -pan_input.y * pan_speed * delta if movement_bounds.size != Vector3.ZERO: current_pan.x = clampf(current_pan.x, movement_bounds.position.x, movement_bounds.position.x + movement_bounds.size.x) current_pan.y = clampf(current_pan.y, movement_bounds.position.y, movement_bounds.position.y + movement_bounds.size.y) var right_dir = rot_basis.x.normalized() var up_dir = rot_basis.y.normalized() var final_pan_offset = (right_dir * current_pan.x) + (up_dir * current_pan.y) var orbit_pos = rotation_target.global_position + (rot_basis * Vector3(0, 0, orbit_distance)) global_position = orbit_pos + final_pan_offset global_basis = rot_basis func take_photo_async() -> void: if !is_active: return var targets = get_tree().get_nodes_in_group("collectible") var space_state = get_world_3d().direct_space_state await CollectionManager.save_photo_to_disk_async() for target in targets: if not target.collectible_data: continue if camera.is_position_in_frustum(target.global_position): var query = PhysicsRayQueryParameters3D.create(camera.global_position, target.global_position) query.collide_with_areas = true query.collide_with_bodies = true var result = space_state.intersect_ray(query) if result and result.collider == target: CollectionManager.unlock_collectible(target.collectible_data.id) GameState.save_game()