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 4 of 9 of this article. Click here to view the first page.

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.

Game States

You’ve added some states for your Player object; now add some states for the game:

public enum GameStateEnum
{
    Undefined,
    Menu,
    Paused, 
    Play,
    GameOver
}

Here’s an explanation of the various game states:

  • Menu – display your main menu items
  • Pause – present a similar menu to the main menu
  • Play – when the user is actually playing the game
  • Game Over – when play is finished

States acts as gates deterring what path to take (in terms of code branching) based on the current state. The state logic (in this project) is used through-out the methods of this class but exposes itself as a public property which is used to manage the switching of the states.

Next add a getter and setter for the game state, as follows:

public GameStateEnum State {
	get{
		return _state; 	
	}
	set{
		_state = value; 

		// MENU 
		if( _state == GameStateEnum.Menu ){
			Debug.Log( "State change - Menu" ); 				

			player.State = Player.PlayerStateEnum.BouncingBall;	

			// TODO: replace play state with menu (next tutorial)
			StartNewGame();
		}			
		// PAUSED 
		else if( _state == GameStateEnum.Paused ){
			Debug.Log( "State change - Paused" );				 				
			// TODO; add pause state (next tutorial)				
		}			
		// PLAY 
		else if( _state == GameStateEnum.Play ){
			Debug.Log( "State change - Play" ); 								 
		}			
		// GAME OVER 
		else if( _state == GameStateEnum.GameOver ){
			Debug.Log( "State change - GameOver" ); 								
			// TODO; return user back to the menu (next tutorial) 								
			StartNewGame();
		}									
	}
}

Encapsulating the state within a property allows you to easily intercept changes to states and perform necessary logic as required (as you can see above).

Support methods and properties

Next you’ll add some supporting methods and properties.

First add the StartNewGame method as follows:

public void StartNewGame(){		
	GamePoints = 0; 
	TimeRemaining = gameSessionTime; 
	player.State = Player.PlayerStateEnum.BouncingBall; 
	State = GameStateEnum.Play;
}

This method is responsible for resetting the game statistics (variables declared above) and preparing the game entities on your scene for a new game.

Next add the ResumeGame method:

	
public void ResumeGame(){
	if( _timeRemaining < 0 ){
		StartNewGame(); 	
	} else{
		State = GameStateEnum.Play;
	}
}

This is similar to the StartNewGame but performs additional checks. If the game is considered over (time has ran out) then StartNewGame, will be called. Otherwise you switch the GameController state back to Play to resume game play.

Next, define a new property for GamePoints:

public int GamePoints{
	get{
		return _gamePoints; 	
	}
	set{
		_gamePoints = value; 
		scoreBoard.SetPoints( _gamePoints.ToString() ); 
	}
}

This will be responsible for keeping your scoreboard update-to-date with the latest current score.

Finally, add a TimeRemaining property:

public float TimeRemaining {
	get{
		return _timeRemaining; 
	}
	set{			
		_timeRemaining = value; 
		scoreBoard.SetTime( _timeRemaining.ToString("00:00") ); 			

		// reset the elapsed time 
		_timeUpdateElapsedTime = 0.0f; 
	}
}

This is responsible for keeping the scoreboard update-to-date with the current amount of time remaining.

Done with support methods and properties - time for the big guy, Update!

Keeping everything up to date

Now you'll shift your focus to what makes the GameController tick, the Update method and accompanying methods. Add the following code to GameController:

void Update () {
	if( _state == GameStateEnum.Undefined ){
		// if no state is set then we will switch to the menu state 
		State = GameStateEnum.Menu;	
	}
	else if( _state == GameStateEnum.Play ){			
		UpdateStatePlay(); 
	}
	else if( _state == GameStateEnum.GameOver ){
		UpdateStateGameOver();	
	}

}

private void UpdateStatePlay(){
	_timeRemaining -= Time.deltaTime; 

	// accumulate elapsed time 
	_timeUpdateElapsedTime += Time.deltaTime; 

	// has a second past? 
	if( _timeUpdateElapsedTime >= 1.0f ){
		TimeRemaining = _timeRemaining; 
	}

	// after n seconds of the player being in the miss or score state reset the position and session 
	if( (player.State == Player.PlayerStateEnum.Miss || player.State == Player.PlayerStateEnum.Score)
		&& player.ElapsedStateTime >= 3.0f ){

		// check if the game is over 
		if( _timeRemaining <= 0.0f ){
			State = GameStateEnum.GameOver;
		} else{				
			// set a new throw position 
			Vector3 playersNextThrowPosition = _orgPlayerPosition;
			// offset x 
			playersNextThrowPosition.x +=  Random.Range(-throwRadius, throwRadius); 
			player.ShotPosition = playersNextThrowPosition; 			
		}
	}
}

private void UpdateStateGameOver(){		
	// TODO; to implement (next tutorial) 		
}

The Update method delegates the task to a specific method based on what the current state is. As you can see the bulk of the code in this code snippet belongs to the UpdateStatePlay method, let's go through it bit by bit.

_timeRemaining -= Time.deltaTime; 

// accumulate elapsed time 
_timeUpdateElapsedTime += Time.deltaTime; 

// has a second past? 
if( _timeUpdateElapsedTime >= 1.0f ){
	TimeRemaining = _timeRemaining; 
}

The first part is responsible for updating the elapsed game time (or time remaining). You track the last time you updated the TimeRemaining property using the variable _timeUpdateElapsedTime, throttling updates to every second as updating your scoreboard any quicker (which is done via the TimeReamining property) is not necessary (we are not showing milliseconds) and could potentially affect performance.

// after n seconds of the player being in the miss or score state reset the position and session 
if( (player.State == Player.PlayerStateEnum.Miss || player.State == Player.PlayerStateEnum.Score)
	&& player.ElapsedStateTime >= 3.0f ){

	// check if the game is over 
	if( _timeRemaining <= 0.0f ){
		State = GameStateEnum.GameOver;
	} else{				
		// set a new throw position 
		Vector3 playersNextThrowPosition = _orgPlayerPosition;
		// offset x 
		playersNextThrowPosition.x +=  Random.Range(-throwRadius, throwRadius); 
		player.ShotPosition = playersNextThrowPosition; 			
	}
}

The next section is responsible for checking for when the basketball player has finished a throw and checking if the game is finished. The basketball player is considered having finished a throw when he has been in either the Miss or Score state for 3 or more seconds. The reason for the delay is that you want an animation to finish before moving onto the next throw.

You then check if there is any time remaining. If not, you update the state to GameOver, otherwise you ask the basketball player to move into a new position for another shot.