Beginning Unity 3D for iOS: Part 3/3

Christine Abernathy
Learn how to use Unity to make a simple 3D iOS game!

Learn how to use Unity to make a simple 3D iOS game!

This is a post by Tutorial Team Member Christine Abernathy, an Engineer on the Developer Advocacy team at Facebook. You can also find her on .

Welcome to the third and final part the Beginning Unity 3D for iOS tutorial series!

In the first part of this series, you toured the basic Unity tools, created a game with a simple player control mechanism and learned how to deploy your project on iOS.

Then in the second part of the series, you enhanced the movement of your Heroic Cube and brought some life to the world it occupies with sky, grass, trees and a variable terrain.

In this third and final part, you’ll add gameplay to the project. Instead of simply moving around the scene, your Heroic Cube will have to dash (as cubes do) to a finish line within a certain amount of time.

To give the player a challenge, obstacles will rain down on the Cube as it zigs and zags its way to that finish line. A countdown timer will add to the drama. Success will be greeted with cheers – failure with the deafening silence of defeat. :]

You’re almost to the finish line too, so remember – it’s hip to be square!

Getting Started: The End in Sight!

First, save the Level_2 scene as a new scene named Level_3. You’ll be making all your changes for Part 3 of the tutorial in this new scene.

You’ll create a finish line using two posts and a thick line connecting them, so that the player can clearly see the target destination. The finish line will include an invisible wall that’s used as a trigger when the Heroic Cube crosses the finish line.

Select GameObject\Create Empty to create a new object that represents the finish line. This is the parent GameObject under which you’ll add posts, a line, and a wall.

Rename the object to Finish Line via the Inspector panel or by right-clicking the object and selecting Rename. Set the Transform Position to 0,0,0.

To create the first post, select GameObject\Create Other\Cylinder. Rename it to Post1. Set the Transform Scale to 1,3,1. Set the Transform Position to -10,0,20 to put it in front of the player and to the left. Using the Move Tool, adjust the y position so that the bottom of the cylinder object is just a little below the ground.

Hint: View the scene from the z-axis to help you make the adjustments.

Post height adjustment.

Drag the Post1 GameObject and place it under the Finish Line GameObject to set the latter as the parent for Post1.

Post parenting.

To create a second post, select Post1, right-click and select Copy. Right-click once more and select Paste. Rename the new GameObject from Post1 to Post2. Using the Move Tool, adjust the x position so that the post is to the right of the player.

Hint: View the scene from the y-axis (from above) to help you make adjustments. Alternatively, setting the Transform x position to 10 should do the trick.

Second post position adjustment.

Next, create the wall that helps you detect when the finish line is crossed. Select GameObject\Create Other\Cube and rename it to Goal. Set the Transform Scale to 24,10,0.5. Set the initial Transform Position to 0,2,0.

Goal scale adjustment.

Move the wall to just behind the two posts. If you need to, adjust the x scale value so the wall stretches from one post to the other.

Goal scale and position adjustment.

With the wall still selected, open the Inspector\Box Collider component and check the Is Trigger value. Uncheck the Mesh Renderer component to make the wall invisible.

Goal trigger and renderer modifications.

Drag the Goal GameObject and place it under the Finish Line GameObject to parent the object.

Goal parenting.

Connect the Dots

Next you’ll create the line connecting the posts so that the player can clearly see the finish line. You’ll do this by drawing a line from one post to the other via script.

Select Asset\Create\JavaScript to create the new script and name it FinishLineRender. Open the script by double-clicking it in the Project View. Delete the stubbed out functions and add the following code:

// The finish line posts
var post1 : Transform;
var post2 : Transform;

var lineColor : Color = Color.green;

function Start () {
    // Set the visual for the finish line posts
    var lineRenderer : LineRenderer = gameObject.AddComponent(LineRenderer);
    lineRenderer.SetPosition(0, post1.position);
    lineRenderer.SetPosition(1, post2.position);
    lineRenderer.material = new Material (Shader.Find("Particles/Additive"));
    lineRenderer.SetColors(lineColor, lineColor);
}

The LineRenderer class allows you to draw lines in 3D space. Given an array of points, you can use the Line Renderer component (Components\Effects\Line Renderer) to draw straight lines.

You could have added the Line Renderer component to the Finish Line object and hard-coded the transform positions for Post1 and Post2, but it’s easier to create the Line Renderer through code. That’s what you’re doing here.

You draw the line in the Start() function, as it only needs to happen once. First you add the LineRenderer script interface component, then you set the first and second points for the line to the values from the variable inputs that will be tied to the two posts. You set the material for the renderer.

Finally, you set the color for the start and end of the line. The line color variable is made public so you can change it.

Attach the FinishLineRender script component to the Finish Line GameObject.

