extends Camera3D ## Camera with flying script attached to it. class_name Freecam3D ## ## Camera with toggleable freecam mode for prototyping when creating levels, shaders, lighting, etc. ## ## Usage: Run your game, press and fly around freely. Uses Minecraft-like controls. ## ## Customize your own toggle key to avoid collisions with your current mappings. @export var toggle_key: Key = KEY_TAB ## Speed up / down by scrolling the mouse whell down / up @export var invert_speed_controls: bool = false @export var overlay_text: bool = true ## Pivot node for camera looking around @onready var pivot := Node3D.new() ## Main parent for camera overlay. @onready var screen_overlay := VBoxContainer.new() ## Container for the chat-like event log. @onready var event_log := VBoxContainer.new() const MAX_SPEED := 0.25 const MIN_SPEED := 0.01 const ACCELERATION := 0.1 const MOUSE_SENSITIVITY := 0.002 ## Whether or not the camera can move. var movement_active := false: set(val): movement_active = val display_message("[Movement ON]" if movement_active else "[Movement OFF]") ## The current maximum speed. Lower or higher it by scrolling the mouse wheel. var target_speed := MIN_SPEED ## Movement velocity. var velocity := Vector3.ZERO ## Sets up pivot and UI overlay elements. func _setup_nodes() -> void: self.add_sibling(pivot) pivot.position = position pivot.rotation = rotation pivot.name = "FreecamPivot" self.reparent(pivot) self.position = Vector3.ZERO self.rotation = Vector3.ZERO # UI stuff screen_overlay.add_theme_constant_override("Separation", 8) self.add_child(screen_overlay) screen_overlay.add_child(_make_label("Debug Camera")) screen_overlay.add_spacer(false) screen_overlay.add_child(event_log) screen_overlay.visible = overlay_text func _ready() -> void: _setup_nodes.call_deferred() _add_keybindings() movement_active = true Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) func _process(delta: float) -> void: if Input.is_action_just_released("__debug_camera_toggle"): movement_active = not movement_active if movement_active: var dir = Vector3.ZERO if Input.is_action_pressed("__debug_camera_forward"): dir.z -= 1 if Input.is_action_pressed("__debug_camera_back"): dir.z += 1 if Input.is_action_pressed("__debug_camera_left"): dir.x -= 1 if Input.is_action_pressed("__debug_camera_right"): dir.x += 1 if Input.is_action_pressed("__debug_camera_up"): dir.y += 1 if Input.is_action_pressed("__debug_camera_down"): dir.y -= 1 dir = dir.normalized() dir = dir.rotated(Vector3.UP, pivot.rotation.y) velocity = lerp(velocity, dir * target_speed, ACCELERATION) pivot.position += velocity func _input(event: InputEvent) -> void: if movement_active: # Turn around if event is InputEventMouseMotion: pivot.rotate_y(-event.relative.x * MOUSE_SENSITIVITY) rotate_x(-event.relative.y * MOUSE_SENSITIVITY) rotation.x = clamp(rotation.x, -PI/2, PI/2) var speed_up = func(): target_speed = clamp(target_speed + 0.015, MIN_SPEED, MAX_SPEED) display_message("[Speed up] " + str(target_speed)) var slow_down = func(): target_speed = clamp(target_speed - 0.015, MIN_SPEED, MAX_SPEED) display_message("[Slow down] " + str(target_speed)) # Speed up and down with the mouse wheel if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_WHEEL_UP and event.pressed: slow_down.call() if invert_speed_controls else speed_up.call() if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and event.pressed: speed_up.call() if invert_speed_controls else slow_down.call() ## Pushes new message label into "chat" and removes the old ones if necessary func display_message(text: String) -> void: while event_log.get_child_count() >= 3: event_log.remove_child(event_log.get_child(0)) event_log.add_child(_make_label(text)) func _make_label(text: String) -> Label: var l = Label.new() l.text = text return l func _add_keybindings() -> void: var actions = InputMap.get_actions() if "__debug_camera_forward" not in actions: _add_key_input_action("__debug_camera_forward", KEY_W) if "__debug_camera_back" not in actions: _add_key_input_action("__debug_camera_back", KEY_S) if "__debug_camera_left" not in actions: _add_key_input_action("__debug_camera_left", KEY_A) if "__debug_camera_right" not in actions: _add_key_input_action("__debug_camera_right", KEY_D) if "__debug_camera_up" not in actions: _add_key_input_action("__debug_camera_up", KEY_E) if "__debug_camera_down" not in actions: _add_key_input_action("__debug_camera_down", KEY_Q) if "__debug_camera_toggle" not in actions: _add_key_input_action("__debug_camera_toggle", toggle_key) func _add_key_input_action(name: String, key: Key) -> void: var ev = InputEventKey.new() ev.physical_keycode = key InputMap.add_action(name) InputMap.action_add_event(name, ev)