找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 6007|回复: 0

一个MMO游戏服务逻辑系统(二)

[复制链接]
发表于 2011-8-25 14:50:49 | 显示全部楼层 |阅读模式
对于NPC的设计,一般的考量是,NPC属性和玩家属性是差不多的。或者很多是通用的,由于在游戏设计中存在各种NPC,各种血量,各种外形和各种AI,所以需要一个虚类来实现基础的NPC属性记录。而实际的NPC属性,则可以用一个结构体来实现。
  1. #define OBJECT_NAME_MAX 50     //对象最大名称长度
  2. class IObjectAI;
  3. class IObject
  4. {
  5. public:
  6.         virtual bool Update()                                  = 0;      //对象执行,根据不同的状态执行相应的代码
  7.         virtual bool SetState(int nState)                      = 0;      //设置对象状态(需要继承者自己实现)
  8.         virtual int  GetState()                                = 0;      //获得对象状态(需要继承者自己实现)
  9.         virtual int  GetNomalAttackMin()                       = 0;      //得到普通攻击力(最小)
  10.         virtual int  GetNomalAttackMax()                       = 0;      //得到普通攻击力(最大)
  11.         virtual int  SetHP(int nHP)                            = 0;      //设置一个HP的数值,返回当前HP
  12.         virtual int  GetHP()                                   = 0;      //得到当前HP的数值
  13.         virtual IObjectAI* GetObjectAI()                       = 0;      //得到这个对象的AI引擎指针
  14.         virtual int  GetRunRatio()                             = 0;      //得到逃跑比例
  15.         virtual void SetObjectID(int nObjectID)                = 0;      //设置ObjectID
  16.         virtual int  GetObjectID()                             = 0;      //获得ObjectID
  17.         virtual void SetName(const char* pName)                = 0;      //设置对象的名称
  18.         virtual char* GetName()                                = 0;      //得到对象的名称
  19.         virtual void SetObjectPosIndex(int nIndex)             = 0;      //设置当前对象由哪个刷新点刷出
  20.         virtual int  GetObjectPosIndex()                       = 0;      //获取当前对象由哪个刷新点刷出
  21.         virtual _MapPointPath* GetMapPointPath()               = 0;      //获取当前对象的路径参数
  22.         virtual void SetCurrPoint(_MapPoint* pCurrPoint)       = 0;      //设置当前位置
  23.         virtual _MapPoint GetCurrPoint()                       = 0;      //获取当前位置
  24.         virtual void SetTimeStamp(unsigned long lNPCTimeStamp) = 0;      //设置当前对象处理时间
  25.         virtual unsigned long GetTimeStamp()                   = 0;      //获得当前对象处理时间
  26.         virtual void SetSpeed(int nSpeed)                      = 0;      //设置对象速度
  27.         virtual int  GetSpeed()                                = 0;      //获得对象速度
  28.         virtual int  GetStandTimeInterval()                    = 0;      //得到停留的时间长度
  29.         virtual int  GetRangeID()                              = 0;      //得到区块ID
  30.         virtual void SetRangeID(int nRangID)                   = 0;      //设置区块ID
  31. };
复制代码
这里我把NPC需要的所有属性方法,主要是set和Get封装成了一个虚类,用于NPC对象的继承。对于一个NPC而言,它应该至少由两部分组成,一个是NPC的属性部分,一个是NPC AI处理部分。
首先,让我们来看看如何管理一个NPC对象是如何构成的。
  1. class CNPCObject : public IObject
  2. {
  3. public:
  4.         CNPCObject();
  5.         ~CNPCObject(void);
  6.         //初始化方法
  7.         void Init(_NPCAttribute objNPCAttribute, IObjectAI* pObjectAI);
  8.         void SetMapData(CMapBaseData* pMapBaseData);
  9.         void SetRangeBaseManager(CRangeBaseManager* pRangeBaseManager);
  10.         //对应属性值的各种读取与写入
  11.         int  GetState();
  12.         bool SetState(int nState);
  13.         int  GetNomalAttackMin();
  14.         int  GetNomalAttackMax();
  15.         int  GetRunRatio();
  16.         int  SetHP(int nHP);
  17.         int  GetHP();
  18.         void SetObjectID(int nObjectID);
  19.         int  GetObjectID();
  20.         void SetName(const char* pName);
  21.         char* GetName();
  22.         void SetObjectPosIndex(int nIndex);
  23.         int  GetObjectPosIndex();
  24.         _MapPointPath* GetMapPointPath();
  25.         void SetCurrPoint(_MapPoint* pCurrPoint);
  26.         _MapPoint GetCurrPoint();
  27.         void SetTimeStamp(unsigned long lNPCTimeStamp);
  28.         unsigned long GetTimeStamp();
  29.         void SetSpeed(int nSpeed);
  30.         int  GetSpeed();
  31.         int  GetStandTimeInterval();
  32.         int  GetRangeID();
  33.         void SetRangeID(int nRangID);
  34.         IObjectAI* GetObjectAI();
  35.         //心跳
  36.         bool Update();
  37.        
  38. private:
  39.         IObjectAI*    m_pIObjectAI;               //对象AI
  40.         _NPCAttribute m_NPCAttribute;             //对象属性
  41.         _MapPointPath m_MapPointPath;             //对象路径属性
  42.         _MapPoint     m_CurrPoint;                //当前NPC的位置
  43.         unsigned long m_lNPCTimeStamp;            //NPC最后被服务器处理的时间戳
  44.         int           m_nStateCooldown;           //状态执行最少持续时间
  45. };
