Skip to main content
The powerup system provides an extensible architecture for creating collectible items that modify player abilities. Built on Unity’s ScriptableObject pattern, it allows designers to create new powerup types without writing code.

Architecture Overview

The system consists of three components:
  • PowerUp - Abstract base class defining the powerup interface
  • JumpBoost - Concrete implementation that increases jump height
  • PowerUpPickUp - GameObject component that applies powerups on collision
ScriptableObjects are asset files that store data independently of scenes. This pattern allows you to create multiple powerup variants (e.g., “Small Jump Boost”, “Large Jump Boost”) as reusable assets.

PowerUp Base Class

The abstract base class that all powerup types must inherit from.

Implementation

Powerup.cs:3-6
public abstract class PowerUp : ScriptableObject
{
    public abstract void Apply(GameObject target);
}

Design Pattern

This uses the Template Method pattern:
  • PowerUp defines the interface (the Apply method)
  • Concrete implementations (like JumpBoost) provide specific behavior
  • PowerUpPickUp can work with any PowerUp subclass polymorphically
By inheriting from ScriptableObject instead of MonoBehaviour, powerup definitions exist as assets, not scene objects. This allows reusability across scenes and prefabs.

JumpBoost Implementation

A concrete powerup that increases the player’s jump height.

Configuration

JumpMultiplier
float
default:"1.5"
Multiplier applied to the player’s jump height (1.5 = 50% increase)

CreateAssetMenu Attribute

JumpBoost.cs:3
[CreateAssetMenu(menuName = "Power Ups/Jump Boost")]
This attribute adds the powerup to Unity’s Create Asset menu:
  1. Right-click in Project window
  2. Navigate to Create → Power Ups → Jump Boost
  3. A new JumpBoost asset is created

Apply Implementation

JumpBoost.cs:8-14
public override void Apply(GameObject target)
{
    if (target.TryGetComponent<PlayerJump>(out PlayerJump jumpScript))
    {
        jumpScript.JumpHeight *= JumpMultiplier;
    }
}

How It Works

  1. Component Check - Uses TryGetComponent to safely check if the target has a PlayerJump component
  2. Multiply Jump Height - Increases the JumpHeight by the multiplier
  3. Permanent Effect - The jump height remains increased for the rest of the game
TryGetComponent is more efficient than GetComponent because it combines existence checking and component retrieval in one call.

PowerUpPickUp Component

The GameObject component that detects player collision and applies the powerup effect.

Configuration

effect
PowerUp
required
The ScriptableObject asset defining which powerup to apply (e.g., a JumpBoost asset)

Implementation

PowerUpPickUp.cs:3-16
public class PowerUpPickUp : MonoBehaviour
{
    [SerializeField]
    private PowerUp effect;

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.TryGetComponent<PlayerJump>(out _))
        {
            effect.Apply(collision.gameObject);
            gameObject.SetActive(false);
        }
    }
}

Collision Detection

The component:
  1. Checks for player - Uses TryGetComponent<PlayerJump> to verify the colliding object is the player
  2. Applies effect - Calls the Apply method on the assigned powerup asset
  3. Disables pickup - Deactivates the GameObject (can be reactivated later if needed)
Using SetActive(false) instead of Destroy() allows powerups to be reused through object pooling or respawn mechanics.

Creating New Powerups

The system is designed for easy extension. Here’s how to create new powerup types:

Example: Speed Boost

using UnityEngine;

[CreateAssetMenu(menuName = "Power Ups/Speed Boost")]
public class SpeedBoost : PowerUp
{
    public float SpeedMultiplier = 1.5f;
    public float Duration = 5f;

    public override void Apply(GameObject target)
    {
        if (target.TryGetComponent<PlayerInput>(out PlayerInput playerInput))
        {
            // For permanent effect:
            // playerInput.speed *= SpeedMultiplier;
            
            // For temporary effect, start a coroutine:
            target.GetComponent<MonoBehaviour>().StartCoroutine(TemporarySpeedBoost(playerInput));
        }
    }

    private System.Collections.IEnumerator TemporarySpeedBoost(PlayerInput player)
    {
        float originalSpeed = player.speed;
        player.speed *= SpeedMultiplier;
        
        yield return new WaitForSeconds(Duration);
        
        player.speed = originalSpeed;
    }
}

Example: Invincibility

using UnityEngine;

[CreateAssetMenu(menuName = "Power Ups/Invincibility")]
public class Invincibility : PowerUp
{
    public float Duration = 10f;

    public override void Apply(GameObject target)
    {
        if (target.TryGetComponent<PlayerHealth>(out PlayerHealth health))
        {
            health.SetInvincible(true);
            target.GetComponent<MonoBehaviour>().StartCoroutine(RemoveInvincibility(health));
        }
    }

    private System.Collections.IEnumerator RemoveInvincibility(PlayerHealth health)
    {
        yield return new WaitForSeconds(Duration);
        health.SetInvincible(false);
    }
}

Example: Double Points

using UnityEngine;

