using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraController : MonoBehaviour { #region Singleton /// <summary> /// Singleton instance of the CameraController. /// </summary> public static CameraController Instance { get; private set; } #endregion #region Camera Settings /// <summary> /// Determines if mouse input is active for camera control. /// </summary> public bool mouseActive = true; /// <summary> /// Locks user input for camera control. /// </summary> public bool lockUserInput = false; /// <summary> /// Normal speed of camera movement. /// </summary> [SerializeField] private float _cameraSpeedNormal = 100.0f; /// <summary> /// Maximum speed of camera movement. /// </summary> [SerializeField] private float _cameraSpeedMax = 1000.0f; /// <summary> /// Current speed of camera movement. /// </summary> private float _cameraSpeed; /// <summary> /// Horizontal rotation speed. /// </summary> public float _speedH = 2.0f; /// <summary> /// Vertical rotation speed. /// </summary> public float _speedV = 2.0f; /// <summary> /// Current yaw angle of the camera. /// </summary> private float _yaw = 0.0f; /// <summary> /// Current pitch angle of the camera. /// </summary> private float _pitch = 0.0f; #endregion #region Orbit Settings /// <summary> /// Determines if the camera should auto-rotate. /// </summary> public bool _autoRotate = false; /// <summary> /// Threat transform for orbit rotation. /// </summary> public Transform target; /// <summary> /// Distance from the camera to the orbit target. /// </summary> [SerializeField] private float _orbitDistance = 5.0f; /// <summary> /// Horizontal orbit rotation speed. /// </summary> [SerializeField] private float _orbitXSpeed = 120.0f; /// <summary> /// Vertical orbit rotation speed. /// </summary> [SerializeField] private float _orbitYSpeed = 120.0f; /// <summary> /// Speed of camera zoom. /// </summary> [SerializeField] private float _zoomSpeed = 500.0f; /// <summary> /// Minimum vertical angle limit for orbit. /// </summary> public float orbitYMinLimit = -20f; /// <summary> /// Maximum vertical angle limit for orbit. /// </summary> public float orbitYMaxLimit = 80f; /// <summary> /// Minimum distance for orbit. /// </summary> private float _orbitDistanceMin = 10f; /// <summary> /// Maximum distance for orbit. /// </summary> [SerializeField] private float _orbitDistanceMax = 20000f; /// <summary> /// Current horizontal orbit angle. /// </summary> private float _orbitX = 0.0f; /// <summary> /// Current vertical orbit angle. /// </summary> private float _orbitY = 0.0f; #endregion #region Rendering /// <summary> /// Renderer for the orbit target. /// </summary> public Renderer targetRenderer; /// <summary> /// Renderer for the floor. /// </summary> public Renderer floorRenderer; /// <summary> /// Alpha value for material transparency. /// </summary> public float matAlpha; #endregion #region Autoplay Settings /// <summary> /// Speed of camera movement during autoplay. /// </summary> public float autoplayCamSpeed = 2f; /// <summary> /// Duration of horizontal auto-rotation. /// </summary> public float xAutoRotateTime = 5f; /// <summary> /// Duration of vertical auto-rotation. /// </summary> public float yAutoRotateTime = 5f; /// <summary> /// Coroutine for autoplay functionality. /// </summary> private Coroutine autoplayRoutine; #endregion #region Camera Presets /// <summary> /// Represents a preset camera position and rotation. /// </summary> [System.Serializable] public struct CameraPreset { public Vector3 position; public Quaternion rotation; } /// <summary> /// Preset camera position for key 4. /// </summary> CameraPreset fourPos = new CameraPreset(); /// <summary> /// Preset camera position for key 5. /// </summary> CameraPreset fivePos = new CameraPreset(); /// <summary> /// Preset camera position for key 6. /// </summary> CameraPreset sixPos = new CameraPreset(); #endregion #region Movement /// <summary> /// Mapping of translation inputs to movement vectors. /// </summary> private Dictionary<TranslationInput, Vector3> _translationInputToVectorMap; /// <summary> /// Forward movement vector. /// </summary> Vector3 wVector = Vector3.forward; /// <summary> /// Left movement vector. /// </summary> Vector3 aVector = Vector3.left; /// <summary> /// Backward movement vector. /// </summary> Vector3 sVector = Vector3.back; /// <summary> /// Right movement vector. /// </summary> Vector3 dVector = Vector3.right; /// <summary> /// Angle between forward vector and camera direction. /// </summary> public float forwardToCameraAngle; #endregion void SetCameraRotation(Quaternion rotation) { transform.rotation = rotation; _pitch = rotation.eulerAngles.x; _yaw = rotation.eulerAngles.y; } public void SetCameraSpeed(float speed) { _cameraSpeed = speed; } public float GetCameraSpeedMax() { return _cameraSpeedMax; } public float GetCameraSpeedNormal() { return _cameraSpeedNormal; } public bool IsAutoRotate() { return _autoRotate; } public void SetAutoRotate(bool autoRotate) { if (autoRotate && !_autoRotate) { _autoRotate = true; autoplayRoutine = StartCoroutine(AutoPlayRoutine()); } else if (!autoRotate && _autoRotate) { _autoRotate = false; StopCoroutine(autoplayRoutine); } } public static float ClampAngle(float angle, float min, float max) { if (angle < -360F) angle += 360F; if (angle > 360F) angle -= 360F; return Mathf.Clamp(angle, min, max); } private void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } _translationInputToVectorMap = new Dictionary<TranslationInput, Vector3> { {TranslationInput.Forward, wVector}, {TranslationInput.Left, aVector}, {TranslationInput.Back, sVector}, {TranslationInput.Right, dVector}, {TranslationInput.Up, Vector3.up}, {TranslationInput.Down, Vector3.down} }; } // Start is called before the first frame update void Start() { fourPos.position = new Vector3(0, 0, 0); fourPos.rotation = Quaternion.Euler(0,0,0); fivePos.position = new Vector3(0, 0, 0); fivePos.rotation = Quaternion.Euler(0, 0, 0); sixPos.position = new Vector3(0, 0, 0); sixPos.rotation = Quaternion.Euler(0, 0, 0); Vector3 angles = transform.eulerAngles; _orbitX = angles.y; _orbitY = angles.x; UpdateTargetAlpha(); ResetCameraTarget(); } IEnumerator AutoPlayRoutine() { while (true) { float elapsedTime = 0f; while (elapsedTime <= xAutoRotateTime) { _orbitX += Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f; UpdateCamPosition(_orbitX, _orbitY); elapsedTime += Time.unscaledDeltaTime; yield return null; } elapsedTime = 0f; while (elapsedTime <= yAutoRotateTime) { _orbitY -= Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f; UpdateCamPosition(_orbitX, _orbitY); elapsedTime += Time.unscaledDeltaTime; yield return null; } elapsedTime = 0f; while (elapsedTime <= xAutoRotateTime) { _orbitX -= Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f; UpdateCamPosition(_orbitX, _orbitY); elapsedTime += Time.unscaledDeltaTime; yield return null; } elapsedTime = 0f; while (elapsedTime <= yAutoRotateTime) { _orbitY += Time.unscaledDeltaTime * autoplayCamSpeed * _orbitDistance * 0.02f; UpdateCamPosition(_orbitX, _orbitY); elapsedTime += Time.unscaledDeltaTime; yield return null; } yield return null; } } void ResetCameraTarget() { RaycastHit hit; if(Physics.Raycast(transform.position, transform.forward, out hit, float.MaxValue, LayerMask.GetMask("Floor"), QueryTriggerInteraction.Ignore)) { target.transform.position = hit.point; _orbitDistance = hit.distance; Vector3 angles = transform.eulerAngles; _orbitX = angles.y; _orbitY = angles.x; UpdateCamPosition(_orbitX, _orbitY); } else { target.transform.position = transform.position + (transform.forward * 100); _orbitDistance = 100; Vector3 angles = transform.eulerAngles; _orbitX = angles.y; _orbitY = angles.x; //UpdateCamPosition(); } } public void EnableTargetRenderer(bool enable) { targetRenderer.enabled = enable; } public void EnableFloorGridRenderer(bool enable) { floorRenderer.enabled = enable; } public void OrbitCamera(float xOrbit, float yOrbit) { if (target) { _orbitX += xOrbit * _orbitXSpeed * _orbitDistance * 0.02f; _orbitY -= yOrbit * _orbitYSpeed * _orbitDistance * 0.02f; _orbitY = ClampAngle(_orbitY, orbitYMinLimit, orbitYMaxLimit); UpdateCamPosition(_orbitX, _orbitY); } } public void RotateCamera(float xRotate, float yRotate) { _yaw += xRotate * _speedH; _pitch -= yRotate * _speedV; transform.eulerAngles = new Vector3(_pitch, _yaw, 0.0f); } private void UpdateCamPosition(float x, float y) { Quaternion rotation = Quaternion.Euler(y, x, 0); RaycastHit hit; //Debug.DrawLine(target.position, transform.position, Color.red); if (Physics.Linecast(target.position, transform.position, out hit, ~LayerMask.GetMask("Floor"), QueryTriggerInteraction.Ignore)) { _orbitDistance -= hit.distance; } Vector3 negDistance = new Vector3(0.0f, 0.0f, -_orbitDistance); Vector3 position = rotation * negDistance + target.position; UpdateTargetAlpha(); SetCameraRotation(rotation); transform.position = position; } public void ZoomCamera(float zoom) { _orbitDistance = Mathf.Clamp(_orbitDistance - zoom * _zoomSpeed, _orbitDistanceMin, _orbitDistanceMax); UpdateCamPosition(_orbitX, _orbitY); } void UpdateTargetAlpha() { matAlpha = (_orbitDistance - _orbitDistanceMin) / (_orbitDistanceMax - _orbitDistanceMin); matAlpha = Mathf.Max(Mathf.Sqrt(matAlpha) - 0.5f, 0); Color matColor = targetRenderer.material.color; matColor.a = matAlpha; targetRenderer.material.color = matColor; } void UpdateDirectionVectors() { Vector3 cameraToTarget = target.position - transform.position; cameraToTarget.y = 0; forwardToCameraAngle = Vector3.SignedAngle(Vector3.forward, cameraToTarget, Vector3.down); if(forwardToCameraAngle >-45f && forwardToCameraAngle <= 45f) { _translationInputToVectorMap[TranslationInput.Forward] = Vector3.forward; _translationInputToVectorMap[TranslationInput.Left] = Vector3.left; _translationInputToVectorMap[TranslationInput.Back] = Vector3.back; _translationInputToVectorMap[TranslationInput.Right] = Vector3.right; } else if(forwardToCameraAngle > 45f && forwardToCameraAngle <= 135f) { _translationInputToVectorMap[TranslationInput.Forward] = Vector3.left; _translationInputToVectorMap[TranslationInput.Left] = Vector3.back; _translationInputToVectorMap[TranslationInput.Back] = Vector3.right; _translationInputToVectorMap[TranslationInput.Right] = Vector3.forward; } else if(forwardToCameraAngle > 135f || forwardToCameraAngle <= -135f) { _translationInputToVectorMap[TranslationInput.Forward] = Vector3.back; _translationInputToVectorMap[TranslationInput.Left] = Vector3.right; _translationInputToVectorMap[TranslationInput.Back] = Vector3.forward; _translationInputToVectorMap[TranslationInput.Right] = Vector3.left; } else if(forwardToCameraAngle > -135f && forwardToCameraAngle <= -45f) { _translationInputToVectorMap[TranslationInput.Forward] = Vector3.right; _translationInputToVectorMap[TranslationInput.Left] = Vector3.forward; _translationInputToVectorMap[TranslationInput.Back] = Vector3.left; _translationInputToVectorMap[TranslationInput.Right] = Vector3.back; } } public enum TranslationInput { Forward, Left, Back, Right, Up, Down } public void TranslateCamera(TranslationInput input) { UpdateDirectionVectors(); target.Translate(_translationInputToVectorMap[input] * Time.unscaledDeltaTime * _cameraSpeed); UpdateCamPosition(_orbitX, _orbitY); } }