Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Windows-Phone-7

UNITY 3D – Game Programming – Part 6

4.94/5 (18 votes)
8 May 2015CPOL15 min read 53.4K   283  
The sixth article in a series to discuss Unity 3D and how to get started with your own 3D projects.

Introduction

In part six of the series, we will be expanding on the game idea we have started in Part 5 of the series.

If you have not already done so, please take a moment and read:

  1. Unity 3D – Game Programming – Part 1
  2. Unity 3D – Game Programming – Part 2

  3. Unity 3D – Game Programming – Part 3

  4. Unity 3D – Game Programming – Part 4

  5. Unity 3D – Game Programming – Part 5

  6. Unity 3D – Game Programming – Part 6

  7. Unity 3D – Game Programming – Part 7

  8. Unity 3D – Game Programming – Part 8

  9. Unity 3D – Game Programming – Part 9

  10. Unity 3D – Game Programming – Part 10

Unity 3D Networking Article(s):

  1. Unity 3D - Network Game Programming

Unity 3D Leap Motion and Oculus Rift Article(s):

  1. Unity 3D - Leap Motion Integration

In the first part of the series we started by the very basics of the Unity 3D environment. Getting a feel of the IDE and the different sections which you will be working with throughout your project. We also covered how to use the tools in the designer to apply different transformation to a selected Game Object: positioning, rotation and scaling. We finally looked at how to create our first script and using the script apply a rotation transform on the Y-Axis of our cube.

In the second part of the series, we looked at more of the transformation of a given object through coding. We also looked at how to create light sources that are crucial for the rendering of your objects in the scene.

In the third part of the series we looked at how to process user input through the keyboard and based on the key code take particular actions.

In the fourth part of the series, we looked at creating a simple user interface. The user interface that we developed provided us a means to feedback to the user, and also another method for the user to input to our game or simulation.

In the fifth part, we started the idea of a simple game. We also looked at how to import 3D models into the game engine.

In Part 6, we will be expanding on our game idea and making it more interesting and more complete.

Introduction to Game Programing: Using C# and Unity 3D (Paperback) or (eBook) is designed and developed to help individuals that are interested in the field of computer science and game programming. It is intended to illustrate the concepts and fundamentals of computer programming. It uses the design and development of simple games to illustrate and apply the concepts.
Image 1
Paperback
ISBN: 9780997148404
Edition: First Edition
Publisher: Noorcon Inc.
Language: English
Pages: 274
Binding: Perfect-bound Paperback (Full Color)
Dimensions (inches): 6 wide x 9 tall
Support independent publishing: Buy this book on Lulu.
Image 3
eBook (ePUB)
ISBN: 9780997148428
Edition: First Edition
Publisher: Noorcon Inc.
Language: English
Size: 9.98 MB
Support independent publishing: Buy this e-book on Lulu.
Available From:

Windows Phone 8.x Demo:

I have provided a free phone application that you can download and preview the demos on your Windows Phone. To download the mobile application, follow the link: CodeProjectArticleSample

Image 9
Code Project Articles Sample Mobile App

Live Preview of Article Code and Visuals:

Image 10

Link to live preview: http://www.noorcon.com/CodeProject/CodeProjectArticlePreview.html

Background

NOTE: For this particle, I will be using SketchUp to create some simple building blocks, which I will use to import into Unity! I am not a 3D Modeler or Designer, so please be patient and excuse the mess!

It is assumed that the reader of this article is familiar with programming concepts in general. It is also assumed that the reader has an understanding and experience of the C# language. It is also recommended that the reader of the article is familiar with Object-Oriented Programming and Design Concepts as well. We will be covering them briefly throughout the article as needed, but we will not get into the details as they are separate topics altogether. We also assume that you have a passion to learn 3D programming and have the basic theoretical concepts for 3D Graphics and Vector Math.

Lastly, the article uses Unity 3D version 4.6.1 which is the latest public release as of the initial publication date. Most of the topics discussed in the series will be compatible with older versions of the game engine, and perhaps also the new version which is supposed to be release sometime this year. There is however, one topics which is significantly different in the current 4.6.1 version compared to the older version of the game engine, and that is the UI (User Interface) pipeline. This is due to the new UI architecture in the engine which is far superior to what we had prior to this release. I for one, am very happy with the new UI architecture.

