2D Galaxy Shooter: Creating Modular Power-up Systems

Gabriel Perez
4 min readApr 10, 2021

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.

With the “Shield power-up collectible” game object selected, I can now assign the proper type.

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!

--

--

Gabriel Perez

Hello everyone, My name is Gabriel Perez, I am a Unity Developer and a creator who is always learning and experimenting.