Introduction to Unity Scripting

Georgi Ivanov
We’re releasing this tutorial as part of our Unity Games by Tutorials launch celebration. The book is still in early release — check it out here. We hope you enjoy this excellent introduction to Unity Scripting in C#!

demo

Much of the power of Unity is in its rich scripting language. You can use it to handle user input, move objects in the scene, detect collisions, make use of Prefab objects, and cast directional rays around the scene to help with your game logic. It might sound intimidating, but Unity exposes well-documented APIs that make these tasks a breeze – even for novice developers!

In this tutorial, you’ll create a top-down shooter that uses Unity scripting to handle the spawning of enemies, player control, firing projectiles and other important aspects of the gameplay.

Note: This tutorial assumes you have experience with C# or similar programming languages. If you need a refresher on Unity’s interface or navigation, check out our detailed Introduction to Unity tutorial.

This tutorial was written for Unity 5.3 or greater. If necessary, you can download the latest version from here.

Getting Started

Download the BlockBuster starter project and unzip it. To open the starter project in Unity, click Open from the Start Up Wizard, then navigate to the project folder. Alternatively, you can open the Main.unity scene located in BlockBuster/Assets/Scenes if you wish.

This is how your scene should look:

StarterScene

Take a look around in the Scene view. There’s a small arena, which will be the battleground for the game, and a camera that will follow the Player as they move around. If your layout is different than in the screenshot, select the top-right drop down menu and change it to 2 by 3.

Screen Shot 2016-03-28 at 2.38.37 PM

What’s a game without a hero? Your first task is to create a game object to represent the Player in the scene.

Creating the Player

In the Hierarchy, click the Create button and select Sphere from the 3D section. Position the Sphere at (0, 0.5, 0) and name it Player:

PlayerSphere

From now on, you’ll reference this Sphere as the Player object.

Unity uses a component system to build its game objects; this means all objects in a scene can be created from any combination of components such as Transform, which describes the position of an object; Mesh Filter, which contains graphic geometry; or any number of Scripts.

The Player object will need to respond to collisions with other objects in the scene.

To make this happen, select the Player in the Hierarchy. From the Inspector tab, press the Add Component button. In the Physics category, select the Rigidbody component; this will put the Player object under the control of Unity’s physics engine.

Change the Rigidody’s values as follows:

  1. Drag: 1
  2. Angular Drag: 0
  3. Constraints: Freeze Position: Y

RigidBodySetup

Creating the Player Movement Script

Now that you have a Player object, it’s time to create the script that will take input from the keyboard and move the Player around.

In the Project Browser, click the Create button and select Folder. Name the new folder Scripts and create a subfolder within named Player. Inside the Player folder, click the Create button and select C# Script. Name your new script PlayerMovement. The sequence looks like this:

FolderStructure

Note: The Player object will contain multiple scripts, each responsible for different parts of its behavior. Keeping all related scripts in a separate folder makes project navigation easier and reduces clutter.

Double-click the PlayerMovement.cs script. On a Mac, this will open MonoDevelop, the IDE packaged with Unity on the Mac; on Windows, it should open Visual Studio. This tutorial assumes you’re using MonoDevelop.

Inside the class declare the following two public variables:

public float acceleration;
public float maxSpeed;

acceleration describes how the Player's speed increases over time. maxSpeed is the “speed limit”. Making a variable public exposes it in the Inspector so you can set its value through Unity’s interface and tweak it as needed.

Just below that, declare the following variables:

private Rigidbody rigidBody;
private KeyCode[] inputKeys;
private Vector3[] directionsForKeys;

Private variables can’t be set through the Inspector; It’s the programmer’s responsibility to initialize them at the appropriate time.

Change Start() to look like the following:

void Start () {
  inputKeys = new KeyCode[] { KeyCode.W, KeyCode.A, KeyCode.S, KeyCode.D };
  directionsForKeys = new Vector3[] { Vector3.forward, Vector3.left, Vector3.back, Vector3.right };
  rigidBody = GetComponent<Rigidbody>();
}