Using the code

Downloading the project/source code for article series: Download source.

With each consecutive article that is submitted, the project/source code will be also expanding. The new project files and source files will be inclusive of older parts in the series.

NOTE: To get the latest code, go to the most recent published part in the series and download the code.

Recalling the Idea

If you recall from Part 5, we started to create a level resembling a maze. The level had to be large enough to allow us to freely move and interact with other object within it. You can go back to Part 5 to review the brief.

I used SketchUp to create the following model, which was used as our level base.

Image 11
Figure 1-SketchUp Model for My Level

Then we introduced, the Character Player (CP) represented by a Sphere primitive, and a coin object represented by a modified Capsule primitive.

We made our CP movable and converted it to a rigid body so that we can use the physics engine and the collision detection provided by the engine. We also made the coin object’s Collider component be trigger.

Implementing the Game

From the brief, we are told that we want to be able to collect as many coins as possible in a given time frame. We have thus far created the main GameObjects needed for our game. Now we need to start thinking about the logistics of our game.

Thinking about the Game Design and Game Play

Every game has some set of rules. These rules are defined and created by the designer / developer / architect of the game. The more sophisticated the rules, the more sophisticated the design and implementation.

In our case, we have a very simple game in front of us, but nevertheless, we still need to think about and define and create a set of rules. For instance, let’s take a look at the following questions:

  1. How will your coins be placed in the level?

    1. Will they be placed manually at design time? Or

    2. Will they be placed dynamically at runtime?

  2. Will they have a life-span? That is:

    1. Will they be available for the whole duration of the game, or until picked up by the player? Or

    2. Will they appear and disappear based on some logic?

      1. If they do disappear, how will they reappear, or do they?

  3. Will each coin have the same exact value?

  4. How will your player be interacting with the coins?

    1. How will you keep score?

  5. How will your player be interacting with other objects in the level? (if any)

  6. What will your UI display to the player?

    1. Number of coins collected?

    2. Current run-time of the game?

    3. Amount of time to complete?

Let’s go ahead and try to answer some of these questions and start working towards implementing them in our game.

For simplicity, let’s go ahead and decide that we will be placing our coins in the level at design time. We can also assign a random value to our coin to make it more interesting in the game. We can program the coins to have a random value between 1 and 9. For now, we can also decide that the coins will have a full lifespan throughout the game.

The CP will interact by the coin objects by collision. When the player collides with a coin object, it will trigger an event which will pick-up the coin and perform the necessary accounting to increase the player’s score in the game.

The player, will have a finite set of time to pick up as many coins as possible.

Implementing Our Game Play

Let’s go ahead and create the script which will give our coins some life. Let’s call this Coin.cs:

using UnityEngine;
using System.Collections;

public class Coin : MonoBehaviour {

 private int value;

 public int VALUE
 {
  get{ return this.value; }
 }

 // Use this for initialization
 void Start () {
  this.value = Random.Range (1, 9);
 }
 
 // Update is called once per frame
 void Update () {
  
 }
}

This script defined in Coin.cs will be attached to the coin object. At the start of the game it will randomly generate a number between 1 and 9 and place it in the value variable. This will be the value of the coin throughout the game.

Prefabs

It is convenient to build a GameObject in the scene by adding components and setting their properties to the appropriate values. This can create problems, however, when you have an object like an NPC, prop or piece of scenery that is reused in the scene several times. Simply copying the object will certainly produce duplicates but they will all be independently editable. Generally, you want all instances of a particular object to have the same properties, so when you edit one object in the scene, you would prefer not to have to make the same edit repeatedly to all the copies.

A Prefab does exactly that, and since we need to create multiple instances of our Coin GameObject, we would like to create a Coin prefab. There are several ways to creating a Prefab, I will show you one way that is supper easy.

In your Project Window select your prefab folder. Drag and drop your coin GameObject from the Hierarchy Window into the Project Window under the prefab folder.

Image 12
Figure 2-Displaying Creation of a Prefab for the Coin GameObject

