状态驱动智能体设计——消息功能
1. 如何设计游戏的消息功能?
设计精度的游戏趋向于事件驱动。即当一个事件发生了(武器发射了子弹等),事件被广播给游戏中的相关的对象。这样它们可以恰当地做出反应。而这个消息可以是立即执行,也可以设定多久后才执行。
于是,我们可以设计一个消息处理器类,包含处理消息的方法、加入消息的方法,还有用于储存延迟消息的队列以及游戏循环时刻调用的用于检测延迟消息队列是否有消息到达时间点然后处理它。
在处理消息的方法中,可以让接收者调用自身处理消息的方法,在这个方法中又去调用FSM有限状态机的处理消息的方法,让当前的状态响应该消息。
消息可以用一个“电报”类来包装,包含了发送者、接受者、消息内容、执行时间等。
2. 主要代码展示
#ifndef MESSAGE_DISPATCHER_H
#define MESSAGE_DISPATCHER_H
//------------------------------------------------------------------------
//
// Name: MessageDispatcher.h
//
// Desc: A message dispatcher. Manages messages of the type Telegram.
// Instantiated as a singleton.
//
// Author: Mat Buckland 2002 (fup@ai-junkie.com)
//
//------------------------------------------------------------------------
#pragma warning (disable:4786)
#include <set>
#include "misc/ConsoleUtils.h"
#include "messaging/Telegram.h"
class BaseGameEntity;
//to make code easier to read
const double SEND_MSG_IMMEDIATELY = 0.0f;
const int NO_ADDITIONAL_INFO = 0;
//to make life easier...
#define Dispatch MessageDispatcher::Instance()
class MessageDispatcher
{
private:
//a std::set is used as the container for the delayed messages
//because of the benefit of automatic sorting and avoidance
//of duplicates. Messages are sorted by their dispatch time.
std::set<Telegram> PriorityQ;
//this method is utilized by DispatchMessage or DispatchDelayedMessages.
//This method calls the message handling member function of the receiving
//entity, pReceiver, with the newly created telegram
void Discharge(BaseGameEntity* pReceiver, const Telegram& msg);
MessageDispatcher(){}
//copy ctor and assignment should be private
MessageDispatcher(const MessageDispatcher&);
MessageDispatcher& operator=(const MessageDispatcher&);
public:
//this class is a singleton
static MessageDispatcher* Instance();
//send a message to another agent. Receiving agent is referenced by ID.
void DispatchMessage(double delay,
int sender,
int receiver,
int msg,
void* ExtraInfo);
//send out any delayed messages. This method is called each time through
//the main game loop.
void DispatchDelayedMessages();
};
#endif
#ifndef TELEGRAM_H
#define TELEGRAM_H
//------------------------------------------------------------------------
//
// Name: Telegram.h
//
// Desc: This defines a telegram. A telegram is a data structure that
// records information required to dispatch messages. Messages
// are used by game agents to communicate with each other.
//
// Author: Mat Buckland (fup@ai-junkie.com)
//
//------------------------------------------------------------------------
#include <iostream>
#include <math.h>
struct Telegram
{
//the entity that sent this telegram
int Sender;
//the entity that is to receive this telegram
int Receiver;
//the message itself. These are all enumerated in the file
//"MessageTypes.h"
int Msg;
//messages can be dispatched immediately or delayed for a specified amount
//of time. If a delay is necessary this field is stamped with the time
//the message should be dispatched.
double DispatchTime;
//any additional information that may accompany the message
void* ExtraInfo;
Telegram():DispatchTime(-1),
Sender(-1),
Receiver(-1),
Msg(-1)
{}
Telegram(double time,
int sender,
int receiver,
int msg,
void* info = NULL): DispatchTime(time),
Sender(sender),
Receiver(receiver),
Msg(msg),
ExtraInfo(info)
{}
};
//these telegrams will be stored in a priority queue. Therefore the >
//operator needs to be overloaded so that the PQ can sort the telegrams
//by time priority. Note how the times must be smaller than
//SmallestDelay apart before two Telegrams are considered unique.
const double SmallestDelay = 0.25;
inline bool operator==(const Telegram& t1, const Telegram& t2)
{
return ( fabs(t1.DispatchTime-t2.DispatchTime) < SmallestDelay) &&
(t1.Sender == t2.Sender) &&
(t1.Receiver == t2.Receiver) &&
(t1.Msg == t2.Msg);
}
inline bool operator<(const Telegram& t1, const Telegram& t2)
{
if (t1 == t2)
{
return false;
}
else
{
return (t1.DispatchTime < t2.DispatchTime);
}
}
inline std::ostream& operator<<(std::ostream& os, const Telegram& t)
{
os << "time: " << t.DispatchTime << " Sender: " << t.Sender
<< " Receiver: " << t.Receiver << " Msg: " << t.Msg;
return os;
}
//handy helper function for dereferencing the ExtraInfo field of the Telegram
//to the required type.
template <class T>
inline T DereferenceToType(void* p)
{
return *(T*)(p);
}
#endif
#include "MessageDispatcher.h"
#include "BaseGameEntity.h"
#include "Time/CrudeTimer.h"
#include "EntityManager.h"
#include "Locations.h"
#include "MessageTypes.h"
#include "EntityNames.h"
#include <iostream>
using std::cout;
using std::set;
#ifdef TEXTOUTPUT
#include <fstream>
extern std::ofstream os;
#define cout os
#endif
//------------------------------ Instance -------------------------------------
MessageDispatcher* MessageDispatcher::Instance()
{
static MessageDispatcher instance;
return &instance;
}
//----------------------------- Dispatch ---------------------------------
//
// see description in header
//------------------------------------------------------------------------
void MessageDispatcher::Discharge(BaseGameEntity* pReceiver,
const Telegram& telegram)
{
if (!pReceiver->HandleMessage(telegram))
{
//telegram could not be handled
cout << "Message not handled";
}
}
//---------------------------- DispatchMessage ---------------------------
//
// given a message, a receiver, a sender and any time delay , this function
// routes the message to the correct agent (if no delay) or stores
// in the message queue to be dispatched at the correct time
//------------------------------------------------------------------------
void MessageDispatcher::DispatchMessage(double delay,
int sender,
int receiver,
int msg,
void* ExtraInfo)
{
SetTextColor(BACKGROUND_RED|FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
//get pointers to the sender and receiver
BaseGameEntity* pSender = EntityMgr->GetEntityFromID(sender);
BaseGameEntity* pReceiver = EntityMgr->GetEntityFromID(receiver);
//make sure the receiver is valid
if (pReceiver == NULL)
{
cout << "\nWarning! No Receiver with ID of " << receiver << " found";
return;
}
//create the telegram
Telegram telegram(0, sender, receiver, msg, ExtraInfo);
//if there is no delay, route telegram immediately
if (delay <= 0.0f)
{
cout << "\nInstant telegram dispatched at time: " << Clock->GetCurrentTime()
<< " by " << GetNameOfEntity(pSender->ID()) << " for " << GetNameOfEntity(pReceiver->ID())
<< ". Msg is "<< MsgToStr(msg);
//send the telegram to the recipient
Discharge(pReceiver, telegram);
}
//else calculate the time when the telegram should be dispatched
else
{
double CurrentTime = Clock->GetCurrentTime();
telegram.DispatchTime = CurrentTime + delay;
//and put it in the queue
PriorityQ.insert(telegram);
cout << "\nDelayed telegram from " << GetNameOfEntity(pSender->ID()) << " recorded at time "
<< Clock->GetCurrentTime() << " for " << GetNameOfEntity(pReceiver->ID())
<< ". Msg is "<< MsgToStr(msg);
}
}
//---------------------- DispatchDelayedMessages -------------------------
//
// This function dispatches any telegrams with a timestamp that has
// expired. Any dispatched telegrams are removed from the queue
//------------------------------------------------------------------------
void MessageDispatcher::DispatchDelayedMessages()
{
SetTextColor(BACKGROUND_RED|FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
//get current time
double CurrentTime = Clock->GetCurrentTime();
//now peek at the queue to see if any telegrams need dispatching.
//remove all telegrams from the front of the queue that have gone
//past their sell by date
while( !PriorityQ.empty() &&
(PriorityQ.begin()->DispatchTime < CurrentTime) &&
(PriorityQ.begin()->DispatchTime > 0) )
{
//read the telegram from the front of the queue
const Telegram& telegram = *PriorityQ.begin();
//find the recipient
BaseGameEntity* pReceiver = EntityMgr->GetEntityFromID(telegram.Receiver);
cout << "\nQueued telegram ready for dispatch: Sent to "
<< GetNameOfEntity(pReceiver->ID()) << ". Msg is " << MsgToStr(telegram.Msg);
//send the telegram to the recipient
Discharge(pReceiver, telegram);
//remove it from the queue
PriorityQ.erase(PriorityQ.begin());
}
}