The inputKeys array contains the key codes you’ll use to move the Player. directionsForKeys contains the corresponding directions for each key, e.g. pressing W moves the object forward. As for the last line – do you remember the Rigidbody you added earlier? This is one way you can obtain a reference to that component.

To move the Player, you’ll have to handle input from the keyboard.

Rename Update() to FixedUpdate() and add to it the following code:

// 1
void FixedUpdate () {
  for (int i = 0; i < inputKeys.Length; i++){
    var key = inputKeys[i];
 
    // 2
    if(Input.GetKey(key)) {
      // 3
      Vector3 movement = directionsForKeys[i] * acceleration * Time.deltaTime;
    }
  }
}

There are couple of important things going on here:

  1. FixedUpdate() is frame rate independent and should be used when working with Rigidbodies.
  2. This loop checks to see if any of the input keys were pressed.
  3. Here you get the direction for the pressed key, multiply it by the acceleration and the number of seconds it took to complete the last fixed frame. This produces a vector that you’ll use to move the Player object.

Hmm…what other secret methods like FixedUpdate are available?

When you create a new script in Unity, you’re in fact creating a new MonoBehaviour object. If you come from the iOS world, this object is the equivalent of a UIViewController; that is, you can use this object to respond to events inside of Unity and to access your own data objects.

MonoBehaviours have lots of different methods, and they respond to a variety of events. For instance, if you want to initialize some variables when the MonoBehaviour is instanced, then you can implement Awake(). To run code when the MonoBehaviour is disabled, you can implement OnDisable().

For a full list of events, check out Unity’s documentation.

If you are new to game programming, you might ask yourself why you have to multiply by Time.deltaTime. The general rule is when you perform an action in every fixed time frame, you need to multiply by Time.deltaTime. In this case you want to accelerate in the direction of the key press proportional to the fixed update time.

Add the following method below FixedUpdate():

void movePlayer(Vector3 movement) {
  if(rigidBody.velocity.magnitude * acceleration > maxSpeed) {
    rigidBody.AddForce(movement * -1);
  } else {
    rigidBody.AddForce(movement);
  }
}

The above method applies force to the ridigbody, causing it to move. If the current speed exceeds maxSpeed, the force goes in the opposite direction…it’s kind of like a speed limit.

In FixedUpdate(), before the closing brace of the if-statement, add the following line::

movePlayer(movement);

Perfect! Return to Unity; in the Project Browser, drag the PlayerMovement script onto the Player object inside the Hierarchy. Use the Inspector to set Acceleration to 625 and Max Speed to 4375:

AssignScript

Run the scene and move the Player around with the WASD keys:

The ball is able to move around by player control

That’s a pretty good result for only a few lines of code! :]

However, there is one obvious issue – the player can quickly move out of sight, which makes it a little difficult to fight the bad guys.

Creating the Camera Script

In the Scripts folder, create a new script called CameraRig and attach it to the Main Camera. Need a bit of help to figure out the steps? You can check the hint below for the solution.

Solution Inside SelectShow

Now create the following variables inside the newly created CameraRig class:

public float moveSpeed;
public GameObject target;
 
private Transform rigTransform;

As you might have guessed, moveSpeed is the speed with which the camera will follow the target – which can be any game object inside the scene.

Inside of Start(), add the following line:

rigTransform = this.transform.parent;

This code gets a reference to the parent Camera object’s transform in the scene hierarchy. Every object in a scene has a Transform, which describes the position, rotation and scale of an object.

CameraRigHierarchy

In the same script, add the method below:

void FixedUpdate () {
  if(target == null){
    return;
  }
 
  rigTransform.position = Vector3.Lerp(rigTransform.position, target.transform.position, 
    Time.deltaTime * moveSpeed);
}

