Javascript required
Skip to content Skip to sidebar Skip to footer

Video Game Design How to Make the Camera Zoom

For a developer, the camera is one of the cornerstones of the game development process. From just showing your game view in a chess app to masterfully directing camera movement in a 3D AAA game to obtain cinematic effects, cameras are basically used in any video game ever made, even before actually being called "cameras".

In this article I'm going to explain how to design a camera system for 2D games, and I'm also going to explain some points on how to go about implementing it in one of the most popular game engines out there, Unity.

From 2D to 2.5D: An Extensible Camera System

The camera system we are going to design together is modular and extensible. It has a basic core consisting of several components which will ensure the basic functionality, and then various components/effects that can be optionally used, depending on the situation at hand.

The camera system we are building here is targeted at 2D platform games, but can easily extended to other types of 2D games, 2.5D games or even 3D games.

Mastering 2D Camera in Unity: A Tutorial for Game Developers

I am going to split the camera functionality into two main groups: camera tracking and camera effects.

Tracking

Most of the camera movement we'll do here will be based on tracking. That is the ability of an object, in this case the camera, to track other objects as they move about in the game scene. The types of tracking that we'll be implementing are going to solve some common scenarios encountered in 2d platform games, but they can be extended with new types of tracking for other particular scenarios you might have.

Effects

We will be implementing some cool effects like camera shake, camera zoom, camera fade, and color overlay.

Getting Started

Create a new 2D project in Unity and import standard assets, especially the RobotBoy character. Next, create a ground box and add a character instance. You should be able to walk and jump with your character in your current scene. Make sure the camera is set to Orthographic mode (it is set to Perspective by default).

Tracking a Target

The following script will add basic tracking behavior to our main camera. The script must be attached as a component to the main camera in your scene and it exposes a field for assigning a target object to track. Then the script ensures the x and y coordinates of the camera are the same with the object it tracks. All this processing is done during the Update step.

          [SerializeField] protected Transform trackingTarget;  // ...  void Update() {     transform.position = new Vector3(trackingTarget.position.x,          trackingTarget.position.y, transform.position.z); }                  

Drag the RobotBoy character from your scene hierarchy over the "Tracking Target" field exposed by our following behavior in order to enable tracking of the main character.

Adding Offset

All good, but we can see a limitation straight off the bat: the character is always in the center of our scene. We can see a lot behind the character, which is usually stuff we are not interested in, and we are seeing too little of what is ahead of our character, which might be detrimental to the gameplay.

To solve this, we are adding some new fields to the script that will allow the positioning of the camera at an offset from its target.

          [SerializeField] float xOffset;  [SerializeField] float yOffset;  // ...  void Update() {     transform.position = new Vector3(trackingTarget.position.x + xOffset,          trackingTarget.position.y + yOffset, transform.position.z); }                  

Below you can see a possible configuration for the two new fields:

Smoothing Things Out

The camera movement is pretty stiff and will also produce dizziness in some players from the constant perceived movement of the environment. In order to fix this we'll be adding some delay in camera tracking using linear interpolation, and a new field to control how fast the camera gets into place after the character starts changing its position.

          [SerializeField] protected float followSpeed;  // ...  protected override void Update() {     float xTarget = trackingTarget.position.x + xOffset;     float yTarget = trackingTarget.position.y + yOffset;      float xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed);     float yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed);      transform.position = new Vector3(xNew, yNew, transform.position.z); }                  

Stop the Dizziness: Axis Locking

Since it is not pleasant for your brain to watch the camera going up and down all the time along with the character, we are introducing axis locking. This means we can limit the tracking to only one axis. Then we'll separate our tracking code into axis independent tracking, and we'll take the new locking flags into account.

          [SerializeField] protected bool isXLocked = false;  [SerializeField] protected bool isYLocked = false;  // ...  float xNew = transform.position.x; if (!isXLocked) {     xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); }  float yNew = transform.position.y; if (!isYLocked) {      yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); }                  

Lane System

