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

Time to test

Lets make sure your animations are all set up and running correctly. To do this, add the following line to the end of your Player's start method:

CurrentAnimation = animPrepareThrow;

Then disable the GameController script by unchecking the GameObjects component (as shown below):

And click on the play button; if all is well then you will see the basketball player play the "prepare to throw" animation! :]

Note: Before continuing remember to enable the GameController again and remove the test code snippet.

Managing state

Time to flesh out your State property (that you created previously); but before you do that lets stub out a method that you need.

private void AttachAndHoldBall(){

}

This method will be explained when we talk through how the basketball player bounces the ball. For now, replace your previous State property with the follow code snippet:

public PlayerStateEnum State{
	get{
		return _state; 
	}
	set{
		CancelInvoke("OnAnimationFinished"); 

		_state = value; 
		_elapsedStateTime = 0.0f; 

		switch( _state ){
		case PlayerStateEnum.Idle:
			SetCurrentAnimation( animIdle ); 				
			break;			
		case PlayerStateEnum.BouncingBall:
			_collider.enabled = false; 
			AttachAndHoldBall(); 			
			SetCurrentAnimation( animBounceUp );				
			break;
		case PlayerStateEnum.PreparingToThrow:
			SetCurrentAnimation( animPrepareThrow ); 
			break;
		case PlayerStateEnum.Throwing:				
			SetCurrentAnimation( animThrow ); 
			break;
		case PlayerStateEnum.Score:
			SetCurrentAnimation( animScore ); 
			break;
		case PlayerStateEnum.Miss:
			SetCurrentAnimation( animMiss ); 
			break;
		case PlayerStateEnum.Walking:
			if( _shotPosition.x < _transform.position.x ){
				SetCurrentAnimation( animWalkForward ); 
			} else{
				SetCurrentAnimation( animWalkBackward ); 
			}
			break;
		}															 									
	}
}

Most of the code is related to setting up the appropriate animation based on the currently set state, using your SetCurrentAnimation method we created above. So lets concentrate on the less trivial code.

One of the first statements is:

CancelInvoke("OnAnimationFinished");

This statement asks Unity to cancel any invoke that maybe queued up called OnAnimationFinished, which should look pretty familiar to you because we created this invoke when playing a non-looping animation.

The next interesting piece of code is for the state PlayerStateEnum.Walking; in this block you are determining the animation based on the target (shot) position compared to your current position to determine whether the basketball player is walking backwards or forwards.

Time to test

Similar to above, let's perform a quick test to check that your states and animations are working correctly together. Add the following code to the Start method of your Player class:

State = PlayerStateEnum.Score;

As you did before, disable the GameController script by unchecking the GameObjects component so that it does not interfere with your test.

And click on the play button; if all is well then you will see your basketball player plays the "score" animation (the animation that will run when the user successfully gets a ball in the hoop).

Note:Before continuing remember to enable the GameController again and remove the test code snippet.

Bouncing the Ball

One responsibility of the basketball player is to bounce the ball while waiting for user input. In this section we will cover the code and setup required to make this happen.

Start by declaring the following variables at the top of your Player class:

	
public Ball basketBall; 
public float bounceForce = 1000f;
private Transform _handTransform; 

The variable _handTransform will hold reference to the Transform Component of the bone that will be touching the ball and the bounceForce is used to determine how much force is applied to the ball when bouncing (the basketball variable should be pretty obvious).

One of the first problems to solve is how to position the ball in the player's hand when the Player's state changes to BouncingBall. Implement the AttachAndHoldBall method you stubbed out earlier to do that:

public void AttachAndHoldBall ()
{
    _holdingBall = true; 

    Transform bTransform = basketBall.BallTransform; 
    SphereCollider bCollider = basketBall.BallCollider;  
    Rigidbody bRB = basketBall.BallRigidbody; 

    bRB.velocity = Vector3.zero; 

    bTransform.rotation = Quaternion.identity; 

    Vector3 bPos = bTransform.position;         
    bPos = _handTransform.position;
    bPos.y -= bCollider.radius; 
    bTransform.position = bPos;                     

}

One of the publicly exposed variables (named basketball) holds a reference to the basketball object. This function needs a reference to the ball's transform, collider, and rigid body, so the first part of this method gets those.

