using UnityEngine; using Unity.Cinemachine; #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif /// /// Feeds mouse input into a so the camera /// orbits the follow target. Mouse movement drives yaw/pitch, the wheel zooms. /// The orbit behaviour depends on (usually set by the /// GameModeManager). Attach to the camera rig (the CinemachineOrbitalFollow). /// [RequireComponent(typeof(CinemachineOrbitalFollow))] [DisallowMultipleComponent] public class ThirdPersonOrbitInput : MonoBehaviour { public enum OrbitLookMode { /// Rotate with every mouse move; cursor locked/hidden. AlwaysActive, /// Rotate only while the right mouse button is held; cursor free otherwise. HoldRightMouseButton, /// No rotation; cursor free. Disabled } [Header("Look Mode")] [Tooltip("How the camera orbit is driven (usually set by GameModeManager).")] [SerializeField] private OrbitLookMode lookMode = OrbitLookMode.AlwaysActive; [Header("Rotation")] [Tooltip("Horizontal look sensitivity (degrees of yaw per pixel).")] public float horizontalSensitivity = 0.2f; [Tooltip("Vertical look sensitivity (degrees of pitch per pixel).")] public float verticalSensitivity = 0.15f; [Tooltip("Invert vertical look (mouse up tilts the camera down).")] public bool invertY = false; [Tooltip("Eases orbit rotation so quick mouse moves ramp in/out (higher = snappier, 0 = raw/instant).")] public float rotationSmoothing = 20f; [Header("Zoom")] [Tooltip("Mouse wheel zooms the camera in and out (changes orbit radius).")] public bool enableZoom = true; [Tooltip("World units the orbit radius changes per mouse wheel notch.")] public float zoomSpeed = 0.5f; [Tooltip("Eases the orbit radius toward the zoom target (higher = snappier, 0 = instant).")] public float zoomSmoothing = 10f; [Tooltip("Closest orbit radius (zoom in).")] public float minRadius = 1.5f; [Tooltip("Farthest orbit radius (zoom out).")] public float maxRadius = 8f; [Header("Cursor")] [Tooltip("Lock and hide the cursor while the camera is actively rotating.")] public bool manageCursor = true; CinemachineOrbitalFollow _orbit; Vector2 _smoothedLook; // low-pass-filtered look delta (deg/frame) for eased rotation float _targetRadius; // zoom radius the actual radius eases toward bool _zoomInit; // _targetRadius seeded from the live radius yet? /// Current orbit mode. Setting it also refreshes the cursor state. public OrbitLookMode LookMode { get => lookMode; set { lookMode = value; ApplyCursorState(); } } /// /// While true, mouse-look is paused (orbit frozen) without changing the cursor /// state. The player controller sets this while dragging the wall-climb offset so /// the same mouse motion is not also spent orbiting the camera. /// public bool LookSuppressed { get; set; } void Awake() { _orbit = GetComponent(); if (!_orbit) Debug.LogError("[ThirdPersonOrbitInput] No CinemachineOrbitalFollow found on this GameObject.", this); } void OnEnable() => ApplyCursorState(); void OnDisable() => SetCursorLocked(false); void Update() { if (!_orbit) return; bool modeWantsLook = ShouldRotate(); if (manageCursor) SetCursorLocked(modeWantsLook); if (modeWantsLook && !LookSuppressed) { Vector2 delta = ReadMouseDelta() * MouseSensitivity.Multiplier; Vector2 raw = new Vector2( delta.x * horizontalSensitivity, delta.y * verticalSensitivity * (invertY ? 1f : -1f)); // Ease the per-frame look toward the raw delta so fast flicks ramp in/out // instead of snapping; steady motion still converges to full sensitivity. Vector2 applied = rotationSmoothing > 0f ? (_smoothedLook = Vector2.Lerp(_smoothedLook, raw, 1f - Mathf.Exp(-rotationSmoothing * Time.deltaTime))) : raw; if (applied.sqrMagnitude > 0f) { ApplyAxisDelta(ref _orbit.HorizontalAxis, applied.x); ApplyAxisDelta(ref _orbit.VerticalAxis, applied.y); } } else { _smoothedLook = Vector2.zero; } if (enableZoom) { if (!_zoomInit) { _targetRadius = _orbit.Radius; _zoomInit = true; } // Shift+wheel is reserved for the paint brush resize (MeshPainter), so the orbit camera // ignores the wheel while Shift is held - otherwise it would zoom and resize at once. float scroll = ShiftHeld() ? 0f : ReadScroll(); if (!Mathf.Approximately(scroll, 0f)) { // One wheel notch = one zoom step (sign only), independent of the // platform's raw wheel magnitude. Scrolling up zooms in. _targetRadius = Mathf.Clamp(_targetRadius - Mathf.Sign(scroll) * zoomSpeed, minRadius, maxRadius); } // Ease the actual radius toward the target (frame-rate independent). _orbit.Radius = zoomSmoothing > 0f ? Mathf.Lerp(_orbit.Radius, _targetRadius, 1f - Mathf.Exp(-zoomSmoothing * Time.deltaTime)) : _targetRadius; } } bool ShouldRotate() { switch (lookMode) { case OrbitLookMode.AlwaysActive: return true; case OrbitLookMode.HoldRightMouseButton: return IsRightMouseHeld(); default: return false; } } void ApplyCursorState() { if (manageCursor) SetCursorLocked(ShouldRotate()); } /// Adds to an input axis; wrapping axes wrap, others clamp to their range. static void ApplyAxisDelta(ref InputAxis axis, float amount) { float value = axis.Value + amount; float min = axis.Range.x; float max = axis.Range.y; if (axis.Wrap && max > min) { float span = max - min; value = ((value - min) % span + span) % span + min; } else { value = Mathf.Clamp(value, min, max); } axis.Value = value; } static void SetCursorLocked(bool locked) { Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None; Cursor.visible = !locked; } bool IsRightMouseHeld() { #if ENABLE_INPUT_SYSTEM return Mouse.current != null && Mouse.current.rightButton.isPressed; #else return Input.GetMouseButton(1); #endif } static bool ShiftHeld() { #if ENABLE_INPUT_SYSTEM var kb = Keyboard.current; if (kb != null) return kb.leftShiftKey.isPressed || kb.rightShiftKey.isPressed; #endif #if ENABLE_LEGACY_INPUT_MANAGER return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); #else return false; #endif } Vector2 ReadMouseDelta() { #if ENABLE_INPUT_SYSTEM return Mouse.current != null ? Mouse.current.delta.ReadValue() : Vector2.zero; #else return new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y")) * 10f; #endif } float ReadScroll() { #if ENABLE_INPUT_SYSTEM // Raw wheel delta; only its sign is used (one notch = one zoom step). return Mouse.current != null ? Mouse.current.scroll.ReadValue().y : 0f; #else return Input.GetAxis("Mouse ScrollWheel"); #endif } }