In our 3D game, the main camera mode will follow a third-person algorithm. This means that it will follow the player from behind, trying to keep the player on screen and centered in view at all times. Before we start developing the camera, we need to think about the basic requirements of our game in order to be able to program the camera to achieve good cinematographic results. This list of requirements will grow over time; however, by considering the requirements early on, we build an extensible system throughout the course of this book by applying good system design in our software. In no particular order, we list the requirements of a good camera system as follows:
It needs to be able to track the hero at a pleasing distance and speed and in an organic way
It needs to be able to transition in an appealing way, from tracking various objects
It needs to be able to frame objects in the field of view, in a cinematic and pleasing way
Starting with an initial camera and motion system based on the Unity3D examples, we will extend these over time. We do this not only because it is instructive but also with the aim of extending them and making them our own over time. With these requirements in mind, let's build the camera code. Before we do, let's consider some pseudocode for the algorithm.
The GameCam
script is the class that we will attach our MainCamera
object to; it will be responsible for the motion of our in-game camera and for tracking the player on screen. The following five steps describe our GameCam
camera algorithm:
For every frame that our camera updates, if we have a valid
trackObj
GameObject reference, do the following:Cache the facing angle and the height of the object we are tracking.
Cache the current facing angle and height of the camera (the
GameObject
that this script is attached to).
Linearly interpolate from current facing to desired facing according to a dampening factor.
Linearly interpolate from current height to desired height according to another dampening factor.
Place the camera behind the track object, at the interpolated angle, facing the track object so that the object of interest can be seen in view, as shown in the following screenshot:
Now let's implement this algorithm in C# code by performing the following steps:
Right click on the
Chapter1 assets
folder and select Create New C# Script. Name itGameCam
and add it to theMain Camera
object.Create a public
GameObject
reference calledTrackObj
with the following code. This will point to theGameObject
that this camera is tracking at any given time, as shown in the following code:public GameObject trackObj;
Create the following four public float variables that will allow adjustment of the camera behavior in the object inspector. We will leave these uninitialized and then find working default values with the inspector, as shown in the following code:
Public float height; Public float desiredDistance; Public float heightDamp; Public float rotDamp;
Recall that the
Update()
loop of any GameObject gets called repeatedly while the game simulation is running, which makes this method a great candidate in which we can put our main camera logic. Hence, inside theUpdate()
loop of this script, we will call aUpdateRotAndTrans()
custom method, which will contain the actual camera logic. We will place this logic inside theUpdateRotAndTrans()
method. This method will update the rotation (facing angle) and translation (position) of the camera in the world; this is howGameCam
will accomplish the stated goal of moving in the world and tracking the player:void Update() { UpdateRotAndTrans(); }
Above the update loop, let's implement the
UpdateRotAndTrans()
method as follows:void UpdateRotAndTrans () { // to be filled in }
Inside this method, step 1 of our algorithm is accomplished with a sanity check on
trackObj
. By checking for null and reporting an error todebugLog
, we can make catching bugs much easier by looking at the console. This is shown in the following code:if (trackObj) { } else { Debug.Log("GameCamera:Error,trackObj invalid"); }
Step 2 of our algorithm is to store the desired rotation and height in two local float variables. In the case of the height, we offset the height of
trackObj
by an exposed global variableheight
so that we can adjust the specifics of the object as shown in the following code (sometimes an object may have its transform 0 at the ground plane, which would not look pleasing, we need numbers to tweak):DesiredRotationAngle = trackObj.transform.eulerAngles.y; DesiredHeight = trackObj.transform.position.y + height;
We also need to store the local variants of the preceding code for processing in our algorithm. Note the simplified but similar code compared to the code in the previous step. Remember that the
this
pointer is implied if we don't explicitly place it in front of a component (such astransform
):float RotAngle = transform.eulerAngles.y; float Height = transform.position.y;
Step 3 of our algorithm is where we do the actual LERP (linear interpolation) of the current and destination values for y-axis rotation and height. Remember that making use of the LERP method between two values means having to calculate a series of new values between the start and end that differs between one another by a constant amount.
Remember that Euler angles are the rotation about the cardinal axes, and Euler y indicates the horizontal angle of the object. Since these values change, we smooth out the current rotation and height more with a smaller dampening value, and we tighten the interpolation with a larger value.
Also note that we multiply
heightDamp
byTime.deltaTime
in order to make the height interpolation frame rate independent, and instead dependent on elapsed time, as follows:RotAngle = Mathf.LerpAngle (RotAngle, DesiredRotationAngle, rotDamp); Height = Mathf.Lerp (Height, DesiredHeight, heightDamp * Time.deltaTime);
The fourth and last step in our
GameCam
algorithm is to compute the position of the camera.Now that we have an interpolated rotation and height, we will place the camera behind
trackObject
at the interpolated height and angle. To do this, we will take the facing vector oftrackObject
and scale it by the negative value ofdesiredDistance
to find a vector pointing in the opposite direction totrackObject
; doing this requires us to converteulerAngles
toQuaternion
to simplify the math (we can do it with one API function!).Adding this to the
trackObject
position and setting the height gives the desired offset behind the object, as shown in the following code:Quaternion CurrentRotation = Quaternion.Euler (0.0f, RotAngle, 0.0f); Vector3 pos = trackObj.transform.position; pos -= CurrentRotation * Vector3.forward * desiredDistance; pos.y = Height; transform.position = pos;
As a final step, we point the
LookAt
GameObject reference of the camera to the center oftrackObject
so that it is always precisely in the middle of the field of view. It is most important to never lose the object you are tracking in a 3D game. This is critical!transform.LookAt (trackObj.transform.position);
Congratulations! We have now written our first camera
class that can smoothly track a rotating and translating object. To test this class, let's set the following default values in the Inspector pane as seen in the previous screenshot:
TrackObj: Set this to the Player1 object by dragging-and-dropping the object reference from the Hierarchy tab to the
trackObj
reference in the object inspector.Height: Set this to
0.25
. In general, the lower the camera, the more dramatic the effect but the less playable the game will be (because the user can see less of the world on screen).Desired Distance: Set this to
4
. At this setting, we can see the character framed nicely on screen when it is both moving and standing still.Rot Damp: Set this to
0.01
. The smaller this value, the looser and more interesting the rotation effect. The larger this value, the more tense the spring in the interpolation.Height Damp: Set this to
0.5
. The smaller this value, the looser and more interesting the height blending effect.
Once the player controls are developed (refer to the next section), try experimenting with these values and see what happens.
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased via your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.