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
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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!

Kevin Small

Contributors

Kevin Small

Author

Gijs Bannenberg

Tech Editor

Chris Belanger

Editor

Eric Van de Kerckhove

Final Pass Editor and Team Lead

Over 300 content creators. Join our team.