Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / game / Unity

Day 85 of 100 Days of VR: Endless Flyer – Creating a Magnet Power-Up

0.00/5 (No votes)
6 Feb 2019CPOL12 min read 2K  
How to create a magnet power up

Are you ready for another exciting installment of adding power-ups? Today, we’re going to start implementing the first power-up! The coin magnet!

With the magnet power-up, whenever we get close to a coin, the coin will start flying towards us.

There are quite a bit of things that we need to set up to even have a power-up system, so this will be a 2-part post on adding the Magnet power-up.

Here’s our action plan for today:

  1. Find an asset that we’re going to use for our power-up and make configure it.
  2. Add the magnet into our spawn point script.
  3. Create the logic to store information about the power-up in our manager.

Let’s get started!

Step 1: Find the Magnet Asset

Step 1.1: Find a Magnet Asset

The first place I thought of going to when looking for a magnet asset was the Unity Asset Store. Unfortunately, with Unity deprecating older assets, I just don’t see the same number of assets that I used to see anymore.

Instead, I’m going to try and find some free assets elsewhere.

I found a site called free3d.com and found a great magnet asset that we can use.

  1. Download the asset

Step 1.2: Add the Asset Into Unity

The file will come in a .zip file.

Unzip and then look for iman.fbx, .fbx is a file format that Unity understands for our game object.

  1. Drag and drop the coin asset into our Unity project.

With this, we now have the asset in our game to use!

Step 1.3: Configure the Asset

Now that we have the asset in the game, it’s time to tweak some of the settings so that we can use it later.

  1. Drag the iman.fbx into the Unity scene

Here’s what it looks like:

Currently, it has some extra things that we don’t really need, so let’s make some changes.

  1. There’s a Lamp child game object. We don’t need that, get rid of it.
  2. We want the Magnet (which is called Cube) to be positioned upwards. Change the rotation of Cube to be (180, 90, 90). Make sure that we’re changing Cube and not the container
  3. Finally let’s just change the position of Cube to be at (0, 0, 0).
  4. Let’s rename iman and call it
  5. Add a Magnet tag so we can identify it later.
  6. While we’re at it, we’re going to collide into this, let’s add a Box Collider to Magnet. I’ve set Center to be (1, -0.65, 0) and Size to be (3.5, 3.8, 1).
  7. Drag the game object into our Prefabs folder to make a prefab of it.

It’s important that we change Cube and not the container Magnet because when we create our magnet game object, we will automatically overwrite whatever settings are on the container game object, however that’s not the case for any child elements. By doing this now, we don’t need to adjust the position or rotation of the prefab when we try to instantiate it.

With that, we should have this now:

Step 2: Add the Magnet to Our Scripts

Step 2.1: Add the Magnet to our ItemLoaderManager

Now we have the game object, it’s time to add it to our script!

If we recall, we have another manager class that oversees the items that we’ll use: the ItemLoaderManager!

We’re going to make a quick change to this script:

C#
using UnityEngine;

public class ItemLoaderManager : MonoBehaviour {
	public static ItemLoaderManager Instance;
	public GameObject Coin;
    public GameObject[] PowerUps;

    void Start ()
	{
	    if (Instance != null)
	    {
	        // If Instance already exists, we should get rid of this game object
	        // and use the original game object that set Instance   
	        Destroy(gameObject);
	        return;
	    }

	    // If Instance doesn't exist, we initialize the Player Manager
	    Init();
	}
	
    private void Init()
    {
        Instance = this;
    }
}

I’m not going to dive too deep into this, but all we added was a new array to store our Power-Ups.

Setting Up the Script

The Manager game object holds our ItemLoaderManager, let’s add our new Magnet power-up to the script.

  1. Go to ItemLoaderManager, in the Power-Ups slot change the size to be 1 and add our Magnet prefab into that slot.

When we’re done, we should have this.

Step 2.2: Use our Magnet in our PathItemGenerator Script

Now that we have a way to access our Power-ups, we need to use it in our PathItemGenerator script to make it appear.

Here’s what it looks like:

C#
using UnityEngine;

public class PathItemGenerator : MonoBehaviour {
    public float PowerupSpawnRate = 0.2f; // from 0 to 1

	private string containerString = "Container";
	private string spawnPointString = "Spawn Points";      // string to find 
                                                           // our Spawn Points container
    private string powerupSpawnPointString = "Powerup Spawn Points"; // string to find 
                                                   // our powerup spawn points container
	private int numberOfCoinsToGenerate = 5;
	private int coinDistanceGap = 20;

