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:
- Unity 3D – Game Programming – Part 1
-
Unity 3D – Game Programming – Part 2
-
Unity 3D – Game Programming – Part 3
-
Unity 3D – Game Programming – Part 4
-
Unity 3D – Game Programming – Part 5
-
Unity 3D – Game Programming – Part 6
-
Unity 3D – Game Programming – Part 7
-
Unity 3D – Game Programming – Part 8
-
Unity 3D – Game Programming – Part 9
-
Unity 3D – Game Programming – Part 10
Unity 3D Networking Article(s):
- Unity 3D - Network Game Programming
Unity 3D Leap Motion and Oculus Rift Article(s):
- 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.
|
|
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 |
| |
eBook (ePUB)
ISBN: 9780997148428
Edition: First Edition
Publisher: Noorcon Inc.
Language: English
Size: 9.98 MB |
|
|
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
Code Project Articles Sample Mobile App
Live Preview of Article Code and Visuals:
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.
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:
-
How will your coins be placed in the level?
-
Will they be placed manually at design time? Or
-
Will they be placed dynamically at runtime?
-
Will they have a life-span? That is:
-
Will they be available for the whole duration of the game, or until picked up by the player? Or
-
Will they appear and disappear based on some logic?
-
If they do disappear, how will they reappear, or do they?
-
Will each coin have the same exact value?
-
How will your player be interacting with the coins?
-
How will you keep score?
-
How will your player be interacting with other objects in the level? (if any)
-
What will your UI display to the player?
-
Number of coins collected?
-
Current run-time of the game?
-
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; }
}
void Start () {
this.value = Random.Range (1, 9);
}
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.
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:
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;
public int SCORE
{
get{ return this.score; }
}
void Start () {
this.score = 0;
}
void Update () {
if(Input.GetKey(KeyCode.UpArrow)){
this.transform.Translate(Vector3.forward * Time.deltaTime);
}
if(Input.GetKey(KeyCode.DownArrow)){
this.transform.Translate(Vector3.back * Time.deltaTime);
}
if(Input.GetKey(KeyCode.LeftArrow)){
this.transform.Rotate(Vector3.up, -5);
}
if(Input.GetKey(KeyCode.RightArrow)){
this.transform.Rotate(Vector3.up, 5);
}
}
void OnTriggerEnter(Collider c){
if(c.tag.Equals("coin")){
Coin coin = c.GetComponent<Coin>();
this.score += coin.VALUE;
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:
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;
public Text lblTimer;
private int score;
public int SCORE
{
get{ return this.score; }
}
private float timer;
void Start () {
this.score = 0;
this.timer = 30.00f;
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);
}
void Update () {
if (this.lblTimer != null)
this.lblTimer.text = string.Format("{0:F2}", this.timer - Time.time);
if(Input.GetKey(KeyCode.UpArrow)){
this.transform.Translate(Vector3.forward * Time.deltaTime);
}
if(Input.GetKey(KeyCode.DownArrow)){
this.transform.Translate(Vector3.back * Time.deltaTime);
}
if(Input.GetKey(KeyCode.LeftArrow)){
this.transform.Rotate(Vector3.up, -5);
}
if(Input.GetKey(KeyCode.RightArrow)){
this.transform.Rotate(Vector3.up, 5);
}
}
void OnTriggerEnter(Collider c){
if(c.tag.Equals("coin")){
Coin coin = c.GetComponent<Coin>();
this.score += coin.VALUE;
if (this.lblScore != null)
this.lblScore.text = this.score.ToString();
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:
-
The game should stop when the timer is 0.00.
-
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;
public Text lblTimer;
private int score;
public int SCORE
{
get{ return this.score; }
}
private float levelTime;
private float timeLeft;
public bool END_GAME;
public int numOfCoinsInLevel;
public int numOfCoinsCollected;
void Start () {
this.score = 0;
this.timer = 60.00f;
this.numOfCoinsCollected = 0;
this.END_GAME = false;
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);
this.numOfCoinsInLevel = GameObject.FindGameObjectsWithTag ("coin").Length;
}
void Update () {
if (!this.END_GAME) {
this.timeLeft = this.timer - Time.time;
if (this.lblTimer != null){
this.lblTimer.text = string.Format("{0:F2}", this.timeLeft);
}
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);
}
}
if(Input.GetKey(KeyCode.UpArrow)){
this.transform.Translate(Vector3.forward * Time.deltaTime);
}
if(Input.GetKey(KeyCode.DownArrow)){
this.transform.Translate(Vector3.back * Time.deltaTime);
}
if(Input.GetKey(KeyCode.LeftArrow)){
this.transform.Rotate(Vector3.up, -5);
}
if(Input.GetKey(KeyCode.RightArrow)){
this.transform.Rotate(Vector3.up, 5);
}
}else{
Debug.Log("GAME ENDED!!!");
}
}
void OnTriggerEnter(Collider c){
if(c.tag.Equals("coin")){
Coin coin = c.GetComponent<Coin>();
this.score += coin.VALUE;
this.numOfCoinsCollected += 1;
if (this.lblScore != null)
this.lblScore.text = this.score.ToString();
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:
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;
public Text lblTimer;
public Canvas endGameCanvas;
public Text lblEndOfGameScore;
public Text lblEndOfGameTime;
public Text lblEndOfGameCoinCont;
private int score;
public int SCORE
{
get{ return this.score; }
}
private float levelTime;
private float timeLeft;
public bool END_GAME;
public int numOfCoinsInLevel;
public int numOfCoinsCollected;
void Start () {
this.score = 0;
this.levelTime = Time.time + 30.00f;
this.numOfCoinsCollected = 0;
this.END_GAME = false;
this.endGameCanvas.gameObject.SetActive(false);
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);
this.numOfCoinsInLevel = GameObject.FindGameObjectsWithTag ("coin").Length;
}
void Update () {
if (!this.END_GAME) {
this.timeLeft = this.levelTime - Time.time;
if (this.lblTimer != null){
this.lblTimer.text = string.Format("{0:F2}", this.timeLeft);
}
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.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();
}
}
if(Input.GetKey(KeyCode.UpArrow)){
this.transform.Translate(Vector3.forward * Time.deltaTime);
}
if(Input.GetKey(KeyCode.DownArrow)){
this.transform.Translate(Vector3.back * Time.deltaTime);
}
if(Input.GetKey(KeyCode.LeftArrow)){
this.transform.Rotate(Vector3.up, -5);
}
if(Input.GetKey(KeyCode.RightArrow)){
this.transform.Rotate(Vector3.up, 5);
}
}else{
this.endGameCanvas.gameObject.SetActive(true);
}
}
void OnTriggerEnter(Collider c){
if(c.tag.Equals("coin")){
Coin coin = c.GetComponent<Coin>();
this.score += coin.VALUE;
this.numOfCoinsCollected += 1;
if (this.lblScore != null)
this.lblScore.text = this.score.ToString();
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.
- Unity 3D – Game Programming – Part 1
-
Unity 3D – Game Programming – Part 2
-
Unity 3D – Game Programming – Part 3
-
Unity 3D – Game Programming – Part 4
-
Unity 3D – Game Programming – Part 5
-
Unity 3D – Game Programming – Part 6
-
Unity 3D – Game Programming – Part 7
-
Unity 3D – Game Programming – Part 8
-
Unity 3D – Game Programming – Part 9
-
Unity 3D – Game Programming – Part 10
Unity 3D Networking Article(s):
- Unity 3D - Network Game Programming
Unity 3D Leap Motion and Oculus Rift Article(s):
- Unity 3D - Leap Motion Integration