GAME DEVELOPMENT WITH UNITY

Unity: Prefab Instantiation, making it move and setting speed

Of course, we can put instantiation to much better uses. Let's try firing a bullet from a player who can move. First of all, let's get our little pal into the game.

Complicated Prefab Instantiation


Next up, we'll give him something to shoot. How about a tiny little fireball?

Complicated Prefab Instantiation

Complicated Prefab Instantiation


Wonderful. Now, let's make him move using the arrow keys. We already know how to do that, so we'll skip over that part. Now, let's create a prefab out of that fireball. Once again, drag and drop the fireball into the Hierarchy to make it an active gameObject, then drag it back into the Assets to generate a prefab out of it.

NOTE: A great way to test, if a prefab exists, is to simply look at the name of the gameObject in the Hierarchy. Game Objects which have a prefab existing for them will have their name written in blue.

Now, we will go ahead and create a new script named Shooter.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Shooter : MonoBehaviour
{
    public GameObject fireball;
    public RigidBody2D body;
    public float speed;
    
    void Update() 
    {
        body.velocity = new Vector2(Input.GetAxisRaw("Horizontal")*speed, Input.GetAxisRaw("Vertical")*speed);
        
        // When spacebar is hit
        if(Input.GetKeyDown(KeyCode.Space))
        {
            // instantiate the fireball object
            Instantiate(fireball);
        }
    }

}

This is simply a combination of the code we have written for movement and instantiation so far. Nothing too complicated. If you save this script and attach it to your main character, you can now move him around and if you press the Spacebar, the fireballs will appear and fall down.

More Complicated Prefab Instantiation


Setting Position for new Prefabs

The bullets are not really doing anything, just falling (you can make them stop falling by setting value of gravity property as 0, like we did before) and they always spawn in the middle of the screen, not from our lead character's hand (palm). Can you guess why this is happening?

That's because we are simply telling the game to generate a fireball clone whenever you press the spacebar, that's it. It doesn't know where to generate it, so the game simply generates it at the coordinates (0, 0), that is, the dead center of the game world.

How do we fix that? Well, let's explore the overloads (other variations of a method) of the Instantiate method, we might get some help there.

Setting Position for new Prefabs


Above is the default overload (variation) of Instantiate method that we have been using up until now. Let's scroll through the overloads to see which one we might find useful.

More Complicated Instantiation


The above overload (variation) method takes in an instance of Object (gameObject) as an argument to generate and an instance of Vector3 as an argument to define where to generate it! Perfect. Don't worry about the Quaternion argument value, we will simply set it to 0.

Now let's deviate from our game for a bit, and talk about the gameObject in general. Any normal gameObject has a position property, which is stored as a Vector2 for 2D elements (or a Vector3 for 3D elements). This property of position can be accessed by the following:

// For X
gameObject.transform.position.x
// For Y
gameObject.transform.position.y

Both of these values are stored as float. If we can read these values for our main character (Shooting Savi), we can figure out the exact position of our character when the spacebar is pressed, to fire the fireball, and then we spawn a new fireball there. We need to capture the x, y coordinates of the main character everytime, because our character can be moved using arrow keys.

So, let's use the overload method instead of the default Instantiate() method. Replace the old Instantiate() method with this one in the character's script and save it:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Shooter : MonoBehaviour
{
    public GameObject fireball;
    public RigidBody2D body;
    public float speed;
    
    void Update() 
    {
        body.velocity = new Vector2(Input.GetAxisRaw("Horizontal")*speed, Input.GetAxisRaw("Vertical")*speed);
        
        // When spacebar is hit
        if(Input.GetKeyDown(KeyCode.Space))
        {
            // instantiate the fireball object
            Instantiate(fireball,
                new Vector3(gameObject.transform.position.x, gameObject.transform.position.y, 0),
                new Quaternion(0, 0, 0, 0));
        }
    }

}

So what exactly is going on here? Well, our new overloaded method takes in three parameters:

  1. GameObject: The gameObject to generate.
  2. Vector3: Where to generate the gameObject in 3D space.
  3. Quaternion: Data related to the rotation of the gameObject in 3D space.