[CreateAssetMenu(menuName = "Power Ups/Double Points")]
public class DoublePoints : PowerUp
{
    public float Duration = 15f;

    public override void Apply(GameObject target)
    {
        ScoreSystem scoreSystem = FindObjectOfType<ScoreSystem>();
        if (scoreSystem != null)
        {
            scoreSystem.StartCoroutine(ApplyMultiplier(scoreSystem));
        }
    }

    private System.Collections.IEnumerator ApplyMultiplier(ScoreSystem scoreSystem)
    {
        scoreSystem.ScoreMultiplier = 2f;
        yield return new WaitForSeconds(Duration);
        scoreSystem.ScoreMultiplier = 1f;
    }
}

Setting Up Powerups in Unity

Step 1: Create the ScriptableObject Asset

  1. Right-click in the Project window
  2. Select Create → Power Ups → Jump Boost
  3. Name it (e.g., “JumpBoost_Medium”)
  4. In the Inspector, set JumpMultiplier to desired value (e.g., 1.5)

Step 2: Create the Pickup GameObject

  1. Create a new GameObject in the scene (e.g., “JumpBoostPickup”)
  2. Add a SpriteRenderer with an appropriate sprite
  3. Add a 2D Collider (Circle or Box)
    • Check Is Trigger
  4. Add the PowerUpPickUp component
  5. Drag the JumpBoost asset into the effect field

Step 3: Create Multiple Variants

Create different variants by duplicating the asset:
  • JumpBoost_Small - JumpMultiplier = 1.2 (20% increase)
  • JumpBoost_Medium - JumpMultiplier = 1.5 (50% increase)
  • JumpBoost_Large - JumpMultiplier = 2.0 (100% increase)
Each can be used with different pickup GameObjects.

Integration with Player System

The powerup system integrates seamlessly with the player system:
// JumpBoost modifies PlayerJump.JumpHeight
public class PlayerJump : MonoBehaviour
{
    public float JumpHeight;  // Modified by JumpBoost
    // ... rest of implementation
}

// PowerUpPickUp detects the player
private void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.TryGetComponent<PlayerJump>(out _))
    {
        // Player detected, apply powerup
    }
}
The system uses PlayerJump as the player identifier. Any GameObject with a PlayerJump component is treated as the player.

Advanced Patterns

Stacking Powerups

To allow multiple powerups to stack:
[CreateAssetMenu(menuName = "Power Ups/Stacking Jump Boost")]
public class StackingJumpBoost : PowerUp
{
    public float JumpIncrease = 0.5f;

    public override void Apply(GameObject target)
    {
        if (target.TryGetComponent<PlayerJump>(out PlayerJump jumpScript))
        {
            jumpScript.JumpHeight += JumpIncrease; // Additive instead of multiplicative
        }
    }
}

Powerup with Visual Feedback

[CreateAssetMenu(menuName = "Power Ups/Visual Jump Boost")]
public class VisualJumpBoost : PowerUp
{
    public float JumpMultiplier = 1.5f;
    public GameObject ParticleEffectPrefab;

    public override void Apply(GameObject target)
    {
        if (target.TryGetComponent<PlayerJump>(out PlayerJump jumpScript))
        {
            jumpScript.JumpHeight *= JumpMultiplier;
            
            // Spawn visual effect
            if (ParticleEffectPrefab != null)
            {
                Instantiate(ParticleEffectPrefab, target.transform.position, Quaternion.identity);
            }
        }
    }
}

Powerup with Audio

public class PowerUpPickUp : MonoBehaviour
{
    [SerializeField] private PowerUp effect;
    [SerializeField] private AudioClip pickupSound;
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.TryGetComponent<PlayerJump>(out _))
        {
            effect.Apply(collision.gameObject);
            
            if (pickupSound != null)
            {
                AudioSource.PlayClipAtPoint(pickupSound, transform.position);
            }
            
            gameObject.SetActive(false);
        }
    }
}

Best Practices

  1. Use descriptive asset names - Name assets like “JumpBoost_Large” not “JumpBoost1”
  2. Create asset variants - Make multiple ScriptableObject instances with different values for variety
  3. Fail safely - Always use TryGetComponent to handle cases where the target doesn’t have required components
  4. Consider temporary effects - Use coroutines for time-limited powerups
  5. Visual feedback - Add particle effects or sound when powerups are collected
  6. Balance carefully - Test multiplier values to ensure powerups are impactful but not overpowered
  7. Document in Inspector - Use [Tooltip] attributes to explain what each parameter does
  8. Reusability - Design powerups to be reusable across different scenes and game modes

Advantages of ScriptableObject Pattern

  • Designer-friendly - Non-programmers can create powerup variants
  • Memory efficient - Assets are shared, not duplicated per instance
  • Easy balancing - Change values in assets without touching code
  • Version control - Assets are text files, easy to track changes
  • Runtime independence - Assets exist outside scenes, surviving scene changes
  • Polymorphism - PowerUpPickUp works with any PowerUp subclass

Build docs developers (and LLMs) love