Gigi Labs

Please follow Gigi Labs for the latest articles.
Showing posts with label linear interpolation. Show all posts
Showing posts with label linear interpolation. Show all posts

Saturday, June 1, 2013

Unity3D: Space Invaders (Part 5 - Behaviour)

Hi all! :)

In yesterday's article - Unity3D: Space Invaders (Part 4 - Collisions) - we learned how to handle collisions in Unity. At the end of the article, we listed several items that are still missing from Ranch Invaders.

Today's article deals with some of those issues, mainly relating to Player and Alien behaviour.

Player Movement

In Space Invaders, the Player's ship moves only left and right. If you've read "Unity3D: Moving an Object with Keyboard Input", you'll know that this is really easy to do. Open the Player script with MonoDevelop. You should currently have the code for shooting:

    void Update ()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Instantiate(bullet, this.transform.position, Quaternion.identity);
        }
    }

Change it as follows to allow the Player to move using the left and right arrows:

    void Update ()
    {
        Vector3 pos = this.transform.position;
       
        if (Input.GetKeyDown(KeyCode.Space))
            Instantiate(bullet, this.transform.position, Quaternion.identity);
        if (Input.GetKeyDown(KeyCode.LeftArrow))
            this.transform.position = new Vector3(pos.x - 0.1f, pos.y, pos.z);
        if (Input.GetKeyDown(KeyCode.RightArrow))
            this.transform.position = new Vector3(pos.x + 0.1f, pos.y, pos.z);
    }

Great, you can now move the Player's ship left and right. There's a problem, however. If you hold down an arrow key, the Player's ship does not continue moving. We can fix this by using Input.GetKey() instead. Also, we need to set constraints to prevent the Player's ship from leaving the screen:

    void Update ()
    {
        Vector3 pos = this.transform.position;
       
        if (Input.GetKey(KeyCode.Space))
            Instantiate(bullet, this.transform.position, Quaternion.identity);
        if (Input.GetKey(KeyCode.LeftArrow) && pos.x > -2.5f)
            this.transform.position = new Vector3(pos.x - 0.1f, pos.y, pos.z);
        if (Input.GetKey(KeyCode.RightArrow) && pos.x < 2.5f)
            this.transform.position = new Vector3(pos.x + 0.1f, pos.y, pos.z);
    }

Cooldown

As it is, the Player can fire as many bullets as it likes, with no gap between one and another. In order to implement a cooldown, we use an approach similar to this. When a bullet is fired, the time is recorded and no bullet may be fired before the cooldown period has passed. To do this, declare the following variables in the Player class:

    public float cooldown = 1.0f; // minimum time between bullets
    private float nextFire = 1.0f; // next time when a bullet can be fired

Then, adjust the firing code as follows:

        if (Input.GetKey(KeyCode.Space) && Time.time >= nextFire)
        {
            nextFire += cooldown;
            Instantiate(bullet, this.transform.position, Quaternion.identity);
        }

Then, try it out, and adjust cooldown as you like:


Then, rejoice.


Alien Movement

In Space Invaders, aliens move right, down, left, down, etc. Making this work properly takes a bit of work, so I'll just give you the code:

public class Alien : MonoBehaviour
{
    public float horDistance = 1.0f;
    public float verDistance = 0.5f;
    public float speed = 0.4f;
    private float startTime;
    private Vector3 startingPosition;
    private Vector3 target;
   
    // Use this for initialization
    void Start ()
    {
        this.startTime = Time.time;
        this.startingPosition = this.transform.position;
        this.target = this.startingPosition + new Vector3(this.horDistance, 0, 0);
    }
   
    // Update is called once per frame
    void Update ()
    {
        Vector3 currentPosition = this.transform.position;
        Vector3 newPosition = Vector3.MoveTowards(currentPosition, this.target, speed * Time.deltaTime);
        this.transform.position = newPosition;
       
        //print (this.target);
       
        if (newPosition == target)
        {
            if (newPosition.x == startingPosition.x)
            {
                if (((newPosition.y - startingPosition.y) / verDistance) % 2 == 0)
                    this.target = newPosition + new Vector3(this.horDistance, 0, 0);
                else
                    this.target = newPosition - new Vector3(0, this.verDistance, 0);
            }
            else
            {
                if (((newPosition.y - startingPosition.y) / verDistance) % 2 != 0)
                    this.target = newPosition - new Vector3(this.horDistance, 0, 0);
                else
                    this.target = newPosition - new Vector3(0, this.verDistance, 0);              
            }
        }
    }
}

Don't fret about what the code is doing... just use it. Tweak the public variables as you like.

More Aliens

