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 10

4.80/5 (9 votes)
8 May 2015CPOL15 min read 39.1K  
The tenth article in a series to discuss Unity 3D and how to get started with your own 3D projects.

Introduction

In part ten of the article series we will enhance our GameMaster.cs code base and also give the player some options in the game play. I will also introduce another element into the game.

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 the sixth part, we expanded on our game idea and making it more interesting and more complete. We introduced several concepts regarding game play and game design as well as created a simple user interface for our game.

In the seventh part, we answered some of the questions that got raised in Part 6.

In the eighth part, we creating a better user experience and also visuals for our players.

In part nine of the series, we enhanced the visual effects of our little game. We also be introduced a start menu for the game where the user could select some options and also determine the difficulty of the game. In addition, we introduced sound effects and visual effects to enhance the overall look and feel of the game.

In part ten, we are going to implement the game option feature that will allow the user to control the volume of the background noise and the volume of the sound effects we implemented in part 9. In addition, we are going to introduce another game play element, a health bar object to increase our health if we can capture it in time!

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 Expression Design to create some simple menus and buttons or other visual components, which will be used to enhance the visual appeal of the game!

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.

Implementing the Game Options

You might have noticed from Part 9 that in the main menu there is an icon which is usually associated with settings / options in a given application. In Part 10 we are going to implement the button that will be responsible for our Options Menu in the game.

Image 11
Figure 1-Screenshot of Main Menu with Options Showing

Figure 1 display the full UI (User Interface) for our game’s main menu. In order to implement the illustrated interface, I had to add a few UI elements to our existing UI Canvas. I will go ahead and list the elements, but will not be discussing the basics. Please refer to Part 4 and Part 5 for the concept.

Image 12
Figure 2-Hierarchy Window displaying Canvas Hierarchy

Figure 2 displays the hierarchy of the GoldRushCanvas that was implemented through Part 9 and Part 10 of the articles. The main additions for Part 10 are the items illustrated by the numbers. Pay attention to the naming and you will see that the naming is done in such a way to give you a sense of what UI components have been used and connected to one another. If you notice, there is an OptionsCanvas, this is the canvas that represents the whole options UI elements. The main reason we have this child canvas is so that we can enable and disable it through our code. The rest of the UI elements are just children of the OptinosCanvas. We have the following:

  • OptionsPanel

  • lblMusicControl

  • MusicSlider

  • lblSoundFx

  • FxSlider

OptionsPanel is used to contain and organize the child elements. lblMusicControl is just a Text UI object displaying the caption, and so is lblSoundFx. MusicSlider is a Slider UI object that will be used to control the volume of the background music, and so is FxSlider.

As always, you will need to create the event handlers for the two sliders. So go ahead and create the On Value Changed event for both sliders. If you have been following along with the instructions and have used the same naming and structure, you can go ahead and assign the MainCamera GameObject into the slot.

NOTE: In my project, the MainCamera GameObject contains the MenuSelection.cs script which handles all of the menu interactions. Yours might be different if you have done it differently.

Now, before you can actually assign a function, we will need to create the function to handle the volume level for both the background music and the sound effects.

We will need to update our MenuSelection.cs script:

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

public class MenuSelection : MonoBehaviour {

 public Scrollbar level8progressbar;
 public Scrollbar level9progressbar;

 public Canvas optionsCanvas;
 public Slider gameMusicControl;
 public Slider gameFxControl;

 private bool DISPLAY_OPTIONS;
 
 // Use this for initialization
 void Start () {
  this.DISPLAY_OPTIONS = false;

  if (this.gameMusicControl != null) {
   this.gameMusicControl.value = GameMaster.gameMusicVolume;
   this.gameObject.audio.volume = GameMaster.gameMusicVolume;
  }

  if (this.gameFxControl != null) {
   this.gameFxControl.value = GameMaster.gameFxVolume;
  }

  if (this.optionsCanvas != null)
   this.optionsCanvas.enabled = this.DISPLAY_OPTIONS;
 }
 
