事件管理器(EventMgr)
Ascent的每个事件是一个TimedEvent对象,其中包含了一个回调函数对象、间隔时间、回调次数和标志信息。
EventMgr只是封装了添加事件的操作(并不负责运行事件的回调函数),真正执行事件回调函数的是EventableObjectHolder的update()函数。当使用EventMgr::AddEvent的时候,需要指定目标对象(一个EventableObject对象)、一个回调函数和1~4个参数,类型、间隔时间、回调次数(0不限)和标志。函数原型:
void AddEvent(Class *obj, void (Class::*method)(P1), P1 p1, uint32 type, uint32 time, uint32 repeats, uint32 flags)
事件会加入到EventableObject对象的m_events(multimap)中,并会加入到EventableObjectHolder对象中。在EventableObjectHolder::update()函数中将会执行到时间的事件回调函数。
EventableObjectHolder中,活动事件都在表m_events中,但是对事件的添加删除各添加了一个缓冲链表(m_insertPool,m_deletePool),并对两个链表单独加锁。当再次回调update()的时候才真正的添加删除事件。因为活动的事件比较多,这样就可以避免对m_events的频繁锁竞争,或者其他线程添加事件被阻塞的情况以及线程死锁的情况。
关系如图1的上半部分。附两段注释:
/**
* @class EventableObject
* EventableObject means that the class inheriting this is able to take
* events. This 'base' class will store and update these events upon
* receiving the call from the instance thread / WorldRunnable thread.
*/
/**
* @class EventableObjectHolder
* EventableObjectHolder will store eventable objects, and remove/add them when they change
* from one holder to another (changing maps / instances).
*
* EventableObjectHolder also updates all the timed events in all of its objects when its
* update function is called.
*
*/
AI与脚本管理器(ScriptMgr):
与大多管理器一样,ScriptMgr也是一个singleton类。主要负责AI和脚本的加载注册及管理。
脚本加载
关于脚本,都是以动态库的形式实现,放在配置的脚本目录中(默认为script_bin)。ScriptMgr加载脚本时会遍历该目录下的所有dll文件,并显式加载。
脚本动态库需要有三个导出函数:_exp_get_version
typedef void(*exp_script_register)(ScriptMgr * mgr);
typedef uint32(*exp_get_script_type)();
typedef uint32(*exp_get_version)();
这三个导出函数将被ScriptMgr用来判断该脚本动态库的版本和类型(脚本引擎,脚本函数。),并注册该脚本动态库。
exp_script_register需要提供ScriptMgr的对象指针给脚本动态库,由脚本动态库调用ScriptMgr的注册函数注册EntryId和对应的创建函数。注册的创建函数以EntryId为Key分类放在hashmap中。
脚本使用
图1中的下半部分列出了脚本类与Ascent类的关系,针对不同的类(需要用到脚本的类)有一个对应的脚本类基类(图中只列出了相关的3种,还有一些script类目前没有涉及到,但思想是一样的),该基类提供了各种虚函数接口,会在对应的地方调用。当需要一个脚本对象时,会使用在ScriptMgr中注册的创建函数来生成一个脚本对象。而该脚本对象一般就是从脚本类基类派生实现的(可参考脚本目录中的moon等项目,有c++和lua的实现)。
以类Creature为例, Creature属性中有CreatureAIScript的对象指针_myScriptClass,该指针指向一个CreatureAIScript对象或一个其的派生类对象。脚本就是通过从CreatureAIScript派生并实现其相应接口来实现逻辑扩展。当脚本动态库加载后,脚本动态库会向ScriptMgr注册一个EntryID和一个创建函数指针。该创建函数可以创建并返回一个脚本中实现的派生类对象,并返回一个基类对象指针。当Creature对象加入到游戏世界时,会调用Creature::LoadScript加载脚本对象:
void Creature::LoadScript()
{
_myScriptClass = sScriptMgr.CreateAIScriptClassForEntry(this);
}
其中调用了ScriptMgr::CreateAIScriptClassForEntry创建脚本对象:
CreatureAIScript* ScriptMgr::CreateAIScriptClassForEntry(Creature* pCreature)
{
//查找注册的函数指针
CreatureCreateMap::iterator itr = _creatures.find(pCreature->GetEntry());
if(itr == _creatures.end())
return NULL;
//调用函数指针创建对象
exp_create_creature_ai function_ptr = itr->second;
return (function_ptr)(pCreature);
}
CreateAIScriptClassForEntry中以EntryID查找注册的创建函数(该函数的一般实现就是return new CLASSNAME;),并调用创建函数返回一个派生的脚本类对象。该对象指针将赋给_myScriptClass,在合适的时候通过宏 CALL_SCRIPT_EVENT调用其虚函数接口。
AI实现:
基本的AI实现都在类AIInterface中,AIInterface是一个基于事件的有限状态机,在HandleEvent中完成状态转换,在Update时刷新当前状态的行为动作。每个Unit对象都会有一个AIInterface的指针(如图1),在需要的时候通过GetAIInterface获取指针并调用相应的函数接口。将AI单独分离出来而作为成员指针,降低了状态机的实现复杂度,也使脚本对象一样可以派生扩展,从而也可以动态替换AI。
阅读(794) | 评论(0) | 转发(0) |