The CameraRig movement code is a bit simpler than the one in PlayerMovement. This is because you don’t need a Rigidbody; simply interpolating between the positions of the rigTransform and target is enough.

Vector3.Lerp() takes two points in space and a float in the range of [0, 1], which describes a point along the two endpoints. The left endpoint is 0, and the right endpoint is 1. Passing in 0.5 to Lerp() would return a point exactly between both endpoints.

This moves the rigTransform closer to the target position with a little bit of easing. In short – the camera follows the player.

Return to Unity. Make sure the Main Camera is still selected in the Hierarchy. In the Inspector, set the Move Speed to 8 and the Target to the Player:

RigHookup

Run the game and move around the scene; the camera should follow the target transform smoothly wherever it goes.

The camera follows the player around.

Creating an Enemy

A shooter game without enemies would be easy to beat, but boring. :] Create an enemy cube by clicking GameObject\3D Object\Cube from the top menu. Rename your Cube to Enemy and add a Rigidbody component.

In the Inspector, first set the Cube’s Transform to (0, 0.5,4). In the Constraints section of the Rigidbody component, check the Y checkbox in the Freeze Position category.

The properties that need to be updated are highlighted in the screenshot.

Excellent – now to make your enemies move around in a menacing manner. Create a script named Enemy in the Scripts folder. You should be a pro at this by now, but if not, check the instructions earlier in the tutorial for reference.

Next, add the following public variables inside the class:

public float moveSpeed;
public int health;
public int damage;
public Transform targetTransform;

You probably can figure out what those variables represent without too much difficulty. You used moveSpeed earlier to create the camera rig, and it has the same effect here. health and damage help determine when an enemy should die and how much their death will hurt the Player. Finally, targetTransform references the Player's transform.

Speaking of the Player, you’ll need to create a class to represent all the Player goodness that the enemy wants to destroy.

In the Project Browser, select Player folder and create a new script named Player; this script will react to collisions and keep track of the Player’s health. Double-click the script to edit it.

Add the following public variable to store the Player’s the health:

public int health = 3;

This provides a default value for the health, but it can be modified in the Inspector as well.

To handle collisions, add the following methods:

 
void collidedWithEnemy(Enemy enemy) {
  // Enemy attack code
  if(health <= 0) {
    // Todo 
  }
}
 
void OnCollisionEnter (Collision col) {
    Enemy enemy = col.collider.gameObject.GetComponent<Enemy>();
    collidedWithEnemy(enemy);
}

OnCollisionEnter() triggers when two rigidbodies with colliders touch. The Collision argument contains information about such things as contact points and impact velocities. In this case, you’re only interested in the Enemy component of the colliding object, so you can call collidedWithEnemy() and execute the attack logic – which you’ll add next.

Switch back to Enemy.cs and add the following methods:

void FixedUpdate () {
  if(targetTransform != null) {
    this.transform.position = Vector3.MoveTowards(this.transform.position, targetTransform.transform.position, Time.deltaTime * moveSpeed);
  }
}
 
public void TakeDamage(int damage) {
  health -= damage;
  if(health <= 0) {
    Destroy(this.gameObject);
  }
}
 
public void Attack(Player player) {
  player.health -= this.damage;
  Destroy(this.gameObject);
}

You’re already familiar with FixedUpdate(), the slight difference is you’re using MoveTowards() instead of Lerp(). This is because the Enemy should move at the same speed all the time and not ease-in as it approaches the target. When an enemy is hit with a projectile, TakeDamage() is called; when the Enemy gets to 0 health it will destroy itself. Attack() is similar – it applies damage to the Player and then the enemy destroys itself.

Switch back to Player.cs and in collidedWithEnemy(), replace the Enemy attack code comment with the following:

enemy.Attack(this);

The player will take damage and the enemy will self destruct in the process.

