How to make a Power-Up System in Unity

Power-ups are a critical gameplay component. In this Unity tutorial by @KevnSmall, you’ll learn how to design and build a reusable power-up system. By Kevin Small.

Leave a rating/review
Save for later
Share

How to make a Power-Up System in UnityWhat would Sonic The Hedgehog be without gold rings and power sneakers; Super Mario, without mushrooms; or Pac-Man without power pellets? The games wouldn’t be nearly as much fun!

Power-ups are a critical gameplay component as they add extra layers of complexity and strategy to keep the action moving.

In this tutorial you will learn how to:

  • Design and build a reuseable power-up system.
  • Use message-based communication in your game.
  • Implement these in a top-down “dodge-em-up” game filled with your own power-ups!
Note: This tutorial assumes you are familiar with Unity and have an intermediate knowledge of C#. If you need need some refreshers, you can check out our other Unity tutorials.

You’ll need Unity 2017.1 or later to follow this tutorial, so upgrade your Unity installation if you haven’t already.

Getting Started

The game you’ll work with a 2D top-down arena dodge-em-up; it’s a little like Geometry Wars without the shooting — and without the commercial success. Your helmeted hero has to dodge the enemies to get to the exit; bumping into enemies reduces health. When all health is depleted, it’s game over.

Download the starter project for this tutorial and extract it to a location of your choosing. Open the project in Unity and take a look at the project folders:

  • Audio: Contains the sound effect files for the game.
  • Materials: The materials for the game.
  • Prefabs: Contains the Prefabs for the game, including the play area, player, enemies, particles and power-ups.
  • Scenes: The main game scene is in here.
  • Scripts: Contains the C# scripts for the game, which have been thoroughly commented. Feel free to nose around the scripts if you’d like to become more familiar with them before starting.
  • Textures: The source images used for game and splash screen.

Open up the scene named main and press the Play button.

You’ll see the game has no power-ups yet. As a result, it’s hard to complete and perhaps a little dull. Your task is to add some power-ups and liven things up. When the player collects a power-up, a quote from a famous movie series will appear on screen. See if you can guess the movie series, the answer is at the end of this tutorial!

The Power-Up Lifecycle

A power-up has a lifecycle, which consists of several distinct states:

  • The first stage is creation which can happen during gameplay or at design time when you manually place power-up GameObjects into your scene.
  • Next comes attract mode, when power-ups can animate or otherwise do something to attract attention.
  • The collection stage is the act of picking up the power-up, which can trigger sounds, particle systems or other special effects.
  • The collection leads to the issuance of the payload, which is the power-up “doing its thing”. The payload can be anything you can imagine, from a humble health bonus to giving the player some awesome superpowers. The payload step also sets up the expiry check. The power-up may be set to expire after a set amount of time has elapsed, after the player has been hurt, after a certain number of uses, or after some other gameplay condition has occurred.
  • The expiry check leads to the expiry stage proper. The expiry stage will destroy the power-up and marks the end of the cycle.

The above lifecycle contains elements you’d want to use repeatedly in every game and elements that would only pertain to the current game. For example, detecting if a player has collected a pickup is a feature you would like in every game, but a specific payload of making the player invisible might be something you only want in your current game. This is important to consider when designing the script logic.

Creating a Simple Star Power-Up

You’re likely familiar with “entry-level” power-ups, such as gold coins, stars or rings that provide a simple score or health bonus. You will now create a Star power-up in the scene that will instantly provide a health bonus to the player and give your hero a better chance of making it out alive.

Relying solely on the star is not going to be good enough to escape unscathed, but you’ve still got other power-ups to add later on in this tutorial that will definitely give your hero a competitive edge.

Create a new Sprite, name it PowerUpStar, and position it just above the player at a location of (X:6, Y:-1.3). To keep the scene tidy, make the sprite a child of the empty PowerUps “folder” GameObject in the scene:

Now define the sprite’s appearance. Set the Transform Scale to (X:0.7, Y:0.7), in the Sprite Renderer component, assign the star to the Sprite slot and set Color to a pale brown color (R:211, G:221, B:200).

Add a Box Collider 2D component, tick the Is Trigger checkbox and set the Size to (X:0.2, Y:0.2):

You’ve just created your first power-up! Play the game to see if everything looks good. The power-up appears, but nothing happens when you try to pick it up. To fix this, you will need to do some scripting.

