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 .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Intermediate Unity 3D for iOS: Part 2/3
65 mins
- Making Sure Everybody Plays Together Nicely
- Scripting, Scripting, Scripting
- ScoreBoard
- Time to test
- Controlling Collisions
- Time to test
- Player Framework
- GameController
- Variables
- Game States
- Support methods and properties
- Keeping everything up to date
- Time to test
- Handling User Input
- Ball Handling: Dealing With Messages
- Handling Messages from the Player Component
- The Player: "Stubby" No More!
- Character animation
- Time to test
- Managing state
- Time to test
- Bouncing the Ball
- Time to test
- Throwing the Ball
- Positions Please
- Testing it out!
- Where To Go From Here?
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.