using Unity.Cinemachine; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; namespace BrewMonster { public class CameraController : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler { public static CameraController Instance; [SerializeField] private CinemachineCamera _cinemachineCamera; [SerializeField] private CinemachineOrbitalFollow orbital; private bool fingerDown = false; [SerializeField, Min(0f)] private float dragDeadZone = 10f; [SerializeField] private float horizontalRotationPerScreen = 60f; [SerializeField] private float verticalRotationPerScreen = 500f; [SerializeField, HideInInspector] private bool _migratedDeadZoneToNormalized; [Header("Lock view")] [Tooltip("When locked: no zoom; drag only orbits around world Y (horizontal axis).")] [SerializeField] private bool _lockView; [SerializeField] private UnityEvent onViewLockChanged; [Header("Zoom (orbit scale)")] [Tooltip("RadialAxis scales camera distance: effective distance = Orbital Follow Radius × this value.")] [SerializeField] private bool enableZoom = true; [SerializeField] private float orbitScaleMin = 0.5f; [SerializeField] private float orbitScaleMax = 2f; [SerializeField] private float defaultOrbitScale = 1f; [SerializeField] private float zoomSensitivityScroll = 0.15f; [SerializeField] private float zoomSensitivityPinch = 0.004f; private float _lastPinchDistance; private bool _pinchZoomActive; private int _touchCount = 0; public CinemachineOrbitalFollow Orbital { get => orbital;} public bool ViewLocked => _lockView; public void SetViewLocked(bool locked) { if (_lockView == locked) { return; } _lockView = locked; onViewLockChanged?.Invoke(_lockView); } public void ToggleViewLocked() { SetViewLocked(!_lockView); } private bool CanApplyZoom => enableZoom && !_lockView; void OnEnable() { Instance = this; MigrateLegacyDeadZone(); } private void Start() { SyncRadialAxisRange(); } public void OnDrag(PointerEventData eventData) { if (!fingerDown || orbital == null) { return; } float screenReference = Mathf.Max(1f, Mathf.Min(Screen.width, Screen.height)); Vector2 normalizedDelta = eventData.delta / screenReference; float normalizedDeadZone = Mathf.Max(0.0001f, dragDeadZone); if (normalizedDelta.sqrMagnitude >= normalizedDeadZone * normalizedDeadZone) { orbital.HorizontalAxis.Value += normalizedDelta.x * horizontalRotationPerScreen; if (!_lockView) { orbital.VerticalAxis.Value -= normalizedDelta.y * verticalRotationPerScreen; orbital.VerticalAxis.Value = Mathf.Clamp(orbital.VerticalAxis.Value, -360f, 360f); } } } public void OnPointerDown(PointerEventData eventData) { fingerDown = true; if(_touchCount < 2) { _touchCount++; } } public void OnPointerUp(PointerEventData eventData) { fingerDown = false; if(_touchCount > 0) { _touchCount--; } } private void OnValidate() { MigrateLegacyDeadZone(); orbitScaleMin = Mathf.Max(0.01f, orbitScaleMin); orbitScaleMax = Mathf.Max(orbitScaleMin, orbitScaleMax); defaultOrbitScale = Mathf.Clamp(defaultOrbitScale, orbitScaleMin, orbitScaleMax); SyncRadialAxisRange(); } /// Keeps Cinemachine RadialAxis range in sync with serialized min/max. private void SyncRadialAxisRange() { if (orbital == null) { return; } float min = Mathf.Min(orbitScaleMin, orbitScaleMax); float max = Mathf.Max(orbitScaleMin, orbitScaleMax); var radial = orbital.RadialAxis; var range = radial.Range; range.x = min; range.y = max; radial.Range = range; radial.Value = radial.ClampValue(radial.Value); orbital.RadialAxis = radial; } private void ResetFollowCameraAxes() { if (orbital == null) { return; } orbital.HorizontalAxis.Value = 208; orbital.VerticalAxis.Value = -268; SyncRadialAxisRange(); var radial = orbital.RadialAxis; radial.Value = radial.ClampValue(defaultOrbitScale); orbital.RadialAxis = radial; } private void ApplyRadialZoomDelta(float delta) { if (!CanApplyZoom || orbital == null || Mathf.Abs(delta) < 1e-6f) { return; } var radial = orbital.RadialAxis; radial.Value = radial.ClampValue(radial.Value + delta); orbital.RadialAxis = radial; } private void UpdatePinchZoom() { if (!CanApplyZoom || orbital == null) { _pinchZoomActive = false; return; } if (_touchCount == 2) { Touch t0 = Input.GetTouch(0); Touch t1 = Input.GetTouch(1); float dist = Vector2.Distance(t0.position, t1.position); if (!_pinchZoomActive) { _pinchZoomActive = true; _lastPinchDistance = dist; } else { float delta = dist - _lastPinchDistance; _lastPinchDistance = dist; // Pinch outward increases distance → larger scale ApplyRadialZoomDelta(delta * zoomSensitivityPinch); } } else { _pinchZoomActive = false; } } private void MigrateLegacyDeadZone() { if (_migratedDeadZoneToNormalized) { return; } // Older values were serialized in pixels; convert once to normalized short-side ratio. if (dragDeadZone > 0.05f) { dragDeadZone /= 1080f; } dragDeadZone = Mathf.Max(0.0001f, dragDeadZone); _migratedDeadZoneToNormalized = true; } private void Update() { //todo: should not always update if (_cinemachineCamera.Follow == null && CECGameRun.Instance.GetHostPlayer() != null) { if(CECGameRun.Instance.GetHostPlayer().PointCam != null) { _cinemachineCamera.Follow = CECGameRun.Instance.GetHostPlayer().PointCam; _cinemachineCamera.ForceCameraPosition(CECGameRun.Instance.GetHostPlayer().PointCam.position, Quaternion.identity); ResetFollowCameraAxes(); } else { _cinemachineCamera.Follow = CECGameRun.Instance.GetHostPlayer().transform; _cinemachineCamera.ForceCameraPosition(CECGameRun.Instance.GetHostPlayer().ObjectPosition, Quaternion.identity); ResetFollowCameraAxes(); } } #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBGL if (CanApplyZoom && orbital != null) { float scroll = Input.mouseScrollDelta.y; if (Mathf.Abs(scroll) > Mathf.Epsilon) { ApplyRadialZoomDelta(scroll * zoomSensitivityScroll); } } #endif UpdatePinchZoom(); } public void UpdateFollowObject(Transform followObject) { _cinemachineCamera.Follow = followObject; _cinemachineCamera.ForceCameraPosition(followObject.position, Quaternion.identity); ResetFollowCameraAxes(); } } }