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

Handling User Input

In many cases you’ll find yourself developing on your desktop machine, and porting over to an actual device with greater frequency as the project nears completion. Therefore you need to handle input in both contexts: from the device touchscreen, as well as the keyboard and mouse.

To do this, first add a helper method into GameController to detect whether the app is running on a mobile device or not:

public bool IsMobile{
	get{
		return (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.Android); 	
	}
}

Luckily your design requires minimal interaction; all that is required is determining if a finger is down or not, the following snippet does just that.

public int TouchCount {
	get{
		if( IsMobile ){
			return Input.touchCount; 
		} else{
			// if its not consdered to be mobile then query the left mouse button, returning 1 if down or 0 if not  
			if( Input.GetMouseButton(0) ){
				return 1; 	
			} else{
				return 0; 
			}
		}
	}	
}

public int TouchDownCount {
	get{
		if( IsMobile ){
			int currentTouchDownCount = 0; 
			foreach( Touch touch in Input.touches ){
				if( touch.phase == TouchPhase.Began ){
					currentTouchDownCount++; 	
				}
			}

			return currentTouchDownCount;
		} else{
			if( Input.GetMouseButtonDown(0) ){
				return 1; 	
			} else{
				return 0; 
			}
		}
	}
}

To determine if the user is touching the screen or not you use the TocuhCount and TouchDownCount properties that branch depending on what platform the app is running on.

If it's running on the mobile platform then you query (and return) the Input class for the number of touches that have been detected, otherwise you assume we are running on your desktop and query the Input class if the MouseButton is down (returning 1) or not (returning 0).

The only difference between TouchCount and TouchDownCount is that TouchCount counts the number of fingers present on the screen regardless of their phase while TouchDownCount only counts those fingers whose phase is set to Began.

Node:The Touch class has an enumeration called TouchPhase, a touch phase is essentially the current state of the touch e.g. when first detected (when the finger first touches the screen) the touch will be assigned a Began phase, once moving the touch will be assigned the Moved phase, and when finally lifted off the touch will be assigned the Ended phase.

For a full overview of Unity's Input class, please refer to the official Unity site (http://docs.unity3d.com/Documentation/ScriptReference/Input.html).

Ball Handling: Dealing With Messages

Recall that the Ball object sends messages to GameController in the case of two events: when the ball goes through the net, and when it hits the ground.

Replace the OnBallCollisionEnter method to handle the case when the ball collides with the ground:

public void OnBallCollisionEnter (Collision collision)
{
    if (!player.IsHoldingBall) {
        if ((collision.transform.name == "Ground" ||
            collision.transform.name == "Court") &&
            player.State == Player.PlayerStateEnum.Throwing) {

            player.State = Player.PlayerStateEnum.Miss;

        }
    }
}

OnBallCollisionEnter() checks to see if the player is holding the ball. If not, then it’s assumed the ball has been thrown. Therefore, if the ball collides with the ground or the sides of the court, then the turn is over. If the ball hits the ground or the court and the player missed the hoop, then set the player state to Miss.

OnBallCollisionEnter() is called explicitly by the Ball Component and its HandleBasketBallOnNet event. How do you associate OnBallCollisionEnter() with the event from HandleBasketBallOnNet? You do this in the Start() method by registering 'interest' in the OnNet event.

You should add this in a new method called Start(), which is a good place to put initialization code like this:

void Start () {
	// register the event delegates 
	basketBall.OnNet += HandleBasketBallOnNet; 				
}

This is how you assign a callback to the event delegates. When the Ball raises the Net event, the HandleBasketBallOnNet method will be called.

Then add the implementation for HandleBasketBallOnNet as follows:

public void HandleBasketBallOnNet(){
	GamePoints += 3; 
	player.State = Player.PlayerStateEnum.Score;
}

Handling Messages from the Player Component

The other Component that communicates with the GameController is the Player. At this point the Player is only stubbed out, but here you'll implement the handling of messages and events in the GameController. The Player raises an event when an animation is finished playing, which in turn triggers an update in the GameController's game state.

Add the following code to the end of the Start() method to register for the event:

player.OnPlayerAnimationFinished += HandlePlayerOnPlayerAnimationFinished;

Along with its accompanying method:

public void HandlePlayerOnPlayerAnimationFinished (string animationName)
{
    if (player.State == Player.PlayerStateEnum.Walking) {
        player.State = Player.PlayerStateEnum.BouncingBall;
    }
}

This code updates the state of the Player to BouncingBall once he has finished walking.

The next portion of this tutorial will tie all of these events together and allow you to finally shoot a few hoops! :]

The Player: "Stubby" No More!

