游戏管理器组件给我们提供了在不修改游戏管理器的情况下灵活扩展我们的自定义行为的能力。游戏管理器组件是基于消息来工作的,定义自定义行为的基本流程就是创建自定义类型的消息,在合适的时候发送消息,创建自定义游戏管理组件并重写自己的消息处理。将自定义组件添加到游戏管理器,游戏管理器会自动将消息发送给游戏管理器组件(调用组件的消息处理函数)。下面我们首先来结合实例分析该过程的具体流程及其使用方法。
添加自定义行为有两种方式,一种是不定义自己的消息类型和消息,直接用现有的消息类型和消息对象,只是扩展自己的游戏管理器组件,也就是直接对现有消息扩展消息处理。总之这两种方式都少不了要定义自己的游戏管理器组件。那么我们就先来看看如果定义自己的游戏管理器组件并使其工作。后续我们会介绍如何扩展自己的消息类型以及消息对象使得我们可以更为灵活的添加自己的游戏行为,该过程可以参考testAAR例子。
首先我们来看游戏管理器组件,该类有四个虚函数是非常重要的,那就是virtual void DispatchNetworkMessage(const Message& message)和virtual voidProcessMessage(const Message& message)以及virtual voidOnAddedToGM();和virtual void OnRemovedFromGM();我们在定义自己的组件时,至少会重写它们中的一个,第一个是处理网络消息,第二个是处理本地消息。有一点要特别注意,前两个函数都接受一个Message对象作为输入参数。添加自己的行为,只需要根据具体情况重写这四个函数即可。最简单的两个例子就是dtGame:: DefaultMessageProcessor类和dtGame::BaseInputComponent类,在我们扩展自己的行为时可以参考。扩展了自己的游戏管理器组件类后,我们就可以创建组件对象并将组件对象添加到游戏管理器对象中,具体就是调用dtGame::GameManager的voidAddComponent(GMComponent& component, const ComponentPriority& priority= ComponentPriority::NORMAL)函数。
好了,上面就是简单的如何扩展自己的游戏管理器组件并让其起作用的简单描述。下面我们看看它是具体如何工作的,也就是这几个函数是在哪调用的。通过AddComponent函数的实现代码可以看到OnAddedToGM就是在这里调用的,OnRemovedFromGM是在RemoveComponent中调用的,那么DispatchNetworkMessage和ProcessMessage呢?我来告诉你,是在void GameManager::DoSendMessageToComponents(const Message&message, bool toNetwork)中调用的,在该函数中会遍历GameManager的所有组件,同时我们可以看到如下代码:
if (toNetwork)
{
component->DispatchNetworkMessage(message);
}
else
{
component->ProcessMessage(message);
}
这里就是我们说的不需要修改游戏管理器就能添加自定义行为的过程的关键所在,但是还没有到头儿,我们继续向上追溯,我们发现void GameManager::DoSendNetworkMessages()
和void GameManager::DoSendMessage(const Message& message)
中调用了上面的函数。继续追踪
void GameManager::DoSendMessage(constMessage& message)
发现
void GameManager::DoSendMessage()
中调用了
void GameManager::DoSendMessage(constMessage& message),
继续追踪发现
void GameManager::PreFrame(doubledeltaSimTime, double deltaRealTime)
中调用了void GameManager::DoSendNetworkMessages()
和
void GameManager::DoSendMessage(),
参数中的消息从哪来的?就是在这两个函数中遍历消息队列从消息队列获取来的。你还没感觉到已经到了源头吗?那我们接着跟踪,你会发现最终是在
void GameManager::OnMessage(MessageData*data)
中又调用了
void GameManager::PreFrame(doubledeltaSimTime, double deltaRealTime)。
到这里我们应该明白了整个流程,System触发
void GameManager::OnMessage(MessageData*data)
后引起了这一连串动作。这个过程已经明确说明了游戏管理器和游戏管理器组件是如何工作的了。我们说到这里,你应该能够通过扩展自己的游戏管理器组件添加自己的行为了。
但在应用开发中,情况往往会更复杂,针对不同的应用情况,系统自带的消息类型以及消息对象不能满足需要,这时候我们不但需要扩展自己的游戏管理器组件对象,同时还要扩展自己的消息。下面我们就来看看消息如何扩展。
这个问题涉及到消息和消息类型两种对象,主要问题就是如何创建自己的消息类型和消息,如何发送自己的消息。
每一个消息对象都对应一个消息类型,我们可以通过同一个消息类来搭载不同消息类型,消息类型也是通过一个类对象来实现,一个消息类中有一个消息类型对象,我们可以只扩展消息类型而用现有的消息类来构造该类型的消息对象,但有时情况很复杂,现有的消息类不能满足我们的要求,比如我们需要在消息对象中传递我们自己定义的一些参数,这时候就需要我们扩展自己的消息类。
我们首先来看看如何扩展自己的消息类型。要扩展自己的消息类型非常简单,一般我们可以创建一个头消息类型头文件(我们以testAAR例子为例),在该文件中通过宏添加我们的消息类型的声明如下:
DT_DECLARE_MESSAGE_TYPE_CLASS_BEGIN(TestAARMessageType,TEST_AAR_EXPORT)
static TestAARMessageType PLACE_ACTOR;
static TestAARMessageType RESET;
static TestAARMessageType REQUEST_ALL_CONTROLLER_UPDATES;
static TestAARMessageType PRINT_TASKS;
static TestAARMessageType UPDATE_TASK_CAMERA;
static TestAARMessageType PLACE_IGNORED_ACTOR;
DT_DECLARE_MESSAGE_TYPE_CLASS_END()
在实现文件中添加:
DT_IMPLEMENT_MESSAGE_TYPE_CLASS(TestAARMessageType);
TestAARMessageTypeTestAARMessageType::PLACE_ACTOR("PLACE_ACTOR", "Place actormessage", "", USER_DEFINED_MESSAGE_TYPE + 1,DT_MSG_CLASS(dtGame::Message));
TestAARMessageTypeTestAARMessageType::RESET("RESET", "Reset message","Resets the scene", USER_DEFINED_MESSAGE_TYPE + 2,DT_MSG_CLASS(dtGame::Message));
TestAARMessageTypeTestAARMessageType::REQUEST_ALL_CONTROLLER_UPDATES("REQUEST_UPDATES","Requests for updates from the controller", "",USER_DEFINED_MESSAGE_TYPE + 3, DT_MSG_CLASS(dtGame::Message));
TestAARMessageTypeTestAARMessageType::PRINT_TASKS("PRINT_TASKS", "Prints thetasks", "", USER_DEFINED_MESSAGE_TYPE + 4,DT_MSG_CLASS(dtGame::Message));
TestAARMessageTypeTestAARMessageType::UPDATE_TASK_CAMERA("UPDATE_TASK_CAMERA","Updates the task camera", "", USER_DEFINED_MESSAGE_TYPE +5, DT_MSG_CLASS(dtGame::Message));
TestAARMessageTypeTestAARMessageType::PLACE_IGNORED_ACTOR("PLACE_IGNORED_ACTOR","Place ignored actor message", "",USER_DEFINED_MESSAGE_TYPE + 6, DT_MSG_CLASS(dtGame::Message));
我们注意到上面的DT_MSG_CLASS(dtGame::Message),这就是告诉系统该消息类型对应的消息类是什么。
系统默认自带了很多消息类型供我们使用,详情可参看MessageType类。
下面我们通过将上面的宏展开看看消息类型是如何让游戏管理器识别的。
我们将宏展开后如下:
#defineDT_DECLARE_MESSAGE_TYPE_CLASS_BEGIN(CLS, EXPORT_MACRO)
class EXPORT_MACRO CLS : publicdtGame::MessageType
{
DECLARE_ENUM(CLS)
protected:
template<typename MessageClass>
CLS(const std::string& name, const std::string& category,
const std::string& description,const unsigned short id, const MessageClass*)
: dtGame::MessageType(name, category, description, id, (constMessageClass*)(NULL))
{
AddInstance(this);
}
virtual ~CLS() {}
public:
};
再将其中的DECLARE_ENUM宏展开如下:
#define DECLARE_ENUM(EnumType) \
public: \
typedef std::vector<EnumType*> EnumerateListType; \
\
static const EnumerateListType& EnumerateType() \
{ \
return EnumType::mInstances; \
} \
\
static const std::vector<dtUtil::Enumeration*>& Enumerate() \
{ \
return EnumType::mGenericInstances; \
} \
\
static EnumType* GetValueForName(const std::string& name); \
\
private: \
static EnumerateListType mInstances; \
static std::vector<dtUtil::Enumeration*> mGenericInstances; \
static void AddInstance(EnumType* instance); \
public:
所以最后DT_DECLARE_MESSAGE_TYPE_CLASS_BEGIN宏展开后完整如下:
class EXPORT_MACRO CLS : publicdtGame::MessageType
{
public:
typedef std::vector<EnumType*> EnumerateListType;
static const EnumerateListType& EnumerateType()
{
return EnumType::mInstances;
}
static const std::vector<dtUtil::Enumeration*>& Enumerate()
{
return EnumType::mGenericInstances;
}
static EnumType* GetValueForName(const std::string& name);
private:
static EnumerateListType mInstances;
static std::vector<dtUtil::Enumeration*> mGenericInstances;
static void AddInstance(EnumType* instance);
public:
protected:
template<typename MessageClass>
CLS(const std::string& name, const std::string& category,
const std::string& description,const unsigned short id, const MessageClass*)
: dtGame::MessageType(name, category, description, id, (constMessageClass*)(NULL))
{
AddInstance(this);
}
virtual ~CLS() {}
public:
};
对于我们上面的例子就是把上面的CLS替换成TestAARMessageType就可以了。
实现文件中的宏展开后如下:
#defineIMPLEMENT_ENUM(EnumType) \
EnumType::EnumerateListType EnumType::mInstances; \
std::vector<dtUtil::Enumeration*> EnumType::mGenericInstances; \
void EnumType::AddInstance(EnumType* instance) \
{ \
EnumType::mInstances.push_back(instance); \
EnumType::mGenericInstances.push_back(instance); \
} \
EnumType* EnumType::GetValueForName(const std::string& name) \
{ \
for (unsigned i = 0; i < mInstances.size(); ++i) \
{ \
if ((*mInstances[i]) == name) \
{ \
return mInstances[i]; \
} \
} \
return NULL; \
}
实现文件中该宏后面的代码就是调用消息类型的构造函数来构造对应的全生命周期对象(静态对象):
MessageType(const std::string&name, const std::string& category,
const std::string&description, const unsigned short id, const MessageClass*)
: dtUtil::Enumeration(name)
, mCategory(category)
, mDescription(description)
, mId(id)
{
AddInstance(this);
dtGame::MessageFactory::RegisterMessageType<MessageClass>(*this);
}
我们可以看到,在构造函数中首先将该消息类型对象保存了起来,然后将该消息类型及其对应的消息注册到消息工厂。注册代码如下:
template<typename DerivedType>
bool RegisterType(UniqueIdType id)
{
if (this->objectTypeMap.find(id) !=this->objectTypeMap.end())
{
return false;
}
this->objectTypeMap[id] =&construct<BaseType,DerivedType>;
return true;
}
将该消息类型注册进来并创建了相应的消息对象创建函数实例,以后就从这个objectTypeMap中返回这个对象创建函数构造一个新的对象供用户使用了。
简单说就是我们扩展了消息类型后并创建对应的全生命周期对象,在创建对象时,对象自己会自动将其以及其对应的消息注册到消息对象工厂,当创建指定类型的消息时通过游戏管理器的消息工厂进行创建,消息工厂会从注册的消息类型以及消息对象实例映射表中创建指定消息类型对应的消息对象实例并返回。具体可参见函数MessageFactory::CreateMessage。
扩展消息类型已经明白了,扩展消息非常简单,我们可以把消息看做一个数据结构,不同的消息主要就是参数不同而已,在创建我们自己的消息时直接从Message类派生自己的消息类,并在构造函数中添加自己的自定义参数即可。要发送自定义的消息只要在你需要的地方创建该类型的消息并调用游戏管理器的SendMessage
说到这里,我们对于如何在程序中定义自己的行为有了大致了解,三种策略可用,也可以组合一起用,就是扩展游戏管理器组件,扩展消息类型,扩展消息。