复制代码

这里,所有的NPC属性是由一个结构体管理的。_NPCAttribut结构体负责存储指定NPC的一些信息。
  1. //NPC所有属性的结构体
  2. struct _NPCAttribute
  3. {
  4.         int  m_nObjectID;                 //设置对象ID
  5.         char m_szNpcName[NPC_MAX_NAME];   //当前NPC的名字
  6.         int  m_nHP;                       //当前NPC的HP
  7.         int  m_nAIType;                   //所绑定的AI策略ID
  8.         int  m_nAttackMin;                //最小攻击力
  9.         int  m_nAttackMax;                //最大攻击力
  10.         int  m_nRunRatio;                 //逃跑比例
  11.         int  m_nObjectState;              //对象当前状态
  12.         int  m_nNPCTypeID;                //NPC的类型ID
  13.         int  m_nObjectPosIndex;           //刷新点Index
  14.         int  m_nBaseSpeed;                //对象基础移动速度
  15.         int  m_nStandTimeInterval;        //行走间隔停留时间
  16.         int  m_nRangeID;                  //对象所在区块ID
  17.         int  m_nScriptID;                 //此对象对应的脚本ID,用于NPC扩展行为
  18.        
  19.         _NPCAttribute()
  20.         {
  21.                 m_nObjectID          = OBJECT_VAILD_ID;
  22.                 m_nObjectPosIndex    = OBJECT_VAILD_ID;
  23.                 m_szNpcName[0]       = '\0';
  24.                 m_nHP                = 0;
  25.                 m_nAIType            = 0;
  26.                 m_nAttackMin         = 0;
  27.                 m_nAttackMax         = 0;
  28.                 m_nRunRatio          = 0;
  29.                 m_nNPCTypeID         = 0;
  30.                 m_nBaseSpeed         = 0;
  31.                 m_nStandTimeInterval = 0;
  32.                 m_nRangeID           = 0;
  33.                 m_nScriptID          = 0;
  34.                 m_nObjectState       = ENUM_OBJECTSTATE::OBJECTSTATE_INIT;
  35.         };
  36.         _NPCAttribute& operator = (const _NPCAttribute& ar)
  37.         {
  38.                 this->m_nObjectID       = ar.m_nObjectID;
  39.                 this->m_nNPCTypeID      = ar.m_nNPCTypeID;
  40.                 this->m_nAIType         = ar.m_nAIType;
  41.                 this->m_nHP             = ar.m_nHP;
  42.                 this->m_nAttackMin      = ar.m_nAttackMin;
  43.                 this->m_nAttackMax      = ar.m_nAttackMax;
  44.                 this->m_nRunRatio       = ar.m_nRunRatio;
  45.                 this->m_nObjectState    = ar.m_nObjectState;
  46.                 this->m_nObjectPosIndex = ar.m_nObjectPosIndex;
  47.                 this->m_nBaseSpeed      = ar.m_nBaseSpeed;
  48.                 this->m_nRangeID        = ar.m_nRangeID;
  49.                 this->m_nScriptID       = ar.m_nScriptID;
  50.                 sprintf_safe(this->m_szNpcName, NPC_MAX_NAME, "%s", ar.m_szNpcName);
  51.                 return *this;
  52.         }
  53. };
