I often see people struggling with their scripts for controlling a camera in their scene. Usually, this comes from cameras being a thing you have to confront very early as a beginner. And often, people have to work with a limited palette of tools while tackling this task, until they get to know Unity better.
This article will summarize how I build different types of camera setups. You don’t have to do it the same way, but the techniques I use might help you get a new point of view for how you can accomplish things in Unity – and not just camera-specific tasks!
The very first, and very important thing to learn is:
You don’t need to put your camera controls in one script.
Feel free to use multiple scripts, perhaps on multiple GameObject
s, to achieve your goal. The following examples will show you how, by explaining a setup for a first-person camera and a top-down camera.
Creating a first-person camera
A first person camera is usually positioned within the collision boundaries of the player character, like a CharacterController
. Its movement is usually completely bound to that of the character, so the camera itself must only handle rotation.
Accordingly, it makes sense to use a camera as a child to the player GameObject
, which has the CharacterController
.
There’s two axes the camera has to rotate around: The local x axis for pitching up and down, and the global y axis for rotating the character. In fact, rotating the character itself so the camera never turns “within the character’s body” is a good idea. So let’s create a little script that rotates the character around the y axis:
public class RotateAroundYAxis : MonoBehaviour { public float turnSpeed = 1f; private void Update() { var input = Input.GetAxis("Mouse X"); transform.Rotate(Vector3.up * input * turnSpeed); } }
We don’t multiply by Time.deltaTime
here as mouse movement, due to the hardware’s nature, already implicitly has a “per second” in it.
If you add this to your CharacterContoller
, the entire character turns freely around the Y axis. Additional things like mouse smoothing can be added later. Now, let’s add the script that pitches the camera up and down.
When writing this script, beginners often struggle with the hurdle of clamping the pitch between two values, as the camera is not supposed to pitch up and down further than 80 degrees or so. The issue people often encounter is that things can rotate in two ways, and without limit. That means that turning -90° yields the result same as turning 270°. In addition, there’s a not-so-obvious mechanic that works in the background as you work with euler angles. Without going into detail, you might assign a -90° rotation, and when you read the value you just set, you see a 270° rotation. This makes clamping hard. But there’s an easy solution: Create your own rotation variable.
public class CameraPitch : MonoBehaviour { private float currentPitch = 0f; public float minPitch = -80f; public float maxPitch = 80f; public float turnSpeed = 1f; private void Update() { var input = Input.GetAxis("Mouse Y"); currentPitch -= input * turnSpeed; currentPitch = Mathf.Clamp(currentPitch, minPitch, maxPitch); transform.localEulerAngles = Vector3.right * currentPitch; } }
This simple script is the key to a headache-free camera pitch. Instead of reading the camera’s rotation, which might have “magically” changed since the last frame, we maintain a simple float variable that doesn’t have any magic to it. We change it rather than the rotation vector. Then, we can easily clamp it and finally, we assign it as the rotational value around the local x axis.
This approach is very simple and easy to understand compared to many camera scripts I’ve seen, and the reason for that is: We used two scripts instead of one. Managing the rotation of an object around two axes at once can make your head hurt. Managing two objects, each rotating around one axis, is much easier. So, even if you don’t have a CharacterController
moving around, consider using a small GameObject
hierarchy which allows you to control pitch and yaw of your camera with two separate scripts.
Creating a top-down camera
Top-down cameras vary in the amount of control they offer to the player. A top-down camera in a strategy game usually allows the player to freely move, rotate, tilt and/or zoom the camera. In an RPG, the camera might simply follow the player character and cannot be controlled any further. But even in this case, a clever camera setup can benefit the design process of setting up the camera just right in the editor.
A battle-proven setup for a top-down camera starts with a GameObject
hierarchy like shown here. An empty GameObject
has another empty GameObject
as child, and the camera is that GameObject
‘s child. What might come across as needlessly complicated might make your life a lot easier. Let me explain what each object does.
The Camera Pivot either follows the object of interest, like the player character:
public class FollowTarget : MonoBehaviour { public Transform target; private void LateUpdate() { if (!target) return; transform.position = target.position; } }
…or it is controlled directly by the player, like in a strategy game:
public class SimpleMove : MonoBehaviour { public float speed = 10; private void Update() { var input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); transform.position += input * speed * Time.deltaTime; } }
While the latter script would probably have to be extended by mouse controls, both scripts are very short so far and it’s very clear what they do. However, both scripts could be extended to add things like smoothness.
Next, the Camera Pitch GameObject
. As the name suggests, it controls nothing but the camera’s pitch, and it does so in the simplest of ways: By pitching itself. In the editor, set its rotation to, say, (60, 0, 0).
The Camera itself also does only one thing now: It moves back. Set its position to (0, 0, -10).
Picture what you have now as a selfie stick. The Camera Pitch object is your wrist, and the camera is positioned at the end of the stick, pointing downwards along the rod to your wrist, which is the center of attention. Play around with the position of the camera and the x axis rotation of the Camera Pitch object. You will see that the camera always points to the position of the Camera Pivot object, but it will be tilted up and down, or it will move closer or further from that point.
You can now add any scripts to this setup. To allow the player to move the camera closer, you can add a short script like this to the camera:
public class CameraDistance : MonoBehaviour { private float currentDistance = 10f; public float minDistance = 5f; public float maxDistance = 15f; public float moveSpeed = 10f; private void Update() { var input = Input.GetAxis("Mouse Scrollwheel"); currentDistance -= input * moveSpeed; currentDistance = Mathf.Clamp(currentDistance, minDistance, maxDistance); transform.localPosition = Vector3.back * currentDistance; } }
This script works the same way as the first-person pitch script above.
To rotate the camera around the y axis, simply rotate the Camera Pivot object using Transform.Rotate. A script to tilt the camera would simply rotate the Camera Pitch object around its x axis, just like the first-person pitch script does.
You might have noticed that all of these scripts do exactly one thing and one thing only – and they all work by changing the position of one object or rotating around one axis. This makes them incredibly easy to maintain, and a good result can be achieved with minimal headache.
But even if your top-down RPG camera is very simple and doesn’t allow the player to control it directly through scripts, setting the camera up like this allows you to quickly tweak your top-down camera in the editor.
Smoothing
To add some sugar to the camera controls, smoothing is your friend.
In the camera setup context, smoothing describes a little piece of code that softens the movement or rotation of the camera.
The basic idea of smoothing is: Instead of setting the new position or rotation, set the value as a target and interpolate towards it. In code, instead of this:
currentDistance += input; transform.localPosition = Vector3.back * currentDistance;
…do this:
targetDistance += input; currentDistance = Mathf.Lerp(currentDistance, targetDistance, smoothingSpeed * Time.deltaTime); transform.localPosition = Vector3.back * currentDistance;
By splitting camera setup functionality into very small scripts, smoothing doesn’t add much complexity. The CameraDistance
script from above would look like this with smoothing:
public class CameraDistance : MonoBehaviour { private float targetDistance = 10f; private float currentDistance = 10f; public float minDistance = 5f; public float maxDistance = 15f; public float moveSpeed = 10f; public float smoothingSpeed = 10f; private void Update() { var input = Input.GetAxis("Mouse ScrollWheel"); targetDistance -= input * moveSpeed; targetDistance = Mathf.Clamp(targetDistance, minDistance, maxDistance); currentDistance = Mathf.Lerp(currentDistance, targetDistance, smoothingSpeed * Time.deltaTime); transform.localPosition = Vector3.back * currentDistance; } }
Conclusion
This article might help you build a nice camera setup, but the lesson goes beyond that.
Split your code into small, easy-to-understand scripts.
And don’t be afraid to use multiple GameObject
s for one task.