Hint: You can add the script to the GameObject by selecting the Finish Line object and then tapping the Add Component button in the Inspector. This should bring up a search box – simply type the first few letters of the word “FinishLineRender” and it should show you the script.

Finish Line script added.

Assign the Post1 and Post2 GameObjects to the Post 1 and Post 2 variables, respectively.

Finish Line script variables assigned.

Preview the game in the Unity Editor. You should see two goal posts and a green line across them indicating the finish line. Stop the game.

Game view with finish line.

Next you’ll create a new script that detects the finish line crossing event, and will attach this new script to the Goal GameObject. To do this, select Assets\Create\JavaScript and name the script FinishLineDetection.

Open the new script and delete the stubbed out functions. Add the following code:

function OnTriggerEnter(other : Collider) {
      
    if (other.gameObject.tag == "Player") 
    { 
        Debug.Log("You made it!!!"); 
    } 
}
@script RequireComponent(Collider)

You call the OnTriggerEnter() function whenever another collider enters the GameObject. The GameObject needs to be set up as a trigger for the event to fire (which you’ve already done for the Goal object).

The player GameObject has a Character Controller component that is a Collider. So when the player runs into the Goal GameObject, the OnTriggerEnter() event is fired.

In your code, you check if the GameObject that entered the Goal has a tag named “Player”. If that’s the case, the Heroic Cube has crossed the finish line.

Attach the FinishLineDetection script to the Goal GameObject.

Hint: With the Goal object selected in the Hierarchy View, you can drag the FinishLineDetection script from the Project View to the Inspector\Add Component button to attach the script component to the GameObject.

Before you tag the player, give the player GameObject a name other than plain old “cube”. To keep things consistent, rename the Cube GameObject to Player.

Player GameObject renamed.

Now, add a tag to the player object to enable the finish line detection logic. In the Inspector for the player, drop down the Tag selection and choose Player.

Player tag selection.

Player tagged.

Player is one of the pre-built tags available. Later on, you’ll create your own tags to help identify all enemies in the game.

Click Play and move the Heroic Cube past the goal line. You should see a “You made it!!!” log message letting you know that the player crossed the finish line.

Finish line detection test.

Yes, the Heroic Cube can get to the goal line and win the game, but you can’t let it off so easy. You’ll add two levels of complexity to the game: a time-based challenge and obstacles. First, let’s add the obstacles.

Create the Launcher

Create an empty GameObject and add it to the scene. Name it Launcher. This represents the evil block empire that’s launching obstacles to put a stop to the Heroic Cube’s advances.

Using the Move Tool, place the launcher object in between the player and the finish line in the z direction and above the player. You can start with a Transform Position of 0,12,8 and tweak it as necessary.

Launcher adjustment.

The main reason for the launcher’s existence is to launch obstacles, so you need to give it some to launch!

Ammunition is typically created in Unity by designing GameObjects and then creating Prefabs that can be instantiated in the scene, as required, during gameplay. You’ll create an Obstacle GameObject, turn it into a Prefab, and then let the Launcher take care of launching it onto the hapless player.

Create the Obstacles

Create a cube GameObject and name it Obstacle. Set the Transform Scale to 2,2,2 so it’s bigger than the player and hence more intimidating. These are cubes that have gone to the Dark Side. :]

Give the obstacle an interesting look other than the default grey matter. To match the completed sample, first import a material from the Character Controller package: select Assets\Import Package\Character Controller, then select the constructor_done material and the relevant textures as shown in the image below, and finally click Import.

Import package material for obstacle.

The new material should show up in your Project View.

Material for obstacle imported.

Select the Obstacle GameObject. Change the render material by modifying the Inspector\Mesh Renderer\Materials\Element 0 property. Click on the circular icon next to the property to bring up the Select Material dialog.

Selection of obstacle material.

Select the constructor_done material you just imported. Close the Select Material dialog.

Obstacle material assigned.

Now you must tag the Obstacle GameObject so that later on you can take care of clearing out Obstacle instances of the scene when a new game is started.

For this, create a new tag named Enemy. Click on Inspector\Tag\Add Tag. The TagManager will show up in the right side panel. Expand the Tags array by clicking on the triangle next to the Tags label). Set the value of Element 0 to Enemy.

Adding an enemy tag.

Select the Obstacle GameObject and tag the object with the new Enemy tag.

Adding an enemy tag.

When Obstacle is instantiated, the code you’ll add will expect a Rigidbody component to be attached to the obstacle. Set that up by adding a Rigidbody. Select Component\Physics\Rigidbody (with Obstacle still selected):

Rigidbody component added to obstacle.

Click on the Assets folder in the Project View. Create a Prefab of your obstacle by selecting Assets\Create\Prefab. The Project View should show an empty Prefab. Name it Obstacle.