Separating Game Logic with a Class Hierarchy

Being a conscientious developer, you want to make the best use of your time and reuse elements from previous projects. To do this with a power-up system, you need to design it with a class hierarchy. The class hierarchy separates the power-up logic into a reusable engine part and a game-specific part. If you are new to the idea of class hierarchies and inheritance we’ve got a video that explains all of this.

The diagram above shows the class PowerUp as the parent class. It contains the game-independent logic, so you can reuse this mostly as-is in other projects. The tutorial project already contains the parent class. The parent class orchestrates the power-up lifecycle, manages the different states a power-up can have and handles collisions, collection, payloads, messages and expiry.

The parent class implements a simple state machine that tracks the power-up lifecycle. What you must implement is a subclass and some inspector values for each new power-up, and you will be good to go!

Power-Up Coding Checklist

Note: To create a power-up script, you must create a subclass of the PowerUp class and observe the following checklist. This tutorial refers back to this checklist several times, so keep it handy!
  1. Implement PowerUpPayload to issue the payload.
  2. Optionally, implement PowerUpHasExpired to remove the payload from the previous step.
  3. Call PowerUpHasExpired when the power-up has expired. If the power-up expires immediately, tick the ExpiresImmediately checkbox in the inspector as there is no need to call PowerUpHasExpired.

Think about what the Star power-up does: it simply provides a small health bonus. You’ll only need a bit of scripting to make this happen.

Creating Your First Power-Up Script

Add a new script to the PowerUpStar GameObject, name it PowerUpStar and open it up in your editor.

Add the following code, replacing most of the boilerplate Unity starter code while leaving the using statements at the top as they are.

class PowerUpStar : PowerUp
{
    public int healthBonus = 20;

    protected override void PowerUpPayload()  // Checklist item 1
    {
        base.PowerUpPayload();

        // Payload is to give some health bonus
        playerBrain.SetHealthAdjustment(healthBonus);      
    }
}

The code is pretty short, but this is all you need to implement your Star logic! The script addresses each of the checklist points as follows:

  1. PowerUpPayload awards some health by calling playerBrain.SetHealthAdjustment. The PowerUp parent class has already taken care of obtaining the playerBrain reference. The fact that you have a parent class means you will need to call base.PowerUpPayload to ensure all core logic executes before your code.
  2. You don’t need to implement PowerUpHasExpired because the health award is permanent.
  3. This power-up expires immediately, so again, you don’t need to code anything; simply tick the ExpiresImmediately checkbox in the inspector. This is a good time to save the method and switch back to Unity to make some inspector settings.

Creating Your First Power-Up in the Scene

After you save and switch back to Unity, the StarPowerUp GameObject will look like this in the inspector:

Populate the inspector values as follows:

  • Power Up Name: Star
  • Explanation: Recovered some health…
  • Power Up Quote: (I will become more powerful than you can possibly imagine)
  • Expires Immediately: Ticked
  • Special Effect: Drag in the prefab from project folder Prefabs/Power Ups/ParticlesCollected
  • Sound Effect: Drag in the audio clip from project folder Audio/power_up_collect_01
  • Health Bonus: 40

Once you have done this, your power-up will look like this:

Once you’re happy with your PowerUpStar GameObject settings, drag it into the project tree folder Prefabs/Power Ups to create a prefab.

Use your new prefab to add a few Stars to the right of the scene in places you find pleasing.

Run the scene and steer the hero towards the first power-up. Some audio and a glorious particle effect accompany your collection. Awesome!

Message-Based Communication

The next power-up you will create needs some background information. To get that information, you need a way for your GameObjects to communicate with each other.

For example, when you collect a power-up, the UI must know what information to display. When the player’s health changes, the health bar needs to know what the updated health level should be. There are lots of ways you can do this, but the Unity manual lists several mechanisms to accomplish this.

Each communication method has its pros and cons, and one size certainly does not fit all. What you see in this game is message based communication as outlined in the Unity manual.

You can categorize GameObjects as being message broadcasters, message listeners, or both:

On the left in the above diagram are some message broadcasters. You can think of this as the objects “shouting out” when something interesting happens. For example, the player broadcasts the message “I got hurt”. On the right in the above diagram are the message listeners. As the name suggests, these listen for messages. Listeners don’t have to listen to every message; they simply listen for those messages that they might want to react to.

