class_name FreeLookCamera extends Camera3D # Modifier keys' speed multiplier const SHIFT_MULTIPLIER = 2.5 const ALT_MULTIPLIER = 1.0 / SHIFT_MULTIPLIER @export_range(0.0, 1.0) var sensitivity: float = 0.25 # Mouse state var _mouse_position = Vector2(0.0, 0.0) var _total_pitch = 0.0 # Movement state var _direction = Vector3(0.0, 0.0, 0.0) var _velocity = Vector3(0.0, 0.0, 0.0) var _acceleration = 30 var _deceleration = -10 var _vel_multiplier = 4 # Keyboard state var _w = false var _s = false var _a = false var _d = false var _q = false var _e = false var _shift = false var _alt = false func _input(event): # Receives mouse motion if event is InputEventMouseMotion: _mouse_position = event.relative # Receives mouse button input if event is InputEventMouseButton: match event.button_index: MOUSE_BUTTON_RIGHT: # Only allows rotation if right click down Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED if event.pressed else Input.MOUSE_MODE_VISIBLE) MOUSE_BUTTON_WHEEL_UP: # Increases max velocity _vel_multiplier = clamp(_vel_multiplier * 1.1, 0.2, 20) MOUSE_BUTTON_WHEEL_DOWN: # Decereases max velocity _vel_multiplier = clamp(_vel_multiplier / 1.1, 0.2, 20) # Receives key input if event is InputEventKey: match event.keycode: KEY_W: _w = event.pressed KEY_S: _s = event.pressed KEY_A: _a = event.pressed KEY_D: _d = event.pressed KEY_Q: _q = event.pressed KEY_E: _e = event.pressed KEY_CTRL: _shift = event.pressed KEY_ALT: _alt = event.pressed # Updates mouselook and movement every frame func _process(delta): _update_mouselook() _update_movement(delta) # Updates camera movement func _update_movement(delta): # Computes desired direction from key states _direction = Vector3( (_d as float) - (_a as float), (_e as float) - (_q as float), (_s as float) - (_w as float) ) if Input.is_key_pressed(KEY_SPACE): _direction.y += 1.0 elif Input.is_key_pressed(KEY_SHIFT): _direction.y -= 1.0 # Computes the change in velocity due to desired direction and "drag" # The "drag" is a constant acceleration on the camera to bring it's velocity to 0 var offset = _direction.normalized() * _acceleration * _vel_multiplier * delta \ + _velocity.normalized() * _deceleration * _vel_multiplier * delta # Compute modifiers' speed multiplier var speed_multi = 10 if _shift: speed_multi *= SHIFT_MULTIPLIER if _alt: speed_multi *= ALT_MULTIPLIER # Checks if we should bother translating the camera if _direction == Vector3.ZERO and offset.length_squared() > _velocity.length_squared(): # Sets the velocity to 0 to prevent jittering due to imperfect deceleration _velocity = Vector3.ZERO else: # Clamps speed to stay within maximum value (_vel_multiplier) _velocity.x = clamp(_velocity.x + offset.x, -_vel_multiplier, _vel_multiplier) _velocity.y = clamp(_velocity.y + offset.y, -_vel_multiplier, _vel_multiplier) _velocity.z = clamp(_velocity.z + offset.z, -_vel_multiplier, _vel_multiplier) translate(_velocity * delta * speed_multi) # Updates mouse look func _update_mouselook(): # Only rotates mouse if the mouse is captured if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: _mouse_position *= sensitivity var yaw = _mouse_position.x var pitch = _mouse_position.y _mouse_position = Vector2(0, 0) # Prevents looking up/down too far pitch = clamp(pitch, -90 - _total_pitch, 90 - _total_pitch) _total_pitch += pitch rotate_y(deg_to_rad(-yaw)) rotate_object_local(Vector3(1,0,0), deg_to_rad(-pitch))