✨ Using the Status Effects System¶
The Status Effects system is a modular, deterministic buff/debuff/CC pipeline with stacking rules, dispels, cleanses, potency, resistance, tagging, ScriptableObject authoring, UI support, and optional server‑authoritative multiplayer gating via IStatusAuthority.
It is entirely interface‑driven, runtime‑safe, and netcode‑agnostic.
Important: Status Effects does not define gameplay rules or balance. It provides a predictable runtime pipeline for applying them.
📦 Overview¶
Attach StatusEffectController to any GameObject to manage:
- Damage‑over‑time / Heal‑over‑time (
PoisonStatus,BurnStatus,RegenStatus) - Multipliers (
SlowStatus,HasteStatus,VulnerabilityStatus) - Utility (
StunStatus,ThornsStatus,ShieldStatus) - Context attribution (
StatusContext: instigator, source, tags) - Potency & resistance scaling
- Dispels & cleanses (tier‑based or tag‑based)
- Optional UI buff bars (icons, stacks, radial timers)
- Optional authority gating via
IStatusAuthority
Responsibility Boundaries¶
Clear ownership keeps the system predictable and debuggable:
StatusEffectControllerowns lifecycle, stacking, time, and authority- Effects own behaviour only (what happens on apply / tick / remove)
- Receiver‑side modifiers influence incoming math, never effect state
- UI mirrors controller state; it never drives gameplay
🚀 Quick Start¶
- Add
StatusEffectControllerto a target - (Optional) Add receiver‑side modifiers:
IStatusImmunityIStatusResistanceIStatusPotency- Create a ScriptableObject definition or build an effect directly
- Apply effects using controller APIs
Minimal usage¶
ctrl.ApplyStatus(
new PoisonStatus(5f, 10f),
StatusContext.FromAbility("firebolt")
);
// Preferred: centralized refresh/stack handling
ctrl.ApplyOrRefresh(
() => new PoisonStatus(5f, 10f),
StatusContext.FromAbility("firebolt")
);
🧩 Composition Cheat Sheet¶
| Layer | Component | Purpose | Required |
|---|---|---|---|
| Controller | StatusEffectController |
Core runtime manager | Yes |
| Receiver Modifiers | IStatusImmunity, IStatusResistance, IStatusPotency |
Modify incoming effects | No |
| Effects | IStatusEffect, TimedStatusEffect |
Behaviour implementations | No |
| Definitions | StatusEffectDefinitionBase |
ScriptableObject authoring | No |
| Authority | IStatusAuthority |
Multiplayer gating | No |
| UI | StatusBuffBar, StatusIconView |
Visual display | No |
| Integration | Movement / Health / Shields | Gameplay bridges | No |
Only the controller is mandatory. All other layers are opt‑in.
🧮 Public API¶
All public methods on StatusEffectController are runtime‑safe and may be called by gameplay code, AI, abilities, or netcode — provided authority rules allow mutation.
Apply & Refresh¶
void ApplyStatus(IStatusEffect effect);
void ApplyStatus(IStatusEffect effect, StatusContext ctx);
void ApplyOrRefresh(Func<IStatusEffect> build, StatusContext ctx);
Remove / Cleanse¶
void RemoveStatus(StatusId id);
void RemoveStatusAt(int index);
void ClearAll();
int Dispel(DispelType type, int minTier = 0);
int CleanseByTag(StatusTag mask);
Queries¶
bool HasStatus(StatusId id);
int GetStackCount(StatusId id);
IReadOnlyList<IStatusEffect> Active { get; }
bool TryGetFirst(StatusId id, out IStatusEffect e);
IStatusEffect GetFirstOrNull(StatusId id);
bool TryGetFirstContext(StatusId id, out StatusContext ctx);
StatusContext GetContext(IStatusEffect e);
int TryGetAll(StatusId id, List<IStatusEffect> buffer);
Stack Policy¶
ctrl.SetStackCap(StatusId.Poison, 3);
ctrl.ClearStackCap(StatusId.Poison);
ctrl.SetPerSourceStacks(StatusId.Poison, true);
Potency / Time¶
ctrl.RecomputePotencyForAll();
ctrl.SetTimeMode(StatusTimeMode.Custom, customTimeSource);
🔔 Events¶
Context‑aware C# events¶
OnStatusAppliedCtxOnStatusRefreshedCtxOnStatusRemovedCtxOnStatusExpiredCtx
UnityEvents¶
OnStatusAppliedOnStatusRefreshedOnStatusRemovedOnStatusExpired
🧱 Stacking & Caps¶
| Policy | Behaviour |
|---|---|
| Replace | Remove old → add new |
| Refresh | Reset or extend timer |
| Stack | Multiple concurrent instances |
Supports global caps and optional per‑source stacking semantics.
⚖️ Potency & Resistance¶
| Modifier | Interface | Affects |
|---|---|---|
| Potency | IStatusPotency |
Magnitude (DPS / HPS / multipliers) |
| Resistance | IStatusResistance |
Duration scaling (0–1) |
After stat or composition changes:
ctrl.RecomputePotencyForAll();
Updates any effect implementing IAdjustableMagnitude.
🎨 UI¶
buffBar.controller = ctrl;
buffBar.iconLibrary = iconLibrary;
buffBar.iconPrefab = iconPrefab;
Components:
StatusBuffBarStatusIconViewStatusIconLibrary
UI is fully decoupled from runtime logic and may be removed or replaced freely.
🌐 Authority (Multiplayer)¶
When requireAuthority = true:
- Mutations and ticks resolve via
IStatusAuthority - Typical flow:
- Client requests
- Server validates
- Server mutates
- Netcode replicates state
If authority is denied or no binder resolves:
No partial behaviour occurs — no ticking, refresh, expiry, or mutation.
This is intentional and prevents desync.
Example binder:
public class MyStatusAuthority : MonoBehaviour, IStatusAuthority
{
public bool HasAuthority(StatusEffectController ctrl)
=> IsServer; // or IsOwner / HasStateAuthority / etc.
}
Multiplayer Wiring Checklist¶
- Add a scene‑level
IStatusAuthoritybinder - Enable
requireAuthorityon server‑owned controllers - Tick and mutate statuses server‑side only
- Replicate status state via your netcode
- Disable global FX on server (
enableGlobalFx = false) - Do not mutate effect state directly on clients
🎆 FX¶
Status effects expose an optional global FX seam via StatusFx.
- Register FX handlers on clients only
- Or disable FX entirely on server controllers
FX handlers must never mutate gameplay state.
⚠️ Gotchas¶
TimedStatusEffect.Apply()resets time; useRefresh()to extend- Effects must never be reused or cached — always create fresh instances
- Do not modify
TimeRemainingdirectly - UI jitter is coalesced via destroy‑grace in
StatusBuffBar - Legacy shield bridges may clear all shields (prefer
IShieldTicketPool) - Potency scaling is absolute from base magnitude
- Authority required + no binder = frozen effects (by design)
🧩 Extending¶
Effects¶
- Subclass
TimedStatusEffect - Implement
IStatusEffectfor passive/custom logic - Avoid querying other effects directly — use controller queries
Definitions¶
- ScriptableObject authoring via
StatusEffectDefinitionBase
Receiver‑side modifiers¶
IStatusPotency,IStatusResistance,IStatusImmunity
Integration¶
- Movement, cooldowns, damage, shields, custom sinks
Authority¶
- Custom binders implementing
IStatusAuthority
Registry¶
- Optional registration via
StatusRegistry
📘 See Also¶
- Abstractions
- Core
- Effects
- Integration
- Teaching (Editor Panels & Guides)
- UI
- Samples