Since we need a lot of aliens, it's a good idea to make the Alien object a prefab. To do this, drag it from the Hierarchy panel to the Assets panel. If you did it right, its listing in the Hierarchy panel should turn blue. This way, if you make any changes, you can apply them across all Aliens.

To make multiple Aliens, you can either press Ctrl+D (duplicate) to create additional objects, or else instantiate all the aliens in code using for loops. In my case, I just duplicated a handful of Aliens, just for testing:


Don't get too fussy about the Alien placement and all that. The reason we're still working with cubes and spheres is that you should first concentrate on making sure that your game mechanics work correctly. Only once that is done should you spend some time (a lot of time, actually) making it look good.

Aliens Shooting

Allowing Aliens to shoot the Player is no different from allowing the Player to shoot the Aliens. However, you now need a different bullet that moves downwards and that can destroy the player.

Once you attach a rigidbody to the Player (Component -> Physics -> Rigidbody, and remember to turn off "Use Gravity"), you will find some unexpected behaviour: at times, when the Player shoots a bullet, the Player itself is destroyed! That's because we don't yet have any code in the Bullet script that distinguishes between objects it can destroy. So far it worked correctly only because the Player didn't have a rigidbody, in which case the collision doesn't happen.

An easy way to solve this is to tag the object. Select the Player. At the top of the Inspector is a section where you can select a tag to assign to the object. For the Player, you can choose the predefined Player tag:


For the Aliens, you will need to add a new tag. From the drop-down list containing tags (shown above), select "Add Tag...".


In the resulting interface (shown above), expand the "Tags" node. Next to "Element 0", type "Alien". This creates the "Alien" tag, but does not assign it to the object.


To tag the Alien as such, select an Alien object, and select the newly available "Alien" tag from the drop-down list. Remember to click "Apply" to make this work for all Alien objects (since it's a prefab).

The Bullet script's collision code now becomes:

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Alien")
        {
            GameObject.Destroy(collision.gameObject);
            GameObject.Destroy(this.gameObject);
        }
    }

The Player's bullets should now destroy only Aliens.

For the aliens to shoot at the player, you will first need to create another bullet prefab (e.g. "AlienBullet") with the same script as the Bullet, except for two key differences. First, the bullet needs to go downwards instead of upwards. So:

    void Start ()
    {
        this.target = this.transform.position - new Vector3(0, 20, 0);
    }

Secondly, it needs to collide with the Player instead of the Alien:

void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Player")
{
GameObject.Destroy(collision.gameObject);
GameObject.Destroy(this.gameObject);
}
}


Finally, the code for shooting (in the Alien script) will be a little different from that of the Player. Instead of shooting when the user presses a key, the Alien will shoot with a random delay. First, declare the following variables in the Alien class:

    public GameObject bullet;
    public float minShootDelay = 1.0f;
    public float maxShootDelay = 7.0f;
    private float nextShootTime = 0.0f;

In the Unity editor, set the AlienBullet prefab in the Alien script's bullet slot.

In the Start() method, add code to initialise nextShootTime:

        this.nextShootTime = Random.Range(minShootDelay, maxShootDelay);

Unity's Random.Range() method is similar to .NET's Random.Next() method, but not quite the same.

Change the code that handles when bullets leave the game area, since these bullets are going down instead of up:

        if (this.transform.position.y < -3.0f)
            GameObject.Destroy(this.gameObject);

Finally, in the Update() method, we add code for actual shooting:

        if (Time.time > nextShootTime)
        {
            Instantiate(bullet, this.transform.position, Quaternion.identity);
            nextShootTime = Time.time + Random.Range(minShootDelay, maxShootDelay);
        }

If you try it now, things will get a little bit messy as the Aliens' bullets will collide with themselves:


Explaining how to solve this requires an article in itself. Notwithstanding this, in this (long) article we have taken care of several behaviour issues with both the Player and the Aliens, and brought the game much closer to a working Space Invaders clone.

This is the last article in the Ranch Invaders series. I hope you'll return to the Ranch to read about other interesting topics! :)

Thursday, May 30, 2013

Unity3D: Space Invaders (Part 3 - Prefabs)

Hello, and welcome back to the Ranch! :)

This article continues with the Ranch Invaders series. However, like with yesterday's article, if you haven't read the previous articles, you can simply follow along by creating a new Unity project.

In today's article, we're going to add the player's ship into the game, and allow it to shoot bullets. In doing this, we will learn what prefabs are, and how to create objects while the game is running.

From the GameObject menu, go to Create Other -> Sphere. In the Hierarchy section, select the Sphere, press F2, and rename it to "Player". Move it so that it appears in the bottom of the camera's view (you might want to select the Main Camera to see its preview while you do this).

Next, from the GameObject menu, go to Create Other -> Capsule. Rename it to "Bullet". After pressing the 'R' key to go into scaling mode, resize the bullet (capsule) so that it is much smaller than the player (sphere).