Switch back to Unity. Attach the Enemy script to the Enemy object and in the Inspector, set the following values on the Enemy:

  1. Move Speed: 5
  2. Health: 2
  3. Damage: 1
  4. Target Transform: Player

By now you should be able to do all of this yourself. Try it on your own, and then compare the result with the GIF below:

Solution Inside SelectShow

In the game, an Enemy collising with the Player constitutes a valid enemy attack. Detecting collisions with Unity’s physics is almost a trivial task.

Finally, attach the Player script to the Player in the Hierarchy.

Run the game, and keep an eye on the console:

Enemy collisions create console errors

When the enemy reaches the Player, it performs the attack successfully and reduces the Player’s health variable to 2. However there is a NullReferenceException thrown in the console, pointing to the Player script:

CollisionException

Aha – the Player can collide not only with enemies, but also other parts of the game world, such as the Arena. These game objects don’t have an Enemy script and therefore GetComponent() returns null.

Open Player.cs. In OnCollisionEnter(), wrap collidedWithEnemy() in an if statements:

if(enemy) {
  collidedWithEnemy(enemy);
}

No more nulls!

Working with Prefabs

Simply running around and avoiding enemies is a pretty one-sided game. Its time to arm the Player for combat.

Click the Create button in the Hierarchy and select 3D Object/Capsule. Name it Projectile and give it the following transform values:

  1. Position: (0, 0, 0)
  2. Rotation: (90, 0, 0)
  3. Scale: (0.075, 0.246, 0.075)

Setting default values

Each time the Player shoots, it will fire an instance of Projectile. To make this happen, you need to create a Prefab. Unlike the objects you already have in the scene, Prefabs are created on-demand by the game logic.

Create a new folder under Assets, called Prefabs. Now drag the Projectile object into this folder. That’s it: you have a Prefab!

CreatingPrefab

Your Prefab will need a bit of script. Create a new script inside the Scripts folder named Projectile and add to it the following class variables:

public float speed;
public int damage;
 
Vector3 shootDirection;

Just like any moving object so far in this tutorial, this one will also have speed and damage variables, since it’s part of the combat logic. The shootDirection vector determines where the Projectile will go.

Put that vector to work by implementing the following methods inside the class:

// 1
void FixedUpdate () {
  this.transform.Translate(shootDirection * speed, Space.World);
}
 
// 2
public void FireProjectile(Ray shootRay) {
  this.shootDirection = shootRay.direction;
  this.transform.position = shootRay.origin;
}
 
// 3
void OnCollisionEnter (Collision col) {
  Enemy enemy = col.collider.gameObject.GetComponent<Enemy>();
  if(enemy) {
    enemy.TakeDamage(damage);
  }
  Destroy(this.gameObject);
}

Here’s what’s going on in the above code:

  1. The Projectile moves differently than everything else in this game. It doesn’t have a target, or some force applied to it over time; instead, it moves in a predetermined direction for its entire lifecycle.
  2. Here you set the starting position and direction of the Prefab. This Ray argument seems pretty mysterious, but you’ll soon learn how it’s calculated.
  3. If a projectile collides with an enemy, it calls TakeDamage() and destroys itself.

In the scene Hierarchy, attach the Projectile script to the Projectile GameObject. Set the Speed to 0.2 and Damage to 1, then click the Apply button located near the top of the Inspector. This will apply the changes you just made to all instances of this prefab.

Setting values in the Inspector

Remove the Projectile object from the scene Hierarchy – you don’t need it anymore.

Firing Projectiles

Now that you have a prefab that can move and apply damage, you’re ready to start shooting.

Inside the Player folder, create a new script named PlayerShooting and attach it to the Player game object in the scene. Inside the class, declare the following variables:

public Projectile projectilePrefab;
public LayerMask mask;

The first variable will contain a reference to the Projectile Prefab you created earlier. Every time your Player fires a projectile, you’ll create a new instance from this Prefab. The mask variable is used to filter GameObjects.

