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:
- Find an asset that we’re going to use for our power-up and make configure it.
- Add the magnet into our spawn point script.
- 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.
- 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.
- 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.
- 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.
- There’s a
Lamp
child game object. We don’t need that, get rid of it. - 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 - Finally let’s just change the position of Cube to be at (0, 0, 0).
- Let’s rename
iman
and call it - Add a
Magnet
tag so we can identify it later. - 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). - 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:
using UnityEngine;
public class ItemLoaderManager : MonoBehaviour {
public static ItemLoaderManager Instance;
public GameObject Coin;
public GameObject[] PowerUps;
void Start ()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
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.
- 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:
using UnityEngine;
public class PathItemGenerator : MonoBehaviour {
public float PowerupSpawnRate = 0.2f;
private string containerString = "Container";
private string spawnPointString = "Spawn Points";
private string powerupSpawnPointString = "Powerup Spawn Points";
private int numberOfCoinsToGenerate = 5;
private int coinDistanceGap = 20;
void Start () {
SpawnCoin();
SpawnPowerUp();
}
private void SpawnCoin()
{
Transform spawnPoint = PickSpawnPoint(containerString, spawnPointString);
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()
{
bool generatePowerUp = Random.Range(0, 100) / 100f < PowerupSpawnRate;
if (generatePowerUp)
{
Transform spawnPoint =
PickSpawnPoint(containerString, powerupSpawnPointString);
Vector3 newPosition = spawnPoint.transform.position;
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)
{
Transform container = transform.Find(spawnPointContainerString);
Transform spawnPointContainer = container.Find(spawnPointString);
Transform[] spawnPoints = new Transform[spawnPointContainer.childCount];
for (int i = 0; i < spawnPointContainer.childCount; i++)
{
spawnPoints[i] = spawnPointContainer.GetChild(i);
}
if (spawnPoints.Length == 0)
{
Debug.Log("We have a path has no spawn points!");
}
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:
- 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.
- We can create a script that we attach to our plane that will handle the interactions for each specific power-up.
- 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.
- 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:
public class PowerUp {
public float Duration
{
get; set;
}
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 PowerUp
s with a key that is made up of string
s (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:
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
{
get { return _currentState; }
private set { _currentState = value; }
}
private PlayerState _currentState;
void Start ()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Init();
}
void Update()
{
foreach (KeyValuePair<PowerUpType, PowerUp> entry in powerUpDictionary)
{
entry.Value.Duration -= Time.deltaTime;
if (entry.Value.Duration <= 0)
{
itemsToRemove.Add(entry.Key);
}
}
for each (PowerUpType powerUpType in itemsToRemove)
{
powerUpDictionary.Remove(powerUpType);
}
itemsToRemove.Clear();
}
private void Init()
{
Instance = this;
CurrentState = PlayerState.Alive;
powerUpDictionary = new Dictionary<PowerUpType, PowerUp>();
itemsToRemove = new List<PowerUpType>();
}
public void GameOver()
{
CurrentState = PlayerState.Dead;
}
public void AddPowerUp(PowerUpType powerUpType)
{
powerUpDictionary[powerUpType] = new PowerUp(powerUpDuration);
}
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!
- 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. - 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! - 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
. - 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:
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
CodeProject
The post Day 85 of 100 Days of VR: Endless Flyer – Creating a Magnet Power-Up appeared first on Coding Chronicles.