Here's a quick review of what the Player is responsible for and what functionality will be required:

  • During idle time, the Player should bounce the ball.
  • When in the Play game state, the Player should react to user input; in this case when the user holds their finger on the screen, the Player will ‘wind’ up for the throw.
  • The Player should affect the position of the Ball and influence its behavior.
  • The Player should move around the court after each turn.
  • The Player should animate based on the current state, e.g. show a winning animation when the ball goes through the hoop and a disappointed animation when the ball misses.
  • The Player should notify the GameController when each of the above animations completes.

Head back and open up the Player script, and lets slowly make your way through the code.

Character animation

Unity provides a rich set of classes that handle the importing and use of animations from 3D packages. When you imported the player that was created in Blender, it came packaged with a set of animations. If you select the Animation Component of the Player object in the editor you’ll see the following:

The Animation Component has 10 slots, each containing a separate Animation Clip. You can play any of these animations in script by asking the Animation Component to play a specific Animation Clip.

Note: To find out more about the Animation Component, check out the official Unity documentation here: http://docs.unity3d.com/Documentation/Components/class-Animation.html

Inside the Player script, add some variables to manage the current animation and AnimationClips that will reference the various animations:

	
private AnimationClip _currentAnimation = null;
public AnimationClip animIdle; 
public AnimationClip animBounceDown; 
public AnimationClip animBounceUp; 
public AnimationClip animWalkForward; 
public AnimationClip animWalkBackward; 
public AnimationClip animPrepareThrow; 
public AnimationClip animThrow; 
public AnimationClip animScore; 
public AnimationClip animMiss;

Referencing the animations via a variable provides the flexibility to easily update the animations without having to rely on a specific animation file or index/name.

Of course, for this to work you have to connect the appropriate animation to each public property on the script component, so go ahead and do that now:

Setting animation properties in Unity

The next step is to set up each of the animations. You do this by calling a dedicated method from the Player Start method (along with getting reference to the attached animation Component. Add the following code to do this:

void Start(){
   _animation = GetComponent<Animation>(); 
   InitAnimations(); 
}

private void InitAnimations ()
{

    _animation.Stop (); 

    _animation [animIdle.name].wrapMode = WrapMode.Once; 
    _animation [animBounceDown.name].wrapMode = WrapMode.Once; 
    _animation [animBounceUp.name].wrapMode = WrapMode.Once;         
    _animation [animWalkForward.name].wrapMode = WrapMode.Loop; 
    _animation [animWalkBackward.name].wrapMode = WrapMode.Loop; 
    _animation [animPrepareThrow.name].wrapMode = WrapMode.Once; 
    _animation [animThrow.name].wrapMode = WrapMode.Once; 
    _animation [animScore.name].wrapMode = WrapMode.Once; 
    _animation [animMiss.name].wrapMode = WrapMode.Once; 

    _animation [animBounceDown.name].speed = 2.0f; 
    _animation [animBounceUp.name].speed = 2.0f;         
}

The Animation Component acts as a controller and repository for your animations. Each animation is wrapped in a class called AnimationState. You can access each one by index position or key, where key is the name of the animation. This is shown visually in the editor screenshot above.

Take a look at two properties in each animation: wrapMode and speed. Speed determines the playback speed for the specific animation, whereas wrapMode determines how the animation is ‘wrapped’; in other words, what the animation does once it has come to the end. Here the animations are either being played just once, or are looping.

The only thing left to do is to play the animations! :] Add the following code to the Player class:

public bool IsAnimating{
	get{
		return _animation.isPlaying; 	
	}
}

public AnimationClip CurrentAnimation {
    get {
        return _currentAnimation; 
    }
    set {
        SetCurrentAnimation (value);   
    }
}

public void SetCurrentAnimation (AnimationClip animationClip)
{
    _currentAnimation = animationClip; 
    _animation [_currentAnimation.name].time = 0.0f; 
    _animation.CrossFade (_currentAnimation.name, 0.1f); 

    if (_currentAnimation.wrapMode != WrapMode.Loop) {
        Invoke ("OnAnimationFinished", _animation [_currentAnimation.name].length /
            _animation [_currentAnimation.name].speed);
    }
}

private void OnAnimationFinished ()
{ 

    if (OnPlayerAnimationFinished != null) {
        OnPlayerAnimationFinished (_currentAnimation.name);    
    }
}

The above code shows all the methods associated with handling animation. The main method is SetCurrentAnimation().

Here the current animation time is reset to 0 (i.e. back to the start) and then the Animation Component is requested to crossFade the specified animation. Cross fading permits fading in an animation over the currently playing animation over the specified time. This means the current animation will be slowly ‘blended’ out to give a smooth transition between the current and new animation.

After requesting the animation to play via crossFade, check if the animation is a loop. If not, then request a delayed call to OnAnimationFinished() using the Invoke method. The call will be delayed by the time of the animation.

Finally, OnAnimationFinished() is responsible for raising the associated event, and thus notifying the GameController that an animation has finished so that it is aware of the current state and actions of the Player GameObject.