public partial class CameraController : SpringArm3D { [Export] public float MovementSmoothing { get; set; } = 8.0f; [Export] public Vector2 MouseSensitivity { get; set; } = new(0.2f, 0.2f); // Degrees per pixel. [Export] public float PitchMinimum { get; set; } = 30; [Export] public float PitchMaximum { get; set; } = 65; [Export] public float PitchSmoothing { get; set; } = 12.0f; // FIXME: Fix the "snappyness" when moving slowly. // FIXME: Fix spring arm clipping through terrain and similar. // TODO: Gradually return to maximum spring length. // TODO: Turn player transparent as the camera moves closer. public static bool IsMouseCaptured => Input.MouseMode == Input.MouseModeEnum.Captured; Vector3 _offset; Node3D _player; public override void _Ready() { _offset = Position; _player = this.GetParentOrThrow(); Transform = _player.GlobalTransform.Translated(_offset); } public override void _Input(InputEvent ev) { if (IsMouseCaptured && ev.IsActionPressed("ui_cancel")) { // When escape is pressed, release the mouse. Input.MouseMode = Input.MouseModeEnum.Visible; GetViewport().SetInputAsHandled(); } } public override void _UnhandledInput(InputEvent ev) { switch (ev) { case InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true } when !IsMouseCaptured: // When left mouse button is pressed, capture the mouse. Input.MouseMode = Input.MouseModeEnum.Captured; GetViewport().SetInputAsHandled(); break; case InputEventMouseMotion motion when IsMouseCaptured: ApplyRotation(-motion.Relative * MouseSensitivity); break; } } void ApplyRotation(Vector2 delta) { var (pitch, yaw, _) = RotationDegrees; pitch += delta.Y; yaw += delta.X; RotationDegrees = new(pitch, yaw, 0); } public override void _Process(double delta) { Position = Position.Damp(_offset + _player.GlobalPosition, MovementSmoothing, delta); var pitch = RotationDegrees.X; var (min, max) = (-PitchMaximum, -PitchMinimum); if (pitch < min) pitch = pitch.Damp(min, PitchSmoothing, delta); if (pitch > max) pitch = pitch.Damp(max, PitchSmoothing, delta); RotationDegrees = RotationDegrees with { X = pitch }; } }