Intermediate Unity 3D for iOS: Part 2/3

This is a tutorial by Joshua Newnham, the founder of We Make Play, an independent studio crafting creative digital play for emerging platforms. Welcome back to our Intermediate Unity 3D for iOS tutorial series! In this tutorial series, you are learning how to create a simple 3D game in Unity called “Nothing but Net”. In […] By .

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 8 of this article. Click here to view the first page.

Time to test

First things first, you need to create a stub for the GameController script that the Ball script depends on so you can test everything out. So create a new script called GameController and replace the contents with the following:

using UnityEngine;
using System.Collections;

public class GameController : MonoBehaviour {

	private static GameController _instance = null;
	 
	public static GameController SharedInstance {
	    get {
	        if (_instance == null) {
	            _instance = GameObject.FindObjectOfType (typeof(GameController)) as GameController;
	        }
	 
	        return _instance;
	    }
	}
 
	void Awake() {		
		_instance = this; 
	}
	
	public void OnBallCollisionEnter (Collision collision) {
		Debug.Log ( "Game Controller: Ball collision occurred!" );	
	}
	
}

This is known as a Singleton. A Singleton is a design pattern which ensures that only a single instance of this object exists in your system, much like a global variable or Highlander — there can be only one. :]

The is done so that the GameController is easily accessible by other classes. As a well-connected object, it provides a central way for other objects to communicate with each other and check what state the system is in.

Note: if you’re curious about using the Singleton pattern in other iOS projects, a discussion of implementing singletons in iOS 4.1 and above can be found on Stack Overflow.

To implement the Singleton, provide a static method that returns the shared instance. If the shared instance has not been set, then look for it using the GameObjects static method FindObjectOfType() which will return the first active object of the type you’ve requested in the scene.

Note: When implementing a Singleton you would normally set its constructor to private such that the Singleton accessor controlled the instantiation. Since we are inheriting from a Unity MonoBehaviour class we are unable to make the constructor private. So it’s an implied Singleton and must be enforced explicitly by the programmer.

Next, you’ll add a test script to test out the ball’s collision behavior, similar to how you tested the scoreboard earlier.

To do this, ceate a new script called BallTest and copy the following code:

using UnityEngine;

public class BallTest : MonoBehaviour {
	public Ball ball; 
	void Start () {
		ball.OnNet += Handle_OnNet; 
	}

	protected void Handle_OnNet(){
		Debug.Log ( "NOTHING BUT NET!!!" );	
	}
}

Then hook everything up by doing the following:

  • Drag the Ball script on top of the basketball object.
  • Create a new empty GameObject called GameController and drag the GameController script on it.
  • Create a new empty GameObject and rename it to BallTest and drag the BallTest script on it.
  • Click the BallTest object, and drag the basketball onto the Ball variable.

Finally, position the basketball GameObject over the net as shown below:

Then click play, and you should see the output “NOTHING BUT NET!!!” into your console (Window\Console), along with a few other debug messages!

Unity console messages

Here you have tested that the ball script correctly detects normal collisions or trigger collisions, and can forward those events on to an OnNet handler or the GameController, respectively.

Now you know that collisions are working properly, you can proceed to setting up the player!

Player Framework

For now, you’ll just implement the Player code as a stub. You’ll return to this later on, once the GameController has been completed.

Create a new script named Player and edit it in MonoDevelop as follows:

using UnityEngine;
using UnityEngine;
using System.Collections;
 
[RequireComponent (typeof(Animation))]
public class Player : MonoBehaviour
{
 
    public delegate void PlayerAnimationFinished (string animation);
 
    public PlayerAnimationFinished OnPlayerAnimationFinished = null;    
    private Vector3 _shotPosition = Vector3.zero; 
    public Vector3 ShotPosition{
        get{
            return _shotPosition; 
        }
        set{
            _shotPosition = value; 
	}
    }
	
    public enum PlayerStateEnum
    {
        Idle,                   
        BouncingBall,           
        PreparingToThrow,       
        Throwing,               
        Score,                   
        Miss,                   
        Walking                  
    }
 
    private PlayerStateEnum _state = PlayerStateEnum.Idle;
    private float _elapsedStateTime = 0.0f;
    private Transform _transform;
    private Animation _animation;
    private CapsuleCollider _collider;
    private bool _holdingBall = false;
 
    void Awake ()
    {
        _transform = GetComponent<Transform>();
        _animation = GetComponent<Animation>();
        _collider = GetComponent<CapsuleCollider>();
    }
 
    void Start ()
    {                                             
    }
 
    void Update ()
    {                        
    }
 
    public bool IsHoldingBall {
        get {
            return _holdingBall;    
        }
    }
 
    public PlayerStateEnum State {
        get {
            return _state; 
        }
        set {                        
            _state = value; 
            _elapsedStateTime = 0.0f; 
        }
 
    }
 
    public float ElapsedStateTime {
        get {
            return _elapsedStateTime;   
        }
    }       
 
}

The GameController is dependent on knowing when an animation has finished and is able to set and get the current state of the Player. To handle animation events, there is an OnPlayerAnimationFinished() event.

There is also an enumerator for each possible state of the Player: Idle, BouncingBall, PreparingToThrow, Throwing, Score, Miss, and Walking, and there is a property to get the current state.

Note that properties in C# are created with the following format:

public float MyProperty{
    get {
        return MyPropertyValue;	
    }
    set{
        MyPropertyValue = value;	
    }
}

This provides a nice, clean way of managing Getters and Setters.

Before you forget, drag the Player script on top of the player object in the Hierarchy panel to attach it.

That’s it for the Player object is now – this stub implementation is so simple that we could nickname the basketball player “Stubby” :] Let’s move onto implementing more of the GameController!

GameController

The GameController is responsible for coordinating the activities of the game, as well as accepting user input.

What does “coordinating the activities” mean? Games normally function as a state machine. The current state of the game will determine what section of code runs, how user input is interrupted, and what happens on-screen and behind the scenes.

In a complex game, you’d normally encapsulate each state in its own entity, but given the simplicity of this game, it will be sufficient to use an enumeration and switch statement to handle the various game states.

You already created a starter script for the GameController – let’s start building it up.

Variables

You’ll start by declaring the variables you need. Since the GameController’s main job is to act as a co-ordinator between all game entities, you need reference to the majority of them all along with variables used to manage the game statistics (e.g. current score, time remaining, etc).

Add the following code to declare the variables (comments embedded within the code snippet):

	
public Player player; // Reference to your player on the scene 
public ScoreBoard scoreBoard; // Reference to your games scoreboard 
public Ball basketBall; // reference to the courts one and only basketball 
public float gameSessionTime = 180.0f;  // time for a single game session (in seconds) 
public float throwRadius = 5.0f; // radius the player will be positioned for each throw 
private GameStateEnum _state = GameStateEnum.Undefined;	// state of the current game - controls how user interactions are interrupted and what is activivated and disabled 
private int _gamePoints = 0; // Points accumulated by the user for this game session 
private float _timeRemaining = 0.0f; // The time remaining for current game session 
// we only want to update the count down every second; so we'll accumulate the time in this variable 
// and update the remaining time after each second 	
private float _timeUpdateElapsedTime = 0.0f; 
// The original player position - each throw position will be offset based on this and a random value  
// between-throwRadius and throwRadius 
private Vector3 _orgPlayerPosition;

Exposing gameSessionTime (how long the player has to score) and throwRadius (how far your basketball player will potentially move either side of his current/starting position) as public means you can tweak them easily during play testing.