复制代码

当然,这只是一个例子,你可以自由添加你想要的元素,但是切记,需要对应的给IObject提供接口。
这里要说一个小技巧,对于NPC,一般分为两种,一种是战斗类型的NPC,一种是服务类型的NPC,其实这两种NPC并无绝对区分必要,既能在某一定条件下变为战斗NPC也可以在一定条件下变为服务性NPC是一个很灵活的属性,不是吗?其实对于主NPC而言,一般情况是由地图编辑器安插在地图点上的(也就是俗称的NPC刷新点,当某一指定的NPC不存在了,那么一定时间后会在这里复活)。那么在这种情况下,我的所有NPC ID实际不用实时生成,而是在地图编辑的时候就制定了,这样做的好处是,我在地图上生成的所有NPC,我根据ID都能做到心里有数。而且同一个ID不会出现两次。当然,你可以为了游戏观赏性,NPC召唤出小NPC辅助,这部分NPC实际也可以ID指定的。这个ID实际就是资源出生点的ID。类似,在魔兽世界中的采矿点的矿藏,你其实也大可看成一种NPC,和普通攻击的NPC没有多大区别,都是提供服务类型的NPC。在这里还可以引出一个小技巧。比如某一个法师发出一个范围大招,比如火雨,在这个区域内的所有玩家将会受到火雨的伤害,持续时间5秒。其实这个实现非常简单,就是在法师出技能的时候,创造一个时限性的NPC,这个NPC的AI就是在周围放一个火伤害的空间。当然,在游戏中你是看不见这个NPC的,但是实际上,服务器上是有这个对象存在的,5秒后自动销毁。借此,我们可以利用这个机制,实现几乎各种复杂的法术,范围技能,甚至叠加的效果。
好了,对于一个场景而言,我出现的NPC种类是有限的,那么,我怎么把NPC的刷新点,NPC类型封装在一个场景文件中呢?
看看我是怎么做的。
  1.         //创建字典文件
  2.         sprintf_s(pFileName, MAX_MAPNAME, "npc.dict");
  3.         //打开NPC字典文件
  4.         pFile = fopen(pFileName, "wb");
  5.         int nNPCCount = 3;
  6.         fwrite(&nNPCCount, 1, sizeof(int), pFile);
  7.         _NPCAttribute npca1;
  8.         sprintf_s(npca1.m_szNpcName, NPC_MAX_NAME, "自由之眼");
  9.         npca1.m_nAttackMin         = 50;
  10.         npca1.m_nAttackMax         = 70;
  11.         npca1.m_nHP                = 230;
  12.         npca1.m_nRunRatio          = 50;
  13.         npca1.m_nAIType            = 0;
  14.         npca1.m_nBaseSpeed         = 5;
  15.         npca1.m_nNPCTypeID         = 1001;
  16.         npca1.m_nScriptID          = 1001;
  17.         npca1.m_nStandTimeInterval = 1000;
  18.        
  19.         fwrite(&npca1, 1, sizeof(_NPCAttribute), pFile);
  20.         sprintf_s(npca1.m_szNpcName, NPC_MAX_NAME, "蓝翼");
  21.         npca1.m_nAttackMin         = 30;
  22.         npca1.m_nAttackMax         = 45;
  23.         npca1.m_nHP                = 100;
  24.         npca1.m_nRunRatio          = 0;
  25.         npca1.m_nAIType            = 0;
  26.         npca1.m_nBaseSpeed         = 6;
  27.         npca1.m_nNPCTypeID         = 1002;
  28.         npca1.m_nScriptID          = 1002;
  29.         npca1.m_nStandTimeInterval = 1000;
  30.         fwrite(&npca1, 1, sizeof(_NPCAttribute), pFile);
  31.         sprintf_s(npca1.m_szNpcName, NPC_MAX_NAME, "天际");
  32.         npca1.m_nAttackMin         = 300;
  33.         npca1.m_nAttackMax         = 500;
  34.         npca1.m_nHP                = 1200;
  35.         npca1.m_nRunRatio          = 0;
  36.         npca1.m_nAIType            = 0;
  37.         npca1.m_nBaseSpeed         = 10;
  38.         npca1.m_nNPCTypeID         = 1003;
  39.         npca1.m_nScriptID          = 1003;
  40.         npca1.m_nStandTimeInterval = 1000;
  41.         fwrite(&npca1, 1, sizeof(_NPCAttribute), pFile);
  42.         fflush(pFile);
  43.         printf("[map](%s)Create NPC dict OK.\n", pFileName);
  44.         fclose(pFile);
