FANDOM


This camera script provides constrained first-person rotation in both the spin and pitch axes.

Note: This is not a replacement for the regular first-person camera - do not attach this to the Player GameObject.  It is meant for situations where the player is e.g. sitting down, and can only rotate a certain amount.

Steps to use:

  1. Paste the code below into a C# script named ConstrainedFirstPersonCamera.cs
  2. Using AC's Scene Manager, create a new SimpleCamera
  3. Attach the "Constrained First Person Camera" component to the new SimpleCamera
  4. Configure the fields as desired
  5. Assign the camera in the normal way, i.e. through the "Camera: Switch" Action or as the scene's default

ConstrainedFirstPersonCamera.cs:

using UnityEngine;
using UnityEngine.Events;

namespace AC
{

    [RequireComponent (typeof (_Camera))]
    public class ConstrainedFirstPersonCamera : MonoBehaviour
    {

        #region Variables

        [Header ("Input")]
        [SerializeField] private float inputSensitivity = 2f;
        [SerializeField] private string xAxisName = "CursorHorizontal";
        [SerializeField] private string yAxisName = "CursorVertical";

        [Header ("Limits")]
        [SerializeField] protected Vector2 verticalLimits = new Vector2 (-50f, 50f);
        [SerializeField] protected Vector2 horizontalLimits = new Vector2 (-50f, 50f);
        [SerializeField] [Range(0f,1f)] float edgeDampingFactor = 0.2f;

        [Header ("Misc")]
        [SerializeField] private bool snapForwardOnTransition = true;

        protected Transform cameraTransform;

        private float lerpDuration;
        private float lerpStartDuration;
        private LerpUtils.Vector3Lerp forwardLerp = new LerpUtils.Vector3Lerp ();

        private Transform lookAtTransform;
        private float lookAtSpeed = 1f;

        #endregion

        #region UnityStandards

        private void Awake ()
        {
            cameraTransform = GetComponent <_Camera>().Camera.transform;
        }

        private void Update ()
        {
            if (lookAtTransform != null)
            {
                UpdateLookAt ();
            }
            else
            {
                Vector2 inputVector = new Vector2 (Input.GetAxis (xAxisName), Input.GetAxis (yAxisName));
                AddInput (inputVector * inputSensitivity);
                UpdateLerpForward ();
            }
        }

        private void OnEnable ()
        {
            EventManager.OnSwitchCamera += OnSwitchCamera;
        }

        private void OnDisable ()
        {
            EventManager.OnSwitchCamera -= OnSwitchCamera;
        }

        #endregion

        #region PublicFunctions

        public void SnapForward ()
        {
            ClearLookAt ();
            cameraTransform.localRotation = Quaternion.identity;
        }

        public void LerpForward (float transitionTime)
        {
            ClearLookAt ();

            if (transitionTime > 0f)
            {
                lerpDuration = transitionTime;
                lerpStartDuration = transitionTime;
            }
            else
            {
                SnapForward ();
            }
        }

        public float SetPitch (float pitchAngle)
        {
            ClearLookAt ();

            pitchAngle = Mathf.Clamp (pitchAngle, verticalLimits.x, verticalLimits.y);
            cameraTransform.localEulerAngles = new Vector3 (-pitchAngle, cameraTransform.localEulerAngles.y, cameraTransform.localEulerAngles.z);
            return pitchAngle;
        }

        public float SetSpin (float spinAngle)
        {
            ClearLookAt ();

            spinAngle = Mathf.Clamp (spinAngle, horizontalLimits.x, horizontalLimits.y);
            cameraTransform.localEulerAngles = new Vector3 (cameraTransform.localEulerAngles.x, spinAngle, cameraTransform.localEulerAngles.z);

            return spinAngle;
        }

        public Vector2 SetAngle (Vector2 input)
        {
            input.y = Mathf.Clamp (input.y, verticalLimits.x, verticalLimits.y);
            input.x = Mathf.Clamp (input.x, horizontalLimits.x, horizontalLimits.y);

            cameraTransform.localEulerAngles = new Vector3 (-input.y, input.x, cameraTransform.localEulerAngles.z);
                
            return input;
        }