 // Update is called once per frame
 void Update () {
  if (this.level8progressbar != null) {
   this.level8progressbar.size = Application.GetStreamProgressForLevel("part_8");
  }
  if (this.level9progressbar != null) {
   this.level9progressbar.size = Application.GetStreamProgressForLevel("part_9");
  }

  if (this.optionsCanvas != null)
   this.optionsCanvas.enabled = this.DISPLAY_OPTIONS;
 }

 public void ExitApplication(){
  Application.Quit();
 }

 public void DisplayOptions(){
  this.DISPLAY_OPTIONS = !this.DISPLAY_OPTIONS;
 }

 public void GameSound(){
  GameMaster.gameMusicVolume = this.gameMusicControl.value;
  this.gameObject.audio.volume = GameMaster.gameMusicVolume;
 }

 public void GameFxVolume(){
  GameMaster.gameFxVolume = this.gameFxControl.value;
 }

 public void LoadMainMenu(){
  Application.LoadLevel ("main_menu");
 }

 public void LoadPart1(){
  Application.LoadLevel ("part_1");
 }

 public void LoadPart2(){
  Application.LoadLevel ("part_2");
 }

 public void LoadPart3(){
  Application.LoadLevel ("part_3");
 }
 
 public void LoadPart4(){
  Application.LoadLevel ("part_4");
 }

 public void LoadPart5(){
  Application.LoadLevel ("part_5");
 }

 public void LoadPart6(){
  Application.LoadLevel ("part_6");
 }

 public void LoadPart7(){
  Application.LoadLevel ("part_7");
 }

 public void LoadPart8(){
  if (Application.CanStreamedLevelBeLoaded("part_8"))
   Application.LoadLevel("part_8");
 }

 public void LoadPart9(){
  if (Application.CanStreamedLevelBeLoaded ("part_9"))
   Application.LoadLevel ("part_9");
 }
}

I have introduced several variables in the code above: optionsCanvas which is a Canvas object, gameMusicControl and gameFxControl which are Slider objects and finally a DISPLAY_OPTIONS which is a Boolean type.

The Start() function has been updated to make sure that the optionsCanvas does not display by setting the DISPLAY_OPTIONS variable to false. This variable controls the visibility of the canvas. Then we check to see if the gameMusicControl and gameFxControl have been set, and if so, we assign them the desired initial values.

NOTE: We are using the GameMaster class to get and set variables that will be used throughout the game.

In the Update() function we check to see if the DISPLAY_OPTIONS is set to true, and if so we display the optionsCanvas.

The DisplayOptions() function sets the value of the DISPLAY_OPTIONS variable, it basically inverts it each time you press the button in the UI.

The GameSound() and the GameFxVolume() functions are used to set the internal volume for the background music and sound effects. Again note that this is done using the GameMaster object.

Once you have implemented these functions and saved your script, go ahead and assign the appropriate function to the events that you have created through the IDE.

The GameMaster Class

The GameMaster.cs script is used to hold some of the common data that will be shared across the game at different times and scenes. The GameMaster we have implements is very simple and it does not really utilize the concept of a singleton. We will get to that at a later time. But the implementation does simulate something similar for the purposes of the demonstration.

The purpose of the GameMaster is to centralize and share common resources across the game. These are various things such as the over game state, the player’s health, enemies, way points, sound manager, inventory, audio and etc… it really comes down to your game. But in short, it is the glue and the brain of your game that keeps everything in sync!

Our GameMaster is very simple and straight forward as of now, let’s take a look at the listing:

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

public class GameMaster : MonoBehaviour {

 public static GameMaster GM;      // static variable to hols instance of GameMaster

 public static int GameMode;      // variable to define Game Mode
 public static bool EndGame;      // variable to define End of Game

 public static float playerSpeed = 50.0f;  // initial player speed
 public static float gameMusicVolume = 0.75f; // game background music level
 public static float gameFxVolume = 0.75f;  // game sound fx level

 public GameObject playerHitFx;     // visual fx for player hit
 public GameObject cannonBallFireFx;    // visual fx for cannon ball blast off
 public GameObject playerHealthFx;    // visual fx for health up

 public Image progressBar;      // visual representation of level load
 public Canvas loadingPanelCanvas;    // should be moved to MenuSelection.cs

 // Use this for initialization
 void Start () {

  GameMaster.EndGame = false;
  GameMaster.GM = this;
  DontDestroyOnLoad (this);

  if (this.progressBar != null)
   this.progressBar.fillAmount = 0.0f;
 }
 