复制代码

如上,我创建一个字典文件,这个字典主要是服务于当前场景可能出现的所有NPC种类(现在我这里有三种NPC,分别为自由之眼,蓝翼和天际),这里有这些NPC对应的血量,类型等等,如果你愿意,你可以添加NPC颜色,称号。这样更具备趣味性。这个字典是为刷新点文件做对照的,对于刷新点文件,我们可以这么做。
  1.         //创建刷新点
  2.         sprintf_s(pFileName, MAX_MAPNAME, "map1.pos");
  3.         //打开NPC字典文件
  4.         pFile = fopen(pFileName, "wb");
  5.         int nObjectPosCount = 1500;
  6.         fwrite(&nObjectPosCount, 1, sizeof(int), pFile);
  7.         for(int i = 0; i < 500; i++)
  8.         {
  9.                 _MapObjectPos objMapObjectPos1;
  10.                 objMapObjectPos1.m_nGuid         = 100001 + i;
  11.                 objMapObjectPos1.m_nPosX         = 5;
  12.                 objMapObjectPos1.m_nPosY         = 5;
  13.                 objMapObjectPos1.m_nObjectType   = 1001;
  14.                 objMapObjectPos1.m_nTimeInterval = 500;
  15.                 fwrite(&objMapObjectPos1, 1, sizeof(_MapObjectPos), pFile);
  16.         }
  17.         for(int i = 0; i < 500; i++)
  18.         {
  19.                 _MapObjectPos objMapObjectPos2;
  20.                 objMapObjectPos2.m_nGuid         = 100501 + i;
  21.                 objMapObjectPos2.m_nPosX         = 10;
  22.                 objMapObjectPos2.m_nPosY         = 10;
  23.                 objMapObjectPos2.m_nObjectType   = 1002;
  24.                 objMapObjectPos2.m_nTimeInterval = 500;
  25.                 fwrite(&objMapObjectPos2, 1, sizeof(_MapObjectPos), pFile);
  26.         }
  27.         for(int i = 0; i < 500; i++)
  28.         {
  29.                 _MapObjectPos objMapObjectPos3;
  30.                 objMapObjectPos3.m_nGuid         = 101001 + i;
  31.                 objMapObjectPos3.m_nPosX         = 15;
  32.                 objMapObjectPos3.m_nPosY         = 15;
  33.                 objMapObjectPos3.m_nObjectType   = 1003;
  34.                 objMapObjectPos3.m_nTimeInterval = 500;
  35.                 fwrite(&objMapObjectPos3, 1, sizeof(_MapObjectPos), pFile);
  36.         }
  37.        
  38.         fflush(pFile);
  39.         printf("[map](%s)Create map pos OK.\n", pFileName);
  40.         fclose(pFile);
复制代码

