2D Galaxy Shooter: Creating Modular Power-up Systems
With a power-up list growing, I have to refactor my code to be modular and cleaner. We can sometimes implement a feature and not realize that this particular feature needs to be modularized.
Objective
The objective is to modularize and refactor the function that controls the spawn of the power-ups.
Modularize
For a modular power-up system, we need an ID system so that each power-up can be identified. You can either use a variable of int datatype or in my case, an enum. Enums provide a string to identify an object, but also assign a value behind the name.
public enum PowerupType
{
None, // 0
TripleShot, // 1
Speed, // 2
Shield // 3
}
I need to use the enum in the Powerup script so we can assign a type to the power-up collectible. So we can initialize a variable of type PowerupType
and declare it with a PowerupType.None
type for default.
[SerializeField] private PowerupType _powerupType = PowerupType.None;
This will allow us to select the type of power-up in the inspector.
We can now use a switch
statement to check through the power-ups for individual functionality.
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag(“Player”))
{
var player = other.transform.GetComponent<Player>(); if(player != null)
{
switch (_powerupType)
{
case PowerupType.TripleShot:
player.TripleShotActive();
break;
case PowerupType.Speed:
player.SpeedBoostActive();
break;
case PowerupType.Shield:
player.ShieldActive();
break;
default:
_powerupType = PowerupType.None;
break;
}
} Destroy(gameObject);
}
}
When the player collides with a type of power-up, it will go through the switch, find the proper type, and activate it by calling a function from the player script.
Refactoring
Here’s the function that needs to be refactored:
private IEnumerator SpawnPowerUpRoutine()
{
while (!_stopSpawning)
{
float randomX = Random.Range(-11, 11);
Vector3 randonXposition = new Vector3(randomX, 8, 0); Instantiate(_tripleShotPrefab, randonXposition, Quaternion.identity); float randomTime = Random.Range(6, 20);
yield return new WaitForSeconds(randomTime);
}
}
As you can see, in bold, I am only instantiating a single power-up.
I need to change the _tripleShotPrefab
into an array of GameObjects
.
An array is a fixed list that can’t change in size while running the game. It must have a length when defining it and cannot be altered. It is part of the C# collection family, alongside a List, Dictionary, Queue, and Stack.
I changed the _tripleShotPrefab
variable to this:
[SerializeField] private GameObject[] _powerupPrefabs = null;
It now takes a collection of GameObject
’s, and we as programmers or designers can adjust the number of game objects it can store in the inspector.
I must now change how we instantiate the power-ups.
In the SpawnPowerUpRoutine() function, I need to change the _tripleShotPrefab
variable to _powerupPrefabs[]
. The compiler will give us an error.
The _powerupPrefabs[]
needs a value in the subscript (the brackets). Remember, I want to have our power-up system modular. So let’s create a local int datatype that can randomize the length of our _powerupPrefabs
array.
int randomPowerup = Random.Range(0, _powerupPrefabs.Length);
The randomPowerup
variable will hold a random number from the number of power-up’s we store in the _powerupPrefabs
array. It will allow all powerups to instantiate randomly.
private IEnumerator SpawnPowerUpRoutine()
{
while (!_stopSpawning)
{
float randomX = Random.Range(-11, 11);
Vector3 randonXposition = new Vector3(randomX, 8, 0); int randomPowerup = Random.Range(0, _powerupPrefabs.Length);
Instantiate(_powerupPrefabs[], randonXposition, Quaternion.identity);float randomTime = Random.Range(6, 20);
yield return new WaitForSeconds(randomTime);
}
}
I need to pass in the randomPowerup
into the subscript of the _powerupPrefabs
within the Instantiate()
function.
private IEnumerator SpawnPowerUpRoutine()
{
while (!_stopSpawning)
{
float randomX = Random.Range(-11, 11);
Vector3 randonXposition = new Vector3(randomX, 8, 0); int randomPowerup = Random.Range(0, _powerupPrefabs.Length);
Instantiate(_powerupPrefabs[randomPowerup], randonXposition, Quaternion.identity);float randomTime = Random.Range(6, 20);
yield return new WaitForSeconds(randomTime);
}
}
With the refactoring done, I can go back to Unity, click on the Spawn Manager game object where the SpawnManager script is attached, and assign our prefabs into the _powerupPrefabs
array in the inspector.
Drag and drop the power-ups into the array, and done!
We now have a modular power-up system!
Conclusion
With the use of an array, I modularize a system that could have been messy with if and else statements. I am amazed at how efficient and cleaner it is by this small change.
The next power-up to implement will be the shields!
That is all for today. Thank you for reading!