핵심 정리
[CreateAssetMenu(menuName = "Events/Game Event")]
public class GameEvent : ScriptableObject
{
public Action Raised;
public void Raise() => Raised?.Invoke();
}구조 이해
이벤트 채널 패턴의 개념
Unity 공식 how-to와 기술 자료는 ScriptableObject를 이벤트 허브처럼 써서 시스템 간 결합을 줄이는 패턴을 자주 소개합니다. 발신자는 특정 리스너를 몰라도 되고, 수신자는 씬 안의 특정 오브젝트 참조 없이 이벤트 자산만 구독하면 됩니다. 씬이 바뀌거나 오브젝트가 교체되더라도 자산 참조만 유지되면 이벤트 연결이 끊어지지 않는다는 것이 핵심 장점입니다.
발신자와 수신자 분리
발신자는 GameEvent.Raise()를 호출하기만 하고, 누가 반응하는지 알 필요가 없습니다. 수신자는 OnEnable에서 이벤트를 구독하고 OnDisable에서 해제합니다. UI, 사운드, 퀘스트, 전투 연출처럼 여러 시스템이 같은 사건에 반응해야 할 때 특히 유용합니다. 새 시스템이 추가되더라도 발신자 코드를 수정하지 않고 구독만 추가하면 됩니다.
public class DamageDealer : MonoBehaviour
{
[SerializeField] private GameEvent onHit;
public void Hit()
{
onHit.Raise();
}
}규모 확장과 추적성 관리
옵저버 패턴과 함께 쓰기 좋지만, 이벤트 자산이 너무 많아지면 추적이 어려워질 수 있습니다. 어떤 시스템이 어떤 이벤트를 구독하고 있는지 파악하기 어렵고, 디버깅 시 이벤트 흐름을 따라가는 것이 번거로워질 수 있습니다. 이벤트 자산 이름 규칙과 폴더 구조를 처음부터 명확하게 정해 두면 규모가 커져도 관리하기 쉽습니다.
체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 여러 시스템이 한 사건에 반응해야 함 | SO 이벤트 채널로 발신/수신 분리 |
| 씬이 바뀌어도 이벤트 연결 유지 필요 | SO 자산 기반 채널 (씬 독립) |
| 발신자가 수신자를 직접 참조하고 싶지 않음 | Raise() 호출, 수신자는 자산만 구독 |
| 이벤트 흐름이 너무 불투명해짐 | 이벤트 자산 이름 규칙 + 폴더 구조 정비 |
| 비교 | 더 잘 맞는 쪽 | 이유 |
|---|---|---|
| 직접 참조 호출 vs 이벤트 채널 | 소규모 단순 연결 / 느슨한 결합 | 디버깅 쉬움과 확장성 사이의 차이 |
| 씬 오브젝트 이벤트 허브 vs SO 자산 허브 | 씬 수명주기 / 프로젝트 자산 수명주기 | 씬 교체와 재사용성 대응 방식이 다름 |
| 무분별한 채널 증가 vs 규칙 있는 채널 | 규칙 있는 채널 | 흐름 추적과 이름 충돌을 줄이기 쉬움 |
주의할 점
결합을 줄이는 대신 흐름이 눈에 덜 보입니다. 이벤트 자산 이름과 구독 규칙을 명확히 정하지 않으면 디버깅이 어려워집니다.
// ❌ OnEnable 구독만 하고 OnDisable 해제 없음 → 메모리 누수 + 중복 호출
public class SoundListener : MonoBehaviour
{
[SerializeField] private GameEvent onHit;
void OnEnable() => onHit.Raised += PlaySound;
// OnDisable 없음 → 오브젝트 파괴 후에도 Raised에 남아있음
}
// ✅ OnEnable/OnDisable 쌍으로 구독 관리
public class SoundListener : MonoBehaviour
{
[SerializeField] private GameEvent onHit;
void OnEnable() => onHit.Raised += PlaySound;
void OnDisable() => onHit.Raised -= PlaySound;
void PlaySound() => AudioManager.Play("hit");
}
// ❌ 모든 사건을 하나의 Generic Event에 몰아넣음
// → 누가 무엇에 반응하는지 추적이 어려워짐참고 링크
2 sources