using Sirenix.OdinInspector; using UnityEngine; using VOID.Generic.Player.Input; namespace VOID.Generic.Player { /// /// Manager with full control of camera with movement by player. /// [DefaultExecutionOrder(-1)] public class CameraManager : MonoBehaviour { public static CameraManager current {get; private set;} // Camera Movement Settings [Title("Camera Horizontal Movement")] [SerializeField, MinValue(1f), MaxValue(10f), SuffixLabel("m/s")] private float moveSpeed = 5f; [SerializeField, MinValue(1f), MaxValue(5f)] private float moveSpeedFastMultiplier = 2.5f; // Camera's target elevating [Title("Camera Vertical Movement")] [SerializeField, MinValue(1f), MaxValue(20f), SuffixLabel("m/s")] private float elevateSpeed = 1f; // Camera Rotate and Tilt Settings [Title("Camera Rotate and Tilt")] [SerializeField, MinValue(0.01f), MaxValue(1f)] private float rotateSensitivity = 0.1f; [SerializeField, MinValue(0.01f), MaxValue(1f)] private float tiltSensitivity = 0.06f; [SerializeField, MinMaxSlider(10f, 90f, true)] private Vector2 tiltLimit = new(20f, 85f); // Camera Zoom Settings [Title("Camera Zoom")] [SerializeField, MinValue(0.05f), MaxValue(1f)] private float zoomSensitivity = 0.3f; [SerializeField, MinMaxSlider(1f, 30f, true)] private Vector2 zoomLimit = new(2f, 15f); [SerializeField] private float zoomCurrent = 8f; // Camera Offset [Title("Camera Offset")] [SerializeField, MinValue(0f), MaxValue(3f)] private float targetVerticalOffset = 1.0f; [SerializeField] private Vector3 cameraOffset = new(0f, 0f, -1f); // Camera and Target private Camera _camera; private GameObject _target; // Stick properties [Title("Sticking to transform")] public Transform stickTo; // Aditional Components private CameraInputManager _cameraInputManager; private void Awake() { current = this; } private void Start() { _cameraInputManager = CameraInputManager.current; _camera = Camera.main; _target = CameraTarget.InstantiateNew().gameObject; } private void FixedUpdate() { MoveHorizontally(); MoveVertically(); RotateAndTilt(); Zoom(); Offset(); CameraLookAtTarget(); } /// /// Moves camera's target to given position. /// public void MoveTo(Vector3 position) { _target.transform.position = position; stickTo = null; } /// /// Moving camera's target horizontally. /// private void MoveHorizontally() { // No camera movement from player, but it can be sticked to something if (!_cameraInputManager.isMoving) { if (stickTo) _target.transform.position = stickTo.position + new Vector3(0f, 1.5f, 0f); return; } // Player moved camera - unstick camera stickTo = null; // Calculate camera's target next relative position var moveVector = _cameraInputManager.movementDelta * (moveSpeed * Time.deltaTime); if (_cameraInputManager.isMovingFaster) moveVector *= moveSpeedFastMultiplier; var position = _target.transform.TransformDirection(moveVector.x, 0, moveVector.y); _target.transform.position += position; } /// /// Moving camera's target vertically. /// private void MoveVertically() { // IMPORTANT - target's pos already include offset! var targetPos = _target.transform.position; // Find point where camera's target can stick to Physics.Raycast( new Ray(targetPos + Vector3.up*50f, Vector3.down), out var rayCastHit, 100f, LayerManager.Static); // Nothing - seems like camera floating and there is nothing above or below if (!rayCastHit.collider) return; // IMPORTANT - target include offset by default, but not raycast - we need add offset to raycast point! var rayCastPos = rayCastHit.point + new Vector3(0, targetVerticalOffset, 0); // If distance is positive we go UP, otherwise we go DOWN var distance = rayCastPos.y - targetPos.y; // Move camera's target towards new position, if really close then move remaining distance to prevent jiggling var speed = Mathf.Sign(distance) * Mathf.Min(Mathf.Abs(distance), elevateSpeed * Time.fixedDeltaTime); _target.transform.position += new Vector3(0f, speed, 0f); } /// /// Rotating and tilting camera.
/// Rotating camera's target. ///
private void RotateAndTilt() { if (_cameraInputManager.isRotatingAndTilting == false) return; var delta = _cameraInputManager.rotateAndTiltDelta; var tiltDelta = delta.y * tiltSensitivity * -1; var rotateDelta = delta.x * rotateSensitivity; var previousEuler = _camera.transform.rotation.eulerAngles; var nextEuler = previousEuler + new Vector3(tiltDelta, rotateDelta, 0); if (nextEuler.x < tiltLimit.x) nextEuler.x = tiltLimit.x; if (nextEuler.x > tiltLimit.y) nextEuler.x = tiltLimit.y; _camera.transform.rotation = Quaternion.Euler(nextEuler); _target.transform.rotation = Quaternion.Euler(0f, nextEuler.y, 0f); } /// /// Updating camera's zoom. /// private void Zoom() { if (_cameraInputManager.isZooming == false) return; zoomCurrent -= _cameraInputManager.zoomDelta * zoomSensitivity; if (zoomCurrent > zoomLimit.y) zoomCurrent = zoomLimit.y; if (zoomCurrent < zoomLimit.x) zoomCurrent = zoomLimit.x; } /// /// Updating camera's offset from target. /// private void Offset() { var offset = _camera.transform.TransformDirection(Vector3.back * zoomCurrent + cameraOffset); _camera.transform.position = _target.transform.position + offset; } private void CameraLookAtTarget() { _camera.transform.LookAt(_target.transform); } } }