 // Update is called once per frame
 void Update () {

  if (this.progressBar != null) {
   this.progressBar.fillAmount = Application.GetStreamProgressForLevel("part_9_game");
  }

  if (Application.CanStreamedLevelBeLoaded ("part_9_game")) {
   if(this.loadingPanelCanvas!=null)
    this.loadingPanelCanvas.enabled = false;
  }

 }

 // this function should be moved to MenuSelection.cs
 public void butEasy(){
  GameMaster.GameMode = 0;
  this.LoadGame ();
 }

 // this function should be moved to MenuSelection.cs
 public void butHard(){
  GameMaster.GameMode = 1;
  this.LoadGame ();
 }

 // this function should be moved to MenuSelection.cs
 private void LoadGame(){
  if (Application.CanStreamedLevelBeLoaded ("part_9_game")) {
   Application.LoadLevel ("part_9_game");
  }
 }
}

The script GameMaster.cs was included in the previous articles releases but we never discussed it. Take a moment to study the class. Nothing major is going on here. As you can see we are defining variables which we will be using throughout the game.

Let’s take a look at the obvious ones: playerHitFx, cannonBallFireFx, and playerHealthFx are all GameObject variables that will be referencing to the visual effects we will be utilizing in our game. The variables playerSpeed, gameMusicVolume and gameFxVolume are all floating point variables that will be representing the player’s speed based on their health level, the game background music level, and the sound effects level respectively. Notice that these are static variables.

The next variable with importance is the GM variables that is of type GameMaster and is defined as static. This is a variable representing the GameMaster object after it has been initialized.

Going back and visiting MenuSelection.cs script, you will notice that we are accessing GameMaster to get the initial values of the sound volumes which are set to 75% defined by 0.75f. Then using this value we set the actual volume for our background music and sound effects. Each time the slider is changed / adjusted the GameMaster keeps track of the volume level.

There are also some functions, butEasy() and butHard(), defined that are used to control the game mode. These functions would probably be moved to the MenuSelection.cs script. At the moment they have been implemented in the GameMaster.

Adding a New Game Play Element

Usually when you work on a project developing and creating software, or for the matter of a fact anything in general, doesn’t necessarily have to be just software, throughout the process of the design and implementation, you get more and better ideas on how to enhance it. Well, this is not different for our little game demo.

There are few elements that can enhance the game play. One is, if the player is hit by the cannon ball, how will he or she be able to increase their health back? Another one is, can we introduce an element to also increase the time? The answer to both of these questions are yes!

I have gone and implemented only one of the suggestions. It would be nice for the player to be able to increase their health after they have been hit by a cannon ball. Therefore, I have created a new GameObject to handle this for us. Again since, this is not a graphics competition, I have gone ahead and duplicated the CannonBall Prefab and renamed it to HealthBall.

But, there are several modifications that I have done after I have created the HealthBall Prefab. The first thing to do is to change the Tag property to something other than cannon_ball. That is reserved for the CannonBall Prefab and it is how we identify it in our script to handle the OntriggerEnter() event in PlayerInput.cs script. So I created a new tag called health_ball that will identify our HealthBall Prefab.

The next thing I did was remove the CannonBallSize.cs script and created a new script called HealthBallSize.cs and attached it to the HealthBall Prefab. The actual code in the script is the same, but I have separated them because in the future we might have them do different things.

Finally, I created a new material called health_ball and made the color be green. Also attached this material to the HealthBall Prefab replacing the existing material.

The listing for HealthBallSize.cs script:

using UnityEngine;
using System.Collections;

public class HealthBallSize : MonoBehaviour {

 public int mass;      // used to scale and also calculate damage

 public AudioClip healthPickUp;   // clip to play after we pick-up health

 // Use this for initialization
 void Start () {
  
  this.gameObject.audio.volume = GameMaster.gameFxVolume;
  
  this.mass = Random.Range (1, 3);
  
  this.transform.localScale = new Vector3 (this.transform.localScale.x * this.mass,
                                           this.transform.localScale.y * this.mass,
                                           this.transform.localScale.z * this.mass); 
 }
 
