using Godot; public partial class PlayerCamera : Camera3D { [ExportCategory("Camera Movement")] [Export] public float CameraPanSpeed { get; set; } = 20f; [Export] public float CameraZoomSpeed { get; set; } = 20f; [Export] public float CameraZoomMin { get; set; } = 10f; [Export] public float CameraZoomMax { get; set; } = 50f; [ExportCategory("Edge Scrolling")] [Export] public float EdgeScrollMargin { get; set; } = 20f; [Export] public float EdgeScrollSpeed { get; set; } = 15f; [ExportCategory("Rotation")] [Export] public float YawSensitivity { get; set; } = 0.5f; [Export] public float PitchSensitivity { get; set; } = 0.18f; [Export] public float StepDegreeMax { get; set; } = 3f; [Export] public float PitchDegreeMin { get; set; } = 10f; [Export] public float PitchDegreeMax { get; set; } = 80f; [Export] public bool CaptureMiddleButton { get; set; } = false; Vector3 origin = Vector3.Zero; float orbitDistance = 25f; float currentHeight = 20f; float orbitRadius = 20f; bool isMiddleMouseRotating = false; float yaw = 0f; float pitch = 0f; public override void _Ready() { var pitchMin = Mathf.DegToRad(PitchDegreeMin); var pitchMax = Mathf.DegToRad(PitchDegreeMax); pitch = Mathf.Clamp(pitch, pitchMin, pitchMax); UpdateCameraPosition(); } public override void _Process(double delta) { var movement = Vector3.Zero; // Keyboard Input if (Input.IsActionPressed("camera_pan_right")) { movement.X += 1f; } if (Input.IsActionPressed("camera_pan_left")) { movement.X -= 1f; } if (Input.IsActionPressed("camera_pan_up")) { movement.Z += 1f; } if (Input.IsActionPressed("camera_pan_down")) { movement.Z -= 1f; } // Edge Scrolling var mousePosition = GetViewport().GetMousePosition(); var viewportSize = GetViewport().GetVisibleRect().Size; if (mousePosition.X < EdgeScrollMargin) { movement.X -= 1f; } if (mousePosition.X > viewportSize.X - EdgeScrollMargin) { movement.X += 1f; } if (mousePosition.Y < EdgeScrollMargin) { movement.Z -= 1f; } if (mousePosition.Y > viewportSize.Y - EdgeScrollMargin) { movement.Z += 1f; } // Rotating if (movement.Length() > 0f) { movement = movement.Normalized().Rotated(Vector3.Up, yaw); origin += movement * CameraPanSpeed * (float)delta; UpdateCameraPosition(); } } public override void _UnhandledInput(InputEvent @event) { if (@event is InputEventMouseButton mouseButtonEvent) { if (mouseButtonEvent.Pressed) { var delta = (float)GetProcessDeltaTime(); if (mouseButtonEvent.ButtonIndex is MouseButton.WheelUp) { orbitDistance = Mathf.Max(CameraZoomMin, orbitDistance - CameraZoomSpeed * delta); } else if (mouseButtonEvent.ButtonIndex is MouseButton.WheelDown) { orbitDistance = Mathf.Min(CameraZoomMax, orbitDistance + CameraZoomSpeed * delta); } UpdateCameraPosition(); } if (mouseButtonEvent.ButtonIndex is MouseButton.Middle) { isMiddleMouseRotating = mouseButtonEvent.Pressed; if (CaptureMiddleButton) { Input.MouseMode = mouseButtonEvent.Pressed ? Input.MouseModeEnum.Captured : Input.MouseModeEnum.Visible; } } } else if (@event is InputEventMouseMotion mouseMotionEvent && isMiddleMouseRotating) { var viewportSize = GetViewport().GetVisibleRect().Size; var viewMin = (float)Mathf.Min(viewportSize.X, viewportSize.Y); var delta = (float)GetProcessDeltaTime() * 60f; // px => normalized fraction of screen -> radians, scaled by dt var dx = (mouseMotionEvent.Relative.X / viewMin) * YawSensitivity * Mathf.Tau * delta; var dy = (mouseMotionEvent.Relative.Y / viewMin) * PitchSensitivity * Mathf.Tau * delta; // Safety cap per event var maxStep = Mathf.DegToRad(StepDegreeMax); dx = Mathf.Clamp(dx, -maxStep, maxStep); dy = Mathf.Clamp(dy, -maxStep, maxStep); yaw -= dx; pitch += dy; var pitchMin = Mathf.DegToRad(PitchDegreeMin); var pitchMax = Mathf.DegToRad(PitchDegreeMax); pitch = Mathf.Clamp(pitch, pitchMax, pitchMax); UpdateCameraPosition(); } } void UpdateCameraPosition() { var direction = new Vector3( x: Mathf.Sin(yaw) * Mathf.Cos(pitch), y: Mathf.Sin(pitch), z: Mathf.Cos(yaw) * Mathf.Cos(pitch) ).Normalized(); Position = origin + direction * orbitDistance; LookAt(origin, Vector3.Up); currentHeight = orbitDistance * Mathf.Sin(pitch); orbitRadius = orbitDistance * Mathf.Cos(pitch); } }