好了,比如我创造了1500个NPC,按照上面的字典一样500个,这里objMapObjectPos3.m_nGuid就是我的NPC场景中的唯一ID,m_nPosX和m_nPosY是出生点,m_nObjectType会对应NPC字典文件中的npca1.m_nNPCTypeID。这样我就知道这个刷新点上的NPC是一个什么家伙。npca1.m_nAIType指的是绑定AI的类型,我可以在服务器上提供一批AI供NPC选择,比如激进的,保守的,勇敢的,怯懦的,爱主人的。。。这样,在地图编辑器上,我就可以实现策划随意使用我创建的NPC和AI,这两个对象是独立的,可以组合的,这样就能组合成丰富多彩的NPC体系。
那么或许你会问我,为什么要将刷新点和NPC字典分开,呵呵,这是从地图编辑器上考虑,更多的时候,策划是在调整地图上的NPC对象而非创造NPC,这两个事情完全可以并行去做。分为两个文件对于游戏开发者更适合专注的去做好其一。
好了,NPC和刷新点的文件已经创造好了,让我们来看看游戏服务器是怎么读取的。
  1.         //加载NPC字典信息
  2.         if(false == m_objNPCObjectManager.Init(objSenceInfo.m_szNPCDictFileName, objSenceInfo.m_nMaxObjectCount))
  3.         {
  4.                 printf_s("[CScene::LoadScene]Read CNPCObjectManager file error.\n");
  5.                 return false;
  6.         }
  7.         //加载刷新点信息
  8.         if(false == m_objMapObjectPosManager.Init(objSenceInfo.m_szObjectPosFileName))
  9.         {
  10.                 printf_s("[CScene::LoadScene]Read m_objMapObjectPosManager file fail.\n");
  11.                 return false;
  12.         }
  13.         //根据刷新点,创建相应的NPC
  14.         for(int i = 0; i < m_objMapObjectPosManager.GetCount(); i++)
  15.         {
  16.                 _MapObjectPos* pMapObjectPos = m_objMapObjectPosManager.GetObjectPos(i);
  17.                 if(NULL != pMapObjectPos)
  18.                 {
  19.                         int nNPCID = m_objNPCObjectManager.CreateNPCObject(pMapObjectPos->m_nGuid, pMapObjectPos->m_nObjectType);
  20.                         CNPCObject* pNPCObject = (CNPCObject* )m_objNPCObjectManager.GetNPCObject(nNPCID);
  21.                         if(NULL != pNPCObject)
  22.                         {
  23.                                 _MapPoint objCurrPoint;
  24.                                 objCurrPoint.m_nX = pMapObjectPos->m_nPosX;
  25.                                 objCurrPoint.m_nY = pMapObjectPos->m_nPosY;
  26.                                 pNPCObject->SetCurrPoint(&objCurrPoint);
  27.                                 pNPCObject->SetObjectPosIndex(i);
  28.                                 pNPCObject->SetState(ENUM_OBJECTSTATE::OBJECTSTATE_NOMAL);
  29.                                 pNPCObject->SetMapData((CMapBaseData* )&m_objMapData);
  30.                                 pNPCObject->SetTimeStamp(GetTimeStamp());
  31.                                 pNPCObject->SetRangeBaseManager((CRangeBaseManager* )&m_objRangeManager);
  32.                                 pNPCObject->GetObjectAI()->Init();
  33.                                 printf("[CScene::LoadScene]Add NPC (%s).\n", pNPCObject->GetName());
  34.                         }
  35.                 }
  36.         }
复制代码

我根据上述原则,先加载所有的NPC字典信息,然后加载刷新点文件,根据文件的定义,我把NPC创建出来,并还原到地图的点上,细心的你,可能会发现SetRangeBaseManager()这个东东是干什么的。呵呵,这是一个"区域"管理器,用于NPC移动广播周边信息的内容。这个东东我会在下一讲详细的说明,姑且你先当它是一个NPC位置相关的属性好了。
好了,NPC的基础数据还原了,让我们看看AI是怎么做的。
首先,我们要理解什么是AI,AI实际并不是一组数据,更确切的讲,AI是一种在一定事件发生后,你需要做怎样的反应。拿《游戏编程精粹6》里面所讲的Quick3,当NPC出生后,我先要确定周围的情况(OnLookAround),然后做出思考,我应该怎么设计我的移动路线(OnMove),当我在移动过程中,受到了攻击的时候(OnBeAttack),我应该如何处理?或者说当我看见敌人(OnSeeTargetIn),我应该区分是敌是友,决定是否靠近到攻击距离(OnApproach),然后攻击或者防御(OnAttack)。如果敌人离开我的视觉范围(OnSeeTargetLeave),我会怎么办?最后,敌人被我打死了(OnTargetDead),我是因该怎么处理?亦或者,我死了(OnDead),我因该在死的时候做些什么动作?
看看,随着想象,你已经非常清楚的知道了,一个AI应该包含多少个基础方法,至于实现,我们只需要填充这些事件产生后的处理结果即可。其实仔细想想,这些事件几乎能覆盖你所接触到的基本反应需求。
那么我来设计一个通用的AI接口吧,所有的AI处理,都可以继承于此。
  1. class IObjectAI
  2. {
  3. public:
  4.         IObjectAI()
  5.         {
  6.                 m_pObject = NULL;
  7.         };
  8.         virtual ~IObjectAI() {};
  9.         //设置类型本体,AI拥有者对象指针
  10.         void SetObject(IObject* pObject)
  11.         {
  12.                 m_pObject = pObject;
  13.         };
  14.         //设置AI初始化信息
  15.         virtual bool Init()                            = 0;
  16.     //进入视野,pTarget为进入视野的对象
  17.         virtual bool OnSightInEvent(IObject* pTarget)  = 0;
  18.         //离开视野,pTarget为离开视野的对象
  19.         virtual bool OnSightOutEvent(IObject* pTarget) = 0;
  20.         //被攻击,pTarget是攻击者名称,nType为攻击类型,nSkillID为技能ID
  21.         virtual bool OnBeAttackEvent(IObject* pAttack, int nType, int nSkillID) = 0;
  22.         //死亡,被那个攻击者杀死,pAttack为攻击者对象
  23.         virtual bool OnDeadEvent(IObject* pAttack)     = 0;
  24.         //逃跑,打不过的时候
  25.         virtual bool OnRunEvent(IObject* pAttack)      = 0;
  26.         //设置地图指针,用于AI寻路等计算
  27.         virtual void SetMapBaseData(CMapBaseData* pMapBaseData) = 0;
  28.         //设置曲别针指针,用于区域对象搜索
  29.         virtual void SetRangeBaseManager(CRangeBaseManager* pRangeBaseManager) = 0;
  30.         //平常调用,当什么事件都没有发生的时候会调用此
  31.         virtual bool Update()                          = 0;
  32. public:
  33.         IObject* m_pObject;
  34. };
