Skip to main content

Buff / Debuff System

A general-purpose buff pipeline for duration, stacks, ticks, and priority. Poison, stun, attack-speed boosts, and custom gameplay effects can be implemented by inheriting from BuffEffect.

Structure

BuffSystem (pure C# instance)
- Updates timers, ticks, and expiration for active buffs

BuffDefinition (ScriptableObject)
- Name, icon, duration, tick, and stack settings
[SerializeReference] List<BuffEffect>

BuffInstance (runtime)
- Runtime state for a buff applied to a target

BuffEffect (abstract, [Serializable])
├── DamageOverTimeEffect - Damage on each tick
├── StunEffect - Disable movement or input
└── StatModifyEffect - Add/remove stat modifiers automatically

Quick Start

1. Create a BuffDefinition

Create -> AchUtils/Buff/Buff Definition

BuffId : "Poison"
DisplayName : "Poison"
Duration : 8
TickInterval : 1
MaxStacks : 3
RefreshOnReapply: true

Effects:
[0] DamageOverTimeEffect
DamagePerTick: 15

2. Create a BuffSystem Instance

BuffSystem is not a MonoBehaviour. Create it from the owner that needs it, then call Tick every frame to update durations and ticks.

3. Apply and Remove Buffs

using AchUtils.Buff;

private readonly BuffSystem buffSystem = new();

[SerializeField] BuffDefinition poisonDef;
[SerializeField] GameObject target;

void Update()
{
buffSystem.Tick(Time.deltaTime);
}

buffSystem.Apply(poisonDef, target);

buffSystem.Remove("Poison", target);

bool hasPoisoned = buffSystem.Has("Poison", target);

buffSystem.RemoveAll(target);

API

BuffSystem

BuffInstance Apply(BuffDefinition definition, GameObject target)
bool Remove(string buffId, GameObject target)
void RemoveAll(GameObject target)
bool Has(string buffId, GameObject target)
List<BuffInstance> GetBuffs(GameObject target)

void Tick(float deltaTime)

event Action<BuffInstance> OnBuffApplied
event Action<BuffInstance> OnBuffRemoved

BuffDefinition Inspector

FieldDescription
BuffIdUnique identifier
DurationDuration in seconds. -1 means permanent
TickIntervalTick interval in seconds. 0 disables ticks
MaxStacksMaximum stack count
RefreshDurationOnReapplyWhether reapply refreshes the duration

BuffInstance

BuffDefinition Definition
GameObject Target
int Stacks
float RemainingDuration
bool IsPermanent
bool IsExpired

Built-In Effects

DamageOverTimeEffect

float DamagePerTick // Damage per tick, multiplied by stacks

The target must implement IDamageable.

public class Player : MonoBehaviour, IDamageable
{
public void TakeDamage(float amount)
{
hp -= amount;
}
}

StunEffect

The target must implement IStunnable.

public class PlayerController : MonoBehaviour, IStunnable
{
private int _stunCount;

public void ApplyStun() => _stunCount++;
public void RemoveStun() => _stunCount--;

void Update()
{
if (_stunCount > 0) return;
// Movement...
}
}

StatModifyEffect

Adds and removes a stat modifier through StatSheetComponent.

StatKey : "Attack"
ModifierType : PercentAdd
Value : 0.3

Custom Effects

[Serializable]
public class FreezeEffect : BuffEffect
{
public float SlowRatio = 0.5f;

public override void OnApply(BuffInstance buff, GameObject target)
{
var rb = target.GetComponent<Rigidbody>();
if (rb) rb.velocity *= (1f - SlowRatio);
}

public override void OnTick(BuffInstance buff, GameObject target)
{
// Keep suppressing speed on each tick.
}
}

Examples

Poison
Duration: 8s, TickInterval: 1s, MaxStacks: 3
DamageOverTimeEffect: 15 x stacks

Freeze
Duration: 2s, MaxStacks: 1
StunEffect

Berserk
Duration: 10s
StatModifyEffect: Attack PercentAdd +0.5

Curse
Duration: -1 (permanent), MaxStacks: 1
StatModifyEffect: Defense Flat -20

::: tip Stat integration StatModifyEffect integrates with Stat Modifier. The target needs StatSheetComponent. :::