	void Start () {
        SpawnCoin();
        SpawnPowerUp();
	}

    private void SpawnCoin()
    {
        Transform spawnPoint = PickSpawnPoint(containerString, spawnPointString);
        // We then create a loop of X items that are Y units apart from each other
        for (int i = 0; i < numberOfCoinsToGenerate; i++)
        {
            Vector3 newPosition = spawnPoint.transform.position;
            newPosition.z += i * coinDistanceGap;
            Instantiate(ItemLoaderManager.Instance.Coin, 
                        newPosition, Quaternion.identity);
        }
    }

    private void SpawnPowerUp()
    {
        // We randomly generate a number and divide it by 100. 
        // If it is lower than the spawn rate chance we set,
        // then we create the powerup.
        bool generatePowerUp = Random.Range(0, 100) / 100f < PowerupSpawnRate;
        if (generatePowerUp)
        {
            // Get our spawn point and its position.
            Transform spawnPoint = 
                      PickSpawnPoint(containerString, powerupSpawnPointString);
            Vector3 newPosition = spawnPoint.transform.position;

            // Get our Power-ups and randomly pick one of them to show
            GameObject[] powerUps = ItemLoaderManager.Instance.PowerUps;
            int powerUpIndex = Random.Range(0, powerUps.Length);
            Instantiate(powerUps[powerUpIndex], newPosition, Quaternion.identity);
        }
    }

    private Transform PickSpawnPoint
            (string spawnPointContainerString, string spawnPointString) 
    {
        // We get container game object and then  the spawnPointContainer 
        // and get its children which are all spawned points to create a spawn point. 
        // The benefit of this is so that we don't have to manually attach any 
        // game objects to the script, however, we're more likely to have our code break
        // if we were to rename or restructure the spawn points
        Transform container = transform.Find(spawnPointContainerString);
        Transform spawnPointContainer = container.Find(spawnPointString);

        // Initially, I first used GetComponentsInChildren, 
        // however it turns out that the function is
        // poorly named and for some reason that also includes the parent component, 
        // i.e., the spawnPointContainer. 
        Transform[] spawnPoints = new Transform[spawnPointContainer.childCount];

        for (int i = 0; i < spawnPointContainer.childCount; i++)
        {
            spawnPoints[i] = spawnPointContainer.GetChild(i);
        }

        // If we don't have any spawn points the rest of our code will crash, 
        // let's just leave a message
        // and quietly return
        if (spawnPoints.Length == 0)
        {
            Debug.Log("We have a path has no spawn points!");
        }

        // We randomly pick one of our spawn points to use
        int index = Random.Range(0, spawnPoints.Length);
        return spawnPoints[index];
    }
}

Walking Through the Code

Here, we changed our PathItemGenerator to finally use our magnet power-up.

Here’s what we did:

  1. In SpawnPowerUp(), I changed it so that now we grab our list of power-ups from the ItemLoaderManager, and then we randomly pick an index from our list for the power-up we want to display. In this case, we only have the magnet, so that’s all that will appear, however, the benefit of structuring our code this way is that now we can add new power-ups in our ItemLoaderManager and we don’t need to make any code changes anywhere!

Step 3: Adding a Power-Up System

Now that we got the miscellaneous work to get our magnet power-up to show up in the game, it’s time to get the groundwork for being able to support a power-up system in our existing script.

I’ve done some thinking ahead on this and there are 2 approaches that we can try and deal with this problem.

  1. We can create a script that we attach to our plane that will handle the interactions for each specific power-up.
  2. We create a manager script that will keep track of the buffs and then we use this knowledge in other scripts to implement our power-up specific behavior.

In the end, I opted to go with option 2. Specifically, it’s because I started thinking about how we would handle collision when we have different power-ups.

We already have a base behavior for when we collide against a game object, however, if we were to add another script, let’s say the invincible power-up script, we would have 2 scripts that deal with collision.

Instead, I decided to use the same collision script and check to see if one of our manager class says if we have the invincible power-up.

The tradeoff however is that instead of having our power-up specific behavior in one consolidated script, it’s spread all around the other scripts, but I suspect even if we went with the first option, we still must do some special checks to ignore specific collision behaviors, so Option 2 might not be that bad.

Step 3.1: Creating a Class That Will Represent Our Power-Up

The first thing we need to do is create a class that will represent a Power-up.