Your work so far might look something like this:



Now, from the Hierarchy panel, drag the Bullet into the Assets panel:


The result is that the Bullet is added to the Assets panel, and in the Hierarchy panel, its entry turns blue. This means that the Bullet is now a prefab.

The Player and the Alien objects are simply objects in the scene. Since the Bullet is a prefab, we can refer to it in scripts and create as many Bullets as we like through code. A great thing about prefabs is that you can customise the prefab once (e.g. make a bullet with a yellow material), and all instances of that prefab will have the same features. Let's see how this works in practice.

In the Assets panel, right click somewhere, and in the context menu, select Create -> Material. Name it "Bullet". In the Main Color property, select whichever colour you think is best for a bullet (e.g. yellow). Drag the material onto the Bullet object either in the Hierarchy panel or in the world view:

Right click the Assets panel and Create -> C# Script. Name it "Bullet". Drag the script onto the Bullet object. Double-click the script to launch MonoDevelop.

In the Bullet class, add a public variable to represent the speed:

    public float speed = 1.0f;

When you expose a public variable like this, it appears in the Inspector in the section where the script is:


You can now change this variable as you like through the Inspector, and when you Play the game in Unity, that value will be used. Note, however, that if you change such variables while Playing, changes will be reverted once you stop playing.

Now, we need to write a script that will take care of the bullet's movement. Since we're doing Sp-- sorry, Ranch Invaders, we're looking to do something like this:


The bullet starts at the player's position, and is shot upwards where the Aliens will be. In code, we can do this as follows:

public class Bullet : MonoBehaviour
{
    public float speed = 1.0f;
    private Vector3 target;
   
    // Use this for initialization
    void Start ()
    {
        this.target = this.transform.position + new Vector3(0, 20, 0);
    }
   
    // Update is called once per frame
    void Update ()
    {
        Vector3 currentPosition = this.transform.position;
        Vector3 newPosition = Vector3.MoveTowards(currentPosition, this.target, speed * Time.deltaTime);
        this.transform.position = newPosition;
    }
}

Remember that we're going to have the Player create (shoot) bullets. When the bullets are created, they will be at the Player's position. We want the bullets to go upwards, so we define a target that is a certain distance away (e.g. 20 units). By adding a Vector3(0, 20, 0) that simply points directly upwards, to the initial bullet position, we can set a destination for the bullet that is directly above its starting position.

In the Update() method, we use Vector3.MoveTowards() which works pretty much like Lerp (see yesterday's article). Except that instead, we are moving the Bullet from its current position towards the target in steps of speed * Time.deltaTimeTime.deltaTime is a measure of frame time, so the bullet will move a short length upwards with every frame. We multiply speed to be able to control the speed.

From the Unity editor, press the Play button and watch the bullet move upwards. If you think it's too fast or too slow, tweak the speed by selecting the Bullet and changing the speed value in the Inspector.

You can now delete the Bullet from the scene. Since it's among our Assets, we can create it at runtime. Create a new C# script called "Player" and drag it onto the Player object. Double-click the Player script to open it in MonoDevelop.

In the Player class, add the following public variable:

    public GameObject bullet;

Save the script. Back in Unity, select the Player object, and in the Inspector, under the Player Script, notice that there is now a slot for the bullet game object:


From the Assets panel, drag the Bullet prefab into the appropriate slot in the Inspector, where it currently says "None (Game Object)".

Now, from the Player script, we can make instances of this prefab. In the Update() method of the Player script, just add the following:

        if (Input.GetKeyDown(KeyCode.Space))
        {
            Instantiate(bullet, this.transform.position, Quaternion.identity);
        }

So all we're doing here is that when the player presses the Space key, we use the Instantiate() method to create a bullet. We give it the object we want to instantiate (in this case the bullet prefab), and its starting position and orientation (rotation). I'm not going to go into quaternions here, but Quaternion.identity simply means that the bullet isn't rotated.

You can now press Play to try out the game. Press Space to watch bullets move like sausages in the sunset:


If it doesn't work, don't worry! :) Depending on how you carried out the steps above, it is possible that the bullet prefab wasn't saved correctly. In my case, for example, I found that the Bullet script was missing. Since a prefab is really just a template for an object, you need to make sure that any changes to it apply to all the objects that use that template. So in the case of the missing script, once you drag the Bullet script onto a Bullet object in the scene, you need to click "Apply" at the top of the Inspector in order to apply the changes to all affected objects.

Great! We can now shoot sausage-like bullets to our heart's content. We still have some work left to do, though: we need more aliens, the player's ship needs to move sideways, bullets need to destroy aliens, etc. We'll handle some of these things in the next article. Until then, au revoir! :)