Prefabs are very useful and we will see how to use them more effectively in more complex scenarios. At this stage, we just want to make a copy of our object and be able to create instances of that object at any time, either through the designer of through code!

Let’s go ahead and use the newly created Prefab to place a few coins on the level. I have placed about eight coins in the scene, and tried to space them on the grid as much as possible. The following figure is what I have:

Image 13
Figure 3-Display of 8 Prefab Coins in the Scene

This should do us fine for the purposes of the demonstration. Now, let’s take a look at the code that needs to be applied to the Character Player (CP) to detect a collision with the Coin object.

Here is the listing so far for the CP object:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class playerInput : MonoBehaviour {

 private int score;    // internal score variable
 
 public int SCORE
 {
  get{ return this.score; }
 }

 // Use this for initialization
 void Start () {  
  this.score = 0;
 }
 
 // Update is called once per frame
 void Update () {
   
  // code for the movement of player (CP) forward
  if(Input.GetKey(KeyCode.UpArrow)){
   this.transform.Translate(Vector3.forward * Time.deltaTime);
  }
  // code for the movement of player (CP) backward
  if(Input.GetKey(KeyCode.DownArrow)){
   this.transform.Translate(Vector3.back * Time.deltaTime);
  }
  // code for the movement of player (CP) left
  if(Input.GetKey(KeyCode.LeftArrow)){
   this.transform.Rotate(Vector3.up, -5);
  }
  // code for the movement of player (CP) right
  if(Input.GetKey(KeyCode.RightArrow)){
   this.transform.Rotate(Vector3.up, 5);
  }

 }
 
 // This event will be raised by object that have their Is Trigger attributed enabled.
 // In our case, the coin GameObject has Is Trigger set to true on its collider.
 void OnTriggerEnter(Collider c){
  if(c.tag.Equals("coin")){
   Coin coin = c.GetComponent<Coin>();
   
   // increase score
   this.score += coin.VALUE;
   
   // remove the Coin object from the scene
   Destroy(c.gameObject);
  }
 }
}

The new modification does a few things. First we have introduced a variable to keep track of the score for the player. Then in the OnTriggerEnter(Collider c) function we are checking to see if we have collided with a coin object, and if so, we get the Coin Script Component of the Coin object and extract the value of the coin through the VALUE property. Then we increment our score by the value of the coin. Then we want to remove the Coin object from the scene, hence we use the Destroy(c.gameObject) function to destroy the object from the scene.

If you have followed with the instructions so far, when you run the program you will see the results.

Thinking about the User Interface

The user interface on the game level is very simple. We just need to be able to display the player’s score and a timer to indicate how much time the player has to meet his / her objectives.

I will not be covering the steps for creating the UI since we dedicated Part 4 of the series to cover the basics of UI creation.

First I would like to tackle the scoring UI for the game. The following figure shows how it will look:

Image 14
Figure 4-Score UI

To create the score UI, I used a Panel that has been anchored to the top left corner of the screen, an Image within the panel that displays the coin icon, and a Text element for the label. To get a custom look and feel, you can apply a texture to your panel’s background and etc… refer to Part 4 for details.

Now, let’s do something similar for our timer. For the timer, I have used a new Panel anchored to the right top corner of the screen, and a Text element to display the timer.

I am pretty happy with what I have as a UI design for this particular game at the moment. So now, we need to make sure our code has access to the UI elements to update them accordingly.