For now, the power-ups don’t have any special behaviors we just need to know how long it’ll last, so let’s create a basic object that keeps track of the time.

  1. In the Unity Project, right click and select Create Script, call the new Script PowerUp

This script will just be a normal C# class and not a script that extends MonoBehavior. What this means is that we don’t need to use any of the Unity code that it brings like Start() and Update().

Instead, PowerUp will just be a pure data like float or int that we use to store information.

Here’s what it looks like:

C#
public class PowerUp {
    // getter and setter for changing the value of the Power Up's duration.
    public float Duration
    {
        get; set;
    }

    // Constructor that takes in the duration of the power up.
    public PowerUp(float duration)
    {
        Duration = duration;
    }
}

Looking at the Fields

We only have a public getter and setter for a float called Duration, this will be something we update over time to see if we should get rid of the power-up effect.

Walking Through the Code

If Unity is your first attempt at coding and you’ve never formally learned how to code, this will be completely foreign to you.

I won’t dive too much into the details, but what we created here is a PowerUp object. For every object, we need to create a constructor which is PowerUp(), that will be used to create an instance of the object for us to use in code.

The name of the constructor has to be the same name as the name of our class and the variable it takes in are the variables that we pass in when we create the class.

In our case, the PowerUp() constructor only takes in the duration of the power-up and then saves it to the instance of the object.

In an analogy to what we know with Unity, we can have multiple zombie game objects, while each of the zombie game objects is technically the same, they have their own differences, for example, they are all located at different positions in the game.

Step 3.2: Using the New PowerUp Object in PlayerManager

Now that we have some way to represent our PowerUp, it’s time to use in PlayerManager.

What I’m going to do is store the PowerUp in a Dictionary, which is another object like a list that stores data. A Dictionary stores our objects in a key-value pair.

For example, I can have a dictionary that stores the PowerUps with a key that is made up of strings (or enum). If I store the Magnet PowerUp object with the string “Magnet” in the dictionary, I can retrieve the Magnet PowerUp object by giving the dictionary the “Magnet” string to the dictionary, I’ll get the Magnet PowerUp object back.

Anyways here’s our new PlayerManager code:

C#
using UnityEngine;
using System.Collections.Generic;

public class PlayerManager : MonoBehaviour
{
    public static PlayerManager Instance;
    public enum PlayerState { Alive, Dead }

    public enum PowerUpType { Magnet }
    private Dictionary<PowerUpType, PowerUp> powerUpDictionary;
    private float powerUpDuration = 10f;
    private List<PowerUpType> itemsToRemove;

    public PlayerState CurrentState
    {
        // I chose to be explicit, but we could also have done:
        // get; private set;
        // read more: https://stackoverflow.com/questions/3847832/understanding-private-setters
        get { return _currentState; }
        private set { _currentState = value; }
    }

    private PlayerState _currentState;

    void Start ()
	{
	    if (Instance != null)
	    {
	        // If Instance already exists, we should get rid of this game object
	        // and use the original game object that set Instance   
	        Destroy(gameObject);
	        return;
	    }

	    // If Instance doesn't exist, we initialize the Player Manager
	    Init();
	}

    void Update()
    {
        foreach (KeyValuePair<PowerUpType, PowerUp> entry in powerUpDictionary)
        {
            entry.Value.Duration -= Time.deltaTime;

            // We can't remove an item from a dictionary if we're iterating through it.
            // Instead we have to keep track of it in a list and 
            // then remove the items from the
            // dictionary later.
            if (entry.Value.Duration <= 0)
            {
                itemsToRemove.Add(entry.Key);
            }
        }

        // Go through all of the power-ups that need to be removed and remove it
        // from the dictionary.
        for each (PowerUpType powerUpType in itemsToRemove)
        {
            powerUpDictionary.Remove(powerUpType);
        }

        // We've removed everything, let's clear our list.
        itemsToRemove.Clear();
    }

    private void Init()
    {
        Instance = this;
        CurrentState = PlayerState.Alive;

        // Create an empty dictionary and list, otherwise they'll be null later when we
        // try to access them and crash.
        powerUpDictionary = new Dictionary<PowerUpType, PowerUp>();
        itemsToRemove = new List<PowerUpType>();
    }

    /// <summary>
    /// Sets the PlayerManager to be in the game over state.
    /// </summary>

    public void GameOver()
    {
        CurrentState = PlayerState.Dead;
    }

