Practical Game Camera with Cinemachine

May 19, 2019

At Frolic Labs, we have been Using Unity to develop our upcoming game Dune Sea for the last 2+ years.

Cinemachine has been around for some time, but only recently became an official part of Unity (starting version 2018.2). While initially it was aimed at cinematographers, Unity recently started promoting it for use in games, as a solution to the game camera. While very sophisticated, it offers the basic usage with easy to use controls and mostly zero scripting.

We have been eyeing Cinemachine for using it in the game for a while, until we finally decided the time was right. To achieve that, we had to go through a process of learning and trying out different setups. In this post I will cover the main points and issues we had to go through and the practices we used.

In our case we already had a working follow camera that we wrote. The motivation to replace it was to gain flexibility and add multiple cameras to the game.

Finding help

There are many available tutorials for intro level usage showing the basics, like this video from GDC. However if you are a game developer looking for a recipe for integrating Cinemachine as your game camera, there is very little. One good place to start is the original Cinemachine website which actually offers a useful summary of the system. Besides the generic Tutorial videos on the Unity web site, the best source for specific answers is the Cinemachine Forum, where you can ask questions for any related use-case.

Adding Cinemachine to a project is done via package manager.

The Basics

To start, add a virtual camera (Menu: Cinemachine -> Create Virtual Camera) to get at least one. A Virtual Camera is the core component that we will mostly use here. In short, a Cinemachine virtual camera is made of mainly three components: Transposer, Composer and Noise.

  • Transposer moves the camera body relative to a target object, complete with controls for dampening and offset to place the camera relative to the target.
  • Composer determines camera direction based on a set of controls for tracking objects. It will adjust zoom/FOV, camera position and angle, to frame the target in the right place on the screen.
  • Noise is a procedural noise system that can be used to rattle, shake or vibrate the camera in a multitude of combinations and settings.

I started with adding a single virtual camera designated as the player / game camera. The tracking we used previously was only transforming the camera position, while the angle stayed fixed. However, I quickly discovered that to mimic that we need to use both a transposer and composer, with most settings in the composer.

Basic 2D follow keeps one axis constant while following on a plane. This is equivalent to

camPos = target.transform.position + Vector3.back * dist;

Setting that up on the virtual camera is as easy as setting the follow target and a follow offset in the body panel.

BodyUI

As opposed to a plain 2D camera that is orthographic, we are using a perspective camera in our game. That make issues like framing and screen-space conversion a bit more complex, but fortunately most of if is taken care of by Cinemachine. To set this up we assign the same object to the 'Look At' target. Setting up the dead zone is straight forward (the units are screen ratio from 0 to 1) and easily adjustable.

BodyUI

Note that Cinemachine has a pre-set 2D camera, but that did not fit our use case since it mostly relied on orthographic camera.

Where is my target?

One of the first real world issues that come up is actually setting the target on the camera UI in edit mode. In most games the scene gets loaded with only the static elements in it, before the game characters are spawned. So how do we set the player as a follow target in edit mode when its not there yet? or even at runtime?

This can be done in two easy steps.

Step 1 - Edit mode

Start by creating an empty game object in the hierarchy - this will serve as a dummy target. Then place the dummy target approximately where the player should spawn. Use the inspector for the virtual camera and assign a dummy game object as the target.

We can now test it and see that when the game starts, the camera is aimed exactly at the player as it spawns. If we try to run the game at this point, the player will go out of view without the camera following it - since it is fixed on the dummy target. We need a way to switch the target when the player is spawned.

BodyUI

Step 2 - Use Script to assign the target

Next we would need to access the virtual camera from a script. I wrote a simple wrapper class called CamDriver for easy access to the virtual camera and its parameters at runtime. Get it from Pastebin here. To use, instantiate the wrapper with the virtual camera object and it's ready to go. Now, use the following code to set the player as a target:

  GameObject vcam = GameObject.Find("FollowCam");
  cam = new CamDriver(vcam);

  // player was spawned
  cam.SetTarget(player.transform );

Note that the camera will smoothly transform from the dummy target to the player, if they are located separately. The movement smoothness and duration is controlled by the damping parameters in the transposer. You could possibly create a little intro sequence as the aim will move from one to the other based on the follow parameters.

Adding Zoom

So now that we have a fully functional follow camera, we needed a way to control the zoom, or frame size. In our game, different events will trigger changes in the zoom or call for a specific zoom level. First we need to add a zoom component. Cinemachine has one as part of the camera extensions, right below the noise panel on the camera inspector.

Add the Cinemachine Follow Zoom extension by selecting it from the extension list. Set the min and max FOV according to needs and let our CamDriver do the rest by calling to ZoomTo function:

  // Zoom to (width, damping)
  cam.ZoomTo(20, 4);

This will zoom to whatever size specified with the given damping, so we can have variable zoom with whatever size and speed that are passed from the game logic. That kind of zoom control fits perfectly with our requirement for contextual zoom in or out triggered by game events.

Conclusion

This covers the initial steps taken to replace our camera with Cinemachine driven virtual camera. On a future post we will cover more advanced features like shake, camera motion and multi camera setup. Until then, be sure to check out our progress on Twitter and Discord.