Message broadcasters can also be message listeners. For example, sthe power-up broadcasts messages, but also listens for player messages. A good example of this is a power-up that will expire when a player gets hurt.

You can imagine that if there are many broadcasters and many listeners, there could be many lines criss-crossing between the left and right sides. To make life easier, Unity provides an EventSystem component that sits in between, like this:

Unity uses the extendable EventSystem component to handle input. The component also manages much of the sending and receiving event logic.

Phew! That’s a lot of theory, but the bottom line is that such a messaging system will allow power-ups to easily listen in to the gameplay and reduce hard-wiring between objects. This should make adding new power-ups quite simple, particularly later in development, because most messages will already be broadcast.

Steps to Set Up Message-Based Communication

This is the last bit of theory before you can get back to building. To broadcast a message, you need to take care of these steps:

  • Broadcasters define what message they want to broadcast as a C# Interface.
  • Broadcasters then send messages to listeners stored in a listener list.

See this video on C# Interfaces if you need a refresher.

In short, an interface defines method signatures. Any class that implements the interface promises to provide functionality for these methods.

You will see it more clearly with an example. Look at the code in the IPlayerEvents.cs file:

public interface IPlayerEvents : IEventSystemHandler
{
   void OnPlayerHurt(int newHealth); 
   void OnPlayerReachedExit(GameObject exit); 
}

This C# interface has methods for OnPlayerHurt and OnPlayerReachedExit. These are the messages that the player can send. Now look at the method SendPlayerHurtMessages in the PlayerBrain.cs file. The snippet below contains numbers referenced in the explanation that follows:

   private void SendPlayerHurtMessages()
   {
      // Send message to any listeners
      foreach (GameObject go in EventSystemListeners.main.listeners)  // 1
      {
         ExecuteEvents.Execute<IPlayerEvents>                   // 2
             (go, null,                                               // 3
              (x, y) => x.OnPlayerHurt(playerHitPoints)            // 4
             );
      }
   }

The method above handles the sending of the message OnPlayerHurt. The foreach loop iterates through every listener held in the list EventSystemListeners.main.listeners and calls ExecuteEvents.Execute against each listener, which sends the messages.

Taking each numbered comment in turn:

  1. EventSystemListeners.main.listeners is a list of GameObjects globally visible in the singleton EventSystemListeners object. Any GameObject that wants to listen to any message must be in this list. You can add GameObjects to this list by giving the GameObject a tag of Listener in the inspector, or by calling EventSystemListeners.main.AddListener.
  2. ExecuteEvents.Execute is a method provided by Unity that sends the message to a GameObject. The type in the angle brackets is the interface name that contains the message you want to send.
  3. This specifies the GameObject to send the message to and null for extra event information, following the Unity manual syntax example.
  4. This is a lamba expression. This is an advanced C# topic that is beyond the scope of this tutorial. Basically, a lambda expression lets you pass code as a parameter into a method. In this case, the code contains the message you want to send (OnPlayerHurt) together with the parameters it needs (playerHitPoints).

The project is already set up to broadcast all necessary messages. You might find some of these useful if you want to extend the project and add your own power-ups later. By convention, all interface names begin with the letter I:

  • IPlayerEvents: Used for messages when player gets hurt or reaches exit.
  • IPowerUpEvents: Used for messages when a power-up gets collected or expires.
  • IMainGameEvents: Used for messages when player wins or loses.

The above interfaces have comments throughout if you do choose to explore them. It’s not critical that you understand them for this tutorial, so you can move along if you like.

A Power-Up for Increased Speed

Now that you know about message-based communication, you’re going to put it into practice and listen for a message!

You will create a power-up that gives the player extra speed until they bump into something. The power-up will detect when the player bumps into something by “listening in” to the gameplay. More specifically, the power-up will listen for the player to broadcast the message saying “I am hurt”.

To listen to a message, you need to follow these steps:

  1. Implement the appropriate C# interface to define what the listener GameObject should listen to.
  2. Make sure that the listener GameObjects themselves exist in the list EventSystemListeners.main.listeners.

Create a new Sprite, name it PowerUpSpeed and position it somewhere at the top left corner of the arena above the player. Set its Scale to (X:0.6, Y:0.6). This GameObject will be a listener, so give it a tag of Listener in the inspector.

Add a Box Collider 2D and Size it to (X:0.2, Y:0.2). In the Sprite Renderer, assign the Sprite as fast and color it as you did previously with the Star power-up. Make sure you also enable Is Trigger. When you have finished, the GameObject should look something like this:

