项目简介:基于各种开源库(OGRE、OpenSteer、Newton、Raknet、OpenAL)的游戏引擎项目,目前项目正在进行2.0版本的开发,主要工作是对前一版本从整体框架上进行大幅度改进,建立健壮的异常和运行时错误处理机制,以及完成各个子系统框架的重构,这篇文章就是根据笔者对AI子系统重构实践写成。
设计动机:
1、抽象程度低。引入任何一个新类型事物,都需要加入其对应的个体类或群体类及群体管理器。
2、不同个体之间的耦合程度高。具体体现在有着某种关系的个体之间,如追踪与被追踪关系。其中,一类个体A的状态受到另一类个体B的直接干涉(B通过设置A的一类标志,来迫使A的状态转变),这显然与自然规则和OOD原则相违背。按照面向对象的设计思想,一个对象应该尽量做到内聚,也就是说对象应该有自主能力,它掌控着自身所有的状态和变化,必要时提供自身的一些信息(比如,位置信息)。
3、灵活度低。如果需要添加/删除/更新功能,则需要对每个个体类型的代码进行相应修改。
设计思路:
1、对于抽象程度低的设计缺陷,采用提炼所有存在类中并集,加之配置文件运行时动态具类化(即,根据配置文件和创建参数动态确定创建对象的类型,如Tiger、Deer等)。之所有考虑提炼并集而不是交集,目的是让其以配置文件形式而非代码(即,继承)来具类。
2、通过提炼AI对象之间的相互作用关系,得出一种以角色(Role)为基础的关系抽象。在此设计中,每个对象都一个或者多个角色,同时又有其感兴趣的一个或者多个角色。角色管理器担当着管理角色-对象列表(如图1)。对象在创建时根据其角色将自身注册到全局的角色管理器中。一个对象能够在需要时在角色管理器中搜索其感兴趣的角色对象,获取其参数后,根据计算值更新自己的状态。如时序图2所示。
图1
图2 示例时序图(省略了Deer的状态更新过程)
实现:
1、AI个体抽象类CIndividual,维护着个体的智能,是OpenSteer::SimpleVehicle和Ogre::UserDefinedObject的子类。
2、AI个体抽象包装类CIndividualWrapper,负责对象的视觉表现,对CIndividual起着包装作用,是CIndividual的友元。
3、角色管理器模板类CRolesManager,实现如下:
000002
000003 /** 全局角色管理器
000004 @author Groov0V(groov0v.luo@gmail.com)
000005
000006 负责管理角色对应的对象列表
000007 */
000008
000009 template<typename T>
000010 class CRolesManager
000011 {
000012 public:
000013 ~CRolesManager()
000014 {
000015 RoleRegistryMapIter iter;
000016 TRAV(m_roleRegistryMap , iter)
000017 {
000018 SAFE_DELETE( iter->second );
000019 }
000020 }
000021
000022 /** 将对象指针添加到角色ID对应的列表中
000023 */
000024 void Add( unsigned int roleID , T* pObj)
000025 {
000026 RoleRegistryMapIter iter = m_roleRegistryMap.find(roleID);
000027 if(iter != m_roleRegistryMap.end())
000028 iter->second->push_back(pObj);
000029 else
000030 {
000031 std::list<T*>* listPtr = new std::list<T*>;
000032 listPtr->push_back(pObj);
000033 m_roleRegistryMap.insert(make_pair(roleID , listPtr));
000034 }
000035 }
000036
000037 /** 从角色列表中移除对象
000038 */
000039 void Remove( unsigned int roleID , T* pObj)
000040 {
000041 RoleRegistryMapIter iter = m_roleRegistryMap.find(roleID);
000042 if(iter != m_roleRegistryMap.end())
000043 iter->second->erase(pObj);
000044 }
000045
000046 /** 返回指定角色的对象列表
000047 */
000048 std::list<T*>* GetObjList( unsigned int roleID)
000049 {
000050 RoleRegistryMapIter iter = m_roleRegistryMap.find(roleID);
000051 if(iter != m_roleRegistryMap.end())
000052 return iter->second;
000053 else
000054 return NULL;
000055 }
000056 private:
000057 /** 角色-对象注册表
000058 */
000059 stdext::hash_map< unsigned int , std::list<T*>*> m_roleRegistryMap;
000060 typedef typename stdext::hash_map< unsigned int , std::list<T*>*>::iterator RoleRegistryMapIter;
000061 };
000062
000063 #define g_rolesManager DMF::globe_instance< CRolesManager<CIndividual> >::Instance()
000064
4、配置脚本(利用TinyXML实现读/存):
000002 <Object name="Tiger">
000003 <Roles>
000004 <Role name="Tracer" id="0" />
000005 </Roles>
000006 <DisRoles>
000007 <DisRole name="BeTracer" id="1" />
000008 </DisRoles>
000009 <NumericParams>
000010 <Param name="SIGHT_DISTANCE" value="1200" />
000011 <Param name="PURSUIT_DISTANCE" value="800" />
000012 <Param name="ATTACK_DISTANCE" value="200" />
000013 <Param name="WALK_STRAIGHT_TIME" value="2.5" />
000014 <Param name="WALK_TIME" value="10.0" />
000015 <Param name="IDLE_TIME" value="2.5" />
000016 <Param name="PURSUIT_TIME" value="15" />
000017 <Param name="WALK_SPEED" value="20" />
000018 <Param name="PURSUIT_SPEED" value="100" />
000019 <Param name="ATTACK_SPEED" value="120" />
000020 <Param name="MOVE_SPEED" value="50" />
000021 <Param name="LENGTH" value="35" />
000022 <Param name="ENERGY" value="100" />
000023 <Param name="STEER_FORCE" value="200" />
000024 <Param name="STEER_MAX_SPEED" value="150" />
000025 <Param name="STEER_RADUIS" value="25" />
000026 <Param name="WANDER_JITTER" value="100" />
000027 <Param name="WANDER_RADUIS" value="50" />
000028 <Param name="ENERGY_DES" value="20" />
000029 </NumericParams>
000030 <StringParams>
000031 <Param name="PLACEHOLDERMATNAME" value="GOOFPlaceHolderTiger" />
000032 <Param name="PLACEHOLDERMESHNAME" value="soundnode.mesh" />
000033 <Param name="Type" value="GameTigerObject" />
000034 <Param name="MeshFile" value="dire_cat.mesh" />
000035 </StringParams>
000036 <AnimationStates>
000037 <State id="0" name="cpause1" />
000038 <State id="3" name="crun" />
000039 <State id="-1" name="cwalk" />
000040 </AnimationStates>
000041 </Object>
000042 <Object name="Deer">
000043 <Roles>
000044 <Role name="BeTracer" id="1" />
000045 </Roles>
000046 <DisRoles>
000047 <DisRole name="Tracer" id="0" />
000048 </DisRoles>
000049 <NumericParams>
000050 <Param name="WALK_SPEED" value="20" />
000051 <Param name="RUN_SPEED" value="120" />
000052 <Param name="IDLE_TIME" value="8.0" />
000053 <Param name="WALK_TIME" value="8.0" />
000054 <Param name="ENERGY" value="100" />
000055 <Param name="LENGTH" value="35" />
000056 <Param name="WALK_STRAIGHT_TIME" value="1.5" />
000057 <Param name="RUN_DISTANCE" value="600" />
000058 <Param name="SAFE_DISTANCE" value="820" />
000059 <Param name="STEER_FORCE" value="200" />
000060 <Param name="STEER_MAX_SPEED" value="150" />
000061 <Param name="STEER_RADUIS" value="25" />
000062 <Param name="WANDER_RADUIS" value="50" />
000063 <Param name="WANDER_JITTER" value="40.0" />
000064 <Param name="ENERGY_DES" value="20" />
000065 </NumericParams>
000066 <StringParams>
000067 <Param name="PLACEHOLDERMATNAME" value="GOOFPlaceHolderDeer" />
000068 <Param name="PLACEHOLDERMESHNAME" value="soundnode.mesh" />
000069 <Param name="Type" value="GameDeerObject" />
000070 <Param name="MeshFile" value="deer.mesh" />
000071 </StringParams>
000072 <AnimationStates>
000073 <State id="0" name="creadyl" />
000074 <State id="6" name="crun" />
000075 <State id="-1" name="cwalk" />
000076 </AnimationStates>
000077 </Object>
000078 <Object name="Bird">
000079 ... ...
000080 </Object>
000081 <Object name="Butterfly">
000082 ... ...
000083 </Object>
000084 </AIObjects>