This example demonstrates how to implement a first-person controller in Unity using Cinemachine. It utilizes the CinemachineVirtual Camera's 3rd Person Follow for a comprehensive experience. The script includes configurations for camera settings, mouse sensitivity, and managing player input for movement, rotation, crouching, and jumping.
Furthermore, the example seamlessly integrates events to handle camera transitions during crouch and uncrouch actions. Overall, it provides a solid foundation for a first-person perspective with Cinemachine integration, effectively managing both camera and player interactions.
In this setup, the Character will manage the yaw rotation (rotation along the character's up-axis), while the parented 'Camera Target' GameObject will handle the camera's pitch rotation.
public class FirstPersonController : MonoBehaviour
{
..
/// <summary>
/// Add input (affecting Yaw).
/// This is applied to the Character's rotation.
/// </summary>
public void AddControlYawInput(float value)
{
_character.AddYawInput(value);
}
/// <summary>
/// Add input (affecting Pitch).
/// This is applied to the cameraTarget's local rotation.
/// </summary>
public void AddControlPitchInput(float value, float minValue = -80.0f, float maxValue = 80.0f)
{
if (value == 0.0f)
return;
_cameraTargetPitch = MathLib.ClampAngle(_cameraTargetPitch + value, minValue, maxValue);
cameraTarget.transform.localRotation = Quaternion.Euler(-_cameraTargetPitch, 0.0f, 0.0f);
}
}
It's important to disable the character's rotation mode since, in this example, we'll handle it ourselves.
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
// Disable Character's rotation mode, we'll handle it here
_character.SetRotationMode(Character.RotationMode.None);
}
Additionally, we'll use the CharacterCrouched and UnCrouched events to trigger a crouch/uncrouch animation, relying on Cinemachine's transitions.
private void OnEnable()
{
// Subscribe to Character events
_character.Crouched += OnCrouched;
_character.UnCrouched += OnUnCrouched;
}
private void OnDisable()
{
// Unsubscribe to Character events
_character.Crouched -= OnCrouched;
_character.UnCrouched -= OnUnCrouched;
}
..
/// <summary>
/// When character crouches, toggle Crouched / UnCrouched cameras.
/// </summary>
private void OnCrouched()
{
crouchedCamera.SetActive(true);
unCrouchedCamera.SetActive(false);
}
/// <summary>
/// When character un-crouches, toggle Crouched / UnCrouched cameras.
/// </summary>
private void OnUnCrouched()
{
crouchedCamera.SetActive(false);
unCrouchedCamera.SetActive(true);
}
Finally, we handle player input:
private void Update()
{
// Movement input
Vector2 moveInput = new Vector2
{
x = Input.GetAxisRaw("Horizontal"),
y = Input.GetAxisRaw("Vertical")
};
// Movement direction relative to Character's forward
Vector3 movementDirection = Vector3.zero;
movementDirection += _character.GetRightVector() * moveInput.x;
movementDirection += _character.GetForwardVector() * moveInput.y;
// Set Character movement direction
_character.SetMovementDirection(movementDirection);
// Look input
Vector2 lookInput = new Vector2
{
x = Input.GetAxisRaw("Mouse X"),
y = Input.GetAxisRaw("Mouse Y")
};
// Add yaw input, this update character's yaw rotation
AddControlYawInput(lookInput.x * lookSensitivity.x);
// Add pitch input (look up / look down), this update cameraTarget's local rotation
AddControlPitchInput(lookInput.y * lookSensitivity.y, minPitch, maxPitch);
// Crouch input
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.C))
_character.Crouch();
else if (Input.GetKeyUp(KeyCode.LeftControl) || Input.GetKeyUp(KeyCode.C))
_character.UnCrouch();
// Jump input
if (Input.GetButtonDown("Jump"))
_character.Jump();
else if (Input.GetButtonUp("Jump"))
_character.StopJumping();
}
Third-Person Controller
The following example illustrates the implementation of a third-person controller in Unity using Cinemachine. It leverages the Cinemachine Virtual Camera's 3rd Person Follow feature for a comprehensive experience. The controller encompasses configurations for camera settings, mouse sensitivity, and the management of player input for movement, rotation, crouching, and jumping.
Initially, we implement methods to control the camera's rotation and adjust its follow distance:
/// <summary>
/// Add input (affecting Yaw).
/// This is applied to the followTarget's yaw rotation.
/// </summary>
public void AddControlYawInput(float value, float minValue = -180.0f, float maxValue = 180.0f)
{
if (value != 0.0f) _cameraTargetYaw = MathLib.ClampAngle(_cameraTargetYaw + value, minValue, maxValue);
}
/// <summary>
/// Add input (affecting Pitch).
/// This is applied to the followTarget's pitch rotation.
/// </summary>
public void AddControlPitchInput(float value, float minValue = -80.0f, float maxValue = 80.0f)
{
if (value == 0.0f)
return;
if (invertLook)
value = -value;
_cameraTargetPitch = MathLib.ClampAngle(_cameraTargetPitch + value, minValue, maxValue);
}
/// <summary>
/// Adds input (affecting follow distance).
/// </summary>
public virtual void AddControlZoomInput(float value)
{
followDistance = Mathf.Clamp(followDistance - value, followMinDistance, followMaxDistance);
}
Subsequently, we adjust both the rotation of the followTargetGameObject and the CameraDistance of the Cinemachine virtual camera. This essentially means that we are exerting control over the Cinemachine camera by manipulating the followTargetGameObject.
/// <summary>
/// Update followTarget rotation using _cameraTargetYaw and _cameraTargetPitch values and its follow distance.
/// </summary>
private void UpdateCamera()
{
followTarget.transform.rotation = Quaternion.Euler(_cameraTargetPitch, _cameraTargetYaw, 0.0f);
_cmThirdPersonFollow.CameraDistance =
Mathf.SmoothDamp(_cmThirdPersonFollow.CameraDistance, followDistance, ref _followDistanceSmoothVelocity, 0.1f);
}
private void LateUpdate()
{
// Update cameraTarget rotation using our yaw and pitch values
UpdateCamera();
}
Finally, we handle player input:
private void Update()
{
// Movement input
Vector2 inputMove = new Vector2()
{
x = Input.GetAxisRaw("Horizontal"),
y = Input.GetAxisRaw("Vertical")
};
// Set Movement direction in world space
Vector3 movementDirection = Vector3.zero;
movementDirection += Vector3.right * inputMove.x;
movementDirection += Vector3.forward * inputMove.y;
// If character has a camera assigned...
if (_character.camera)
{
// Make movement direction relative to its camera view direction
movementDirection = movementDirection.relativeTo(_character.cameraTransform);
}
// Set Character movement direction
_character.SetMovementDirection(movementDirection);
// Crouch input
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.C))
_character.Crouch();
else if (Input.GetKeyUp(KeyCode.LeftControl) || Input.GetKeyUp(KeyCode.C))
_character.UnCrouch();
// Jump input
if (Input.GetButtonDown("Jump"))
_character.Jump();
else if (Input.GetButtonUp("Jump"))
_character.StopJumping();
// Look input
Vector2 lookInput = new Vector2
{
x = Input.GetAxisRaw("Mouse X"),
y = Input.GetAxisRaw("Mouse Y")
};
AddControlYawInput(lookInput.x * lookSensitivity.x);
AddControlPitchInput(lookInput.y * lookSensitivity.y, minPitch, maxPitch);
// Zoom (in / out) input
float mouseScrollInput = Input.GetAxisRaw("Mouse ScrollWheel");
AddControlZoomInput(mouseScrollInput);
}