In this tutorial, we will implement a configurable third-person camera control in Unity for an animated character. Our target game object for this tutorial will be a proper animated game object rather than a cylinder or a capsule.
Look at the video below on what we will achieve by the end of our tutorial.
This tutorial is divided into three sections.
- In Section 1. we will concentrate on setting up the scene and the animated character.
- In Section 2, we will implement the necessary third-person camera controls for PC, Linux and Mac build settings.
- In Section 3 we will port the third-person camera controls for touch screen devices with a virtual joystick and deploy to an Android phone.
Read also: Implementing Player Controls With Finite State Machine Using C# in Unity
Section 1 – Scene and Character Setup
Create a Unity 3D project
Create a Simple Scene
Download the following free Assets from the Unity Asset Store
Import these assets. We will use this as our player and centre our third-person camera control around this player.
The imported assets comprise three prefabs and one animation controller. We will use one of these prefabs and create a new animation controller that will suit our needs.
Create the Character’s Animation Controller
Create a new folder called Resources in Assets and create another folder called Animations inside the Resources folder.
Right-click on the Project window → Create → Animation Controller.
Name it PlayerAnim.
We will use Blend Tree to create movement control for the player. To know more about Unity Blend Tree refer to Unity documentation.
Right-click anywhere in the Base Layer window of the PlayerAnim animation controller and click on Create State → From New Blend Tree
Double click and select the newly created Blend Tree.
Rename it to Ground Movement or any other name that you fancy. Select Ground Movement and change the Blend Type to 2D Freeform Directional.
Now add two new float parameters called PosX and PosZ. These are the parameters that we will use to manipulate the animations of the player based on inputs.
We will now start adding animations to the Ground Movement Blend Tree. We will do this by clicking on the + sign in Motions field.
Click on the + and select Add Motion Field.
Create the Player
We will now create the actual player. For this, we create an empty game object and rename it to Player. Drag and drop the HPCharacter prefab to the Player game object. Change the Animation Controller to the newly created PlayerAnim.
Add the Character Controller component to Player. Change the Center Y value to 1.1.
For better view add the below image as a texture to the ground.
Go to Window → Rendering → Lighting Settings and open the Lighting window. Now check the Baked Global Illumination and click Generate Lighting.
Create a Player movement script
We will now create a movement controller for the character. This will allow us to control the movement of the character by using keyboard and mouse inputs for PCs and a virtual joystick for touch screen devices. For now, we will only concentrate on build for PC, Linux and Mac. We will handle touch screen devices in the later part of the tutorial.
Create a new script file called PlayerMovement.cs and attach the script to the Player game object. Remember that Player is an empty game object that holds the actual character with the AnimationController.
Add the following content into PlayerMovement.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class PlayerMovement : MonoBehaviour
{
[HideInInspector]
public CharacterController mCharacterController;
public Animator mAnimator;
public float mWalkSpeed = 1.5f;
public float mRunSpeed = 3.0f;
public float mRotationSpeed = 50.0f;
public float mGravity = -30.0f;
private Vector3 mVelocity = new Vector3(0.0f, 0.0f, 0.0f);
// Start is called before the first frame update
void Start()
{
mCharacterController = GetComponent<CharacterController>();
}
void Update()
{
Move();
}
public void Move()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
float speed = mWalkSpeed;
if (Input.GetKey(KeyCode.LeftShift))
{
speed = mRunSpeed;
}
mCharacterController.Move(transform.forward * v * speed * Time.deltaTime);
transform.Rotate(0.0f, h * mRotationSpeed * Time.deltaTime, 0.0f);
if (mAnimator != null)
{
mAnimator.SetFloat("PosZ", v * speed / mRunSpeed);
}
// apply gravity.
mVelocity.y += mGravity * Time.deltaTime;
mCharacterController.Move(mVelocity * Time.deltaTime);
if (mCharacterController.isGrounded && mVelocity.y < 0)
mVelocity.y = 0f;
}
}
Code language: C# (cs)
Set the Animator value by dragging and dropping the HPCharacter into the M Animator field of the PlayerMovement.
Click Play and test the application.
We will come back to PlayerMovement later again to enhance its functionality. We move on to implement the third-person camera control now one step at a time. By the time we finish, we hope to create a robust third-person camera control system.
Section 2 – Implement Third-person Camera Controls
In this tutorial we will implement a wide variety of third-person camera controls and depending on your need you can use one of these camera control types. We will also make the control parameters variable so that they can be configured. Let’s proceed.
Create a new script called ThirdPersonCamera.cs and add it as component of the Main Camera.
1. Track – third-person camera control
In this mode we will let the camera track a game object by setting the LookAt value to be the designated game object’s position. In this mode the camera does not change it position. This is by far the simplest third-person camera control.
Double click on ThirdPersonCamera.cs and open the file in your favourite IDE.
We will implement our third-person camera functionality in LateUpdate.
From Unity: LateUpdate is called every frame if the Behaviour is enabled. LateUpdate is called after all Update functions have been called. This is useful to order script execution. For example, a follow camera should always be implemented in LateUpdate because it tracks objects that might have moved inside Update.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThirdPersonCamera : MonoBehaviour
{
public enum ThirdPersonCameraType
{
Track,
}
public ThirdPersonCameraType mThirdPersonCameraType = ThirdPersonCameraType.Track;
public Transform mPlayer;
void Start()
{
}
void Update()
{
}
void LateUpdate()
{
switch (mThirdPersonCameraType)
{
case ThirdPersonCameraType.Track:
{
CameraMove_Track();
break;
}
}
}
void CameraMove_Track()
{
transform.LookAt(mPlayer.transform.position);
}
}
Code language: C# (cs)
You can see that we have created an enum type called ThirdPersonCameraType. This is because we intend to implement more than one type of third-party camera control.
Click play and view the behavior.
For our animated character, the position of the transform is at height 0. If you want to track the target’s full height then we will need to add the player’s height into the LookAt position.
So, we change the CameraMove_Track function to the cater for the player’s height. For this we will need to add another public variable called mPlayerHeight.
// add a new variable to allow setting of the player's height.
public float mPlayerHeight = 2.0f;
*****
void CameraMove_Track()
{
Vector3 targetPos = mPlayer.transform.position;
targetPos.y += mPlayerHeight;
transform.LookAt(targetPos);
}
Code language: C# (cs)
Now, click play and see the behaviour.
You can see the difference between the two implementations. You can change the height of the player depending on your where you would want the camera to look at.
2. Follow – third-person camera control
Our next implementation of a third-person camera control will be called Follow where the camera follows the target player. The player moves and the camera follows. However, the rotation of the player does not affect the camera.
For this we will introduce three new variables. These are:
- The initial position offset of the camera. This will determine where the camera is with respect to the player.
- The initial rotation offset of the camera
- The damping factor to smoothen the changes to the position and rotation of the camera.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThirdPersonCamera : MonoBehaviour
{
public enum ThirdPersonCameraType
{
Track,
Follow,
}
public ThirdPersonCameraType mThirdPersonCameraType =
ThirdPersonCameraType.Track;
public Transform mPlayer;
public float mPlayerHeight = 2.0f;
public Vector3 mPositionOffset = new Vector3(0.0f, 2.0f, -2.5f);
public Vector3 mAngleOffset = new Vector3(0.0f, 0.0f, 0.0f);
[Tooltip("The damping factor to smooth the changes " +
"in position and rotation of the camera.")]
public float mDamping = 1.0f;
void Start()
{
}
void Update()
{
}
void LateUpdate()
{
switch (mThirdPersonCameraType)
{
case ThirdPersonCameraType.Track:
{
CameraMove_Track();
break;
}
case ThirdPersonCameraType.Follow:
{
CameraMove_Follow();
break;
}
}
}
void CameraMove_Track()
{
Vector3 targetPos = mPlayer.transform.position;
targetPos.y += mPlayerHeight;
transform.LookAt(targetPos);
}
// Follow camera implementation.
void CameraMove_Follow()
{
// We apply the initial rotation to the camera.
Quaternion initialRotation =
Quaternion.Euler(mAngleOffset);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
initialRotation,
mDamping * Time.deltaTime);
// Now we calculate the camera transformed axes.
Vector3 forward = transform.rotation * Vector3.forward;
Vector3 right = transform.rotation * Vector3.right;
Vector3 up = transform.rotation * Vector3.up;
// We then calculate the offset in the
// camera's coordinate frame.
Vector3 targetPos = mPlayer.position;
Vector3 desiredPosition = targetPos
+ forward * mPositionOffset.z
+ right * mPositionOffset.x
+ up * mPositionOffset.y;
// Finally, we change the position of the camera,
// not directly, but by applying Lerp.
Vector3 position = Vector3.Lerp(
transform.position,
desiredPosition,
Time.deltaTime * mDamping);
transform.position = position;
}
}
Code language: C# (cs)
We also see that the mPlayerHeight is redundant now as we can use the mPositionOffset.y as the height of the player for our CameraMove_Track implementation.
*****
void CameraMove_Track()
{
Vector3 targetPos = mPlayer.transform.position;
// We removed mPlayerHeight and replaced
// with the mPositionOffset.y
targetPos.y += mPositionOffset.y;
transform.LookAt(targetPos);
}
*****
Code language: C# (cs)
Click Play and view the behaviour.
You can see that by toggling from Track to Follow how the camera behaves. You can also change the various parameters to see how it affects the camera view.
3. Follow and Track Rotation – third-person camera control
This is an extension of the Follow third-person camera control where the camera follows and rotates based on the player’s rotation. This mode basically keeps track of the player’s position as well as the rotation.
To implement this we add a new type in enum ThirdPersonCameraType called Follow_TrackRotation. After that, we refactor the CameraMove_Follow function with an input bool parameter called allowRotationTracking. We then amend the function to handle the player’s rotation.
// Follow camera implementation.
// Refactored to allow only positional tracking or
// positional tracking and rotational tracking. For this we
// added a bool parameter called allowRotationTracking.
void CameraMove_Follow(bool allowRotationTracking = false)
{
// We apply the initial rotation to the camera.
Quaternion initialRotation = Quaternion.Euler(mAngleOffset);
// added the following code to allow rotation
// tracking of the player
// so that our camera rotates when the player
// rotates and at the same
// time maintain the initial rotation offset.
if (allowRotationTracking)
{
Quaternion rot = Quaternion.Lerp(
transform.rotation,
mPlayer.rotation * initialRotation,
Time.deltaTime * mDamping);
transform.rotation = rot;
}
else
{
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
initialRotation,
mDamping * Time.deltaTime);
}
// Now we calculate the camera transformed axes.
Vector3 forward = transform.rotation * Vector3.forward;
Vector3 right = transform.rotation * Vector3.right;
Vector3 up = transform.rotation * Vector3.up;
// We then calculate the offset in the
// camera's coordinate frame.
Vector3 targetPos = mPlayer.position;
Vector3 desiredPosition = targetPos
+ forward * mPositionOffset.z
+ right * mPositionOffset.x
+ up * mPositionOffset.y;
// Finally, we change the position of the camera,
// not directly, but by applying Lerp.
Vector3 position = Vector3.Lerp(transform.position,
desiredPosition,
Time.deltaTime * mDamping);
transform.position = position;
}
Code language: C# (cs)
Click Play and observe the behaviour.
4. Follow and Independent Rotation – third-person camera control
In this mode of third-person camera the camera follows the target player but the camera has independent rotation based on mouse input. This mode of camera is generally used in touch screen devices where the virtual joystick controls the player’s motion where as the touch and drag on the screen handles the rotation.
Many times instead of applying the rotation to the player directly the rotation is applied on to the camera. Then whenever the player moves it orients itself to the camera’s forward direction. It allows independent rotation of the camera when the player is stationary.
For this mode we add a new enum value called the Follow_IndependentRotation. We then implement the function named Follow_IndependentRotation.
To implement this mode of camera behaviour we also introduce 3 new public variables and 1 private variable.
void Follow_IndependentRotation()
{
float mx, my;
mx = Input.GetAxis("Mouse X");
my = Input.GetAxis("Mouse Y");
// We apply the initial rotation to the camera.
Quaternion initialRotation = Quaternion.Euler(mAngleOffset);
Vector3 eu = transform.rotation.eulerAngles;
angleX -= my * mRotationSpeed;
// We clamp the angle along the X axis to be between
// the min and max pitch.
angleX = Mathf.Clamp(angleX, mMinPitch, mMaxPitch);
eu.y += mx * mRotationSpeed;
Quaternion newRot = Quaternion.Euler(angleX, eu.y, 0.0f) *
initialRotation;
transform.rotation = newRot;
Vector3 forward = transform.rotation * Vector3.forward;
Vector3 right = transform.rotation * Vector3.right;
Vector3 up = transform.rotation * Vector3.up;
Vector3 targetPos = mPlayer.position;
Vector3 desiredPosition = targetPos
+ forward * mPositionOffset.z
+ right * mPositionOffset.x
+ up * mPositionOffset.y;
Vector3 position = Vector3.Lerp(transform.position,
desiredPosition,
Time.deltaTime * mDamping);
transform.position = position;
}
Code language: C# (cs)
Click play and select Follow_IndependentRotation mode from the dropdown. See the behaviour.
However, you might have noticed that this camera is not very useful unless you make the player follow the camera’s rotation. We now open up our PlayerMovement.cs script and modify to allow the player to rotate based on the camera’s forward direction.
We add a new boolean variable called mFollowCameraForward. This flag will determine if the player should align to the camera’s forward direction. We also add a new float variable called mTurnRate that will determine the rate at which the player will turn to alight itself with the camera’s forward direction. It is same as the damping factor but with a different name.
public void Move()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
float speed = mWalkSpeed;
if (Input.GetKey(KeyCode.LeftShift))
{
speed = mRunSpeed;
}
if (mFollowCameraForward)
{
// Only allow aligning of player's direction when
// there is a movement.
if (v > 0.1 || v < -0.1 || h > 0.1 || h < -0.1)
{
// rotate player towards the camera forward.
Vector3 eu = Camera.main.transform.rotation.eulerAngles;
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
Quaternion.Euler(0.0f, eu.y, 0.0f),
mTurnRate * Time.deltaTime);
}
}
else
{
transform.Rotate(0.0f, h * mRotationSpeed * Time.deltaTime, 0.0f);
}
//transform.Rotate(0.0f, h * mRotationSpeed * Time.deltaTime, 0.0f);
mCharacterController.Move(transform.forward * v * speed * Time.deltaTime);
if (mAnimator != null)
{
mAnimator.SetFloat("PosZ", v * speed / mRunSpeed);
}
// apply gravity.
mVelocity.y += mGravity * Time.deltaTime;
mCharacterController.Move(mVelocity * Time.deltaTime);
if (mCharacterController.isGrounded && mVelocity.y < 0)
mVelocity.y = 0f;
}
Code language: C# (cs)
See the amended code in the highlighted lines.
Click Play now and see the camera as well as the Player behaviour.
5. Top-Down – third-person camera control
The final mode is a simple Top-Down camera mode where the camera looks down on the player from an altitude. This mode will not use the mRotationOffset and the x and z values of the mPositionOffset.
The implementation is simple as shown below.
void CameraMove_TopDown()
{
// For topdown camera we do not use the x and z offsets.
Vector3 targetPos = mPlayer.position;
targetPos.y += mPositionOffset.y;
Vector3 position = Vector3.Lerp(
transform.position,
targetPos,
Time.deltaTime * mDamping);
transform.position = position;
transform.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
}
Code language: C# (cs)
Click Play, select the TopDown mode and view the behaviour. Remember to disable the M Follow Camera Forward boolean field for the PlayerMovement.
This concludes the tutorial on how to create a custom third-person camera control in Unity. This tutorial provides almost all possible implementations of a possible third-person camera control. If you have any doubts or any questions please feel free to write in the comments section.
The Final Scripts
PlayerMovement.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class PlayerMovement : MonoBehaviour
{
[HideInInspector]
public CharacterController mCharacterController;
public Animator mAnimator;
public float mWalkSpeed = 1.5f;
public float mRunSpeed = 3.0f;
public float mRotationSpeed = 50.0f;
public float mGravity = -30.0f;
[Tooltip("Only useful with Follow and Independent " +
"Rotation - third - person camera control")]
public bool mFollowCameraForward = false;
public float mTurnRate = 200.0f;
private Vector3 mVelocity = new Vector3(0.0f, 0.0f, 0.0f);
// Start is called before the first frame update
void Start()
{
mCharacterController = GetComponent<CharacterController>();
}
void Update()
{
Move();
}
public void Move()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
float speed = mWalkSpeed;
if (Input.GetKey(KeyCode.LeftShift))
{
speed = mRunSpeed;
}
if (mFollowCameraForward)
{
// Only allow aligning of player's direction
// when there is a movement.
if (v > 0.1 || v < -0.1 || h > 0.1 || h < -0.1)
{
// rotate player towards the camera forward.
Vector3 eu = Camera.main.transform.rotation.eulerAngles;
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
Quaternion.Euler(0.0f, eu.y, 0.0f),
mTurnRate * Time.deltaTime);
}
}
else
{
transform.Rotate(0.0f, h * mRotationSpeed * Time.deltaTime, 0.0f);
}
//transform.Rotate(0.0f, h * mRotationSpeed * Time.deltaTime, 0.0f);
mCharacterController.Move(transform.forward * v * speed * Time.deltaTime);
if (mAnimator != null)
{
mAnimator.SetFloat("PosZ", v * speed / mRunSpeed);
}
// apply gravity.
mVelocity.y += mGravity * Time.deltaTime;
mCharacterController.Move(mVelocity * Time.deltaTime);
if (mCharacterController.isGrounded && mVelocity.y < 0)
mVelocity.y = 0f;
}
}
Code language: C# (cs)
ThirdPersonCamera.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThirdPersonCamera : MonoBehaviour
{
public enum ThirdPersonCameraType
{
Track,
Follow,
Follow_TrackRotation,
Follow_IndependentRotation,
TopDown
}
public ThirdPersonCameraType mThirdPersonCameraType =
ThirdPersonCameraType.Track;
public Transform mPlayer;
public Vector3 mPositionOffset = new Vector3(0.0f, 2.0f, -2.5f);
public Vector3 mAngleOffset = new Vector3(0.0f, 0.0f, 0.0f);
[Tooltip("The damping factor to smooth the changes " +
"in position and rotation of the camera.")]
public float mDamping = 1.0f;
[Header("Follow Independent Rotation")]
public float mMinPitch = -30.0f;
public float mMaxPitch = 30.0f;
public float mRotationSpeed = 5.0f;
private float angleX = 0.0f;
void Start()
{
}
void Update()
{
}
void LateUpdate()
{
switch (mThirdPersonCameraType)
{
case ThirdPersonCameraType.Track:
{
CameraMove_Track();
break;
}
case ThirdPersonCameraType.Follow:
{
CameraMove_Follow();
break;
}
case ThirdPersonCameraType.Follow_TrackRotation:
{
// refactored to not allow rotational tracking.
CameraMove_Follow(true);
break;
}
case ThirdPersonCameraType.Follow_IndependentRotation:
{
// refactored to not allow rotational tracking.
Follow_IndependentRotation();
break;
}
case ThirdPersonCameraType.TopDown:
{
CameraMove_TopDown();
break;
}
}
}
void CameraMove_Track()
{
Vector3 targetPos = mPlayer.transform.position;
// We removed mPlayerHeight and replaced with the mPositionOffset.y
targetPos.y += mPositionOffset.y;
transform.LookAt(targetPos);
}
// Follow camera implementation.
// Refactored to allow only positional tracking or
// positional tracking and rotational tracking. For this we
// added a bool parameter called allowRotationTracking.
void CameraMove_Follow(bool allowRotationTracking = false)
{
// We apply the initial rotation to the camera.
Quaternion initialRotation = Quaternion.Euler(mAngleOffset);
// added the following code to allow rotation tracking of the player
// so that our camera rotates when the player rotates and at the same
// time maintain the initial rotation offset.
if (allowRotationTracking)
{
Quaternion rot = Quaternion.Lerp(transform.rotation,
mPlayer.rotation * initialRotation,
Time.deltaTime * mDamping);
transform.rotation = rot;
}
else
{
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
initialRotation,
mDamping * Time.deltaTime);
}
// Now we calculate the camera transformed axes.
Vector3 forward = transform.rotation * Vector3.forward;
Vector3 right = transform.rotation * Vector3.right;
Vector3 up = transform.rotation * Vector3.up;
// We then calculate the offset in the
// camera's coordinate frame.
Vector3 targetPos = mPlayer.position;
Vector3 desiredPosition = targetPos
+ forward * mPositionOffset.z
+ right * mPositionOffset.x
+ up * mPositionOffset.y;
// Finally, we change the position of the camera,
// not directly, but by applying Lerp.
Vector3 position = Vector3.Lerp(transform.position,
desiredPosition,
Time.deltaTime * mDamping);
transform.position = position;
}
void Follow_IndependentRotation()
{
float mx, my;
mx = Input.GetAxis("Mouse X");
my = Input.GetAxis("Mouse Y");
// We apply the initial rotation to the camera.
Quaternion initialRotation = Quaternion.Euler(mAngleOffset);
Vector3 eu = transform.rotation.eulerAngles;
angleX -= my * mRotationSpeed;
// We clamp the angle along the X axis to be between
// the min and max pitch.
angleX = Mathf.Clamp(angleX, mMinPitch, mMaxPitch);
eu.y += mx * mRotationSpeed;
Quaternion newRot = Quaternion.Euler(angleX, eu.y, 0.0f) *
initialRotation;
transform.rotation = newRot;
Vector3 forward = transform.rotation * Vector3.forward;
Vector3 right = transform.rotation * Vector3.right;
Vector3 up = transform.rotation * Vector3.up;
Vector3 targetPos = mPlayer.position;
Vector3 desiredPosition = targetPos
+ forward * mPositionOffset.z
+ right * mPositionOffset.x
+ up * mPositionOffset.y;
Vector3 position = Vector3.Lerp(transform.position,
desiredPosition,
Time.deltaTime * mDamping);
transform.position = position;
}
void CameraMove_TopDown()
{
// For topdown camera we do not use the x and z offsets.
Vector3 targetPos = mPlayer.position;
targetPos.y += mPositionOffset.y;
Vector3 position = Vector3.Lerp(
transform.position,
targetPos,
Time.deltaTime * mDamping);
transform.position = position;
transform.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
}
}
Code language: C# (cs)
Read Section 3 – Third-Person Camera Control in Unity for Android
Read My Other Tutorials
- Implement a Generic Pathfinder in Unity using C#
- Create a Jigsaw Puzzle Game in Unity
- Generic Finite State Machine Using C#
- Implement Bezier Curve using C# in Unity
- Create a Jigsaw Tile from an Existing Image
- Create a Jigsaw Board from an Existing Image
- Solving 8 puzzle problem using A* star search
- A Configurable Third-Person Camera in Unity
- Player Controls With Finite State Machine Using C# in Unity
- Finite State Machine Using C# Delegates in Unity
- Enemy Behaviour With Finite State Machine Using C# Delegates in Unity
- Augmented Reality – Fire Effect using Vuforia and Unity
- Implementing a Finite State Machine Using C# in Unity
- Solving 8 puzzle problem using A* star search in C++
- What Are C# Delegates And How To Use Them
- How to Generate Mazes Using Depth-First Algorithm
A committed and optimistic professional who brings passion and enthusiasm to help motivate, guide and mentor young students into their transition to the Industry and reshape their careers for a fulfilling future. The past is something that you cannot undo. The future is something that you can build.
I enjoy coding, developing games and writing tutorials. Visit my GitHub to see the projects I am working on right now.
Educator | Developer | Mentor