I have updated the playerInput.cs script to the following:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class playerInput : MonoBehaviour {

 public Text lblScore;  // text UI element for displaying score
 public Text lblTimer;  // text UI element for displaying timer

 private int score;   // internal score variable

 public int SCORE
 {
  get{ return this.score; }
 }

 private float timer;  // variable to keep track of time

 // Use this for initialization
 void Start () {

  this.score = 0;
  this.timer = 30.00f;

  // check to make sure labels are defined before updating
  if (this.lblScore != null)
   this.lblScore.text = this.score.ToString();

  if (this.lblTimer != null)
   this.lblTimer.text = string.Format("{0:F2}", this.timer - Time.time);

 }

 // Update is called once per frame
 void Update () {

  if (this.lblTimer != null)
   this.lblTimer.text = string.Format("{0:F2}", this.timer - Time.time);

  // code for the movement of player (CP) forward
  if(Input.GetKey(KeyCode.UpArrow)){
   this.transform.Translate(Vector3.forward * Time.deltaTime);
  }

  // code for the movement of player (CP) backward
  if(Input.GetKey(KeyCode.DownArrow)){
   this.transform.Translate(Vector3.back * Time.deltaTime);
  }

  // code for the movement of player (CP) left
  if(Input.GetKey(KeyCode.LeftArrow)){
   this.transform.Rotate(Vector3.up, -5);
  }

  // code for the movement of player (CP) right
  if(Input.GetKey(KeyCode.RightArrow)){
   this.transform.Rotate(Vector3.up, 5);
  } 
 }

 // This event will be raised by object that have their Is Trigger attributed enabled.
 // In our case, the coin GameObject has Is Trigger set to true on its collider.
 void OnTriggerEnter(Collider c){
  if(c.tag.Equals("coin")){
   Coin coin = c.GetComponent<Coin>();
   // increase score
   this.score += coin.VALUE;
   // update score on the UI
   if (this.lblScore != null)
    this.lblScore.text = this.score.ToString();
   // remove the Coin object from the scene
   Destroy(c.gameObject);
  }
 }
}

We introduced a few variable in the script: lblScore, lblTimer, and timer. We updated the Start() function to perform the following: initialize the default values for score and timer, check to see if lblScore and lblTimer are not null, and if not, then assign the values to their text property.

The next function we have updated is the Update() function. We just included a couple of lines to update the timer field for us.

The last function that got updated was OnTriggerEnter(Collider c) function. After detecting if we have collided with a Coin object, we extract the value of the coin and increment the player’s score accordingly. Finally, lblScore gets updated to reflect the score accordingly on the user interface.

Enhancing the Logic a Little More

There are a few more enhancements that I would like to make before I end Part 6 of the series. If you notice, we don’t have a means of ending the game! Well, as you know every game has to come to an end. Therefore, we should think about how we might trigger end of game!

To fix this dilemma, we would implement the following logic to have a finish to the game play:

  1. The game should stop when the timer is 0.00.

  2. The game should stop when the player collects all of the coins on the scene.

At the end of the game, we need to display a prompt to the user displaying their score, and also giving them the ability to start a new game or ending the game.

NOTE: Exit of an application has to be defined based on the platform the game is deployed on! There is no such thing as an Application Exit on a Web Application!