Empty Prefab created.

Note: If you have larger asset icons than in the screenshot above and wonder how you can get the list view of asset items, simply slide the slider below the asset list all the way to the left. :]

Drag the Obstacle GameObject into this new Prefab.

Obstacle assigned to Prefab.

The Prefab changes to a blue color to indicate that it has been assigned.

Now that you’ve created the Prefab as a reusable asset, you no longer need it in the scene. The launcher will take care of instantiating an Obstacle instance when needed. In the Hierarchy View, select the Obstacle GameObject, right-click and select Delete.

Release the Krak… Err, Obstacles

Next, complete the process by creating logic through a script to launch obstacles.

Create a new script asset and name it ObstacleLauncher.

Hint: You can also right-click in the Project View and select Create\JavaScript to create a script.

Open the new script and replace the stub functions with the following code:

var projectile : Rigidbody;
var speed = 5;
var maxObstacles = 2;
var launchInterval : float = 5.0;
var target : Transform;

private var nextLaunch : float = 0.0;
private var numObstaclesLaunched = 0;

function Start () {
    if (target == null) {
        // Find the player transform
        target = GameObject.FindGameObjectWithTag("Player").transform;
    }
}

function Update () {
    if ((numObstaclesLaunched < maxObstacles) && (Time.time > nextLaunch)) {
        // Set up the next launch time
        nextLaunch = Time.time + launchInterval;
        
        // Set up for launch direction
        var hit : RaycastHit;
        var ray : Ray;
        var hitDistance : float;
        
        // Instantiate the projectile
        var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position, transform.rotation);
        
        // Simple block, try to get in front of the player
        instantiatedProjectile.velocity = target.TransformDirection(Vector3.forward * speed);     
        
        // Increment the launch count
        numObstaclesLaunched++;   
    }
}

The launcher is programmed to launch a certain number of obstacles just in front of the player. It therefore needs an input that represents the player. Previously, when assigning GameObjects to script, you’ve done so by dragging the GameObject to the script variable using the Editor. The Start() function code shows another way to do this.

In Start(), a check is made to see if there is no target assigned. If no target is found, the code looks for a GameObject with the Player tag and assigns this GameObject to the target variable.

The GameObject.FindGameObjectWithTag() function call is typically an expensive call, as it needs to look through all GameObjects. So you’ll want to call this in Start() (which gets called once) and avoid putting it in, say, Update() (which gets called multiple times).

In the code, Update() first checks if the Launcher has sent out the maximum obstacles allowed. If not, it also checks if a set time interval has passed. This is to avoid launching too many obstacles within a short amount of time.

If it’s time to launch another obstacle, then the Obstacle Prefab is instantiated at the position and rotation corresponding to the Launcher. The instantiated obstacle is then launched in a direction that matches the player’s forward direction, so as to land just in front of the player.

Now save your code and tie up loose ends. First, attach the ObstacleLauncher script to the Launcher GameObject. Assign the Obstacle Prefab to the projectile variable in the script (you can drag the Prefab from the Assets list to the variable). Assign the Player GameObject to the target variable in the script.

Launcher variables assigned.

Play the game in the Unity Editor and verify that the blocks are launched in front of the Heroic Cube as it moves around. Adjust the launcher’s position so that the blocks are launched in between the player and the finish line. You can also adjust the finish line by moving the Finish Line GameObject in the z direction, away from the player.

Hint: You can set the Transform Position to 0,0,2. When you move the Finish Line object, the child objects come along for the ride, which is one perk of parenting or grouping related GameObjects.

Game view of launcher test.

You have most of the game functionality working now. Next you’ll pull everything together with a mission control script that displays the game timer, coordinates the gameplay and resets the scene to start a new game.

The Final Countdown

Create a new script asset and name it GameController. Open the script, delete the stub functions and add the following code:

static var gameRunning : boolean = true;

var gameTimeAllowed : float = 20.0;

private var gameMessageLabel = "";
private var gameMessageDisplay : Rect;
private var timedOut : boolean = false;
private var gameTimeRemaining : float = gameTimeAllowed;

function Awake() {
    gameMessageDisplay = Rect(10, 10, Screen.width - 20, 40);
}

function OnGUI() { 
    GUI.color = Color.yellow;
    GUI.backgroundColor = Color.black;
    
    var text : String = ""; 
    if (timedOut) {
        gameMessageLabel = "Time's up!!";
    } else {
        text = String.Format( "{0:00}:{1:00}", parseInt( gameTimeRemaining / 60.0 ), parseInt( gameTimeRemaining % 60.0 ) );
        gameMessageLabel = "Time left: " + text;
    }
    GUI.Box(gameMessageDisplay, gameMessageLabel);    
}

