Status Effect Quickstart (Passives, Stat Upgrades, and Unique Stat Upgrades) - StudioAspen/Project-Dreamscape-Aspen-2024-2025 GitHub Wiki

Introduction

The StatusEffectSO is a ScriptableObject designed to define and manage status effects for entities in our game. This class provides a highly customizable framework for applying, updating, and removing various status effects on entities during gameplay.

The entity MUST have the EntityStatusEffector as a component in order to receive and store these status effects.

3 Types of Status Effects

A status effect is still very vague and we need to break it down into 3 categories to cover our goals for this project.

Duration-based Status Effects

These effects are given a duration to determine when the effect expires. An example of a duration-based status effect is a temporary speed boost.

Tick-based Status Effects

These effects are given a total number of ticks and duration per tick to determine when the effect expires. Custom behavior can be added to occur on each tick. An example of a tick-based status effect is burning, where an entity takes damage each tick.

Permanent Status Effects

These status effects are best used to describe any stat upgrades and passives. As the name suggests, these effects never expire unless explicitly cancelled or overridden.

Stacking

To turn on stacking, you must enable the boolean Stackable in your created scriptable object.

  • If a status effect is stackable, when you reapply the same type of status effect, the OnStack() function is called, and the OnApply() function is not called.
  • If a status effect is not stackable, when you reapply the same type of status effect, the Cancel() function is called for the old effect, and the OnApply() function is called on the new effect.

Guide

Duration-based Template

[CreateAssetMenu(fileName = "Data", menuName = "Status Effect/Duration Template")]
public class DurationTemplateStatusEffectSO : DurationStatusEffectSO
{
    // Base Class: You have access to the Entity entity being affected and the GameObject source of the object that applied this effect
    // DurationStatusEffectSO Class: You have access to the float Duration and float Remaining Duration

    [field: Header("Duration Template: Settings")]
    [field: SerializeField] public float TemplateFloat { get; private set; } = 1.1f;

    private protected override void OnApply()
    {
        base.OnApply();

        // your logic for when the effect is applied
    }

    private protected override void OnExpire()
    {
        base.OnExpire();

        // your logic for when the effect expires
    }

    public override void Cancel()
    {
        base.Cancel();

        // your logic for when the effect is cancelled
    }

    private protected override void OnStack(StatusEffectSO newStatusEffect)
    {
        base.OnStack(newStatusEffect);

        DurationStatusEffectSO overridingStatusEffect = newStatusEffect as DurationStatusEffectSO;

        // your logic for when the effect is stacked
    }
}

Tick-based Template

`[CreateAssetMenu(fileName = "Data", menuName = "Status Effect/Tick Template")]`
public class TickTemplateStatusEffectSO : TickStatusEffectSO
{
    // Base Class: You have access to the Entity entity being affected and the GameObject source of the object that applied this effect
    // TickStatusEffectSOClass: You have access to the int Ticks and float TicksDuration

    [field: Header("TickTemplate: Settings")]
    [field: SerializeField] public int TemplateInt { get; private set; } = 1;

    private protected override void OnApply()
    {
        base.OnApply();

        // your logic for when the effect is applied
    }

    private protected override void OnTick()
    {
        base.OnTick();

        // your logic for when the effect ticks
    }

    private protected override void OnExpire()
    {
        base.OnExpire();

        // your logic for when the effect expires
    }

    public override void Cancel()
    {
        base.Cancel();

        // your logic for when the effect is cancelled
    }

    private protected override void OnStack(StatusEffectSO newStatusEffect)
    {
        base.OnStack(newStatusEffect);

        DurationStatusEffectSO overridingStatusEffect = newStatusEffect as DurationStatusEffectSO;

        // your logic for when the effect is stacked
    }
}

Permanent Template

[CreateAssetMenu(fileName = "Data", menuName = "Status Effect/Permanent Template")]
public class PermanentTemplateStatusEffectSO : StatusEffectSO
{
    // Base Class: You have access to the Entity entity being affected and the GameObject source of the object that applied this effect

    [field: Header("Permanent Template: Settings")]
    [field: SerializeField] public float TemplateFloat { get; private set; } = 1.1f;

    private protected override void OnApply()
    {
        base.OnApply();

        // your logic for when the effect is applied
    }

    public override void Cancel()
    {
        base.Cancel();

        // your logic for when the effect is cancelled
    }

    private protected override void OnStack(StatusEffectSO newStatusEffect)
    {
        base.OnStack(newStatusEffect);

        DurationStatusEffectSO overridingStatusEffect = newStatusEffect as DurationStatusEffectSO;

        // your logic for when the effect is stacked
    }
}

Making the scriptable object

  1. Ensure that at the top of your custom status effect script, it has this line. Be sure to replace [CUSTOM_NAME_HERE] with your status effect name.
[CreateAssetMenu(fileName = "Data", menuName = "Status Effect/[CUSTOM_NAME_HERE]")]
  1. Go to the Status Effect Scriptable Objects folder in Assets/ScriptableObjects/Status Effects
    image
  2. Right click -> Create -> Status Effect -> [CUSTOM_NAME_HERE] to create your scriptable object. In this example, Burn is the status effect I want to create.
    image
  3. Configure your variables however you like. Here is an example of the Burn status effect.
    image

Applying Status Effects (Two ways)

Through the Aspect Trees

  1. Open the aspect tree in Assets/ScriptableObjects/Aspects. In this example, the fire aspect tree.
    image
  2. Find the corresponding node and add your status effect to it.
    image

Manually Scripting

  1. Get a reference to the status effect you want. Make sure you drag the reference into the slot in the inspector.
[SerializeField] private StatusEffectSO burnStatusEffect;
  1. Get a reference to the object you want to apply the status effect to. You can do this however you like. In this example, a roblox-like obby lava block uses OnCollisionStay to find a reference.
  2. Apply the status effect using public static void TryApplyStatusEffect(GameObject target, StatusEffectSO statusEffect, GameObject source) where target is the gameObject of the Entity victim, statusEffect is the effect being applied, and source is the object applying the effect.
    private void OnCollisionStay(Collision collision)
    {
        // tries to apply the burn status effect to the collided object. gameObject is passed as the last parameter because this lava block is the source.
        EntityStatusEffector.TryApplyStatusEffect(collision.gameObject, burnStatusEffect, gameObject); 
    }