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

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.

Time to test

You already created a GameController object in the Hierarchy earlier and attached the GameController script to it, so you're all set there.

Select the GameController object in the Hierarchy panel, and you'll notice the GameController now has public properties for the player, scoreboard, and basketball. Set those to the appropriate objects in the Inspector by dragging and dropping.

Connecting objects to game controller in Unity's inspector

Now when you click on the play button you will see the time update as the GameController reduces the time every second!