The following code listing shows how we would modify our code to handle case (1) and case (2) to trigger an end of game:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class playerInput : MonoBehaviour {

 public Text lblScore;  // text UI element for displaying score
 public Text lblTimer;  // text UI element for displaying timer

 private int score;   	// internal score variable

 public int SCORE
 {
  get{ return this.score; }
 }

 private float levelTime;   // variable holing time to complete level
 private float timeLeft;    // variable for the actual timer count down

 public bool END_GAME;    		// variable indicating end of game
 public int numOfCoinsInLevel;  	// will be initialized at the Start of the game
 public int numOfCoinsCollected;  	// will be incremented each time we collect a coin

 // Use this for initialization
 void Start () {
  this.score = 0;
  this.timer = 60.00f;
  this.numOfCoinsCollected = 0;

  this.END_GAME = false;

  // check to make sure labels are defined before updating
  if (this.lblScore != null)
   this.lblScore.text = this.score.ToString();

  if (this.lblTimer != null)
   this.lblTimer.text = string.Format("{0:F2}", this.timer - Time.time);

  // get number of coins in the scene at the start of the game
  this.numOfCoinsInLevel = GameObject.FindGameObjectsWithTag ("coin").Length;
 }

 // Update is called once per frame
 void Update () {

  if (!this.END_GAME) {

   // compute time left
   this.timeLeft = this.timer - Time.time;

   // update UI label for timer
   if (this.lblTimer != null){
    this.lblTimer.text = string.Format("{0:F2}", this.timeLeft);
   }

   // check to see if we need to end the game based on the timer
   if(this.timeLeft<=0.00f || this.numOfCoinsInLevel<=this.numOfCoinsCollected){
    this.END_GAME = true;
    if (this.lblTimer != null){
     this.lblTimer.text = string.Format("{0:F2}", 0.00f);
    }
   }

   // code for the movement of player (CP) forward
   if(Input.GetKey(KeyCode.UpArrow)){
    this.transform.Translate(Vector3.forward * Time.deltaTime);
   }

   // code for the movement of player (CP) backward
   if(Input.GetKey(KeyCode.DownArrow)){
    this.transform.Translate(Vector3.back * Time.deltaTime);
   }

   // code for the movement of player (CP) left
   if(Input.GetKey(KeyCode.LeftArrow)){
    this.transform.Rotate(Vector3.up, -5);
   }

   // code for the movement of player (CP) right
   if(Input.GetKey(KeyCode.RightArrow)){
    this.transform.Rotate(Vector3.up, 5);
   } 
  }else{
   Debug.Log("GAME ENDED!!!");
  }
 }

 // This event will be raised by object that have their Is Trigger attributed enabled.
 // In our case, the coin GameObject has Is Trigger set to true on its collider.
 void OnTriggerEnter(Collider c){
  if(c.tag.Equals("coin")){
   Coin coin = c.GetComponent<Coin>();

   // increase score
   this.score += coin.VALUE;
   this.numOfCoinsCollected += 1;

   // update score on the UI
   if (this.lblScore != null)
    this.lblScore.text = this.score.ToString();

   // remove the Coin object from the scene
   Destroy(c.gameObject);
  }
 }
}

Taking a look at the new listing, you will notice that there is a lot of changes in the code. Nothing major or complex, but nevertheless, we have more meat to our code now.

We also have introduced a few more variables to keep track of statistics in our game. A new variable, timeLeft, for the timer is introduced to handle when to terminate the game when the time is up! A Boolean variable END_GAME is introduced as a flag to determine if the game should continue of end. Two variables numOfCoinsInLevel and numOfCoinsCollected to determine if game should be ended if the player has collected all of the coins in the level before the time is up.

The next step is create the UI that will be displayed at the end of the game. Again, nothing fancy here, but we will try to make something pleasing. The following figure will display the look of our End Game panel:

Image 15
Figure 5-End of Game Canvas Display