Now, since we are working in a 2D environment, we simply consider any third-dimension values, that is, the Z value to be 0. You will note that we used gameObject.transform.position in the code. The term gameObject simply refers to whatever gameObject the script is currently attached to. That means in our case, gameObject refers to our main character which is Shooting Savi.

  • Line 20: We create a new Vector3 which is composed of three elements. These three elements are the X, Y and Z coordinates of the position where the fireball is generated.

What we are doing here is basically telling the game to generate a new fireball sprite at the exact location where the player was when they pressed the Spacebar. We set the Quaternion values (which takes 4 parameters, an x, y, z and an additional w value) to zero, since they are really complex. In fact, even the official Unity manual says you shouldn't really deal with them unless you know what you're doing.

More Complicated Instantiation


Why do we even create a Quarter-whatever (Quaternion) in the first place? You must be thinking. Well, have a look at the input parameters for the overloaded method we are using one more time. If you run the game now, you will now see that the fireballs are spawned wherever the character is, at the time when you press the Spacebar.

More Complicated Instantiation


Moving the new Prefabs as they are created

Well, we have gotten further ahead, but the fireballs are still stationary. It's like we are just making fireflies everywhere. So how do we make the fireballs move on their own? For that purpose, let's dive back into scripting by creating a new script called Fireball or FireballBehaviour (Remember, no spaces in the name).

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FireballBehaviour : MonoBehaviour
{
    private float fireballXValue;
    public float fireballSpeed;
    
    void Start()
    {
        // getting the initial position where prefab is created
        fireballXValue = gameObject.transform.position.x;
    }
    
    // Update is called once per frame
    void Update() 
    {
        // adding speed value to the X axis position
        // value
        fireballXValue += fireballSpeed;
        // setting new X value to position
        gameObject.transform.position = new Vector2(fireballXValue, gameObject.transform.position.y);
        }
    }

}

See what we're doing here? We used a private floating variable (we don't really want other scripts to see this x value) named fireballXvalue to store the X value of the fireball's position.

When we initialize a new fireball, that new fireball runs its start() function (Remember, that the start() function is run once at the very beginning when a gameObject becomes active and enabled). What we're doing is setting the value of fireballXvalue to the X value of the object's position at the moment it's initialized. This will make sure the fireball starts moving from the point we fire it, not from the dead center.

Next, have a look at the update() method. The first line simply adds the speed variable to the fireballXvalue every frame. The next line is important. If you read it over, you might be wondering why we can't modify the position.x and position.y values directly, and are using variables instead. Well, if we try to modify the values of the position x and y directly, we will get a compilation error.

More Complicated Instantiation

NOTE: We tried to directly change the x value of the position in line 22.


Instead, we set the position to a new Vector2 position in space every frame. The values changes every frame, making the bullets move in game time. So here's what the script is doing with every bullet (fireball) that's instantiated:

  1. Sets the value of a completely new variable named fireballXvalue to the initial position of the bullet.
  2. Adds the value of speed to the fireballXvalue every frame.
  3. Sets the position of the bullet every frame to fireballXvalue and the same Y value as it was (We don't want the fireball to move vertically, but only horizontally).

Save this script, and let's head back to Unity. Now, since we want every new fireball to have that script, why not simply add it to the template? To do so, just click on the fireball's prefab. You will notice that you can change the components and properties just like a normal, active gameObject. The changes you make here will then apply to every new gameObject generated from that prefab, meaning if you attach the script to the fireball prefab, every generated fireball will have that script, exactly as it is in the prefab.

So, without wasting any more time, attach the FireballBehaviour script to the fireball prefab, and set the value of the speed (We would suggest something around 0.4 if you want to see the bullets actually move and not have them fly off the screen). Yay! If we shoot with the Spacebar, our fireballs will constantly move towards the right.

See how we can use instantiation to our advantage? It's perfect for creating gameObjects that you don't need during the scene-making, but rather when you're actually in-game. Some people, being quite insane, can generate entire levels using a series of for loops and calls to the Instantiate() method. Of course, that's something you should try on your own if you're curious.