Customizable Navigation Bar

Saturday, March 12, 2011

Building A Game In Unity Part 4: Projectiles and The Particle Editor

This week's tutorial covers how to create a few projectiles for your player to shoot. There will also be a few empty classes that we create for the next tutorial on enemies and the animation editor. The particle effects tutorial is located in the videos posted, only because it is much easier not only for me to explain, but for you to understand if I just show your how to use it visually.

Also, follow me on twitter at http://twitter.com/#!/purdyjo!

If you just want to skip to the in depth look at the Particle Editor, skip to the last half of the video.





So before we begin  with the real coding, we need to create two empty classes that extend BaseEntity called Player and Enemy. These are empty because we aren't adding any functionality this week, that is next week's lesson, but we need them for our TrackingRocket script to compile properly.

So just to get them ready create two C# scripts and add the following code to the on you name Enemy:

using UnityEngine;
using System.Collections;

public class Enemy : BaseEntity
{
}

Then add the following code to the one you name Player:


using UnityEngine;
using System.Collections;

public class Player : BaseEntity
{
}


That's all we need for now. Just the empty classes.

OK, so now that we have those two classes out of the way, open up your Projectile class from the last tutorial. There's a quick change that we need to make to the Start() function and the Update() function.

This is what you should have at the declaration of each function:

 void Start () 
 void Update () 
Now what we are going to change this to is:
protected virtual void Start () 
protected virtual void Update () 
Now what this does is make it so that in any class that we extend from Projectile can gain the functionality of the Start and Update functions, while adding their own, with very little extra typing.


Now that we have that out of the way, let's get to filling in two new classes, Rocket and TrackingRocket.
The Rocket class is fairly basic, it just adds a few new properties to the Projectile class.

using UnityEngine;
using System.Collections;

public class Rocket : Projectile
{

        /*Here we declare a public game object. It is what will be instantiated when the rocket collides with something*/

         public GameObject rocketExplosion;

 
        /*We need to override Start() so that we can gain the functionality from Projectile, but at the same time add our own*/

         override protected void Start ()
         {
                 mDamage *= 2;
                 if(rocketExplosion == null)
                 {
                         Debug.LogError("Hey there is no rocket explosion attached to Rocket");
                 }
        /*In order to add the functionality from Projectile we need to call base.Start()*/

                  base.Start();
         }
        /*We need do rewrite the OnTriggerEnter function from Projectile to spawn the rocketExplosion when it hits something*/

         protected virtual void OnTriggerEnter(Collider other)
         {
                  BaseEntity otherObject = other.gameObject.GetComponent<BaseEntity>();
                  if(otherObject != null)
                  {
                          if(otherObject.GetType() != mInstigatorType)
                          {
                                  otherObject.takeDamage(mDamage);

                                /*There it is*/
                                  Instantiate(rocketExplosion, transform.position, Quaternion.identity);
                                  Destroy(gameObject);
                         }
                  }
         }
}

That's literally it for the Rocket class. 

The TrackingRocket class is similar. Basically all we have to do is make the rocket rotate towards it's target.

using UnityEngine;
using System.Collections;

public class TrackingRocket : Rocket
{

        /*So here we create a private transform to hold our target in*/

         private Transform target;
        /*We then override the Start function once more to add a little bit more functionality to the class*/

         override protected void Start()
         {

        /*We set a shorter lifespan because Vector3.Normalize is somewhat expensive, and having dozens of these on the screen at once is just asking from trouble*/

                 mLifeSpan = 4.0f;
                /*We then start two new coroutines that will check for the target's destruction and rotate the projectile towards the target if it exists*/

                 StartCoroutine(checkIfTargetDestroyed());
                 StartCoroutine(rotateTowardsEnemy());
                  base.Start();
         }
        /*Our checkIfTargetIsDestroyed function just checks to see if target is set to null. If it doesn't exist, we call a function called findTarget*/

         private IEnumerator checkIfTargetDestroyed()
         {
                  yield return new WaitForSeconds(0.5f);
                  while (true)
                 {
                          if(target == null)
                         {
                                  findTarget();
                          }
                          yield return new WaitForSeconds(1.0f);
                 }
         }

        /*Our findTarget function is conditional depending on what the instigator of this projectile is*/

         void findTarget()
         {
                  BaseEntity newTarget;
                /*If the instigator is the player, we find all enemies that are alive, and then target a random one*/

                 if(mInstigatorType == typeof(Player))
                  {
                          object[] enemyList = GameObject.FindObjectsOfType(typeof(Enemy));
                         newTarget = enemyList[Random.Range(0, enemyList.Length)] as Enemy;
                  }

                /*If the instigator is an enemy, we find the player and target it*/
                 else
                  {
                          newTarget = GameObject.FindObjectOfType(typeof(Player)) as Player;
                 }
                /*If a target was found we stop the checkIfTargetIsDestroyedCoroutine and set our target*/

                 if(newTarget != null)
                  {
                         target = newTarget.transform;
                         StopCoroutine("checkIfTargetIsDestroyed");
                  }
         }
        /*rotateTowardsEnemy could technically be done in the Update function but square roots are an expensive operation, and we should use them sparingly*/

         private IEnumerator rotateTowardsEnemy()
         {
                 while(true)
                 {
                          yield return new WaitForSeconds(0.2f);
                        /*So if the target exists we rotate towards it, if not we check to see if it is null and find another target*/
                          if(target != null)
                          {
                                  mMovementDirection = Vector3.Normalize(target.position - transform.position);
                          }
                          else
                          {
                                  StartCoroutine(checkIfTargetDestroyed());
                         }
                  }
         }
}

Note that in the video, we did not test the TrackingRocket. I forgot to say something about it but it was because the Player and Enemy classes have not been filled in, and it would have been more trouble than what it's worth to try and get it working. So next week we will see the rockets in actions.

That's it projectile wise. If you were coming here to check out the particle effect walkthrough, check out the last two videos in this post. Next week we are adding the functionality to the Player and Enemy classes, while also looking at the Animation editor. Keep coding!