public partial class CameraController : Node { [Export] public Camera3D Camera { get; set; } // Will be `null` for remote players. [Export] public Vector2 MouseSensitivity { get; set; } = new(0.2f, 0.2f); // Degrees per pixel. [Export] public float PitchMinimum { get; set; } = -85; [Export] public float PitchMaximum { get; set; } = 85; public Transform3D DefaultTransform { get; private set; } [Export] public float CurrentPitch { get; set; } [Export] public float CurrentYaw { get; set; } public static bool IsMouseCaptured => Input.MouseMode == Input.MouseModeEnum.Captured; Player _player; public override void _Ready() { _player = GetParent(); DefaultTransform = Camera.Transform; Camera.Current = _player.IsLocal; } public override void _Input(InputEvent @event) { if (!_player.IsLocal) return; if (IsMouseCaptured && @event.IsActionPressed("ui_cancel")) { // When escape is pressed, release the mouse. Input.MouseMode = Input.MouseModeEnum.Visible; GetViewport().SetInputAsHandled(); } } public override void _UnhandledInput(InputEvent @event) { if (!_player.IsLocal) return; switch (@event) { 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) { delta *= Tau / 360; // degrees to radians CurrentYaw += delta.X; CurrentPitch += delta.Y; CurrentPitch = Clamp(CurrentPitch, DegToRad(PitchMinimum), DegToRad(PitchMaximum)); } }