Wait, casting Rays? What is this sorcery?

No, there’s no black magic afoot – there are times in your game when you’ll need to know if a collider exists in a particular direction. To do this, Unity can cast an invisible ray from a certain point in a direction you specify. You’ll likely encounter a lot of GameObjects that intersect with the ray, so using a mask lets you filter out any unwanted objects.

Raycasts are incredibly useful and can used for a variety of purposes. They’re commonly used to test if another player has been struck by a projectile, but you can also use them to test if there is any geometry underneath a mouse pointer. To learn more about Raycasts, check out this Unity live training video on the Unity site.

The image below shows a ray cast from a cube to a cone. Since the ray has a iconsphere mask on it, it ignores that GameObect and reports a hit on the cone:

Demonstration of a raycast in action

Now it’s time for you to fire your own rays.

Add the following to PlayerShooting.cs:

void shoot(RaycastHit hit){
  // 1
  var projectile = Instantiate(projectilePrefab).GetComponent<Projectile>();
  // 2
  var pointAboveFloor = hit.point + new Vector3(0, this.transform.position.y, 0);
 
  // 3
  var direction = pointAboveFloor - transform.position;
 
  // 4
  var shootRay = new Ray(this.transform.position, direction);
  Debug.DrawRay(shootRay.origin, shootRay.direction * 100.1f, Color.green, 2);
 
  // 5
  Physics.IgnoreCollision(GetComponent<Collider>(), projectile.GetComponent<Collider>());
 
  // 6
  projectile.FireProjectile(shootRay);
}

Here’s what the above code does:

  1. Instantiates a projectile Prefab and gets its Projectile component so it can be initialized.
  2. This point always looks like (x, 0.5, z). X and Z are coordinates on the floor where the ray cast from the mouse click position hits. This calculation is important, because the projectile has to be parallel to the floor – otherwise you’d be shooting downward, and only amateurs shoot towards the ground. :]
  3. Calculates the direction from the Player GameObject to pointAboveFloor.
  4. Creates a new ray that describes the projectile trajectory by its origin and direction.
  5. This line tells Unity’s physics engine to ignore collisions between the Player collider and Projectile collider. Otherwise OnCollisionEnter() in the Projectile script would be called before it had the chance to fly off.
  6. Finally, it sets the trajectory for the projectile.
Note: Using Debug.DrawRay() when ray casting is invaluable as it can help you visualize how the ray looks and what it hits.

With the firing logic in place, add the following methods to let the player actually pull the trigger:

// 1
void raycastOnMouseClick () {
  RaycastHit hit;
  Ray rayToFloor = Camera.main.ScreenPointToRay(Input.mousePosition);
  Debug.DrawRay(rayToFloor.origin, rayToFloor.direction * 100.1f, Color.red, 2);
 
  if(Physics.Raycast(rayToFloor, out hit, 100.0f, mask, QueryTriggerInteraction.Collide)) {
    shoot(hit);
  }
}
 
// 2
void Update () {
  bool mouseButtonDown = Input.GetMouseButtonDown(0);
  if(mouseButtonDown) {
    raycastOnMouseClick();  
  }
}

Taking each numbered comment in turn:

  1. This method casts a ray from the camera to the point where the mouse clicked. Then it checks to see if this ray intersects a game object with the given LayerMask.
  2. On every update, the script checks for a left mouse button press. If it finds one, it calls raycastOnMouseClick().

Return back to Unity and set up the following variables in the Inspector:

  • Projectile Prefab: reference the Projectile from the prefab folder
  • Mask: Floor

ShootingScriptSetup

Note: Unity comes with a limited amount of predefined masks which are also known as layers.

You can create your own by clicking the Layer dropdown of a GameObject and selecting Add Layer:

The layer location

You can also assign masks to GameObjects by selecting a layer from that Layer dropdown:

Selecting a layer