 // Update is called once per frame
 void Update () {
  
 }
}

Notice that the scale of the instantiated prefab will be based on a random number between 1 and 3. This value will be used to indicated how much health the HealthBall will provide to the player. Coincidently, the same logic is used in the CannonBallSize.cs script, but the opposite is true. It will dictate how much damage the player will receive based on the size of the CannonBall.

Also notice that we are adjusting the audio volume based on the GameMaster.gameFxVolume variable. Remember that we have made each CannonBall and HealthBall play the explosion effect upon Awake(). This will make sure that our Sound FX volume is carried over from scene to scene based on the user option selection in the main menu.

A new script was also created called LaunchHealthBall.cs. This script is responsible of instantiating and managing the HealthBalls in the game. This script was then attached to the Cannon Prefab, as the Cannon Prefab is the Prefab that manages the launching of our CannonBalls and HealthBalls.

Listing of the LaunchHealthBall.cs:

using UnityEngine;
using System.Collections;

public class LaunchHealthBall : MonoBehaviour {

 public float canonBallSpeed = 0.5f;  // spped of our cannon ball
 public float rateOfFire = 5.5f;   // cannon blast rate in seconds
 public float fireDelay;
 
 public GameObject healthBall;   // used to store our health ball prefab
 public GameObject cannonBase;   // used to store our cannon base
 public GameObject cannonStart;   // used to store our cannon nozle starting point

 private GameObject fireObject;
 
 // Use this for initialization
 void Start () {
  
  this.canonBallSpeed = Random.Range (3, 4);  // randomize cannon ball speed between 3 and 4
  this.rateOfFire = Random.Range (6.5f, 13.0f); // randomize rate of fire between 3 and 6.5 seconds
  
  // set a fire delay time for the cannon
  this.fireDelay = Time.time + this.rateOfFire;
 }
 
 // Update is called once per frame
 void Update () {
  
  // if the current game running time is larger then our fire delay time, then fire!
  if (Time.time > this.fireDelay){
   
   // cannon ball speed and rate of fire will be randomly generated based
   // on the defined boundaries ...
   this.canonBallSpeed = Random.Range (3, 4);
   this.rateOfFire = Random.Range (6.5f, 13.0f);
   
   // re-set our fire delay
   this.fireDelay = Time.time + this.rateOfFire;
   
   // instantiate our cannon ball at the specified location
   this.fireObject = GameObject.Instantiate(this.healthBall,
                                            this.cannonStart.transform.position,
                                            this.cannonStart.transform.rotation) as GameObject;
   
   // self-destruct our cannon ball after 10 seconds
   Destroy(this.fireObject, 10.0f);
   
   // use the physics engine to push/give velocity to our cannon ball
   this.fireObject.rigidbody.velocity = this.cannonStart.transform.TransformDirection(
    new Vector3(0,this.canonBallSpeed,0));
   
   // used for the FX
   GameObject fx = GameObject.Instantiate(GameMaster.GM.cannonBallFireFx,
                                          this.cannonStart.transform.position,
                                          this.cannonStart.transform.rotation) as GameObject;
   Destroy(fx, 3.0f);
  } 
  
  // rotate the cannon base around its Y-Axis continiously
  this.cannonBase.transform.Rotate (Vector3.up, 10 * Time.deltaTime);
  
 }
}

Everything in this script is similar to that of LaunchCannon.cs, with the exception that we are referencing the HealthBall Prefab and also our Random.Range() criteria is different, which effects the rate of fire.

Modification to Player Input

The PlayerInput.cs script has been also modified and updated a bit. I have decided to display the actual health value on top of the health bar for the player. So there is a variable referencing the Text UI object to update and display the value whenever needed. But the main changes are based on the GameMaster object to set the proper music volume level, and also the player’s speed and etc…