function Update() { 
    if (!gameRunning)
        return; 
        
    // Keep track of time and display a countdown
    gameTimeRemaining -= Time.deltaTime;
    if (gameTimeRemaining <= 0) {
        timedOut = true; 
        gameRunning = false;
    }
}

Unity provides GUI controls that make it easy to add text labels and button functionality. The gameMessageDisplay variable controls where the display is shown. Here you set up the countdown timer to display across the top of the screen.

OnGUI() is called when an event occurs such as a mouse click, or at least once a frame. GUI.Box() creates a Box Control using the dimensions you set up initially and with the current game message, which consists of the countdown time info, a success message or a failure message.

The gameTimeAllowed variable represents the game timer and is set to 20 seconds. The gameTimeRemaining variable tracks the currently remaining time. It is initially set to the gameTimeAllowed value and is decremented by Time.deltaTime in Update().

Time.deltaTime is the time in seconds that the last frame took to complete. Keep in mind that the frame rate may vary and so this value may also vary. When the gameTimeRemaining value is below zero, the timedOut flag is set to true and the player is shown a timeout message.

The code also sets up a gameRunning flag to track if – you guessed it – the game is running. This is useful to stop the countdown logic and you’ll use it later on to control object behavior when the game is not running.

Attach the script to your Main Camera object:

Game Controller script added to Main Camera.

Play the game and dawdle around until time runs out in order to test the countdown display and the failure case. It’s a little hard to see, but don’t worry, you’ll change that soon. Stop the game.

Game view of time out test.

Sometimes You Win, Sometimes You Lose

You should display a success message when the Heroic Cube crosses the finish line, or rather the invisible wall – it deserves some encouragement for finishing the challenge! The message will include the time it took to complete the challenge.

You should also let the player know when time’s up!

The best place to set these messages is the GameController script. However, the finish line detection code is in another script: FinishLineDetection.

One way you can handle this is to define a function in GameController that the FinishLineDetection script can call when the player crosses the line. This function can then trigger the desired message display via the OnGUI() function.

Add two private variables to the GameController script. One will track the time to complete the challenge and the other will flag a successful mission (the following code can go below the other existing private variables):

private var missionCompleted : boolean = false;
private var missionCompleteTime : float = gameTimeAllowed;

Then add the following code to the end of the GameController script:

function MissionComplete() { 
    if (!gameRunning)
        return;
    
    missionCompleted = true; 
    gameRunning = false;
    
    missionCompleteTime =  gameTimeAllowed - gameTimeRemaining;
}

MissionComplete() checks if the game is running. If it is, it sets a private missionCompleted flag to true and the gameRunning flag to false. The time it took to complete the mission is then saved.