    public void AddPowerUp(PowerUpType powerUpType)
    {
        // An interesting part of this is that if we get another power up that if we
        // get a duplicate power up, we will replace it with the new one.
        powerUpDictionary[powerUpType] = new PowerUp(powerUpDuration);
    }

    /// <summary>
    /// See if the player currently have the power up that we pass in.
    /// </summary>

    public bool ContainsPowerUp(PowerUpType powerUpType)
    {
        return powerUpDictionary.ContainsKey(powerUpType);
    }
}

Looking at the Fields

  • public enum PowerUpType – This is the enum that we use to represent the different power-up types that are available in the game.
  • private Dictionary<PowerUpType, PowerUp> powerUpDictionary – This is a dictionary that stores our PowerUp data that are paired up with what PowerUpType it is.
  • private float powerUpDuration – A fixed time for how long each of the power-ups should last.
  • private List<PowerUpType> itemsToRemove – This is a list that we use later to help us remove power-ups from our dictionary.

Walking Through the Code

This might be one of the more involved code that we have written in a while!

  1. In Init(), we create an instance of our Dictionary and List. If we don’t do that, when we try to access them later, we’ll crash with a null reference exception.
  2. In AddPowerUp(), we receive the type of power-up that we wish to add and store it in our dictionary with a new instance of a PowerUp The cool thing about this is that if we happen to get the same power-up twice, we’ll replace the old one with the new one, refreshing our power-up duration!
  3. In Update(), we iterate through all of key-value pairs in our dictionary. We decrement the duration of each power-up by the time that has passed and if any of them reaches 0, we want to remove them. However, because we are currently iterating through the dictionary, we can’t remove them. Instead, we must save the key (the PowerUpType) to a list and then iterate through the list to remove the entries in our dictionary.
  4. In ContainsPowerUp(), we receive a PowerUpType that we want to see if it is in our dictionary. We simply see if the dictionary contains the PowerUpType that was passed in and return if we found it or not.

End Day 85

Phew, it’s been a while since I’ve written as much as I have today!

I hope you came out with a better knowledge of one way we might be able to implement a power-up system!

Today we:

  • Found a magnet asset to use.
  • Add the magnet asset to our paths.
  • Created a system that helps keep track of our power-up that we can use later.

At this point, we still actually haven’t implemented the magnet effect or wrote the code that gives us the magnet effect, but that will be coming up next7. Stay tuned for the next post!

On a different note, we can take advantage of being able to get practice with Object Orientated Programming (OOP)!

Step 3.1: Creating a Power Up Base Script

If we think in the long term, we’re going to have multiple power-ups and while they all do different things, they do share some common behaviors.

Specifically, they will all disappear after some time. We could have this logic all used in all the power-up script, but let’s write shared code to accomplish this!

This is where OOP comes in handy. I found this nifty medium post How to explain object-oriented programming concepts to a 6-year-old that explains a basic concept of what OOP is about.

For us, we’re going to write these Power-Up scripts (magnet, invincible, increase coin, etc.), they’re all “power-ups” this just screams that they might have some common behaviors and we should make a base class that contains any shared logic we might want to use.

This might make more sense as we code this, but let’s say we have 2 Power-ups, MagnetPowerUp and InvinciblePowerUp.

They both have their own special logic that will do something like make the player not die when they run into something or make coins come closer to you.

However, there is also some shared functionality in the code, like, after 10 seconds, the power-up will go away. We could add that logic to both of our scripts, but then it would be the same and if we ever decide to do something differently, then we have to go into every single one of our <Insert>PowerUp script. It’s not the end of the world if we have maybe 2-3 power-ups but if we have 10+ that can easily become a nightmare to manage our code.

The magic of OOP is that we can create a base class like PowerUp that does these shared behaviors and then our MagnetPowerUp or InvinciblePowerUp can inherit and use the shared code all while being able to implement their own specific logic.

We do that in all of the scripts that we write, do you remember seeing:

C#
public class PlayerManager : MonoBehaviour

All our script inherits the MonoBehaviour script. When we create Start() and Update(), we actually replace the existing functions that MonoBehaviour implements with our own logic. Somewhere in that script, they still call Start() and Update(), but instead of using their own logic, they use the ones we give it.

How else do you think our code works? It’s, actually, not magic! Who knew!

Day 84 | 100 Days of VR | Day 86

Home

The post Day 85 of 100 Days of VR: Endless Flyer – Creating a Magnet Power-Up appeared first on Coding Chronicles.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)