For more information about layers, check out Unity’s Layers documentation.

Run the project and fire at will! The projectiles are fired in the desired direction, but something seems a bit off, doesn’t it?

NoProjectileRotation2

It would be much cooler if the projectiles were pointing in the direction of travel. To fix this, open up the Projectile.cs script and add the following method:

void rotateInShootDirection() {
  Vector3 newRotation = Vector3.RotateTowards(transform.forward, shootDirection, 0.01f, 0.0f);
  transform.rotation = Quaternion.LookRotation(newRotation);
}
Note: RotateTowards is very similar to MoveTowards, but it treats vectors as directions instead of positions. Also, you don’t need to change rotation over time so using a step close to zero will suffice. Transform rotations in Unity are expressed using quaternions, which are beyond the scope of this tutorial. All you need to know for this tutorial is that they have advantages over vectors when making calculations that involve rotations in 3D.

Interested in learning more about quaternions and why they’re useful? Check out this excellent article: How I learned to Stop Worrying and Love Quaternions

At the end of FireProjectile(), add a call to rotateInShootDirection(). FireProjectile() should now look like the following:

public void FireProjectile(Ray shootRay) {
  this.shootDirection = shootRay.direction;
  this.transform.position = shootRay.origin;
  rotateInShootDirection();
}

Run the game once again and fire in a few different directions; this time the projectiles will point in the direction they are shot:

ProjectileRotation

Remove the Debug.DrawRay calls as you won’t need them further.

Generating More Bad Guys

Having only one enemy isn’t terribly challenging. But now that you know about Prefabs, you can spawn all the adversaries you want! :]

To keep the player guessing, you can randomize the health, speed and location of each Enemy.

Creating an empty game object – GameObject\Create Empty. Name it EnemyProducer and add a Box Collider component. Set the values in the Inspector as follows:

  1. Position: (0, 0, 0)
  2. Box Collider:

    • Is Trigger: true
    • Center: (0, 0.5, 0)
    • Size: (29, 1, 29)

Setting box collider values

The collider you attached defines a particular 3D space inside the Arena. To see this, select the Enemy Producer GameObject in the Hierarchy and look inside the Scene view:

The green wire outlines represent a collider.

The green wire outlines represent a collider.

You are about to write a script that picks a random location in this space along the X and Z axis and instantiates an enemy Prefab.

Create a new script called EnemyProducer and attach it to the EnemyProducer GameObject. Inside the newly set up class, add the following instance members:

public bool shouldSpawn;
public Enemy[] enemyPrefabs;
public float[] moveSpeedRange;
public int[] healthRange;
 
private Bounds spawnArea;
private GameObject player;

The first variable enables and disables spawning. The script will pick a random enemy prefab from enemyPrefabs and instantiate it. The next two arrays will specify a minimal and maximal value of speed and health. The spawn area is that green box you saw in the Scene view. Finally, you’ll need a reference to the Player and pass it as target to the bad guys.

Inside the script, define the following methods:

public void SpawnEnemies(bool shouldSpawn) {
  if(shouldSpawn) {
    player = GameObject.FindGameObjectWithTag("Player");
  }
  this.shouldSpawn = shouldSpawn;
}
 
void Start () {
  spawnArea = this.GetComponent<BoxCollider>().bounds;
  SpawnEnemies(shouldSpawn);
  InvokeRepeating("spawnEnemy", 0.5f, 1.0f);
}

SpawnEnemies() gets a reference of a game object with tag Player and determines whether an enemy should spawn.

Start() initializes the spawn area and schedules the invocation of a method 0.5 seconds after the game starts. It will get called repeatedly every second. Besides acting as a setter method, SpawnEnemies() also gets a reference of a game object with tag Player.

The Player game object isn’t tagged yet – you’ll do that now. Select the Player object from the Hierarchy and then in the Inspector tab, choose Player from the Tag dropdown menu:

Screen Shot 2016-05-26 at 12.45.49 PM

Now, you need to write the actual spawning code for an individual enemy.

Open up the Enemy script and add the method below:

public void Initialize(Transform target, float moveSpeed, int health) {
  this.targetTransform = target;
  this.moveSpeed = moveSpeed;
  this.health = health;
}

This simply acts as a setter for creating the object. Next up: the code to spawn your leagues of enemies. Open EnemyProducer.cs and add the following methods:

 
Vector3 randomSpawnPosition() {
  float x = Random.Range(spawnArea.min.x, spawnArea.max.x);
  float z = Random.Range(spawnArea.min.z, spawnArea.max.z);
  float y = 0.5f;
 
  return new Vector3(x, y, z);
}
 
void spawnEnemy() {
  if(shouldSpawn == false || player == null) {
    return;
  }
 
  int index = Random.Range(0, enemyPrefabs.Length);
  var newEnemy = Instantiate(enemyPrefabs[index], randomSpawnPosition(), Quaternion.identity) as Enemy;
  newEnemy.Initialize(player.transform, 
      Random.Range(moveSpeedRange[0], moveSpeedRange[1]), 
      Random.Range(healthRange[0], healthRange[1]));
}

All that spawnEnemy() does is pick a random enemy prefab, instantiate it at a random position and initialize the Enemy script public variables.

EnemyProducer.cs is almost ready to go!

Return back to Unity. Create an Enemy prefab by dragging the Enemy object from the Hierarchy to the Prefabs folder. Remove the enemy object from the scene – you don’t need it anymore. Next set the Enemy Producer script public variables like so:

  1. Should Spawn: True
  2. Enemy Prefabs:
    • Size: 1
    • Element 0: Reference the enemy prefab
  3. Move Speed Range:
    • Size: 2
    • Element 0: 3
    • Element 1: 8
  4. Health Range:
    • Size: 2
    • Element 0: 2
    • Element 1: 6

EnemyProducerSetup

Run the game and check it out – an endless stream of bad guys!

SpawningEnemies

Okay, those cubes don’t look terribly frightening. Time to spice things up.

Create a 3D Cylinder and Capsule in the scene. Name them Enemy2 and Enemy3 respectively. Just as you did earlier with the first enemy, add a Rigidbody component and the Enemy script to both of them. Select Enemy2 and change its configuration in the Inspector like so:

  1. Scale: (0, 0.5, 0)
  2. Rigidbody:

    • Use Gravity: False
    • Freeze Position: Y
    • Freeze Rotation: X, Y, Z
  3. Enemy Component:

    • Move Speed: 5
    • Health: 2
    • Damage: 1
    • Target Transform: None

Now do the same for Enemy3, but set its Scale to 0.7:

OtherEnemiesPrefabSettings

Next, turn them into Prefabs, just as you did with the original Enemy, and reference all of them in the Enemy Producer. The values in the Inspector should look like this:

  • Enemy Prefabs:

    • Size: 3
    • Element 0: Enemy
    • Element 1: Enemy2
    • Element 2: Enemy3

EnemyPrefabs

Run the game; you’ll see different prefabs spawn inside the Arena.

SpawningDifferentEnemies

It won’t take long before you realize that you’re invincible! As awesome as that is, you need to level the playing field a bit.

Implementing the Game Controller

Now that you have shooting, movement and enemies in place, you’ll implement a basic game controller. It will restart the game once the Player is “dead”. But first, you’ll have to create a mechanism to notify any interested parties that the Player has reached 0 health.

Open the Player script and add the following above the class declaration:

using System;

Inside the class add the following new public event:

public event Action<Player> onPlayerDeath;

An event is a C# language feature that lets you broadcast changes in objects to any listeners. To learn how to use events, check out Unity’s live training on events..

Edit collidedWithEnemy() to look like the code below:

void collidedWithEnemy(Enemy enemy) {
  enemy.Attack(this);
  if(health <= 0) {
    if(onPlayerDeath != null) {
      onPlayerDeath(this);
    }
  }
}

Events provide a neat way for objects to signal state changes between themselves. A game controller would be very interested in the event declared above. In the Scripts folder, create a new script called GameController. Double-click the file to edit it, and add to it the following variables:

public EnemyProducer enemyProducer;
public GameObject playerPrefab;

The script will need to have some control over the enemy production, as it doesn’t make sense to spawn enemies once the Player has perished. Also, restarting the game means you will have to recreate the Player which means…that’s right, it will become a Prefab.

Add the following methods:

void Start () {
  var player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();
  player.onPlayerDeath += onPlayerDeath;
}
 
void onPlayerDeath(Player player) {
  enemyProducer.SpawnEnemies(false);
  Destroy(player.gameObject);
 
  Invoke("restartGame", 3);
}

In Start(), the script gets a reference to the Player script and subscribes for the event you created earlier. Once the Player’s health reaches 0 onPlayerDeath() will be called, stoping enemy production, removing the Player object from the scene and invoking restartGame() method after 3 seconds.

Finally, add the implementation of the restart game action:

void restartGame() {
  var enemies = GameObject.FindGameObjectsWithTag("Enemy");
  foreach (var enemy in enemies)
  {
    Destroy(enemy);
  }
 
  var playerObject = Instantiate(playerPrefab, new Vector3(0, 0.5f, 0), Quaternion.identity) as GameObject;
  var cameraRig = Camera.main.GetComponent<CameraRig>();
  cameraRig.target = playerObject;
  enemyProducer.SpawnEnemies(true);
  playerObject.GetComponent<Player>().onPlayerDeath += onPlayerDeath;
}

Here you’re doing a bit of cleanup: you destroy all enemies in the scene and create a new Player object. You then reassign the camera rig’s target to this instance, resume enemy production, and subscribe Game Controller to the player death event.

Now return to Unity, open the Prefabs folder and change the tag of all Enemy prefabs to Enemy. Next, make the Player game object into a Prefab by dragging it into the Prefabs folder. Create an empty game object, name it GameController and attach the script you just created. Hookup all the required references in Inspector.

By now you’re pretty familiar with this pattern. Try placing the references by yourself and then check your results against the illustration hidden below:

Solution Inside SelectShow

Run the game again to see the game controller in action.

GameCycle

That’s it –  you’ve scripted your first Unity game! Congratulations! :]

Where to Go From Here?

You can download the completed project here.

By now you should have a good understanding of what it takes to bring together a simple action game. Making games is not a simple task; it definitely takes a lot of work and scripting is only one of the elements needed to bring a project to life. To add a good level of polish, you will also need to add animations and UI to your games. For this reason, I highly recommend checking out our other tutorials on these subjects:

If you’re keen to learn how to design, code and publish your own games built in Unity, check out Unity Games by Tutorials.

The book teaches you everything you need to know about building games in Unity, whether you’re a beginner or a more experienced game developer. In the book, you’ll build four great games:

  • A 3D twin-stick shooter
  • A classic 2D platformer
  • A 3D tower-defense game (with virtual reality mode!)
  • A first-person shooter

The book is still in development, but here’s a preview of the first game you’ll build in the book:

I hope you enjoyed this tutorial and found inspiration to take a crack at that game you always wanted to create. Questions or comments? Join the discussion below!

Georgi Ivanov

Georgi is an independent iOS developer and consultant with background in server-side development. Every so often shares thoughts and experience on his blog. Whenever he's not building apps, he spends his time reading interesting books or concocting a new kitchen disaster.

You can find Georgi on Twtter and LinkedIn.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 28 total!

Android Team

... 15 total!

macOS Team

... 10 total!

Apple Game Frameworks Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 11 total!

Resident Authors Team

... 15 total!