As always, we will need to update our script to handle the new enhancements. Here is an updated listing of the playerInput.cs script:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class playerInput : MonoBehaviour {

 public Text lblScore;   // text UI element for displaying score
 public Text lblTimer;   // text UI element for displaying timer

 public Canvas endGameCanvas; 	// Canvas holding UI elements for End of Game
 public Text lblEndOfGameScore;
 public Text lblEndOfGameTime;
 public Text lblEndOfGameCoinCont;

 private int score;    		// internal score variable

 public int SCORE
 {
  get{ return this.score; }
 }

 private float levelTime;   // variable holing time to complete level
 private float timeLeft;    // variable for the actual timer count down

 public bool END_GAME;    		// variable indicating end of game

 public int numOfCoinsInLevel;  	// will be initialized at the Start of the game
 public int numOfCoinsCollected;  	// will be incremented each time we collect a coin

 // Use this for initialization
 void Start () {

  this.score = 0;
  this.levelTime = Time.time + 30.00f;
  this.numOfCoinsCollected = 0;

  this.END_GAME = false;

  this.endGameCanvas.gameObject.SetActive(false);

  // check to make sure labels are defined before updating
  if (this.lblScore != null)
   this.lblScore.text = this.score.ToString();

  if (this.lblTimer != null)
   this.lblTimer.text = string.Format("{0:F2}", this.levelTime - Time.time);

  // get number of coins in the scene at the start of the game
  this.numOfCoinsInLevel = GameObject.FindGameObjectsWithTag ("coin").Length;

 }

 // Update is called once per frame
 void Update () {
  if (!this.END_GAME) {

   // compute time left
   this.timeLeft = this.levelTime - Time.time;

   // update UI label for timer
   if (this.lblTimer != null){
    this.lblTimer.text = string.Format("{0:F2}", this.timeLeft);
   }

   // check to see if we need to end the game based on the timer
   if(this.timeLeft<=0.00f || this.numOfCoinsInLevel<=this.numOfCoinsCollected){
    this.END_GAME = true;

    if (this.lblTimer != null && this.lblEndOfGameTime != null){

     if(this.timeLeft>=0.00f){
      this.lblTimer.text = string.Format("{0:F2}", this.timeLeft);
      this.lblEndOfGameTime.text = string.Format("{0:F2}", this.timeLeft);
     }else{
      // this else block is written to ensure that if the timer is up, we always get 0.00
      // and not positive or negative values, i.e. 0.01, or -0.01 and etc...
      this.lblTimer.text = string.Format("{0:F2}", 0.00f);
      this.lblEndOfGameTime.text = string.Format("{0:F2}", 0.00f);
     }
    }

    if(this.lblEndOfGameScore != null && this.lblEndOfGameCoinCont != null){
     this.lblEndOfGameScore.text = this.SCORE.ToString();
     this.lblEndOfGameCoinCont.text = this.numOfCoinsCollected.ToString();
    }

   }

   // code for the movement of player (CP) forward
   if(Input.GetKey(KeyCode.UpArrow)){
    this.transform.Translate(Vector3.forward * Time.deltaTime);
   }
   // code for the movement of player (CP) backward
   if(Input.GetKey(KeyCode.DownArrow)){
    this.transform.Translate(Vector3.back * Time.deltaTime);
   }
   // code for the movement of player (CP) left
   if(Input.GetKey(KeyCode.LeftArrow)){
    this.transform.Rotate(Vector3.up, -5);
   }
   // code for the movement of player (CP) right
   if(Input.GetKey(KeyCode.RightArrow)){
    this.transform.Rotate(Vector3.up, 5);
   } 
  }else{
   this.endGameCanvas.gameObject.SetActive(true);
  }

 }

 // This event will be raised by object that have their Is Trigger attributed enabled.
 // In our case, the coin GameObject has Is Trigger set to true on its collider.
 void OnTriggerEnter(Collider c){
  if(c.tag.Equals("coin")){
   Coin coin = c.GetComponent<Coin>();

   // increase score
   this.score += coin.VALUE;
   this.numOfCoinsCollected += 1;

   // update score on the UI
   if (this.lblScore != null)
    this.lblScore.text = this.score.ToString();

   // remove the Coin object from the scene
   Destroy(c.gameObject);
  }
 }

 public void butPlayAgain_Click(){
  Start ();
 }
}

 

Points of Interest

As you can see, things are getting pretty involved! Also there are several scenarios we are not taking into consideration. Assume the following scenario, let’s say the player collects all of the coins in the scene during their play time, at the end of the game, they get an option to play again. But there is a problem here which we have not handled! Can you tell what the issue is? Think a bit before you continue on reading the next paragraph.

The issue is that we have placed our coins in the scene at design time! So when we collect the coins at runtime, we actually remove them from memory! Do you see the problem? Well, the problem is that there is no way for us to recreate these coins on the scene at the moment, so when the game starts again, it will immediately stop, because there are zero coins! We will try to solve this issue in Part 7.

NOTE: Notice I did not get into the details on making the UI. The topic was covered in Part 4.

History

This is the sixth article of a series which I would slowly contribute to the Code Project community.

  1. Unity 3D – Game Programming – Part 1
  2. Unity 3D – Game Programming – Part 2

  3. Unity 3D – Game Programming – Part 3

  4. Unity 3D – Game Programming – Part 4

  5. Unity 3D – Game Programming – Part 5

  6. Unity 3D – Game Programming – Part 6

  7. Unity 3D – Game Programming – Part 7

  8. Unity 3D – Game Programming – Part 8

  9. Unity 3D – Game Programming – Part 9

  10. Unity 3D – Game Programming – Part 10

Unity 3D Networking Article(s):

  1. Unity 3D - Network Game Programming

Unity 3D Leap Motion and Oculus Rift Article(s):

  1. Unity 3D - Leap Motion Integration

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)