With respect to the Rigidbody, any current velocity is removed and the ball (to ensure it has stopped completely and won't 'bounce' out of your hand) is then positioned to the player’s hand using the Ball's Collider to offset based on the diameter of the ball.

You may be wondering where _handTransform comes from. Recall that you added a Box Collider to one of the Player's hand when setting up the scene in Part 1.

To make use of this, add the following code to the end of Awake():

_handTransform = _transform.Find (
              "BPlayerSkeleton/Pelvis/Hip/Spine/Shoulder_R/UpperArm_R/LowerArm_R/Hand_R");

This grabs a reference to the appropriate component and attaches to the _transform variable. An alternative would have been to expose it as a public property and assign it via the editor like you've done so far, but this is a nice opportunity to demonstrate you how you can traverse a GameObject to obtain reference to one of its children.

Once the Player is holding the ball, he needs to start bouncing it! :]

You do this by holding the ball and playing the BounceUp animation. If, during Update(), the game is in the BouncingBall state, the Player is holding the ball, and the Bounce Down animation has finished playing, then push the ball down via the AddRelativeForce method of the Ball's Rigidbody using your bounceForce variable. This will force the ball to the ground and make it bounce back up (hence why the force is so high).

Replace the Update method with the following code:

void Update ()
{
    if( _holdingBall ){
        AttachAndHoldBall(); 	
    }
    _elapsedStateTime += Time.deltaTime; 
}

First you check if the _holdingBall has been set. If it is, you call the AttachAndHoldBall you just implemented above to position the ball in your basketball player's hand.

The _holdingBall method is set to true within the AttachAndHoldBall method (which in turn is called when the state is changed to BouncingBall) and set to false during bouncing and once thrown the ball.

Next add the following to the end of Update():

if( _state == PlayerStateEnum.BouncingBall ){	
    if( _holdingBall ){
        if( GameController.SharedInstance.State == GameController.GameStateEnum.Play && GameController.SharedInstance.TouchDownCount >= 1 ){
            State = PlayerStateEnum.PreparingToThrow;
            return; 
        }
    }

    if( _currentAnimation.name.Equals( animBounceDown.name ) ){
        if( !_animation.isPlaying && _holdingBall ){
            // let go of ball
            _holdingBall = false;  
            // throw ball down 
            basketBall.BallRigidbody.AddRelativeForce( Vector3.down * bounceForce ); 				
	} 				
    } 
    else if( _currentAnimation.name.Equals( animBounceUp.name ) ){						
        if( !_animation.isPlaying ){
            SetCurrentAnimation( animBounceDown ); 
        }					
    }
}

The above block (embedded into your Update method) first checks if we're currently holding the ball, if so asks the GameController if a touch is present. If so, it swaps to the PrepareToThrow state, otherwise checks what animation you're current playing and if it has finished.

If the down animation has finished then you push the ball to the ground, and if the up animation has finished you start the down animation.

As the ball is bouncing back up, it will collide with the hand's Box Collider trigger. Implement a method that will be called when this occurs:

public void OnTriggerEnter (Collider collider)
{
    if (_state == PlayerStateEnum.BouncingBall) {
        if (!_holdingBall && collider.transform == basketBall.BallTransform) {
            AttachAndHoldBall ();
            SetCurrentAnimation (animBounceUp);
        }
    }
}

This makes it re-start the bouncing sequence all over again when the ball bounces back up to the hand!

Note that trigger events don’t propagate up to the parent, whereas Collision events do. Therefore this OnTriggerEnter method of the Player Component that you just wrote will not be automatically called when the collision occurs.

However, you can write a helper script written to facilitate this. Create a new script named PlayerBallHand and enter the following code:

using UnityEngine;
using System.Collections;
 
[RequireComponent (typeof(Collider))]
public class PlayerBallHand : MonoBehaviour
{
 
    private Player _player = null;
 
    void Awake ()
    {
 
    }
 
    void Start ()
    {
        Transform parent = transform.parent;
        while (parent != null && _player == null) {
            Player parentPlayer = parent.GetComponent<Player>();
            if (parentPlayer != null) {
                _player = parentPlayer;
            } else {
                parent = parent.parent;     
            }
        }
    }
 
    void Update ()
    {
 
    }
 
    void OnTriggerEnter (Collider collider)
    {
        _player.OnTriggerEnter (collider); 
    }
}

This Component is responsible for notifying the Player Component when the ball collides with the hand.

Next switch back to Unity and to attach this script to the Hand_R of the player object. Remember that you created a collider on this object in Part 1 of this tutorial.

Also, select the Player object and attach the basketball to the public variable for it.

And finally, select the BallPhyMat and set the bounciness to 1 so the basketball bounces up with enough force.