AgentSkillsCN

03 Message System

03 消息系统

SKILL.md

03-message-system

Status: ACTIVE
AppliesTo: v11
Type: Component Specification

목적

MessageSystem<TKey>instanceId + key 기반 메시지/트리거 라우팅 시스템이다.

  • GameObject/MonoBehaviour 단위로 메시지 핸들러 등록/해제
  • enum 키 기반 메시지 분기
  • 1회성 핸들러 지원 (RegisterOnce)

범위

포함

  • Register / Unregister — instanceId 기반 핸들러 등록/해제
  • RegisterOnce — 1회 호출 후 자동 해제
  • Notify — key 매칭 핸들러 일괄 호출
  • ClearAll — 전체 초기화

제외

  • 시간 기반 이벤트 (타이머/틱/스케줄러) — Unity Invoke / Coroutine 사용 권장
  • 백그라운드 스레드 지원
  • Unity 이벤트 루프 (Update) 연동

네임스페이스

csharp
namespace Devian

핵심 규약 (Hard Rule)

1. Notify reverse 순회

Notify 호출 시 등록 목록을 뒤에서 앞으로(reverse) 순회한다.
이는 순회 중 핸들러 제거 시 컬렉션 안전성을 보장한다.

2. handler true 반환 시 자동 해제

Handler delegate가 true를 반환하면 해당 핸들러는 즉시 제거된다.

csharp
public delegate bool Handler(object[] args);

3. RegisterOnce는 Action 래핑

RegisterOnce는 전달받은 Action<object[]>를 내부에서 래핑하여, 한 번 호출 후 true를 반환하도록 구현한다.

4. owner overload는 null-safe + GetInstanceID

UnityEngine.Object owner 오버로드는:

  • owner == null이면 즉시 return (예외 없음)
  • 내부에서 owner.GetInstanceID() 사용

5. 키 비교

csharp
EqualityComparer<TKey>.Default.Equals(a, b)

API 시그니처

csharp
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Devian
{
    public static class MessageSystem<TKey> where TKey : unmanaged, Enum
    {
        public delegate bool Handler(object[] args);

        public static void ClearAll();

        public static void Register(int instanceId, TKey key, Handler handler);
        public static void Register(UnityEngine.Object owner, TKey key, Handler handler);

        public static void RegisterOnce(int instanceId, TKey key, Action<object[]> handler);
        public static void RegisterOnce(UnityEngine.Object owner, TKey key, Action<object[]> handler);

        public static void Unregister(int instanceId);
        public static void Unregister(UnityEngine.Object owner);

        public static void Notify(TKey key, params object[] args);
    }
}

사용 예시

1. enum 키 정의

csharp
public enum GameMessage
{
    PlayerDied,
    LevelComplete,
    ItemPickup,
    EnemySpawned
}

2. MonoBehaviour에서 사용

csharp
using UnityEngine;
using Devian;

public class GameEventListener : MonoBehaviour
{
    private void Start()
    {
        // this(owner)로 등록
        MessageSystem<GameMessage>.Register(this, GameMessage.PlayerDied, OnPlayerDied);
        MessageSystem<GameMessage>.Register(this, GameMessage.LevelComplete, OnLevelComplete);

        // 1회성 핸들러
        MessageSystem<GameMessage>.RegisterOnce(this, GameMessage.ItemPickup, args =>
        {
            Debug.Log($"First item picked up: {args[0]}");
        });
    }

    private void OnDestroy()
    {
        // owner의 모든 핸들러 해제
        MessageSystem<GameMessage>.Unregister(this);
    }

    private bool OnPlayerDied(object[] args)
    {
        Debug.Log("Player died!");
        return false; // 계속 유지
    }

    private bool OnLevelComplete(object[] args)
    {
        int level = (int)args[0];
        Debug.Log($"Level {level} complete!");
        return false; // 계속 유지
    }
}

3. 메시지 발송

csharp
// 어디서든 Notify 호출
MessageSystem<GameMessage>.Notify(GameMessage.PlayerDied);
MessageSystem<GameMessage>.Notify(GameMessage.LevelComplete, 5);
MessageSystem<GameMessage>.Notify(GameMessage.ItemPickup, "HealthPotion");

4. 시간 기반 호출

MessageSystem은 시간 기반 이벤트를 지원하지 않는다.
타이머/지연 호출이 필요하면 Unity의 Invoke 또는 Coroutine을 사용한다.

csharp
// 3초 후 메시지 발송 (Unity Invoke 사용)
Invoke(nameof(SendDelayedMessage), 3f);

private void SendDelayedMessage()
{
    MessageSystem<GameMessage>.Notify(GameMessage.EnemySpawned);
}

// 또는 Coroutine 사용
IEnumerator DelayedNotify()
{
    yield return new WaitForSeconds(3f);
    MessageSystem<GameMessage>.Notify(GameMessage.EnemySpawned);
}

파일 경로

타입경로
Runtimecom.devian.foundation/Runtime/Unity/MessageSystem/MessageSystem.cs

Reference

  • 인덱스: 30-unity-components/skill.md