Add a new script to this GameObject called PowerUpSpeed, and add the following code to the script:

class PowerUpSpeed : PowerUp 
{
    [Range(1.0f, 4.0f)]
    public float speedMultiplier = 2.0f;

    protected override void PowerUpPayload()          // Checklist item 1
    {
        base.PowerUpPayload();
        playerBrain.SetSpeedBoostOn(speedMultiplier);
    }

    protected override void PowerUpHasExpired()       // Checklist item 2
    {
        playerBrain.SetSpeedBoostOff();
        base.PowerUpHasExpired();
    }
}

Review the coding checklist items. The script addresses each checklist point as follows:

  1. PowerUpPayload. This calls the base method to ensure the parent class code is called, then sets the speed boost on the player. Notice that the parent class defines playerBrain, which contains a reference to the player that collected the power-up.
  2. PowerUpHasExpired. You have to remove the speed boost you gave and then call the base method.
  3. The final checklist item is to call PowerUpHasExpired when the power-up has expired. You’ll handle this in a bit by listening to player messages.

Adjust the class declaration to implement the interface for player messages:

class PowerUpSpeed : PowerUp, IPlayerEvents
Note: If you use Visual Studio, you can hover over the IPlayerEvents term after you type it in and choose the menu option Implement interface explicitly. This will create method stubs for you.

Add or adjust the methods until they look like the following, ensuring they are still part of the PowerUpSpeed class:

    void IPlayerEvents.OnPlayerHurt(int newHealth)
    {
        // You only want to react once collected
        if (powerUpState != PowerUpState.IsCollected)
        {
            return;
        }

        // You expire when player hurt
        PowerUpHasExpired();                 // Checklist item 3
    }

    /// <summary>
    /// You have to implement the whole IPlayerEvents interface, but you don't care about reacting to this message
    /// </summary>
    void IPlayerEvents.OnPlayerReachedExit(GameObject exit)
    {
        
    }

The method IPlayerEvents.OnPlayerHurt is called every time the player receives damage. This is the “listening to the broadcast message” part. In this method, you first check to ensure that the power-up is only going to react after collection. Then the code calls PowerUpHasExpired in the parent class, which will handle the expiry logic.

Save the method and switch back to Unity to make the necessary inspector settings.

Creating the Speed Power-Up in the Scene

The SpeedPowerUp GameObject will now look like this in the inspector:

Fill in the inspector values as follows:

  • Power Up Name: Speed
  • Explanation: Super fast movement until enemy contact
  • Power Up Quote: (Make the Kessel run in less than 12 parsecs)
  • Expires Immediately: unticked
  • Special Effect: ParticlesCollected (same as for Star power-up)
  • Sound Effect power_up_collect_01 (same as for Star power-up)
  • Speed Multiplier: 2

Once you have done this, your power-up will look like this:

Once you’re happy with the Speed power-up settings, drag it into the project tree folder Prefabs/Power Ups to create a prefab. You won’t be using this in the demo project, but it’s a good idea to do this for completeness.

Run the scene and move the hero to collect the Speed power-up, and you’ll gain some extra speed until the next enemy contact.

A Push Power-Up

This next power-up lets the player push objects out of the way by pressing the P key and has a limited number of uses. You should be familiar with the steps to create a power-up by now, so to keep things simple you will only review the interesting parts of the code and then drop in the prefab. This power-up does not need to listen to messages.

In the project hierarchy, find and inspect the prefab called PowerUpPush. Open its script, named PowerUpPush, and review the code.

You will see familiar methods covered already. The interesting activity for the Push power-up happens all in the Update method, as shown below:

    private void Update ()
    {
        if (powerUpState == PowerUpState.IsCollected &&     //1
            numberOfUsesRemaining > 0)                      //2
        {
            if (Input.GetKeyDown ("p"))                     //3
            {
                PushSpecialEffects ();                      //4
                PushPhysics ();                             //5
                numberOfUsesRemaining--;
                if (numberOfUsesRemaining <= 0)
                {
                    PowerUpHasExpired ();                   //7
                }
            }
        }
    }

Here's what's going on in the code above:

  1. The script should only execute for collected power-ups.
  2. The script should only execute if there are uses remaining.
  3. The script should only execute if the player presses 'P'.
  4. The script triggers a pretty particle effect around the player.
  5. The script does the pushing of the enemies away from the player.
  6. The power-up gets used once more.
  7. If the power-up has no more uses remaining, it expires.

