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
| Field | Description |
|---|---|
BuffId | Unique identifier |
Duration | Duration in seconds. -1 means permanent |
TickInterval | Tick interval in seconds. 0 disables ticks |
MaxStacks | Maximum stack count |
RefreshDurationOnReapply | Whether 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.
:::