Camera - Constrained first-person

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. Add a new Unity Camera into the scene (GameObject -> Camera in the top toolbar)
3. Attach the "Constrained First Person Camera" component to the new Camera
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;
#if UNITY\_EDITOR
using UnityEditor;
#endif

namespace AC
{

 public class ConstrainedFirstPersonCamera : \_Camera
 {

 #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

 protected override void Awake ()
 {
 cameraTransform = GetComponent <\_Camera>().Camera.transform;
 base.Awake ();
 }

 private void Update ()
 {
 if (lookAtTransform != null)
 {
 UpdateLookAt ();
 }
 else
 {
 Vector2 inputVector = Vector2.zero;
 if (isDragControlled)
 {
 if (KickStarter.playerInput.GetDragState () == DragState.\_Camera)
 {
 if (KickStarter.playerInput.IsCursorLocked ())
 {
 inputVector = KickStarter.playerInput.GetFreeAim ();
 }
 else
 {
 inputVector = KickStarter.playerInput.GetDragVector () / 500f;
 }
 }
 }
 else
 {
 inputVector = new Vector2 (Input.GetAxis (xAxisName), Input.GetAxis (yAxisName));
 }

 AddInput (inputVector \* inputSensitivity);
 UpdateLerpForward ();
 }
 }

 protected override void OnEnable ()
 {
 EventManager.OnSwitchCamera += OnSwitchCamera;
 base.OnEnable ();
 }

 protected override void OnDisable ()
 {
 EventManager.OnSwitchCamera -= OnSwitchCamera;
 base.OnDisable ();
 }

 #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;
 }
 }

 #if UNITY\_EDITOR

 public void ShowGUI ()
 {
 EditorGUILayout.LabelField ("Input", EditorStyles.boldLabel);
 isDragControlled = EditorGUILayout.Toggle ("Is drag controlled?", isDragControlled);
 if (!isDragControlled)
 {
 xAxisName = EditorGUILayout.TextField ("X-axis input:", xAxisName);
 yAxisName = EditorGUILayout.TextField ("Y-axis input:", yAxisName);
 }
 inputSensitivity = EditorGUILayout.FloatField ("Sensitivity", inputSensitivity);

 EditorGUILayout.Space ();
 EditorGUILayout.LabelField ("Limits", EditorStyles.boldLabel);
 horizontalLimits = EditorGUILayout.Vector2Field ("Horizontal:", horizontalLimits);
 verticalLimits = EditorGUILayout.Vector2Field ("Vertical", verticalLimits);
 edgeDampingFactor = EditorGUILayout.Slider ("Edge damping:", edgeDampingFactor, 0f, 1f);

 EditorGUILayout.Space ();
 EditorGUILayout.LabelField ("Misc", EditorStyles.boldLabel);
 snapForwardOnTransition = EditorGUILayout.Toggle ("Snap on transition?", snapForwardOnTransition);
 }

 #endif

 #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

 }

 #if UNITY\_EDITOR

 [CustomEditor(typeof(ConstrainedFirstPersonCamera))]
 public class ConstrainedFirstPersonCameraEditor : Editor
 {

 public override void OnInspectorGUI ()
 {
 ConstrainedFirstPersonCamera \_target = (ConstrainedFirstPersonCamera) target;
 \_target.ShowGUI ();

 UnityVersionHandler.CustomSetDirty (\_target);
 }

 }

 #endif

}

```