The push power-up is fun to use, so you place two or more in the scene as you see fit. Play the scene once more, and mess around with the new push power-up.

Optional Challenge: An Invulnerability Power-Up

This section is optional, but it's a good bit of fun. Armed with the knowledge you have so far, create a power-up that will make the player invulnerable for a certain amount of time.

Some tips and suggestions to complete this challenge:

  1. Sprite: See the sprite in the project folder Textures/shield
  2. Power Up Name: Invulnerable
  3. Power Up Explanation: You are Invulnerable for a time
  4. Power Up Quote: (Great kid, don't get cocky)
  5. Coding: Work through the same coding checklist you used in the Star and Speed power-up sections. You will need a timer that controls the expiry of the power-up. The SetInvulnerability method in PlayerBrain will toggle invulnerability on and off.
  6. Effects: The project contains a particle effect for a nice pulse effect around the player while they are invulnerable. See the prefab in Prefabs/Power Ups/ParticlesInvuln. You can instantiate this as a child of the player while they are invulnerable.

Need the full solution, or want to check your solution against ours? Have a look at the solution below:

[spoiler]
Create a new Sprite, name it PowerUpInvuln and position it at (X:-0.76, Y:1.29). Set its Scale to X:0.7, Y:0.7. This GameObject will not listen to anything but simply expire after a set time, so there's no need to tag it in the inspector.

Add a Box Collider 2D and Size it to X = 0.2, Y = 0.2. In the Sprite Renderer, assign the Sprite as shield and color it as you did previously with all the other power-ups. Make sure you also enable the Is Trigger. When you have finished, the GameObject should look something like this:

Add a new script to this GameObject named PowerUpInvuln and add the following code:

class PowerUpInvuln : PowerUp
{
    public float invulnDurationSeconds = 5f;
    public GameObject invulnParticles;
    private GameObject invulnParticlesInstance;

    protected override void PowerUpPayload ()     // Checklist item 1
    {
        base.PowerUpPayload ();
        playerBrain.SetInvulnerability (true);
        if (invulnParticles != null)
        {
            invulnParticlesInstance = Instantiate (invulnParticles, playerBrain.gameObject.transform.position, playerBrain.gameObject.transform.rotation, transform);
        }
    }

    protected override void PowerUpHasExpired ()     // Checklist item 2
    {
        if (powerUpState == PowerUpState.IsExpiring)
        {
            return;
        }
        playerBrain.SetInvulnerability (false);
        if (invulnParticlesInstance != null)
        {
            Destroy (invulnParticlesInstance);
        }
        base.PowerUpHasExpired ();
    }

    private void Update ()                            // Checklist item 3
    {
        if (powerUpState == PowerUpState.IsCollected)
        {
            invulnDurationSeconds -= Time.deltaTime;
            if (invulnDurationSeconds < 0)
            {
                PowerUpHasExpired ();
            }
        }
    }
}

Once again, review the coding checklist items. The script addresses each checklist point as follows:

  1. PowerUpPayload: This calls the base method to ensure the parent class code gets called, then sets the invulnerability boost on the player. It also adds the pulse particle effect.
  2. PowerUpHasExpired: You have to remove the invulnerability boost you provided and then call the base method.
  3. The final checklist item is to call PowerUpHasExpired when the power-up has expired. In this case, you will use Update() to count down the time passed.

Save your script, return to Unity and run the game. You will now be able to bash your way through the offending enemies without regard for health and safety — until the power-up expires!

[/spoiler]

Hopefully you gave the challenge a try before peeking!

Where To Go From Here?

You can download the completed project for this tutorial here.

If you want to pursue the project further, you could do the following:

  • Add more power-ups. How about a power-up that shoots out rays and kills enemies?
  • Create a factory class to spawn power-ups randomly in the play area at runtime.
  • If you want to explore Unity further, check out the Unity Games by Tutorials book, available from our store.

Are you still wondering what film series the quotes are from? Have a look at the answer below:

[spoiler]Woohoo! You are (possibly) the only person on the internet to have clicked here! The answer is that the quotes are from the Star Wars film series.
[/spoiler]

I hope you have enjoyed the tutorial! If you have any questions or comments, please join the discussion below.