And finally a few modifications to the PlayerInput.cs:

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 Text lblNumOfCoins;  // text UI element for displaying coins collected

 public Canvas endGameCanvas; // Canvas holding UI elements for End of Game

 public Text lblEndOfGameScore;
 public Text lblEndOfGameTime;
 public Text lblEndOfGameCoinCont;

 public GameObject coinPrefab; // variable used to store the coin prefab

 private int score;    // internal score variable

 public int health;    // keep track of player health
 public Image healthBar;   // visual health bar for player
 public Text healthBarCaption; // display actual health value over the health bar

 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 () {

  GameMaster.EndGame = false;
  GameMaster.playerSpeed = 50.0f;

  this.gameObject.audio.volume = GameMaster.gameMusicVolume;

  #if UNITY_EDITOR
  Debug.Log (GameMaster.GameMode);
  #endif
  this.health = 100;

  if (this.healthBar != null)
   this.healthBar.fillAmount = 1.0f;
  if(this.healthBarCaption != null)
   this.healthBarCaption.text = this.health.ToString();

  #region COIN CREATION
  // This checks to make sure we have the prefab, also so that it does not break previous code
  if(this.coinPrefab!=null){
   // we need to create the coins dynamically per region
   // Coins for Region 1
   for (int i=0; i<16; i++) {
    // Fill the Top part of Region 1
    if(i<4){
     GameObject coin = GameObject.Instantiate(this.coinPrefab,
                                              new Vector3(Random.Range(-4.5f, 4.5f), 0.25f, Random.Range(3.0f, 4.5f)),
                                              this.coinPrefab.transform.rotation) as GameObject;
     coin.name = "R1TopC"+i;
    }
    // Fill the Bottom part of Region 1
    if(i>3 && i<8){
     GameObject coin = GameObject.Instantiate(this.coinPrefab,
                                              new Vector3(Random.Range(-4.5f, 4.5f), 0.25f, Random.Range(-4.5f, -3.0f)),
                                              this.coinPrefab.transform.rotation) as GameObject;
     coin.name = "R1BottomC"+i;
    }
    // Fill the Left part of Region 1
    if(i>7 && i<12){
     GameObject coin = GameObject.Instantiate(this.coinPrefab,
                                              new Vector3(Random.Range(-4.5f, -3.0f), 0.25f, Random.Range(-4.5f, 4.5f)),
                                              this.coinPrefab.transform.rotation) as GameObject;
     coin.name = "R1LeftC"+i;
    }
    if(i>11 & i<16){
     GameObject coin = GameObject.Instantiate(this.coinPrefab,
                                              new Vector3(Random.Range(3.0f, 4.5f), 0.25f, Random.Range(-4.5f, 4.5f)),
                                              this.coinPrefab.transform.rotation) as GameObject;
     coin.name = "R1RightC"+i;
    }
    
   }
   
   // Coins for Region 2
   for (int i=0; i<4; i++) {
    GameObject coin = GameObject.Instantiate(this.coinPrefab,
                                             new Vector3(Random.Range(-2.0f, 2.0f), 0.25f, Random.Range(1.5f, 2.0f)),
                                             this.coinPrefab.transform.rotation) as GameObject;
    coin.name = "R2C"+i;
   }
   
   // Coins for Region 3
   for (int i=0; i<4; i++) {
    GameObject coin = GameObject.Instantiate(this.coinPrefab,
                                             new Vector3(Random.Range(-2.0f, 2.0f), 0.25f, Random.Range(-0.5f, 0.5f)),
                                             this.coinPrefab.transform.rotation) as GameObject;
    coin.name = "R3C"+i;
   }
   
   // Coins for Region 4
   for (int i=0; i<4; i++) {
    GameObject coin = GameObject.Instantiate(this.coinPrefab,
                                             new Vector3(Random.Range(-2.0f, 2.0f), 0.25f, Random.Range(-2.0f, -1.5f)),
                                             this.coinPrefab.transform.rotation) as GameObject;
    coin.name = "R4C"+i;
   }
  }
  #endregion

  this.score = 0;
  this.levelTime = Time.time + Random.Range (30.0f, 60.0f);
  this.numOfCoinsCollected = 0;

  //this.END_GAME = false;

  if (this.endGameCanvas != null) {
   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);

  if (this.lblNumOfCoins != null)
   this.lblNumOfCoins.text = this.numOfCoinsCollected.ToString ();

  // 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 (!GameMaster.EndGame) {

   // 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){
    GameMaster.EndGame = 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{
   if(this.endGameCanvas != null){
    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){

  // used to detect if we collided with a coin GameObject
  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();
   if(this.lblNumOfCoins != null)
    this.lblNumOfCoins.text = this.numOfCoinsCollected.ToString();

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

  // used to detect if we collided with a cannon ball GameObject
  if (c.tag.Equals ("cannon_ball")) {

   // play the explosion sound fx when we collide
   c.gameObject.GetComponent<AudioSource>().Play();

   #if UNITY_EDITOR
   string info = string.Format("{0}-{1}-{2}", c.name, "CANNON BALL!!!", c.gameObject.GetComponent<CannonBallSize>().mass);
   Debug.Log(info);
   #endif

   // reduce the health
   this.health -= 10 * c.gameObject.GetComponent<CannonBallSize>().mass;

   // also change the speed of the player ... when damaged it will go slower
   GameMaster.playerSpeed += c.gameObject.GetComponent<CannonBallSize>().mass*10; //GameMaster.playerSpeed * c.gameObject.transform.localScale.x;

   #if UNITY_EDITOR
   Debug.Log("SPEED:" + GameMaster.playerSpeed);
   #endif

   // remove the cannon ball object from the scene after a hit
   Destroy(c.gameObject);

   if(GameMaster.GM.playerHitFx != null)
   {
    GameObject fx = GameObject.Instantiate(GameMaster.GM.playerHitFx,
                                           this.transform.position,
                                           this.transform.rotation) as GameObject;

    if(this.healthBar != null){
     this.healthBar.fillAmount = (this.health / 100.0f);
    }
    if(this.healthBarCaption != null){
     this.healthBarCaption.text = this.health.ToString();
    }

    Destroy(fx, 3.0f);
   }
  }

  if (c.tag.Equals ("health_ball")) {

//   c.gameObject.GetComponent<AudioSource>().PlayOneShot(
//    c.gameObject.GetComponent<HealthBallSize>().healthPickUp,
//    GameMaster.gameFxVolume);
//   c.gameObject.GetComponent<AudioSource>().SetScheduledEndTime(2.0f);

   if(this.health<100){
    // reduce the health
    this.health += 10 * c.gameObject.GetComponent<HealthBallSize>().mass;
    
    // also change the speed of the player ... when damaged it will go slower
    GameMaster.playerSpeed -= c.gameObject.GetComponent<HealthBallSize>().mass*10;
   }

   if(this.health>100)
    this.health = 100;

   if(GameMaster.GM.playerHealthFx != null)
   {
    GameObject fx = GameObject.Instantiate(GameMaster.GM.playerHealthFx,
                                           this.transform.position,
                                           this.transform.rotation) as GameObject;
    
    if(this.healthBar != null){
     this.healthBar.fillAmount = (this.health / 100.0f);
    }
    if(this.healthBarCaption != null){
     this.healthBarCaption.text = this.health.ToString();
    }
    
    Destroy(fx, 3.0f);
   }

   // remove the cannon ball object from the scene after a hit
   Destroy(c.gameObject);
  }
 }

 public void butPlayAgain_Click(){

  // get all object of type coin
  /*GameObject[] coins = GameObject.FindGameObjectsWithTag ("coin");

  // remove eahc object from the scene
  foreach (GameObject coin in coins){
   Destroy(coin);
  }

  Start ();*/

  Application.LoadLevel (Application.loadedLevelName);
 }
}

The main meat of the work that handles the cannon ball hit and the health ball hit is captured in the OnTriggerEnter() function. The code should be self-explanatory. You will notice that there is a new condition that checks to see if the object we collided with is tagged as health_ball, and if so it performs the logic that we have implemented for gaining health.

Image 13
Figure 3-Displaying the additions to the game play

In Figure 3, you can see the new additions to the game play. (1) The numerical representation of the player’s health, and (2) the Health Ball.

Points of Interest

Our little game is taking shape slowly and is becoming more appealing. Something to consider, you may want to implement another game play element to enable the player to increase or decrease the time he/she has to grab the gold coins.

Also, another idea you might want to implement would be to create special hidden portals that will transport the player from one dimension to the next and back. This would be a cool little enhancement to the gameplay. The new dimension might have some monsters??? Well, the sky is the limit! And you are the creator and the architect of your own world!

History

This is the tenth 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)