Unity3D RaceGame Tutorial
Unity3D RaceGame Tutorial
Contents I – Introduction
I Introduction Welcome to the second tutorial created for Unity! The idea behind
this second tutorial is to provide users the opportunity to create a
II Setting the Scene full, working game. This is a more advanced tutorial and will fo-
A quick chapter setting up the ob- cus more on the general concepts of programming and scripting a
jects and assets for the game, includ- complete game within the Unity environment. It will introduce you
ing the track, cars and lighting. to advanced physics and modeling, camera views and styles that
work for the racing genre, the basics of enemy AI, triggers, setting
III Controlling the Player’s Car up more complex GUIs for a game, scoring and end-game goals.
Scripting the player’s car. Starting This tutorial assumes that you have a decent familiarity with the
with the basics of movement Unity interface and workflow, and also some familiarity with the first
and moving on to explanations tutorial, as we are going to skip some basic concepts to quickly get
of RigidBody, Colliders, friction, in-depth into the game.
force, transform, and variables.
Moving Forward
The tutorial will lead you through a series of steps that will build on
Moving Left and Right
one another, with detailed explanations so that you have an ex-
An Introduction to Variables
cellent working knowledge of the technical aspects of the finished
Basic Physics
racing game. There will also be some detailed explanations of
Advanced Physics
basic programming questions and concepts. When complete, you
will be able to take away scripts and techniques applicable to many
IV The Game Camera
How to create and use a third-person other types of games that can be used in your own projects.
view camera to follow the player’s car.
Simple Camera Theses scripts include such things as: player car/object scripts,
Smooth Camera camera scripts, enemy and AI scripts, goal trigger scripts, creating
waypoints, timer scripts, and game startup and GUI scripts. Be-
V The Other Cars and AI cause of the complexity of this tutorial, it will be broken down into
Introduces basic AI concepts applica- several main sections, some of which will have a number of specific
ble to many genres of games. Explains subsections, as shown in the Contents at left.
basic coroutines in scripting, and how
waypoints are created and used by The Goals Of This Tutorial
game objects. This tutorial was intended to be a script tutorial that really focuses
AI Car Script solely on scripting; it is meant to be an introductory tutorial on script-
Waypoints ing in Unity for beginners. For this reason, the models are kept as
simple as possible, and in the scripts we chose to use the simplest
VI Start Your Engines! possible solutions to get a functional game. The car, for example
Wrapping up the game by adding a race can be tweaked much more than we did in the tutorial. This tutorial
timer, game GUI, text objects, count- is not meant to create a fun game, but rather it is meant as a script-
down timer and scene transitions. ing tutorial to show the complete process of creating a game from
the scripting side.
1
Thanks A Note About The Files
Thanks To Those Who Helped! The most up-to-date versions of the files for this tutorial are always
I was thrilled when the guys at OTEE available for download at the following URLs:
allowed me to help create a second,
more complete, complex tutorial for http://www.otee.dk/tutorials/car_tutorial.zip
the Unity 1.1 release.
http://www.otee.dk/tutorials/car_tutorial_complete.zip
Joachim and Nicholas at OTEE were,
as always, especially helpful and ex- There are two versions of the project files supplied with this tu-
tremely responsive and professional. torial. The first version provides all of the assets (scripts, objects,
David and Keli at OTEE also provided models, textures, physic materials etc) but assumes that the reader
a great deal of help, ideas, direction will assemble these into working form and create the working
and support for this tutorial. scenes by reading through this tutorial and actually doing all of the
steps as outlined in the tutorial. This "bare bones" version starts with
The Unity forums are an active com- a single scene containing only a track and non-functioning player
munity of involved, fun, resourceful
car. This is the recommended starting point and goal for readers
users of Unity and I’d strongly recom-
when starting working on this tutorial.
mend visiting the forums to contrib-
ute and help others create cool things
The purpose of this method is that at the end of each of the tutorial’s
with Unity. The forums are online at
subsections and sections, the reader will have a completed, func-
http://ww.otee.dk/forum/. Thanks
to all the regulars there who help out! tioning scene with which to build the next scene with.
Unity 1.1 brings a whole bunch of very The second version (car_tutorial_complete) is a completed and fully
cool updates and improvements to working version of the tutorial and all of the scenes to be used as a
the application. Among these: the reference should a part of the tutorial not make sense or the reader
ability to build standalone Windows wants to see a section of the tutorial fully functioning.
applications, easier level loading,
updates to hinges and a new raycast Thanks!
collider, water textures, and many Most thanks must go to the guys at OTEE who helped create this
more. joint-effort tutorial. Their help with creating the scripts and explain-
ing them to me for the tutorial was excellent. The tutorial certainly
Unity version 1.5 has introduced wouldn’t exist without especially Joachim’s patience, support, and
specialized wheel colliders. The new desire to help me get it packaged like this. The great community on
wheel collider has support for slip the Unity forums also have contributed a great deal to the develop-
curves, a specialized friction model ment and exciting nature of Unity. Thanks, guys! Thanks also to
for cars, dampers, and allows you to fellow WidgetMonkey, Ron Letkeman, the artist and modeler who
model anything from a dune buggy always come up with fun ideas for games and the art to make them
through to a F1 racing car game. come to life. And finally, hello to my kids, Cal and Luke.
See page 42 for details.
Tutorial Assets II – Setting the Scene
Racing Game Tutorial Assets In this section, we’re going to quickly set up the physical objects
The latest versions of the two files to and assets that will be necessary in our racing game. These include
work through this tutorial are found the players and enemy cars, the racing track, scripts for each of the
online as explained on page 2. One tutorial sections, and pre-made scenes that explain the tutorial’s
file is the tutorial with assets set up concepts. We’ll start by downloading the pre-made assets and cre-
for the reader to work through creat- ating our project by opening them.
ing the full game, while the second
is a completed version of the project Begin by downloading the Tutorial Asset files and then unzipping the
for reference purposes. files to create the "car_tutorial" and "car_tutorial_complete" project
folders. Both folders contain all of the required assets for the tutorial
I’ve assumed that readers will want separated into logically arranged subfolders.
to use this tutorial as a basis to build
larger games, so the game assets
Move these folders where you prefer on your hard drive and then
have been structured into folders
open Unity. Select File -> Open Project and locate and open the
that are based on the asset’s func-
"car_tutorial" project. Unity will restart, update the assets, and then
tion rather than type, the reasons for
open the standard tutorial game world. Open the Basic Setup scene.
which were explained in the first tu-
We’ll make two minor modifications now by changing a couple of
torial (mainly ease of finding assets
in complex game projects). the values in Edit -> Project Settings -> Player. Change the De-
fault Size of the build to 800x600, and change the Default Graphics
Remember, this tutorial was created Quality to 4 (1024 x 768 is now the default setting for new projects).
using the latest version of Unity as of If you work in the 2 Split layout your screen will look like this:
this writing, version 1.1.
We’ll use the provided assets ([A] Car Control through [D] Game
Setup) to work our way through the sections and concepts in the
tutorial. At the end of the tutorial, we will have learned enough to
create a small series of complete scenes that incorporate all of the
tutorial concepts, and create a complete racing game. To begin,
we’ve opened the Basic Setup scene and will start work on the first
working section, III - Controlling the Player’s Car.
3
What is Code? III – Controlling the Player’s Car
Showing Code In The Tutorial This is the longest section of the tutorial, so it’s been broken into
I will be showing all examples of five major subsections. Each of the subsections builds upon the
scripts in a Courier typeface and knowledge learned in the previous subsection so that at the end
inside a faint blue box, as shown be- of Section III, you’ll have a thorough knowledge of many important
low, so that code is easily picked out aspects of Unity and will have saved several scenes of your work.
from the rest of the tutorial content.
Controlling a player’s car in a game requires an understanding of
var power = 3.0; physics in game engines, with a main focus on motion of a ve-
function FixedUpdate () { hicle, over time, through a specific game world. It also involves the
mechanics of collisions (and collision detection), and will introduce
// When you press space the concept of variables that will be required for this sort of player
// we move up the box control of objects.
if (Input.GetButton
Many Unity users will be familiar with much of what is discussed in
("Jump")) {
this section. This section will provide a complete understanding of
rigidbody.AddForce (Vector3. all of these concepts to the beginning user and, for the advanced
up * power); user, I hope to illustrate how Unity uniquely handles all of these
} physical and mechanical concepts, so that they can be used as a
basis and help you develop your own titles.
}
We’ll start with the very basics in the first subsection – moving a
player’s car forward through the world
1. Moving Forward
Objects in a scene are moved by using "Transform" and an "Up-
date" function that is "called" (runs) every frame. Transforms take
care of all the movement of every object in Unity. Before we get to
the code, let’s review some very basic coding stuff.
4
Functions Some functions are automatically called on certain events. An im-
A Complete List portant and very useful function in Unity that is called every frame
You can find a complete list of all of is the "Update" function.
the functions that are automatically
called by Unity in OTEE’s online script To do anything interesting in your game, you’ll have to call some
reference documentation. of Unity’s built in functions. A few paragraphs ago I mentioned that
objects in a scene are moved by using the class "Transform" and an
"Update" function that is called every frame. Transforms take care
of all the movement of every object in Unity. A complete list of all
You Comment, Right? function’s supported by Transform can be found online.
What’s This Section Of Code Do?!
In my day job, I code CSS websites. To call a function on an object, you refer to the object, followed by a
Big, big websites with lots of CSS to period and the function to call. In the following example, we call the
make sure everything is accessible Transform’s Translate function. It has 3 parameters that are used to
and compliant. And the only way to define how much to transform (move) an object along it’s x, y and
make sure that I can remember what z axis.
I did six months ago, or make sure
that the guy in the o�ce next to me // Move the car forward along it’s z-axis
understands what he’s looking at, function Update () {
is to comment my CSS code really, transform.Translate (0, 0, 1);
really well. }
I’d strongly recommend, even if you In the tutorial, this is the MoveForward.js script in the [A] Car Con-
work alone, that you comment your trol -> 1 Move Forward folder, and has the comment line at the be-
code well enough that anyone look- ginning to remind us and anyone else who looks at our code what
ing at it can understand what they’re this does (see the You Comment, Right? note at left). Attach this
looking at and what does what. In script to the Player Car object using drag and drop so that the car
JavaScript, comments are those will simply move forward one unit along it’s z axis each frame. The
helpful lines of code like this ... z axis is the blue arrow when you select the car. Run your scene
to see this script in action. Do a Save as... and save the scene as
// fade screen to black Move Forward in the 1 Move Forward. Play with the values of the
script to make the car go up or sideways instead of forward, or to
lines that you will thank yourself for go faster forward.
later. It’s just good coding practice.
5
2. Moving Left and Right
We’re now going to use Transform and Input to move the car left
and right. Open up the Basic Setup scene again and attach the
[A] Car Control -> 2 Move And Turn -> MoveAndTurn.js script to
the Player Car. Run the scene and use the arrow keys to move
the player car. The motion is very simplistic but we’re now using
input from the player to modify an object within the game world.
Here’s the code (MoveAndTurn.js) that’s making this happen:
We’re still using the Update function to make the car move, but
only every frame it receives player input. Instead of declaring a
number of units to move along either the z or y axis as in our first
section, we’re using a new function to make movement happen:
Input.GetAxis; and one new function called transform.Rotate to
make the car turn. These are basic movement functions in Unity.
To work around this issue, you will most always want to change val-
ues dependent on time. In our simple example, you want to move
a car 1 meter per second. In code this translates to:
function FixedUpdate () {
transform.position.x += Input.GetAxis
("Horizontal") * Time.deltaTime;
}
function Update () {
light.range += 0.1 * Time.deltaTime;
}
function Update () {
transform.Rotate (0, Input.GetAxis ("Mouse X"), 0);
}
7
Case Sensitivity 3. An Introduction to Variables
JavaScript Is Case Sensitive Many Unity users have some programming experience and will be
You will need to remember that able to simply skim this section. But since this tutorial has been
JavaScript is a case-sensitive designed as an introduction to Unity’s programming functionality,
language. This means that language we should explore a cornerstone of computer programming known
keywords, variables, function names as variables for newer users. The concept of variables will come
and other identifiers have to typed into play in the next section of the tutorial when I will add some ad-
with consistent capitalization of vanced physics and behvaiours to the player’s car.
letters.
At its most basic, variables are used to store and manipulate data.
For example, the function keyword A variable has a unique name and stores a value (numeric, text etc)
has to written "function", and not that can be used by the program to do something.
"Function" or "FUNCTION".
The first line of code in the following example creates a variable
named "i" and assigns the value "3" to it, while the second line of
code creates a variable named "sum" whose value is equal to our
first variable "i" multiplied by "2". In this case, "sum" would be equal
to "6".
var i = 3;
var sum = i * 2;
var publicVar = 1;
function Foo () {
var localVar = 1;
}
Any public variables in your script will show up in the inspector when
you attach a script to a game object. In the inspector you can then
change the value. How is this important? The value that is showing
in the Inspector can be changed in the Inspector and overrides the
value used in the script. This means that ...
var i = 5;
print (i);
var playerLives = 3;
function Update () {
// Every frame we decrease playerLives by 1.
// This means playerLives will become
// smaller and smaller every frame.
playerLives -= 1;
So let’s get back to the game now and combine some of this coding
stuff into making something fun happen to the player’s car. Our next
subsection, Moving Left And Right With Variables, will make the
player car move left and right using variables in a new script.
Important Note!
Unity-specific Terminology
I have put comments in the previous two code examples that
state:
9
Moving Left and Right (With Variables)
Open up the Basic Setup scene again and attach the [A] Car
Control -> 3 Variables -> Variables.js script to the Player Car.
Run the scene and use the arrow keys to move the player car.
The motion has now dramatically changed when compared to the
previous scene. It’s now the Variables.js script in this asset folder
making the player’s car move. Here it is in detail:
function Update () {
translation *= Time.deltaTime;
rotation *= Time.deltaTime;
10
Transform The script makes use of everything we discussed in the Introduc-
Transform Is An Important Class tion to Variables section. The player car now slows gradually after
Transform is one of the main classes the player stops holding the arrow keys.This is a bit weird at first
in Unity. Most game object manipu- because our code actually just moves the car forward with a con-
lations are done either through stant velocity. But the Input.GetAxis function automatically smoothes
the game object’s Transform and/or keyboard input and because we multiply the input with velocity the
Rigidbody. velocity is automatically smoothed. You can modify the smoothness
of keyboard input, setup joysticks etc. via the input manager. Edit ->
Every object in a scene has a Project Settings -> Input, and see the Input reference online for more
Transform. It’s used to store and information. With these basic in mind, we can now bring other phys-
manipulate the position, rotation ics into our racing game; specifically, RigidBody and Colliders.
and scale of the object. Transform
has a number of variables and 4. Basic Physics
functions associated with it, and In this scene, we want to start to affect the player’s car through
a complete list of all of these are the use of forces and torques (angular forces) rather than position
online in OTEE’s script reference web and rotation directly. This has several advantages including more
page. realistic motion, but more importantly, we don’t have to multiply
movements by Time.deltaTime when working with physics because
Every Transform can have a parent, forces are already time independent. Once again, start by opening
which allows you to apply position, up the Basic Setup scene, then click on [A] Car Control -> 4 Phys-
rotation and scale hierachically. This ics -> SimplePhysics.js and examine it in the Inspector panel.
is the hierarchy seen in the Hierarchy
pane. They also support enumerators
The SimplePhysics.js script for this is not any more complex than
so you can loop through children.
the script for Moving Left and Right With Variables, shown here:
function FixedUpdate () {
12
Connecting Variables You create a new Physic Material by clicking on the "Create" button
Public Variables In The Inspector in your project panel and selecting Physic Material. These materials
As mentioned much earlier, variables contain all the friction and bounciness parameters that are needed
in a script appear in the Inspector to handle and enhance collisions between objects. Physic Mate-
panel and will need to be "associ- rial also has a setting to combine/multiply these effects to increase
ated" with an object. In this case, these two forces.
our four variables (frontLeftWheel,
frontRightWheel etc) appear linked The third and final step is to attach the SimplePhysics script located
to "None" when we first click on the in your Project panel 4 Simple Physics to the car. If you play with the
Play Car in the Hierarchy panel. You value of the variables of the Simple Physics script in the Inspector make
link a variable to an object by simply sure that Speed is at least 100 and Rotation Speed is at least 80.
dragging the object onto the vari-
able in the Inspector as shown in this We can now Run this scene and test the motion of our car. Save the
screenshot. scene as Simple Physics in the 4 Simple Physics directory.
5. Advanced Physics
In this section we’re going to improve the raycast car. The main
change we’ll make is that we will now rotate the wheels instead of
applying a torque to the entire car to make it rotate. Start by open-
ing up the Basic Setup scene, and adding a box collider, raycast
colliders, and a RigidBody to the various Player Car parts as we did
on page 12 in the previous section. Save as... the scene now in the
5 More Physics directory naming it simply Physics.
Now create and save two new Physic Materials – one for the back
wheel named BackWheel, and one for the front wheel named Front-
Wheel. These will give us more control over the car handling, be-
Private Variables cause we can tweak and fiddle with the friction values for the front
Public vs. Private Variables and back wheels as separate entities in the Inspector. Set these up
In Section 3 we introduced public using the values as shown in the smaller Physic Materials setting
variables. Public variables are de- screen shot in the left sidebar on page 12.
clared like this var i = 5;.
Notice how we refined the values we’ve assigned the Physic Ma-
It is also possible to declare a variable terials by adding a springy contact to the wheel’s physics material
as private as shown here:
to make it soft. This is done by enabling the use Spring flag, and
setting spring to 25 and damper to 5. Feel free to play around with
private var i = 5;
these values to get the dampers in your car right. We can now Run
this scene and watch the improved motion of our car.
Declaring a variable as private is
useful for storing state that should
Now let’s tackle the script associated with this scene: PlayerCar.js.
not be accessed from other scripts.
Private variables are also not visible
There’s two things in this script that will give the car better physics
in the inspector. This is quite useful in this section. 1) In the script we will now tweak the center of mass
to unclutter the inspector from and inertia tensor so that the car has a lower center of mass (to
unneccessary variables. prevent it from flipping over too easily). 2) We remove the torque
from the car because we’ll modify the rotation of the wheels based
We will be using private variables in on the input horizontal axis instead of torque.
the Physics.js script on the following
pages. When we attach this script to the player car, the four declared vari-
ables will have to be connected in the Inspector so that the script
knows which variable is associated with which wheel (shown at top
left). The script is shown in all it’s gory detail on the next page:
13
Dynamic/Static Typing // These transforms need to be connected in the
// Inspector so the script can identify the wheels
Unity Uses Static Typing
var frontLeftWheel : Transform;
Netscape’s JavaScript implementa-
tion is a dynamically typed language.
var
var
frontRightWheel : Transform;
backLeftWheel : Transform;
1
For example you can assign a number
var backRightWheel : Transform;
to a variable and later assign a string
to it, for example:
// Speed is a multiplier of how much force
// we add to the wheels every frame
i = 5;
var speed = 150;
i = "Hello World";
// The maximum steering angle of the wheels
In most cases writing the above is
var maxSteerAngle = 30;
an oversight in the script and not in-
tentional. Statically typed languages
// This is used to track if
usually require you to specify the
private var hasBackWheelContact = false;
type of the variable when declaring
it like this:
// Tweak the center of mass. You’d want a
// low center of mass, a bit towards the front
var i : int = 5; // of the model on a long but not very tall car
14
// countdown is inferred // Called every frame if car collides with something.
to an integer value // Used to calculate if the wheels touch the ground.
function OnCollisionStay (collision : Collision)
var countdown = 5; {
for (var p : ContactPoint in collision.contacts)
function Update () { {
// countdown will never
decrease!!!
// Enable hasBackWheelContact if we are
// touching the ground
5
countdown -= Time.del-
taTime; if (p.thisCollider.transform == backLeftWheel)
hasBackWheelContact = true;
// When subtracing if (p.thisCollider.transform == backRightWheel)
// deltaTime from countdown hasBackWheelContact = true;
// deltaTime is rounded to }
// an int, because }
// deltaTime is normally
// less than 0.5 we will There are several important parts to this script that I will quickly review
// always subtract zero for the reader.
}
1. These are the four transform variables that we needed to connect to
Instead we have to make countdown specific wheels in the Inspector panel as demonstrated on page 13.
a float:
2. This is where we change the player car’s centre of mass based on
// countdown is inferred
our car model. In our case, the car is long and not too tall so we’ve
to be a float value
moved the centre of mass slightly down and toward the front of the
var countdown = 5.0;
car. The center of mass is relative to the transform’s origin. If you don’t
set the center of mass from a script like we are doing here, it will be
function Update () {
calculated automatically from all colliders attached to the rigidbody.
// This works perfectly InertiaTensorRotation and InertiaTensor are the rotation of
fine, since countdown now the inertia tensor and the diagonal inertia tensor of mass relative to
is a float. the center of mass respectively. The inertia tensor is rotated by the
inertiaTensorRotation.
countdown -= Time.del-
taTime; 3. This function checks to see if the car’s back wheels are in contact
} with the ground/track surface, then applies a relative force along the
car’s z axis to move the car either forwards or backwards based on
the player pushing the up or down arrows. This part also sets the
angle of the front wheels turning based on the player pressing the left
or right (horizontal) keys.
4 and 5. These parts of the script track if the wheels are still in contact
with the ground. At the end of every frame we set hasBackWheel-
Contact to false. OnCollisionStay is called every frame if any of
the colliders of the car are colliding with other objects. Inside OnCol-
lisionStay we check if the collider is a backwheel. If it has, we en-
able hasBackWheelContact again. This is all repeated every frame,
thus hasBackWheelContact will track if the car is grounded or not.
Now that we have a fully functional player car, it’s time for us to set up
a camera to follow the player’s progress in the race.
15
IV – The Game Camera
The goal of this shorter section is to create and manage a camera to
follow the player’s car as it races around the track. In a racing game,
one of the most common camera views is from a third-person per-
spective; that is, behind and slightly above the player. This tutorial will
show you how to create one of these cameras and then to move it
along in a fluid manner with the player car. In more advanced games
you might wish to set up numerous cameras to capture the action
from different angles. This more complex camera work is done by
enabling and disabling various cameras based on user input.
1. Basic Camera
Let’s start by first simply getting the main camera positioned above
and behind the player car. Open our previous scene’s Physics.unity
scene and do a Save as... in [B] Camera Control Scripts -> 1 Basic
Follow Camera and name the file Camera. This scene contains all
of the elements and scripts from the previous section of the tutorial.
Now attach the Camera.js script to the main camera.
The Camera.js script has a distance and height variable that we will
be able to modify from the Inspector, but also a "target" variable that
we will need to assign to the player’s car. Connect this variable to
the player car object the same way we connected the wheel control
variables to the wheel objects of the car object back on page 12.
function LateUpdate () {
2
// Early out if we don’t have a target
if (!target)
return;
5. We rotate the camera to always look at the target. Run the scene
and drive the player car to see the camera in action. Neat!
But this is a very simple solution. What we’d really like to see is the
camera reacting better to the motion of the player car. We need to
create and use a camera script that smoothes out the motion of the
camera as it follows the car.
2. Smooth Camera
Start again by opening the Physics.unity scene and do a Save as...
in [B] Camera Control Scripts -> 2 Smooth Camera and name the
file Smooth Camera. This time, attach the Smooth Camera script to
the main camera. Connect the target variable to the car object as
we did in the previous scene and Run the scene to see the camera
react more smoothly to the car’s movement.
What’s now happening is that this camera script smoothes out ro-
tation around the y-axis and height, while still maintaining a static
horizontal distance. This method gives you a lot of control over how
the camera behaves because you can tweak lots of the variables.
For every of those smoothed values we calculate a wanted value
and the current value. Then we smooth it using the Lerp function.
function LateUpdate () {
transform.position = target.position;
transform.position -= currentRotation * Vector3.
forward * distance;
5
18
// Set the height of the camera
transform.position.y = currentHeight;
2. We calculate both current and wanted rotation and height for the camera.
Run the scene and drive the player car to see the improved camera
in action. Notice that we’re using specifically the Mathf.LerpAngle to
damp the rotation around the player car’s vertical (y) axis, and using
Mathf.Lerp to damp the height. It also uses some other basic func-
tions built into Unity such as EulerAngles, Time.deltaTime and oth-
ers. Most of the rest of the script uses basic variables and functions
to move the camera with the car. It’s now time to add opponent ve-
hicles and program them to race around the track against the player.
Otherwise known as the "cool stuff."
AI Cars
Start by opening the Physics.unity scene and do a Save as... in
[C] Camera Control Scripts -> 1 AI Car and Waypoints and name the
file Waypoints. Create a new Physic Material called AI Car Wheels
and set it up as shown at left, then remove the previous wheel phys-
ic materials from the wheels in the Inspector by replacing it with our
new AI Car Wheels material via a drag and drop.
19
Basic AI Remove the Playercar script component from the AI Car in the In-
How Basic AI Cars Function spector and attach the AICar script, making sure to connect the four
AI cars generally work with a simple, wheel variables to their associated wheel objects. Finally, rename
two-step process like this: 1. Calcu- the player car to AI Car. Save the scene at this point. I’ll explain how
late where we should drive towards; to create waypoints after we we examine our AICar script in detail.
this is handled mainly by setting up
"waypoints" and using the Waypoint In our AICar.js script we have the UpdateWithTargetPosition
script. 2. Calculate how to get there. function which is the meat of the AICarScript. It rotates the wheels,
Suprisingly, with a car this is really accelerates forward and slows down in sharp turns. The Update-
simple – we just rotate the wheels WithTargetPosition function is called from inside FixedUpdate. In
towards the waypoint targets and FixedUpdate we also calculate where we should drive towards, and
drive forward. calculating where we drive towards is simple and goes like this:
With the basic in mind, we can now examine the AICar.js script func-
tions in detail, like we did with some of our previous scripts.
function Start () {
// Initialize the waypoint we drive towards!
activeWayPoint = WayPoint.start;
1
// Tweak the center of mass.
// - Low center of mass a bit towards the front
// - model a long long and not very high car
rigidbody.centerOfMass = Vector3 (0, 0, 0);
rigidbody.inertiaTensorRotation = Quaternion.identity;
rigidbody.inertiaTensor = Vector3 (1, 1, 2) * rigid-
body.mass;
}
20
function UpdateWithTargetPosition (target : Vector3) {
rigidbody.drag = 0;
if (hasWheelContact)
{
// Accelerate ...
2B
// force = maxSpeed * force;
rigidbody.AddRelativeForce (0, 0, wheelForce);
21
Debugging // This is handy for debug visualizing where
// we actually want to drive
Debugging Scripts In Unity
// Debug.DrawLine (transform.position, target);
Unity provides a couple of useful
tools to debug scripts, for example,
// This is reset every frame.
the Debug.Log function.
// OnCollisionStay enables it again.
hasWheelContact = false;
function Update () {
}
Debug.Log ("Inside Up-
function FixedUpdate () {
date", gameObject);
// Calculate position the AI car should drive towards
targetPosition = activeWayPoint.CalculateTar-
}
3
getPosition (transform.position);
When you run this script, "Inside
// Apply forces, steer the wheels
Update" will popup in the status bar;
UpdateWithTargetPosition (targetPosition);
when you single click on it, it will
}
popup the console and show the
connection to the gameObject we
// Whenever we hit a waypoint we have to
passed as the second parameter of
Debug.Log.
// skip forward to the next way point 4
function OnTriggerEnter (triggerWaypoint : Collider) {
if (activeWayPoint.collider == triggerWaypoint) {
This can be used for any reference to
activeWayPoint = activeWayPoint.next;
another Object. It is extremely help-
}
ful when you have some bug where
}
you don’t know exactly in which
object the problem occurrs.
// Track if we the wheels are grounded
Debug.DrawLine (trans- There are several important functions of our AICar script that I will
form.position, Vector3.
review in more detail for the reader now.
forward, Color.green);
With the AI car now functioning, it’s time to look at setting up and program-
ming waypoints around our race track for the cars to drive towards.
Waypoints
To set up waypoints you add an empty game object to our scene,
add a box collider to it, and set the isTrigger property to true, then
attach the WayPoint script to it (this also draws the W texture linked
in the Gizmos folder). The waypoints need to be manually connect-
Prefabs ed by setting the next variable of every waypoint. The screenshot
A Perfect Use For 30(!) Waypoints at left shows our completed track with 30 waypoints set up in the
I’d strongly suggest after setting up Scene view. The first waypoint on the track should be named "Start"
one waypoint with it’s box collider in the Inspector; the others should be called "Waypoint".
set up and the Waypoint.js script
attached to create a prefab waypoint The waypoint script is used by the AI cars and is rather simple – the
object from it. Drag more prefab scripts contain a varible to the next waypoint. This variable needs to
waypoints into your scene to create be setup manually in the Inspector for every waypoint. Then there
the rest of the waypoints. That way, is a CalculateTargetPosition function which calculates where the
any changes made to the prefab will car should drive towards. Here’s the script:
be reflected in all of the waypoints
created using the prefab object. // The start waypoint, this is initialized in Awake.
// This variable is static thus all instances
I’d also recommend creating an // of the waypoint script share it.
empty game object that holds all of static var start : WayPoint;
the waypoints you’ll want to create
in one scene. The finished version // The next waypoint, this variable needs to be
of the tutorial uses 30 waypoints. // assigned in the inspector.
// You can select all waypoints to see the
// full waypoint path.
23 var next : WayPoint;
Static Variables // This determines where the start waypoint is.
var isStart = false;
Static Variables Are Shared Variables
What if we want to share (or not
// Returns where the AI should drive towards.
share) a variable between all "in-
// position is the current position of the car.
stances" of the script? Sometimes
function CalculateTargetPosition (position : Vector3) {
you want a variable to be shared
between all instances of the script
(e.g., is a player dead, setting a
standard reset value for a timer, etc).
//
//
If we are getting close to the waypoint,
we return the next waypoint. 1
// This gives us better car behaviour when
An instance of an script occurs when
// cars don’t exactly hit the waypoint
you attach a script to a game object.
The script with its value shows up in
if (Vector3.Distance (transform.position, posi-
the inspector. All variables become
tion) < 6) {
instanced and can be modified for
return next.transform.position;
this particular instance. You use the
}
static keyword for this as shown in
the following code example:
// We are still far away from the next waypoint,
// just return the waypoints position
static var count = 0;
else {
function Awake () {
return transform.position;
count++;
}
print (count);
}
}
// This initializes the start and goal static variables.
If you attach this script to more than
// We have to inside Awake because the waypoints need
one game object, count will increase
// to be initialized before the AI scripts use it
for every one of them. So if you at-
// All Awake function are always called before all
tach it to 3 game objects and hit play,
// Start functions.
it will print 1, 2, and 3 to the console.
function Awake () {
If you leave out the static keyword
if (!next)
(i.e., var count = 0;) it will print
1, 1 and 1.
Debug.Log ("This waypoint is not connected,
2
you need to set the next waypoint!", this);
if (isStart)
start = this;
}
Static variables are shared between all instances of the script and
can also be accessed by other scripts by using the name of the
script followed by a period and the function or variable name, as
shown in this example:
print (SomeScript.staticVar);
// will print 5
The static start variable was used by the car script to find the first
waypoint like this: activeWayPoint = WayPoint.start;. Also the
game controller in the next chapter will use it to find out when we cross
the finish line. Let’s look at the functions in the waypoint script.
Now that we have the script written, we can actually go about set-
ting up waypoints on our track, placing a test car in the scene, and
attaching the AICar script to it to watch it drive itself around the
track. I’ve shown the track from the completed files below. In other
words, to set things up, you simply need to place a few waypoints
in the scene – remember, one of them has to have the isStart flag
25
Future Unity Feature enabled, so we know where to start. All of them have to be con-
Dedicated Wheel Components? nected together by setting the next property in the Inspector of each
Version 1.2 of Unity should introduce waypoint. This scene uses a total of 30 waypoints.
dedicated wheel components that
will allow for very realistic cars. Create duplicates of the AI Car we assembled on page 19 and place
them in the scene. Now Run the scene. While the scene Runs, our
AI cars will simply drive towards the start waypoint. When a car hits
it, the car will start driving towards the next waypoint in turn, and
then keep driving from waypoint to waypoint around the track.
To get ready for the final chapter, Save this scene in [D] Game
Setup -> 1 Finishing the Racing level and name it Track1.unity.
26
Create a text object by selecting GameObject -> Create Other ->
Text in the menubar and position and scale it so it is in the centre of
the screen. You can leave the default text it is displaying or delete it
to leave it blank, in the Inspector, once you are happy with it’s posi-
tion and scale. Attach the StartGame.js script to the text object.
The camera will always display the Text object in front of all oth-
er objects in the game because the camera contains a GUILayer
component. The camera’s GUILayer displays objects that have a
GUIText component and GUITextures always in the front specifi-
cally to make interfaces for games. Text objects have a GUIText
component included, visible in the Inspector panel by default. GUI
objects remain in the same position in relation to the camera even if
we move the camera because they are, for al intents and purposes,
attached to the camera.
Coroutines are functions that can be paused at any point and wait
for a specific event to occur, after which they should continue to
the next instruction. For example, while a function was running,
we could wait for the next frame or wait for 5 seconds. Coroutines
make your life a lot easier when dealing with a sequence of events.
For example, if you want to display some text for 2 seconds and
then want to remove it again you can do it by simply attaching the
following script to an object in your scene; assuming the object has
a GuiText component:
guiText.text = "Hello";
yield WaitForSeconds (2);
guiText.text = "";
27
function Start ()
{
// Disable AI Cars
aiCars = FindObjectsOfType (AICar);
for (var car : AICar in aiCars)
car.enabled = false;
1. We "disable" all the cars in the race, including the AI cars and
the player’s car. We find all the AI cars by calling FindObjectsOfT-
ype. This returns a list of all active loaded objects of Type type. It
will return no assets such as meshes, textures, prefabs or inactive
objects. More information about calling FindObjectsOfType can be
found online. We give it the type of the class we are looking for, i.e.,
the AICar, and get back all instances of that script. We go through
all the script instances we found and set enabled to false. So what
happens if a script is enabled or disabled?
28
The enabled checkbox is also visible for an object or its compo-
nents in the Inspector (the small checkbox next to the title).
2. Then we display the words "Get Ready" in our Text object and
then wait for 3/4 of a second using yield. Then we display the word
"Set" and wait for 3/4 of a second using yield.
3. Then we display the word "Go" and enable all car scripts similar
to how we disabled them.
4. The final step is to wait for another 3/4 second and then remove
the Text object, in a manner of speaking, by putting nothing ("") in
the GUIText, and then the race can begin.
But before the cars will actually go, we have to make sure to have
attached the CarStats.js script to all of the cars (player car and AI
cars). Make sure that has been done, and then save the current
scene so we can examine that script.
The CarStats script simply tracks that we drive through the correct
waypoints. When we hit the start waypoint, it will disable the car
like we did in the StartGame.js script, since the car has completed
the race and it no longer should drive around the track. If it is the
player’s car that has completed the race, then we’ll need to tell the
GameController about it. This "GameController" will be introduced
in 2. GameController and handle a number of tasks in our racing
game such as showing the highscore etc. Here’s the CarStats script
in detail:
function Start () {
}
activeWayPoint = WayPoint.start.next; 1
// Keeps track of when the player reaches the goal
if (activeWayPoint.collider == triggerWaypoint) {
// When we reach the game might be finished!
if (activeWayPoint == WayPoint.start)
ReachedGoal();
2
activeWayPoint = activeWayPoint.next;
}
}
29
function ReachedGoal () {
This will return null if there is no AICar attached to the game ob-
ject. In that case we simply ignore it. If we do have an AICar then
we disable it. We do the same check for the PlayerCar and then
disable the player car and send a message to the GameController.
30
Think Of It As ... 2. The GameController
GameControllers Are Like Includes We’ve mentioned this thing called "GameController" a great deal in
Anyone familiar with web design the last few pages. What we want to create is an object that will ex-
and coding might recognize in this ist throughout the entire game, in all of its scenes, and act as a cen-
"GameController" object many tral place that we can use to, well, control aspects of our game.
aspects of something known as
a server-side include ... and they’d Create a new scene (File -> New) in [D] Game Setup -> 2 Game-
be right. Controller and save it as GameStartup . In this scene create a GUI-
Text object displaying the name of our game; i.e., Racing Car Tu-
An include is a bit of code on a torial. Now create another new scene in the same asset directory
website that exists on numerous called MainMenu. There are two scripts in the [D] Game Setup -> 2
pages, i.e., like a right column menu. GameController folder; a Button script (see page 34) and the game
Since it’s the same on all of the controller. The button script implements a simple mouse over effect
pages, it makes sense from a website
and simply forwards mouse clicks to the GameController. Reopen
maintenance standpoint to create
the GameController scene and create an empty object in the scene
a single version of the right column
and attach the GamerController.js script to it. Let’s take a quick look
menu on its own, and then use a
at the first part of the script.
single line of code on all of the pages
to bring it (Include it) in the page
function Start () {
when the page loads. That way, when
you need to change the right column
// Make sure that the gamecontroller
menu on 50 pages, you only need to
// always survives level loads
change it in one file and then all of
DontDestroyOnLoad (this);
the pages are updated.
// Wait until any key is pressed
A GameController object that is set
while (!Input.anyKeyDown)
to not be destroyed when a di�erent
yield;
scene loads can be used for a myriad
of purposes like this, as the Game-
// Load the main menu
Controller script illustrates.
Application.LoadLevel ("MainMenu");
}
This first part of the script does three simple things: it makes sure
the GameController script and the object it’s attached to are going
to be available in every scene; it waits until a key is pressed; and
then it starts our game by loading the MainMenu scene.
31
DisplayHighscore (), which loads the highscore level, waits until
user pressed a button, then loads the main menu again. This function-
ality is only possible if the object is marked DontDestroyOnLoad.
function CompletedRace () {
// How long did it take the player to finish
1
// The race starts only 0.75 seconds
// after the level is loaded
finishTime = Time.timeSinceLevelLoad - 0.75;
32
// Go back to the main menu
Application.LoadLevel ("MainMenu");
}
function Quit () {
Application.Quit ();
}
3
function NewGame () {
Application.LoadLevel ("Track1");
}
3. Quit simply quits the application and the NewGame button loads
the first track, which in turn starts our game.
Save the scene and reopen the [D] Game Setup -> 2 GameCon-
troller -> MainMenu scene now. Create three text objects (we’ll use
33
as buttons) named "HighScore", "Quit" and "New Game" in the cen-
tre of the screen. Attach the single Button script to these objects.
Let’s take a look at this simple script:
function OnMouseEnter () {
if (audio)
audio.Play ();
guiText.material.color =
1
Color.yellow;
}
2
function OnMouseExit () {
guiText.material.color =
Color.white;
}
function OnMouseDown () {
1. This Button script simply changes the colour of the Text GUI ob-
ject when it’s rolled over and plays a sound if there is an audio
source attached to the button.
2. It changes the text colour back to white when the cursor exits the
button object.
34
This, by the way, is an example of why we used a script and object
that contain DontDestroyOnLoad in the GameStartup scene. The
GameController object will continue to exist in the MainMenu scene
after we’ve loaded this scene from the GameStartup scene.
You will find that this technique will make creating your game GUIs
much easier. By separating the splash screen from the game op-
tions screen, and putting a game controller-type script with a Dont-
DestroyOnLoad function in it, in the same scene as the splash
screen, we don’t end up with multiple instances of a game controller
for each time we load (return to) the main menu level of our game.
3. High Scores
We’re almost done with the racing game tutorial and only need to
create a high score display that will record the top 10 player’s names
and times. Unfortunately, this little requirement happens to be the
hardest part of the tutorial. It was left until the end so that you’d have
well-grounded introduction into programming before tackling it. But,
no racing game would be complete without a way for the player to
record his name after the race. One of the many rewards players
get out of both causal and hardcore computer games is when they
finally get to see their names listed in a game’s top scores. And we
wouldn’t want to leave that out, would we?
Create an new scene in [D] Game Setup -> 3 Highscore and call it
HighScore. Create a text object in the scene called HighScoreTable
and attach the script of the same name to it. The HighScoreTable.js
is a complex script fully shown on the following three pages. Take
your time and review it function by function. Now that you’ve come
this far in the tutorial, you’ll be pleasantly surprised at how much
you’ll understand. Once again, I’ll examine and explain the key
parts of the script after you’ve read through it.
35
// This stores the score and name of the players
class Entry {
var score = 0.0;
var name = "";
}
private var entries = ArrayList ();
// This is a coroutine which runs until the user has entered his name.
// To enter a new highscore in the table do like this:
// highscoreTable.StartCoroutine ("EnterHighScore", 10);
function EnterHighScore (score : float) {
// Insert the entry, it might get rejected if the score is not high enough
var entryIndex = InsertEntry (score);
if (entryIndex == -1)
return;
// Check for the last name the user entered and reuse it
var inputName = PlayerPrefs.GetString ("LastHighscoreName");
while (true) {
for (var c : char in Input.inputString) {
// Backspace - Remove the last character
if (c == "\b"[0]) {
if (inputName.Length != 0)
inputName = inputName.Substring(0, inputName.Length - 1);
}
// End of entry.
else if (c == "\n"[0]) {
// But the user must have at least entered something
if (inputName.Length)
{
ChangeName (entryIndex, inputName);
SaveEntries ();
// Store the name the user entered as the last high score name,
// so next time the user doesn’t have to enter it again
36
PlayerPrefs.SetString ("LastHighscoreName", inputName);
return;
}
}
// Normal text - just append
else {
inputName += c;
}
}
// Make sure the name doesn’t grow above max entry length
if (inputName.Length > maxNameLength)
inputName = inputName.Substring (0, maxNameLength);
yield;
}
}
// In
for (var i=0;i<entries.Count;i++) {
if (entry.score < entries[i].score || entries[i].score == 0.0) {
entries.Insert (i, entry);
break;
}
}
37
// We changed the high score table, so we need to rebuild
// the text we rneder
SetupHighscoreTableText ();
38
function SetupHighscoreTableText () {
text = "";
count = 0;
// Loop through all entries
for (var entry : Entry in entries) {
// Create one line of the entry text
7
text += entry.name + "\t" + FormatScore (entry.score) + "\n";
count++;
}
guiText.text = text;
}
function WipeoutPrefs () {
for (var i=0;i<maxEntryCount;i++) {
PlayerPrefs.SetString ("HighScore Name " + i, "");
PlayerPrefs.SetFloat ("HighScore Score " + i, 0);
8
}
Awake ();
}
return timeText;
}
39
While it may seem complex at first glance, this whole script is actu-
ally only doing a small handful of tasks. These tasks include: setting
up an array (think "table") and populating it with the current high
scores; recording a time and allowing the player to enter his or her
name; automatically adding or removing entries after a race and
then rebuilding the array; saving the scores to the game’s prefer-
ences; adding the ability to clear the high scores; and making sure
the score is correctly formatted. The reason the script is so long is
that some of these tasks are made up of a number of subtasks and
functions. Let’s go through the function in the script in details now.
1. In this part of the script we create a class that contains two vari-
ables named score and name. We will add these two variables
to the entries array. For those readers who have never heard
the term before, an array is simply a list, and is one of the most
basic data structures in computer programming. Arrays hold some
number of data elements, generally of the same data type. Awake
is called when the script is first loaded. In this script, we call two
functions: LoadEntries., which reads the entries from prefs;
and SetupHighscoreTableText, which builds a string out of the
highscore list and assigns it to the HighScoreTable text object we
created in the scene.
The last part of this section of script simply allows us to show a blinking
dot if the user hasn’t completed entering his name yet. We do this by
adding a "." to the entered user string the first half of every second.
And that’s it! Now, that wasn’t too hard was it? Really take your time
to review the sections in this script. Many of the techniques used
here are very simple and quite commonly used in many types of
games. You’ll be using many of these basic scripting ideas in your
own game. Once you’ve read through them and start to use and
alter them for your own games, they will become second nature
and you’ll be able to look at other scripts and better understand and
utilize them for your own projects.
41
Updates (Unity 1.5)
The latest version of Unity (1.5) has provided users with a signifi-
cant number of improvements and enhancements to the Unity en-
gine. One example has been the introduction of a specialized wheel
collider, and thats what users should now use for car wheels in this
tutorial. An example project showing the new wheel collider can be
downloaded from http://www.otee.dk/examples
The new wheel collider has support for slip curves, a specialized
friction model for cars, dampers, and allows you to model anything
from a dune buggy through to a F1 racing car game. The wheel col-
lider is of very high quality, so you can create cars that just feel right,
regardless of the type of vehcile or terrain your game models.
The new wheel collider has changed the way cars are setup from
how we did it in this tutorial. The example project (at the link above)
contains a description on how to set up cars with wheel colliders.
many of the concepts like waypoints and AI can be applied to the
new car model as well.
42