Wednesday, May 29, 2013

Unity3D: Space Invaders (Part 2 - Linear Interpolation)

Hi all! :)

In the previous article ("Unity3D: Space Invaders (Part 1 - Navigation and Materials)"), we began working on a Space Invaders clone called Ranch Invaders. Today we're going to continue where we left off, and we're going to animate the alien moving around. If you didn't follow the previous article, don't worry! :) All you need to do is start a new Unity project and drag a cube onto the scene.

If you've read "Unity3D: Moving an Object with Keyboard Input", you might remember that in order to make an object behave in some way, we need to attach a script to it. So, right-click in the Assets panel, and in the context menu, select Create -> C# Script. You can call it Alien (or whatever you like). Then, drag the script onto the Alien object (cube). You'll know you've done it right when you select the Alien object and the Alien script appears in the Inspector:


Double-click the Alien script to open it in MonoDevelop. You are presented with a default (empty) behaviour script:


You will notice that these scripts all inherit from a MonoBehaviour class and have at least Start() and Update() methods. The Start() method allows us to set certain variables when the object is initialised, while the Update() method is called continuously while the game is running. A game is interactive because it shows (renders) a certain number of images (frames) per second. The Update() method is called every frame. So that is where you put most of your logic. Note: there are other, similar methods if you need more control, but we don't need to get into those right now.

In "Unity3D: Moving an Object with Keyboard Input", we saw that we could set an object's position by assigning a new Vector3 to its transform.position:

        this.transform.position = new Vector3(5, 5, 5);

However, what we'd like to do is have the Alien move gradually from one point to another. You can think of it like this:


So we want the Alien's x-position to start at -5, and move gradually to the right until it reaches x=5. With each frame, x is set to a different intermediate value t.

First, we need a variable to store the time when the animation began. Add this variable in the Alien class:

private float startTime;

The float data type is something we haven't covered in the C# articles so far, but it's simply a number that may have a decimal value (e.g. 5.48). When assigning float values, we need to add an 'f' suffix to the value (e.g. 5.48f).

Next, we set this startTime to the current time in the Start() method:

    void Start ()
    {
        this.startTime = Time.time;
    }

Unlike float, which is a standard data type, Unity uses its own classes to represent time. Instead of the DateTime type we're used to from C#, Unity has its own Time class. The equivalent of DateTime.Now is Time.time, and it gives us a float representing the current time.

We can now change the Update() method as follows to carry out the animation:

    void Update ()
    {
        float t = Time.time - startTime;
        float x = Mathf.Lerp(-5.0f, 5.0f, t);
        this.transform.position = new Vector3(x, 5, 5);
    }

Remember that Update() is called every frame. Thus, based on the current time, we calculate how far along the animation we have come, and store it in t. We can then use Lerp() to set x to the correct intermediate value between -5.0f and 5.0f.

Before I mess up your mind with an explanation on Lerp, press F8 to make sure the code compiles, and then press Play from within Unity. Watch the Alien (cube) move gradually from left to right. This will help you understand better what is happening.

Lerp is short for linear interpolation, which might sound like some scary monster. But to understand it, refer again to this diagram:


Let's say t starts at 0, and goes up to 100 as time goes by. It's not the same as the value of x; think of it as a percentage.

Now, let's say that at a particular point in time, t is 0.6 (60%). In order to find the corresponding value of x, we need the value that marks 60% of the way between -5 and 5. That's

t starts at zAs time goes by, t increases. To keep it simple, let's say it increases in whole numbers. So from -5, it becomes -4, -3, and so on until it finally reaches 5. The x value can be calculated from t as follows:


In Unity, you don't really need to worry about the mathematics. Just keep track of the time, and store the elapsed time in a variable (such as t). To use the Lerp() method, just pass the minimum, maximum, and t values as parameters, in that order. The result is the intermediate, or interpolated value you want.

When you press Play in Unity, you'll notice that the Alien (cube) moves across rather quickly. You can regulate this by multiplying a speed value:

        float t = (Time.time - startTime) * 0.4f;

The value (in this case 0.4f) is up to you to choose. Values less than 1 will make the cube move more slowly, and values greater than 1 will make it move faster.

Wonderful! :) In this article, we learned how to use linear interpolation (lerp) to animate the movement of a cube. As a matter of fact, interpolation is used in all sorts of animation, from video game credits scrolling vertically, to an object gradually changing colour when it is hit by a bullet. A lot of this functionality can be used easily using libraries such as iTween, but learning how it actually works gives you much more control.

Come back for the next article in the Ranch Invaders series, in which we will learn about prefabs and use them to shoot bullets! In the meantime, as an exercise, implement the code that allows the alien to move from left to right, down, right to left, down, etc (see this video to get an idea). :)