复制代码

这里我只是举例,实际情况中,你完全可以丰富我的基类,提供更多的基础事件。
对于AI,我并不是只需要算法就够了,我还可能需要一个列表,举例来说,如果我身边出现了多个敌人,我需要多个敌人来一个顺序,先打哪个,再打哪个,亦或,我需要一个队友列表。如此等等,根据策划的衍生来决定这个基类的构造。
那么,我来做一个真正的AI吧。
  1. class CNPCAI : public IObjectAI
  2. {
  3. //AI事件相关接口
  4. public:
  5.         CNPCAI();
  6.         ~CNPCAI();
  7.         bool Init();
  8.         void SetNPCObjectManager(CObjectManager* pNPCObjectManager);
  9.         void SetMapBaseData(CMapBaseData* pMapBaseData);
  10.         void SetRangeBaseManager(CRangeBaseManager* pRangeBaseManager);
  11.         CObjectManager* GetNPCObjectManager();
  12.         bool OnSightInEvent(IObject* pTarget);
  13.         bool OnSightOutEvent(IObject* pTarget);
  14.         bool OnBeAttackEvent(IObject* pAttack, int nType, int nSkillID);
  15.         bool OnDeadEvent(IObject* pAttack);
  16.         bool OnRunEvent(IObject* pAttack);
  17.         bool Update();
  18. private:
  19.         bool DoRandomPathFound();       //随机路径寻找
  20.         bool DoRandomMove();            //按照指定的路径移动
  21.         bool DoLookAround();            //查看周围目标对象
  22.         bool DoStandState();            //站立状态,如果是被动状态这里只等待一段时间,主动状态切换用户到OBJECTSTATE_FIND状态,开始搜索身边的对象。
  23.         bool DoAccack();                //进入进攻状态
  24. //对象列表相关接口,AI需要根据事件存储相应的其他对象列表
  25. public:
  26.         //添加关注的敌人
  27.         bool AddEnemy(int nObjectID);
  28.         //删除关注的敌人
  29.         bool DelEnemy(int nObjectID);
  30.         //得到当前的敌人,先进先出
  31.         IObject* GetCurrEnemy();
  32.         //清除所有敌人目标
  33.         bool ClearEnemy();
  34.        
  35. private:
  36.         CRandom            m_Random;                   //随机数生成器
  37.         IObjectList        m_ObjEnemyList;             //敌人列表
  38.         CObjectManager*    m_pNPCObjectManager;        //NPC对象管理器
  39.         CMapBaseData*      m_pMapBaseData;             //地图接口类指针
  40.         CRangeBaseManager* m_pRangeBaseManager;        //区域曲别针类指针
  41.         _RangeSearch*      m_pRangeSearch;             //周边区域块信息指针
  42. };
复制代码

在这里,我提供了对虚类定义的实现,你可以参考我的NPC类,里面创建的时候会从AI池中获取一个空闲的AI指针。在里面实现相关的动作。
所有操作都是基于NPC的状态来实现的,bool Update();AI运算的接口,和场景OnUpdate()心跳保持一致。
这样,我就可以根据策划需求,实现一整套NPC的逻辑和AI。
下一讲,我将会讲,怎么把NPC和地图,场景串联起来,成为一个有机的整体。
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-11-22 03:04 , Processed in 0.020121 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表