Quick Comparison
event는 한 종류의 방송이 아닙니다. caller가 즉시 결과를 받아야 하면 direct call, 같은 process의 notification이면 C# event, Inspector persistent listener가 필요한 Unity UI·prefab 연결이면 UnityEvent, 나중에 처리·retry·durable delivery가 필요하면 queued message를 고릅니다.
| 요구 | 선택 | 반드시 정할 것 |
|---|---|---|
| return value·실패 처리·순서 필요 | direct call | caller/callee와 synchronous error contract |
| state 변경을 여러 listener에게 알림 | C# event | publisher, payload, subscribe/unsubscribe owner |
| Inspector 연결이 필요한 UnityEvent | UnityEvent | persistent listener와 runtime listener의 ownership |
| 다음 tick·network·retry 처리 | queued message | ordering, duplicate, cancel, acknowledgement |
public sealed class Health : MonoBehaviour
{
public event Action<int, int> Changed;
public void ApplyDamage(int amount)
{
currentHp = Mathf.Max(0, currentHp - amount);
Changed?.Invoke(currentHp, maxHp);
}
}
void OnEnable() => health.Changed += UpdateBar;
void OnDisable() => health.Changed -= UpdateBar;발행과 구독의 수명
C# event는 publisher만 invoke할 수 있게 public subscription surface를 제한합니다. listener는 언제 등록하고 해제할지 owner를 가져야 합니다. Unity OnEnable/OnDisable 쌍은 active state에 따라 관찰하는 UI·effect에 흔히 맞지만, publisher가 먼저 파괴되거나 scene scope가 다르면 별도 null/dispose 정책이 필요합니다.
UnityEvent에는 Inspector에 저장된 persistent listener와 AddListener로 더한 runtime listener가 구분됩니다. RemoveAllListeners가 어떤 listener를 지우는지, prefab에서 연결한 target이 scene 전환 뒤에도 유효한지 확인하지 않고 전역 event처럼 쓰지 않습니다. UnityEvent가 refactor 안전성과 call graph 추적을 자동으로 해결하는 것은 아닙니다.
event가 아닌 흐름
damage request가 validation result를 반환해야 하거나, purchase를 승인한 뒤에만 state를 바꿔야 하면 direct call 또는 command가 명확합니다. event listener의 호출 순서와 exception 전파에 의존하는 core rule은 hidden coupling이 되기 쉽습니다. 반대로 Health.Changed처럼 state mutation 뒤 UI·sound·quest가 독립적으로 반응하는 notification은 observer에 맞습니다.
queued message는 C# event보다 강한 delivery 계약이 필요할 때의 별도 구조입니다. tick queue, network packet, durable log는 중복·retry·순서·persistence를 설계해야 하므로 단순 Action event로 흉내 내지 않습니다.
payload와 관측성
event 이름은 과거형 notification과 command를 구분하고, payload에는 listener가 필요한 identifier·이전/새 값·tick을 넣되 mutable game object를 무제한 전달하지 않습니다. 이벤트가 많아지면 log·counter·debug view에서 publisher와 subscriber를 추적할 수 있게 하고, 모든 신호를 string 기반 global bus에 몰지 않습니다.
observer는 결합을 없애는 대신 실행 경로를 분산합니다. 누가 invoke하고, 어떤 순서·thread에서 전달되며, 언제 unsubscribe하는지 답할 수 없으면 direct call보다 디버깅이 어려워질 수 있습니다.
참고 링크
2 sources