Post List

[실용적 예제로 본 게임 인공지능 프로그램하기] 2-2. Event Handling(Telegram) 추가하기

2-2. 메시지 처리 기능 추가하기




"잘 디자인된 게임들은 이벤트 대응(Event-Driven)형이다. 말하자면 무기가 발사되든가, 지렛대가 당겨졌다든가 등의 이벤트가 발생할 경우 이 이벤트는 적절하게 대응하도록 관계되는 모든 객체들에게 뿌려진다. 이런 이벤트들은 보내진 이벤트가 무엇인지, 어떤 객체가 대응해야 하는지, Time stamp등의 정보를 포함하고 있는 전형적인 데이터 패킷의 형태로 발송된다."

2-1 에서는 FSM의 구조에 대해 알아보았습니다. 하지만 우리는 Telegram이라고 불리는 메시지를 처리하는 방법에 대해 알아보려고 합니다.

만약 Event Handling이 없다면 객체들은 게임세계에서 특별한 액션이 발생했는지를 알기 위해 끊임없이 조사해보아야만 합니다. 하지만, 이벤트 핸들링이 있다면 객체들은 이벤트가 자신에게 뿌려질 때까지 단지 자기 업무만 하고 있으면 됩니다.

Telegram으로 다음과 같은 일을 할 수 있습니다.

  • 마법사가 괴물에게 불덩이를 던질 때, 마술사는 괴물에게 곧 일어날 비운에 대해 알리는 메시지를 보내서 그에따라 대응하도록 하는데, 예를들어 끔찍하게 죽거나 장엄하게 죽는것 입니다.
  • 병사가 부상을 당했을 때, 그 병사의 동료들에게 각각 도움요청 메시지가 뿌려질 것이고 한 병사가 도와주기위해 당도하면 다른 동료들에게는 원래 하던일을 하도록 복귀해도 좋다는 다른 메시지가 뿌려지게 됩니다.

전보 구조

이전에 전보(Telegram)은 데이터 패킷의 구조로 보내진다고 하였습니다. 데이터 패킷에는 송신자, 수신자, 데이터 본체, 보내진 시각 등이 기록되어 있습니다. 우리의 전보도 마찬가지 입니다. 다음은 전보의 소스코드 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Telegram 
{
    // sender entity.
    int sender;
    // receiver entity.
    int receiver;
    // The Message.
    int msg;
    // Message would be delayed or dispatched immediately
    // If it should be delayed, this field must Timestamped before dispatching.
    private float dispatchTime;
    // Extra info.
    public delegate void ExtraInfo();
    public ExtraInfo info;
}
cs


위 코드에서 sender, receiver, time을 확인할 수 있습니다.
메시지의 종류 msg는 미리 열거된 타입으로 데이터 패킷에 감싸집니다.



전보 관리자

위와 같이, 전보를 보냈다면 전보를 관리하는 관리자가 필요합니다. 이 관리자 클래스는 
메시지를 즉시 보내야 하는지를 판단하고, 바로 보내지 않고 나중에 보내야 하는 메시지는 우선순위 큐에 담아두었다가 때가되면 예약된 수신자에게 메시지를 보냅니다.
다음은 관리자 MessageDispatcher class의 소스코드 입니다.

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class MessageDispatcher : MonoBehaviour
{
    IEnumerator Updating()
    {
        WaitForSeconds ws = new WaitForSeconds(0.5f);
        while (true)
        {
            DispatchDelayedMessages();
            yield return ws;
        }
    }
    [SerializeField]
    private TelegramPriorityQueue priorityQ = new TelegramPriorityQueue();
    public Dictionary<stringint> messageType = new Dictionary<stringint>();
    private void Discharge(BaseGameEntity pReceiver, Telegram msg = null)
    {
        if (msg == null || !pReceiver.HandleMessage(msg))
        {
            Debug.Log("Empty Msg Detected");
        }
    }
    
    
    public void DispatchMessage(float delay, int sender, int receiver, int msg, Telegram.ExtraInfo info = null)
    {
        Telegram telegram = new Telegram();
        telegram.ConstructTelegram(delay, sender, receiver, msg, info);
        if(delay <= 0.0f)
        {
            Discharge(EntityManager.instance.GetEntityFromID(receiver), telegram);
        }
        else
        {
            float currentTime = Time.time;
            telegram.DispatchTime = currentTime + delay;
            // Input telegram into pq.
            priorityQ.Enqueue(telegram);
        }
    }
    // This method should be called from Main loop of the Game.
    public void DispatchDelayedMessages()
    {
        // Get a current time.
        double currentTime = Time.time;
        if(priorityQ.Count() > 0)
        {
            while ((priorityQ.Peek().DispatchTime < currentTime) && (priorityQ.Peek().DispatchTime > 0))
            {
                // Get Front of the queue.
                Telegram telegram = priorityQ.Peek();
                // Find Receiver.
                BaseGameEntity pReceiver = EntityManager.instance.GetEntityFromID(telegram.Receiver);
                // Send Telegram to recevier
                Discharge(pReceiver, telegram);
                // Pop the telegram from queue.
                priorityQ.Dequeue();
                if(priorityQ.Count() == 0)
                {
                    break;
                }
            }
        }
        
    }
   
}
cs


Line 9의 Updating에서 0.5초 주기마다 지연된 메시지를 발송하는 것을 볼 수 있습니다.
Line 29에서 지연정도인 delay가 0이면 메시지 급파, 아니라면 지연된 메시지 큐에 넣는것을 볼 수 있습니다.
Line 49에서 우선순위큐의 Top이 예약된 시간이 다 되었는지 체크 후 다 되었다면 메시지를 보내는 것을 볼 수 있습니다.


메시지 수신 함수

메시지를 보내는 클래스가 있다면, 받는 클래스가 있어야 하겠죠. 
메시지 수신을 원하는 상태에서 메시지를 받는 것은 다음 메소드를 이용합니다.
수신함수 OnMessage()는, switch로 자신이 받을 수 있는 목록의 메시지가 자신에게 왔다면 그에 맞게 자신의 상태를 변경시킵니다.
OnMessage()는 모든 State에 들어가 있고, override되는 함수 이므로 그 상태에 맞게 다시 코딩을 해주면 되는 함수 입니다.

소스코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public override bool OnMessage(Wife entityType, Telegram telegram)
    {
        switch (telegram.GetMessageIndex())
        {
            case Msg_HoneyImHome:
                {
                    GameObject.Find("Elsa").GetComponent<Wife>().GetFSM().ChangeState(CookStew.instance);
                }
                return true;
        }
        return false;
    }
cs



이상으로 Telegram 포스팅을 마치겠습니다.


댓글