Now modify OnGUI() and add the success case (as shown below) to show the time it took to complete the message. The new code goes just after the var text : String = ""; line and alters the existing if condition:

    if (missionCompleted) {
        text = String.Format( "{0:00}:{1:00}", parseInt( missionCompleteTime / 60.0 ), parseInt( missionCompleteTime % 60.0 ) );
        gameMessageLabel = "Mission completed in: " + text;
    } else if (timedOut) {
        gameMessageLabel = "Time's up!!";
        ...

Switch to the FinishLineDetection script and modify it as follows (the additions are marked with comments):

#pragma strict

var gameControllerScript : GameController; // 1: new

function OnTriggerEnter(other : Collider) {
      
    if (other.gameObject.tag == "Player") 
    { 
        Debug.Log("You made it!!!"); 
        gameControllerScript.MissionComplete(); // 2: new
    } 
}
@script RequireComponent(Collider)

The new code is numbered and does the following:

  1. Defines a public variable that points to the GameController script. You'll assign this shortly.
  2. Calls MissionComplete() in the GameController script to trigger the success case.

Finish Line script GameController variable created.

To assign the gameControllerScript variable, select the Goal GameObject, then select Inspector\Finish Line Detection and click the circular icon next to the Game Controller Script. In the pop-up dialog, select the Main Camera GameObject and close the dialog.

Selecting GameController script variable source.

Finish Line script GameController variable assigned.

Play the game and dash to the finish line, dodging those nefarious blocks. Check that the correct message is displayed when you make it in time.

Game view test of success path.

Stop the game and click Play once more. Test the failure case to make sure that that logic still works.

Transformer-ing the Display Font

You may have noticed, particularly if you have tested the game using Unity Remote or your iOS device, that the font displaying the game messages is very small. This is as good an excuse as any to learn about importing fonts into Unity.

There are a wealth of fonts you can get from sites such as http://dafont.com. The sample you’re going to use was built with the free Transformers Font. Yes, it’s the original font from the Transformers movie! Maybe your Cube and blocks are hiding alternate personalities. :]

I added the above font (and a few other resources you'll need later) to a Resources.zip file that you can here.

Download the resource archive and extract its contents. Find the font file and drag the Transformers Movie.ttf into your Project View\Assets folder to import it. Select the Transformers Movie asset in the Project View. The Inspector shows the import settings. Change the Font Size setting to 36. Click Apply.

Font import settings.

You've just imported your custom font and it's ready for use in your project.

Open the GameController script to modify the message font. Define a public variable to set the font:

var gameMessageFont : Font;

Change the font used for display labels by modifying OnGUI(), as shown below:

function OnGUI() { 
    GUI.skin.font = gameMessageFont;
    GUI.color = Color.yellow;
...

You assign the gameMessageFont public variable to GUI.skin.font to change the font.

Now select the Main Camera GameObject. Assign gameMessageFont to your newly imported font by dragging the font asset into the gameMessageFont variable.

Font assignment in Game Controller script.

Preview the game and verify that the messages are displayed using the new font.

Game view test of new font.

Ah, much better!

It’s Always Play Time!

Next, create an in-game button that will control the start of the game and allow the player to restart after a success or failure. You'll display the button whenever the game is not running. Recall that you defined a gameRunning flag that you can use to control the button's visibility.

To create the play button and related functionality, you must modify the GameController script. First define a private variable that controls the play button text:

private var playButtonText = "Play";

Then add a new function called startGame() that sets up a new game:

function startGame() {
    // Reset if starting a new game
    gameTimeRemaining = gameTimeAllowed; 
    timedOut = false;
    missionCompleted = false;
       
    // Change button text after the initial run
    playButtonText = "Play Again";
    
    // Kick off the game
    gameRunning = true;
}

Now modify OnGUI() to show the button when the game is not running by adding the following code to the end of OnGUI() (after all the existing code):

    // The menu button
    if (!gameRunning) {
        var xPos = Screen.width / 2 - 100;
        var yPos = Screen.height / 2 + 100;
        if( GUI.Button( new Rect( xPos, yPos, 200, 50 ), playButtonText ) ) {
            startGame();
        }
    }   

Finally, set the gameRunning flag to false. Just modify the existing line for the variable to switch the initial value from true to false:

static var gameRunning : boolean = false;

OnGUI() places a button using the GUI.Button() function. The button text is a variable, so it says "Play" initially and "Play Again" every subsequent time.

GUI.Button() is wrapped in an if statement. This returns true if the button is clicked. When the user clicks the button, you kick off the game. startGame() first initializes the game, changes the play button text and finally sets the gameRunning flag to true.

Preview the game in the Unity Editor. Verify that the play button is initially visible and is hidden after you click on it. Verify also that when a run is completed, the play button becomes visible once again and that the text has changed from "Play" to "Play Again". Note that each time you click on the play button after the first time, the time is reset and the countdown starts afresh.

But also notice that the player can move even before the play button is tapped. That's a little annoying, isn't it? Don’t let your Heroic Cube get a head start!

To take care of that detail, make use of the gameRunning variable, which is a global variable by virtue of the static modifier. Add the following code to the top of Update() in the MoveAround script:

   if (GameController != null && !GameController.gameRunning)
       return;

You should also disable the launcher from dropping obstacles when the game is not running. Add the following code at the top of Update() in the ObstacleLauncher script:

   if (GameController != null && !GameController.gameRunning)
       return;

Preview the game to ensure that when the game is not running, the player can’t move and obstacles are not launched.

Every Cube Deserves a Fresh Start

While the play button works correctly now, you may notice that even though the timer is reset when a new game is started, other aspects of the game are not reset.

The obstacles stop falling, as they've probably reached the maximum spawn count, but they don't disappear. Nor does the Heroic Cube revert back to its original position. A true reset requires that the game start fresh, i.e., the obstacles are cleared and reloaded and the player position is reset.

The ideal way to accomplish this is to send a message to all interested parties to reset themselves. The Heroic Cube would then slink back to its original position and the launcher would reload.

One way to do this is to have a reset function defined in a script that handles the reset behavior. GameObject.SendMessage() can then be used to call this reset function in the hope that a component (a script) attached to the GameObject handles the function call.

Here's how you'll implement this:

  1. You'll define a function called resetGame() in the MoveAround script that resets the player's position to the original position when the game started.
  2. You'll define a function called resetGame() in the ObstacleLauncher script to reset the obstacle count to zero.
  3. You'll loop through a given list of GameObjects that include the player and launcher GameObjects, calling GameObject.SendMessage("resetGame", …) to initiate the reset.
  4. You'll add the reset logic in GameController when the user resets the game.

But code speaks louder than words. First, open the MoveAround script and add the following variables and functions to it:

var originalPosition : Vector3;
var originalRotation : Quaternion;

function Awake() {
    originalPosition = transform.position;
    originalRotation = transform.rotation;
}

function resetGame() {
    // Reset to original position
    transform.position = originalPosition;
    transform.rotation = originalRotation;
}

Then open the ObstacleLauncher script and add this new function:

function resetGame() {
    // Reset to original data
    numObstaclesLaunched = 0;
}

Next open the GameController script and add the following variable:

var gameObjectsToReset : GameObject [];

The above line defines an array of GameObjects that will have the resetGame function called on them to initiate a reset.

Now replace the existing startGame() function with the following (or just update the code to match):

function startGame() {
    // Reset if starting a new game
    gameTimeRemaining = gameTimeAllowed; 
    timedOut = false;
    missionCompleted = false;
       
    // Change button text after the initial run
    playButtonText = "Play Again";
    
    // Clean out any enemy objects
    var enemies = GameObject.FindGameObjectsWithTag("Enemy");
    for (var enemy : GameObject in enemies) {
        Destroy ( enemy);
    }
    // Call all game reset methods
    for (var gameObjectReceiver : GameObject in gameObjectsToReset) {
        gameObjectReceiver.SendMessage("resetGame", null, SendMessageOptions.DontRequireReceiver);
    }
    
    // Kick off the game
    gameRunning = true;
}

The new code clears out all enemy instances by looking for GameObjects with the Enemy tag. Destroy() is called on the enemy GameObjects. This clears out the obstacles in the scene.

Then the code processes the gameObjectsToReset array and sends a message to each GameObject to call resetGame(). It is not mandatory for a component in the array to have implemented resetGame(). You need to assign the objects to process.

Now select the Main Camera object and note the new public variable for Game Objects To Reset:

Game Objects To Reset variable created.

Set the Size to 2. The array elements will expand. Assign the Player GameObject to Element 0 and the Launcher GameObject to Element 1.

Game Objects To Reset array variables assigned.

Preview the game and verify that the game fully resets itself after a success or failure. Verify that the player position resets to the original one, that the obstacles are cleared, and that the obstacles start falling once more when the game is restarted.

This Message Won’t Self-Destruct

Your basic gameplay functionality is now complete, but it would be a nice additional touch to show some info about the game when it's first launched. All you're currently showing is a play button. The user doesn’t know what they’re in for.

Adding some welcome text and a super-short explanation of what the game's about will make it a lot more user-friendly. :] The welcome text will use the Transformers font you imported. For the description text, you’ll use the Arial font that is bundled with Unity.

Create an empty game object, name it Intro and set the Transform Position to 0,0,0.

Select GameObject\Create Other\3D Text to create a 3D Text GameObject and name it Description Text. Set the Inspector\Text Mesh\Text property to "Get to the finish line before time runs out." Set the initial Transform Position to -10,1,12.

Description text adjustments.

Preview the game with the Unity Editor and adjust the position of the object so that it's horizontally centered and you can see it clearly.

Hint: You'll likely have to play around with the x and z positions: x to center to object, and z to zoom in and out.

Description text position adjustments.

Check out the game with Unity Remote as well and make sure the text is visible when viewed on an iOS device. Make any necessary adjustments.

Place the Description Text object under the Intro GameObject. You’re doing this so you can later show or hide the menu display info easily via code.

Description text parented.

Create a second 3D Text GameObject and name it Welcome Text. This text should appear above the description text, so set the initial Transform Position to -6,5,10. Set the Inspector\Text Mesh\Text property to "Welcome".

Set the Font property to Transformers Movie by dragging that font asset from the Project View and into the Font property in the Inspector (or by tapping the circle with a dot icon next to Font and selecting it from the pop-up list):

Welcome text transform and material modifications.

Adjust the position of Welcome Text so that you can see it clearly when you test the game on both the Unity Editor and through Unity Remote.

Welcome text position adjustments.

Place the Welcome Text object under the Intro GameObject.

You want to hide the Intro GameObject (and its child objects) when the game is running. Open the GameController script and make the following changes:

var intro : Transform;
...
function startGame() {
...
    // Turn off the intro text
    for (var child : Transform in intro ) {
        child.gameObject.renderer.enabled = false;
    }
    
    // Clean out any enemy objects
...

Here you add a new public variable to get a handle to the Intro GameObject. Then you modify startGame() to make the Intro object invisible by turning off the renderers for its child GameObjects.

Now set the Intro variable by selecting Main Camera and dragging the Intro GameObject from the Hierarchy View to the Inspector\Game Controller\Intro variable to assign it. Or use the circle dot icon, since it's easier. :]

Intro transform assigned to Game Controller script.

Preview the game to test that the text is hidden when the play button is clicked and the game begins.

Every Brave Cube Deserves a Soundtrack

Audio plays a big part in the gaming experience, both by providing sensory feedback and creating mood. You'll add audio to further enrich the gameplay.

Sound effects will be triggered when the player crosses the line in time, fails their mission, or when an obstacle hits the ground or bumps into anything. And of course, the game must have some background music! :]

Adding audio with Unity involves attaching an Audio Source component to a GameObject. The Audio Source component has an Audio Clip property that you can assign to the sound you wish to play. The component has additional properties that can control when the clip should be played and whether it should loop. The supported audio file formats include .AIF, .WAV, .MP3, and .OGG.

The following two sites provided the royalty-free music that's used in this tutorial:

The Resources.zip file you downloaded earlier contains all the audio files that you'll be using. Feel free to create your own audio effects instead of using the ones I've provided. :]

For your reference (and for the sake of attribution), the original links to the audio files included in the Resources.zip file are as follows:

Do note though that the files in the Resources.zip file have been renamed for the sake of clarity and brevity. Go to the folder where you originally extracted Resources.zip and import the audio files by dragging them into your Project View\Assets folder.

Victory audio import settings.

When an audio file is imported into Unity, you can specify whether it should be compressed or remain as-is, i.e., native. (But note that MP3 and Ogg Vorbis audio are always imported in the compressed format.)

Why does this matter? Compressed files tend to be smaller, but they need to be decompressed as the game runs, taking up CPU cycles. You generally want to compress background music. For short sound effects, native is better and tends to provide better sound quality.

If the audio format is compressed, you can choose whether to handle the decompression using hardware, e.g., Apple's hardware codec if running on an iOS device. Hardware is faster, but the hardware can handle one compressed music file at a time.

You can also mark sounds as 3D. This means that when the sound is played, the effect will be relative to the 3D position of the GameObject. For example, if the GameObject is far away, the sound will be quieter.

Background audio import settings.

Select the background audio in the Project View to show the Import Settings. Unselect the 3D Sound option. Select Hardware decoding. Click Apply to save the setting changes.

The other audio files are .WAV files and you do not need to modify the default Import settings, which should be set to 3D and native audio format.

For sounds to be heard, your scene needs an Audio Listener component to be added to a GameObject. There can only be one Audio Listener in the scene. The listener will pick up sounds from the audio sources close to it and send it to the device speaker.

By default, an Audio Listener is attached to the Main Camera. You can leave it there or attach it to a different GameObject: the player, for example.

In this game, you'll keep the Audio Listener on the Main Camera, but you can experiment with the different options when you build your own games.

The Sounds of Victory and Defeat

You're going to attach audio to the Goal GameObject that simulates a crowd cheering or jeering at the finish line.

Select the Goal GameObject in the Hierarchy View and add an audio source by selecting Component\Audio\Audio Source. Set the victory audio asset to the Inspector\Audio Source\Audio Clip property. De-select the Play on Awake option.

Audio source added to Goal object.

Now create a new JavaScript asset that will be used to play either a victory sound or a defeat sound. Name the new script FanReaction. Open the new script, remove the stub functions and add the following code:

var audioVictory : AudioClip;
var audioDefeat : AudioClip;
var volumeVictory : float = 2.0;
var volumeDefeat : float = 2.0;

function playSoundOfVictory(isVictory : boolean) {
    // Stop any current audio
    if (audio.isPlaying)
        audio.Stop();
    
    // Play either the sound of victory or defeat.
    audio.clip = isVictory ? audioVictory : audioDefeat;
    audio.volume = isVictory ? volumeVictory : volumeDefeat;
    audio.Play();
}

function resetGame() {
    // Reset to original state, stop any audio
    if (audio.isPlaying)
        audio.Stop();
}

@script RequireComponent(AudioSource)

The script takes in two audio clips, one for victory and one for defeat. The playSoundOfVictory() function first stops any audio that's currently playing, then plays the required audio based on the isVictory input.

The resetGame() function stops any audio that's playing. You'll shortly wire up the GameController to call resetGame() every time the game is restarted.

Attach this new script to the Goal GameObject. Set the victory audio asset to the Audio Victory variable. Set the defeat audio asset to the Audio Defeat variable.

Fan Script added to Goal object.

Edit the GameController script and make the following changes:

var fanReactionScript : FanReaction;
...
function Update() { 
    if (!gameRunning)
        return; 
        
    // Keep track of time and display a countdown
    gameTimeRemaining -= Time.deltaTime;
    if (gameTimeRemaining <= 0) {
        timedOut = true; 
        gameRunning = false;
        
        // Play the sound of defeat
        fanReactionScript.playSoundOfVictory(false);
    }
}
...
function MissionComplete() { 
    if (!gameRunning)
        return;
    
    missionCompleted = true; 
    gameRunning = false;
    
    // Play the sound of victory
    fanReactionScript.playSoundOfVictory(true);
    
    missionCompleteTime =  gameTimeAllowed - gameTimeRemaining;
}

The code defines a new public variable that references the FanReaction script. You modify MissionComplete() to call playSoundOfVictory(), passing in true to play the victory sound. You also modify Update() to call playSoundOfVictory(), passing in false to play the defeat sound.

You can now link the FanReaction script in the Goal GameObject with the variable in the Main Camera's GameController script component. Select Main Camera and then click on the circular icon next to the fanReactionScript variable under the GameController component in the Inspector. In the dialog that pops up, select the Goal GameObject, then close the pop-up.

Fan Reaction script assigned to Game Controller script.

To call resetGame() in FanReaction, select the Main Camera Object. In the Game Controller component section in the Inspector, increase the Game Objects To Reset array size from 2 to 3. Set the Goal GameObject to Element 2.

Goal added to Game Objects To Reset array.

Preview the game and test out the victory and defeat scenarios to make sure the game plays the correct sounds. Verify that the sounds are stopped when you hit Play Again.

Thud in 3D

It would also be nice to have some sort of a sound when obstacles hit the ground. To achieve this, you'll attach an audio source to the Obstacle Prefab, then detect collisions so you can play the impact audio when the obstacles fall or bump into anything.

Add an Audio Source component to the Obstacle Prefab. Assign the impact audio to the Audio Clip property.

Impact audio added to Obstacle Prefab.

Create a new JavaScript asset and rename it to ObjectCollision. Edit the script, delete the stubbed functions and add the following code:

var impact : AudioClip;
function OnCollisionEnter () {
    audio.PlayOneShot(impact);
}

@script RequireComponent(AudioSource)

The code implements the predefined OnCollisionEnter() event function and calls the audio.PlayOneShot() function to play the impact audio clip. audio.PlayOneShot() illustrates another way to play audio, allowing you to pass in the audio you wish to play.

Attach the script to the Obstacle Prefab. Set the impact audio asset to the Impact variable in the script.

Object Collision script and audio variable added.

Preview the game and verify that you hear a pleasing thud sound when obstacles hit the ground or another object. Note that closer the obstacles are to the player, the louder the sounds.

A Little Cube Music

Your game's almost complete. But there's one thing missing – some background tunes.

Music does a lot to set the mood for a game. It can get a user’s adrenaline flowing and help them “feel” the game environment by providing contextual clues like bird sounds or wolves howling. So add some music!

Add an Audio Source component to the Main Camera GameObject. Set the background audio asset to the Audio Clip property.

Select the Play on Awake and the Loop options. These ensure that the background music starts as soon as the game starts and that it will play continuously.

Adjust the volume property to 0.1 so it doesn’t drown out the other sounds. If you're using your own sounds, tweak the volume level depending on your music's default volume level to achieve the same goal. Users should be able to hear all the sound effects while the background music is playing.

Background audio source added.

Preview the project in the Unity Editor. When completely satisfied, deploy the project to your iOS device. You'll need to add the Level_3 scene in the Build Settings.

Build settings when adding final level.

Test out the gameplay on your iOs device while you enjoy the sounds you've added.

Completed game running on iOS.

Your Heroic Cube has a soundtrack to glorify its bravery!

Where To Go From Here?

Congratulations, you've made it to the end of this whirlwind walkthrough of the basics of Unity! You’ve shown as much verve and vigor as your Heroic Little Cube. This could be the beginning of a wonderful journey with Unity gaming.

Here are the source files with all of the code from this tutorial series: Unity Project, Xcode Project.

Believe it or not, you've just scratched the surface – there's a whole lot more to learn. Stay tuned for an upcoming intermediate tutorial series that will take you to the next level with Unity!

In the meantime, be sure to stop by the forums with your questions and feedback, and have fun building awesome games!

This is a post by Tutorial Team Member Christine Abernathy, an Engineer on the Developer Advocacy team at Facebook. You can also find her on .

Christine Abernathy

Christine is an Engineer on the Developer Advocacy team at Facebook. In this role, she is focused on helping grow the mobile developer ecosystem with emphasis on Android, iOS, and the mobile web. Prior to Facebook, Christine headed up engineering at Mshift, a mobile banking software provider, delivering iPhone apps and mobile browser-based products.

You can find Christine on Facebook.

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

... 27 total!

iOS Team

... 74 total!

Android Team

... 33 total!

Unity Team

... 15 total!

Articles Team

... 12 total!

Resident Authors Team

... 29 total!

Podcast Team

... 7 total!

Recruitment Team

... 9 total!