        public bool SetLookAt (Transform lookAtTransform, float lookAtSpeed = 1f)
        {
            if (this.lookAtTransform != lookAtTransform)
            {
                this.lookAtTransform = lookAtTransform;
                forwardLerp.Update (Vector3.zero, Vector3.zero, 100f);
            }

            this.lookAtSpeed = lookAtSpeed;

            Vector3 newForward = (lookAtTransform.position - cameraTransform.position).normalized;
            float dot = Vector3.Dot (cameraTransform.forward, newForward);
            return (dot > 0.99f);
        }

        public float GetPitch ()
        {
            float pitchAngle = -cameraTransform.localEulerAngles.x;
            pitchAngle = Mathf.Clamp (pitchAngle, verticalLimits.x, verticalLimits.y);
            return pitchAngle;
        }

        public void SnapToRotation (Quaternion rotation)
        {
            cameraTransform.rotation = rotation;
        }

        public void ClearLookAt ()
        {
            if (lookAtTransform != null)
            {
                lookAtTransform = null;
            }
        }

        #endregion

        #region PrivateFunctions

        private void AddInput (Vector2 lookInput)
        {
            Vector2 lookRotation = new Vector2 (cameraTransform.localEulerAngles.y, -cameraTransform.localEulerAngles.x);

            if (lookRotation.y < -180f)
            {
                lookRotation.y += 360f;
            }

            if (lookRotation.x > 180f)
            {
                lookRotation.x -= 360f;
            }

            lookInput = AddEdgeDamping (lookRotation + lookInput, lookInput);
            SetAngle (lookRotation + lookInput);
        }

        private void UpdateLookAt ()
        {
            Vector3 newForward = (lookAtTransform.position - cameraTransform.position).normalized;
            cameraTransform.forward = forwardLerp.Update (cameraTransform.forward, newForward, lookAtSpeed);
        }

        private void UpdateLerpForward ()
        {
            if (lerpDuration > 0f)
            {
                float t = 1f - (lerpDuration / lerpStartDuration);
                Quaternion newRotation = Quaternion.Slerp (cameraTransform.localRotation, Quaternion.identity, t);
                cameraTransform.localRotation = newRotation;

                lerpDuration -= Time.deltaTime;
                if (lerpDuration <= 0f)
                {
                    lerpDuration = 0f;
                }
            }
        }

        private Vector2 AddEdgeDamping (Vector2 newLookRotation, Vector2 lookInput)
        {
            if (edgeDampingFactor <= 0f) return lookInput;

            float inverseDamping = 1f - edgeDampingFactor;

            // Horizontal
            float xAmount = newLookRotation.x / horizontalLimits.y;
            if (xAmount > inverseDamping && lookInput.x > 0f)
            {
                lookInput.x = Mathf.Lerp (lookInput.x, 0f, xAmount);
            }

            xAmount = -newLookRotation.x / horizontalLimits.x;
            if (xAmount < -inverseDamping && lookInput.x < -0f)
            {
                lookInput.x = Mathf.Lerp (lookInput.x, 0f, -xAmount);
            }

            // Vertical
            float yAmount = newLookRotation.y / verticalLimits.y;
            if (yAmount > inverseDamping && lookInput.y > 0f)
            {
                lookInput.y = Mathf.Lerp (lookInput.y, 0f, yAmount);
            }

            yAmount = -newLookRotation.y / verticalLimits.x;
            if (yAmount < -inverseDamping && lookInput.y < -0f)
            {
                lookInput.y = Mathf.Lerp (lookInput.y, 0f, -yAmount);
            }

            return lookInput;
        }

        private void OnSwitchCamera (_Camera fromCamera, _Camera toCamera, float transitionTime)
        {
            if (toCamera.transform == cameraTransform && snapForwardOnTransition)
            {
                SnapForward ();
            }
        }

        #endregion

        #region GetSet

        public Vector3 FlatForward
        {    
            get
            {
                return new Vector3 (transform.forward.x, 0f, transform.forward.z).normalized;
            }
        }

        #endregion

    }

}
Community content is available under CC-BY-SA unless otherwise noted.