Now that the camera only tracks the player horizontally, we are limited to the height of one screen. If the character climbs some ladder or jumps higher than this, we have to follow. The way we are doing this is by using a lane system.

Imagine the following scenario:

The character is initially on the lower lane. While the character remains within the boundaries of this lane the camera will be moving only horizontally on lane specific height offset we can set.

As soon as the character enters another lane, the camera will transition to that lane and continue to move horizontally from there on until the next lane change occurs.

Care must be taken on lane design in order to prevent fast lane switching during actions like jumps, which can create confusion for the player. A lane should be changed only if the player's character is going to stay on it for a while.

Lanes' levels can change throughout the game level based on the designer's specific needs, or can be interrupted altogether and another camera tracking system can take their place. Therefore, we need some limiters for specifying lane zones.

Implementation

A possible implementation is to add lanes as simple objects in the scene. We will use their Y position coordinate paired with Y offset in the tracking script above to implement the system. Therefore, their positioning on the X and Z coordinates does not matter.

Add the LaneSystem class to camera, along with the tracking class, and assign the lane objects to the provided array. Also assign the player character to the Reference field. As the reference is positioned between a lane and another lane, the lower one of the two will be used to position the camera.

And the LaneSystem class takes care of moving the camera between lanes, based on reference position. The followSpeed is used here again for position interpolation, to prevent lane switching from being too abrupt:

          [SerializeField] Transform reference;  [SerializeField] List<Transform> lanes;  [SerializeField] float followSpeed = 5f;  // ...  void Update() { 	 float targetYCoord = transform.position.y; 	 if (lanes.Count > 1) 	 { 		 int i = 0; 		 for (i = 0; i < lanes.Count - 1; ++i) 		 { 			 if ((reference.position.y > lanes[i].position.y) && 				 (reference.position.y <= lanes[i + 1].position.y)) 			 { 				 targetYCoord = lanes[i].position.y; 				 break; 			 } 		 }  		 if (i == lanes.Count - 1)  			 targetYCoord = lanes[lanes.Count - 1].position.y; 	 } 	 else 	 { 		 targetYCoord = lanes[0].position.y; 	 } 	 float yCoord = Mathf.Lerp(transform.position.y, targetYCoord, Time.deltaTime * followSpeed); 	 transform.position = new Vector3(transform.position.x, yCoord, transform.position.z); }                  

This implementation is not a WYSIWYG one, and is left as such as an exercise for the reader.

Lock Node System

Having the camera move on lanes is great, but sometimes we need the camera to be locked on to something, a point of interest (POI) in the game scene.

This can be achieved by configuring such POI in the scene and attaching a trigger collider to them. Whenever the character enters that trigger collider, we move the camera and stay on the POI. As the character moves and then leaves the POI's trigger collider, we get back to another type of tracking, usually the standard follow behavior.

The switching of the camera tracking to a lock node and back can be done either by a simple switch or by a stack system, on which tracking modes are pushed and popped.

Implementation

In order to configure a lock node, just create an object (can be empty or like in the screenshot below, a sprite) and attach a large Circle Collider 2D component to it so it marks the area the player will be in when the camera will focus the node. You can choose any type of collider, I'm choosing Circle as an example here. Also create a tag you can easily check for, like "CameraNode" and assign it to this object.

Add the following property to the tracking script on your camera:

          public Transform TrackingTarget {     get     {         return trackingTarget;     }     set     {         trackingTarget = value;     } }                  

Then attach the following script to the player, that will allow it to temporarily switch the camera's target to the lock node you've set. The script will also remember its previous target so it can get back to it when the player is out of the trigger area. You can go ahead and transform this in a full stack if you need that, but for our purpose since we don't overlap multiple lock nodes this will do. Also please be aware that you can tweak the position of the Circle Collider 2D, or again add any other kind of collider to trigger the camera lock, this is just a mere example.

          public class LockBehavior : MonoBehaviour { 	#region Public Fields  	[SerializeField] 	Camera camera;  	[SerializeField] 	string tag;