游戏AI系统

AI学习 2011.3.23

一、游戏服务器玩家对象AI(控制层面AI)

以玩家为例,玩家对象的状态的切换,靠对象状态管理器类来完成。

1、玩家对象的状态机的构造

对象状态管理器类UBI_CStateManager管理所有类别对象(玩家对象、怪物对象、宠物对象等)的状态;该类内部靠下面数据结构为每一个类别的对象都维系一个状态机:

struct  StateHeadm_pStateMatrix[OBJECT_TYPE_NUMBER];     // 每种Object类型一个状态转换矩阵

/

         //       状态转换数据结构:

         //

         //       StateHead 1 -> StateElement 3 ->StateElement 2 -> ...

         //          |

         //       StateHead 2 -> StateElement 1 ->StateElement 3 -> ...

         //          |

         //       StateHead 3 -> StateElement 1 ->StateElement 2 -> ...

         //

         //       - StateHead链表:      

         //  表示某种对象可以有的状态列表;

         //

         //  - StateElement的子链表:

         //       每个StateHead都有一个元素为StateElement的子链表,

         //  用于表示当前状态可以转换的状态列表;

/

struct StateElement

{

                   UBI_INT                                                                    m_StateType;                // 状态类型

                   UBI_INT                                                                    m_nLevel;                     // 状态转换优先级

                   UBI_WCHAR*                                                          m_pScript;                    // 执行脚本

                   UBI_BOOL                                                                m_bInternal;                 // 是否配置为内部执行函数

                   PAIJudgeFunction                                                   m_pFunction;             // 内部实现函数

                   struct StateElement*                                                  m_pNext;                      //可转换下一状态结点的指针

}

struct StateHead

{

                   UBI_INT                                                                    m_StateType;                // 状态类型

                   UBI_WCHAR*                                                          m_pExcuteScript;         // 该状态的逻辑执行脚本

                   UBI_BOOL                                                                m_bInternal;                 // 是否配置为内部执行函数

                   PAIActionFunction                                                  m_pFunction;               // 内部实现函数

                   struct StateElement*                                                  m_pNextElement;         // 后序状态队列

                   struct StateHead*                                                      m_pNextHead;             // 对象允许下一状态结点指针

}

成员函数UBI_CStateManager::_LoadObjectStateSwitchTable负责解析表格HumanStateSwitch.tab(纵向看),生成状态机结构:

// 行列相同,即写对角线上定义的为当前状态下执行的操作

if (nLineIndex == nColumnIndex)

{

         if (pHead[nColumnIndex]->m_pExcuteScript!= UBI_NULL)

         {

                   if (UBI_CTools::Strcmp(pHead[nColumnIndex]->m_pExcuteScript,INTERNAL_FUNCTION) == 0)

                   {

                            pHead[nColumnIndex]->m_pFunction= GETAISYSTEMPTR->GetAIObject((OBJECT_TYPE)index)->GetAIActionFunction(pHead[nColumnIndex]->m_StateType);

                            pHead[nColumnIndex]->m_bInternal = UBI_TRUE;

                   }

         }

}

else

{

         UBI_CStateManager::StateElement *pElement= UBI_NULL;

         UBI_NEW_RETURN(pElement,UBI_CStateManager::StateElement(pData, pHead[nLineIndex]->m_StateType),UBI_FALSE);

         if (pElement->m_pScript != UBI_NULL)

         {

                   if (UBI_CTools::Strcmp(pElement->m_pScript, INTERNAL_FUNCTION)== 0)

                   {

                            // 若配置为内部函数, 则初始化pFunction指针

                            pElement->m_pFunction= GETAISYSTEMPTR->GetAIObject((OBJECT_TYPE)index)->GetAIJudgeFunction(

                            pHead[nColumnIndex]->m_StateType, pElement->m_StateType);

                            pElement->m_bInternal= UBI_TRUE;

                   }

         }

         GETSTATEMANAGERPTR->AddStateElement((OBJECT_TYPE)index,pHead[nColumnIndex], pElement);

}

上面中的GetAIActionFunction是获取状态执行函数,GetAIJudgeFunction是获取状态切换判断函数。他们注册如下:

// 注册:状态切换判断函数

         static AIJudgeFunctionMap sAIJudgeFunctionMap[]         = {

                            REGISTER_JUDGE_FUNCTION(HUMAN_STATE_DEAD,   HUMAN_STATE_IDLE,

                                     static_cast<PAIJudgeFunction>(&UBI_CAIHuman::CanDeadToIdle)),

                            REGISTER_JUDGE_FUNCTION(HUMAN_STATE_IDLE,   HUMAN_STATE_DEAD,

                                     static_cast<PAIJudgeFunction>(&UBI_CAIHuman::CanIdleToDead)),

                            REGISTER_JUDGE_FUNCTION(HUMAN_STATE_IDLE,   HUMAN_STATE_FIGHT,

                                     static_cast<PAIJudgeFunction>(&UBI_CAIHuman::CanIdleToFight)),

                            REGISTER_JUDGE_FUNCTION(HUMAN_STATE_FIGHT,   HUMAN_STATE_DEAD,

                                     static_cast<PAIJudgeFunction>(&UBI_CAIHuman::CanFightToDead)),

                            REGISTER_JUDGE_FUNCTION(HUMAN_STATE_FIGHT,   HUMAN_STATE_IDLE,

                                     static_cast<PAIJudgeFunction>(&UBI_CAIHuman::CanFightToIdle)),

         };

// 注册:状态执行函数

// 基础AI

         m_pAIActionFunctionMap[HUMAN_STATE_IDLE

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Idle_Action));

         m_pAIActionFunctionMap[HUMAN_STATE_DEAD]

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Dead_Action));

         m_pAIActionFunctionMap[HUMAN_STATE_STALL]

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Stall_Action));   

         m_pAIActionFunctionMap[HUMAN_STATE_STALL]

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Transfer_Action));

         m_pAIActionFunctionMap[HUMAN_STATE_CHARGE]

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Charge_Action));

         m_pAIActionFunctionMap[HUMAN_STATE_GATHER]

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Gather_Action));

         m_pAIActionFunctionMap[HUMAN_STATE_CHANNEL]

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Channel_Action));

         m_pAIActionFunctionMap[HUMAN_STATE_FOLLOW]

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Follow_Action));

         m_pAIActionFunctionMap[HUMAN_STATE_SEEK]

                                     =REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Seek_Action));

 

// 扩展AI

         m_pAIActionFunctionMap[HUMAN_STATE_FIGHT]

                                     = REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Fight_Action));

         m_pAIActionFunctionMap[HUMAN_STATE_MOVE]

                                     = REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Move_Action));

 

经过上面过程,就把玩家对象的状态机构造出来了(玩家不同状态间的切换条件、以及切换到某种状态后执行的操作都被解析完毕)。

 

2、玩家对象的状态机的使用

在AI基类AIObject的心跳中,调用:

1)     UBI_CAIObject::UpdateObjectState( UBI_CObject* pObject, UBI_UINT uTime),这里主要做如下工作:

(1)   根据当前对象状态,调用对应的状态执行函数;

UBI_CStateManager::StateHead* pExcuteState = GETSTATEMANAGER.GetExcuteState(pObject->GetObjectType(), nState);

(this->*(pExcuteState->m_pFunction))(pObject,uTime); //调用状态执行函数

(2)   做状态切换;

UBI_CStateManager::StateElement*pStateList = pExcuteState->m_pNextElement;

while(pStateList != UBI_NULL)

{

         if(UBI_TRUE== (this->*(pStateList->m_pFunction))(pObject))//调用状态切换判断函数

         {

                   pObject->ChangeState(pStateList->m_StateType);

break;

         }

         pStateList= pStateList->m_pNext;

}

那么上面的ChangeState里做了什么呢?这里主要是UBI_CAIObject::LeaveState(preState),和UBI_CAIObject::EnterState(curState),除了做了一些状态变化时的附加操作,里面会调用脚本函数以外,主要就是调用:pObject->SetState(curState);pObject->ClearState(preState);服务器上玩家对象状态变化后,就会给客户端发包UBI_CSCUpdateObjectStatePacket

那么可见ChangeState至关重要,什么时候调用它呢?

A、一种是人为调用,比如玩家进入摆摊逻辑了,那么就调用:pHuman->ChangeState(HUMAN_STATE_STALL)

B、另一种就是上面的,在tick中轮询检查状态切换条件满足否,用到一些早已被注册的切换判断函数,例如玩家从“战斗状态”切换到“死亡状态”的判断函数:

UBI_BOOL UBI_CAIHuman::CanFightToDead(UBI_CObject* pObject)

{

         if ( UBI_FALSE == pObject->IsSetState(HUMAN_STATE_FIGHT))

         {

                   Assert(0);

                   UBI_LOG(LM_DEBUG,"Wrong State Switch Call... %d", pObject->GetBaseState());

                   return UBI_FALSE;

         }

         UBI_INT   nHP = static_cast<UBI_CObjectHuman*>(pObject)->GetHP();

         if (nHP <= 0)

         {

                   DoDie(pObject);

                   // 可以切换至死亡状态

                   return UBI_TRUE;

         }

         return UBI_FALSE;

}

从“战斗状态”切换到“休闲状态”的判断函数:

UBI_BOOL UBI_CAIHuman::CanFightToIdle(UBI_CObject* pObject)

{

         UBI_CObjectHuman* pHumanObject= (UBI_CObjectHuman*)pObject;

         UBI_CTimer* pFightStateTimer= pHumanObject->GetFightStateTimer();

         if (UBI_NULL != pFightStateTimer)

         {

                   if (UBI_TRUE == pFightStateTimer->IsSetTimer())

                   {

                            if ( UBI_TRUE == pFightStateTimer->IsReachTerm(pObject->GetCurrentTime()))

                                     // 计时器器到时, 切换状态

                                     return UBI_TRUE;

                            else

                                     return UBI_FALSE;

                  }

                   else

                   {

                            // 战斗状态计时器未启动, 切换状态

                            return UBI_TRUE;

                   }

         }

         return UBI_FALSE;

}

从“休闲状态”切换到“战斗状态”的判断函数:

UBI_BOOL UBI_CAIHuman::CanIdleToFight(UBI_CObject* pObject)

{

         UBI_CObjectHuman* pHumanObject= (UBI_CObjectHuman*)pObject;

         UBI_CTimer* pFightStateTimer= pHumanObject->GetFightStateTimer();

         if (UBI_NULL != pFightStateTimer)

         {

                   if (UBI_TRUE == pFightStateTimer->IsSetTimer())

                   {

                            if (UBI_TRUE != pFightStateTimer->IsReachTerm(pObject->GetCurrentTime()))

                            {

                                     // 战斗状态计时器启动, 切换状态

                                     return UBI_TRUE;

                            }

                   }

         }

         return UBI_FALSE;

}

上面的战斗状态计时器在UBI_CObjectHuman::FightStateTrigger调用后开始计时。

 

2)      在心跳中,除了使用UpdateObjectState轮询基础状态切换外(这种轮询效率上能再改进吗?),还调用了UpdateObjectExtendState进行扩展状态轮询。

扩展状态与基础状态之间不是互斥关系、可共存,扩展轮询里面会调用状态执行函数(有一部分的扩展状态已经注册好了执行函数),以及脚本函数FinishState。它与基础状态轮询不同之处在于:它不做状态切换,基础状态的切换是在轮询中完成的,设置状态等也是在轮询中做的,那么扩展状态的切换是在哪做的呢?

扩展状态一般是由手工调用ChangeState完成切换的,例如:当客户端发包CSMovePacket过来,LogicServer在Handler里试图调用ChangeState(HUMAN_STATE_MOVE),如果成功的话,玩家就变成“移动状态”了,在下一个tick中再次执行UpdateObjectExtendState时,发现玩家是“移动状态”,那么就执行相应的状态执行函数:Move_Action,因为已经注册了:

m_pAIActionFunctionMap[HUMAN_STATE_MOVE]

= REGISTER_ACTION_FUNCTION(static_cast<PAIActionFunction>(&UBI_CAIHuman::Move_Action));

 

3)     扩展状态之间的互斥

前面提到HumanStateSwitch.tab表格中只配置了基础状态的转换关系,。。。。

 

4)     状态更新包UBI_CSCUpdateObjectStatePacket

 

二、游戏客户端对象AI(模拟层面AI)

1、以基础AI为例说明,在UBI_CObjectCharacterAI::Tick_BaseAI中做了如下(1)(2)两方面工作:

(1) BaseAI 命令列表m_listBaseAICommand中取出一条AI基础命令,根据这个AI命令的具体类型,去做相应处理,例如:

switch(pObjectAICommand->m_nAICommandID)

{

                  case OBJECT_AI_COMMAND_MOVE:

                            BeginProcessAIMove(pObjectAICommand);

}

BeginProcessAIMove中会做:

(a)     状态更新:用当前状态去更新基础AI的上一状态m_nObjectBaseAILastState,并把当前状态m_nObjectBaseAICurrentState更新成OBJECT_AI_STATE_MOVE

(b)     更新当前运行的基础AI命令m_pCurrentBaseAICommandpObjectAICommand

 

(2) 此外,根据当前的基础AI命令类型,去真正逻辑处理,例如:

switch(GetCurrentBaseAIState())

{

                  case OBJECT_AI_STATE_MOVE:

                         ProcessAILogicMove(nTime);

}

 ProcessAILogicMove中会做:

(a)     调用PlayAction(OBJECT_AI_STATE_MOVE)播放“移动”动作;

(b)    解析当前基础AI命令m_pCurrentBaseAICommand

 

(3) 从上面可知,基础AI基于是命令方式的,因为AI状态的更新是由AI命令来控制的,那么AI命令又是怎么来的呢?也就是说上面的m_listBaseAICommand里面的数据来源是哪呢?

一般是在UBI_CSC****Packet包发过来时,客户端根据具体的逻辑行为,生成相应的AI命令后在Pushm_listBaseAICommand中的,例如:

UBI_VOID WINAPI UBI_CPacketExecute::SCNewMoveHumanObjecExecute(UBI_CPacket* pPacket) //创建玩家OBJ 移动

{

         //先停止当前移动

         …… …

         //添加新的移动命令

         command.m_nCommandID= OBJECT_AI_COMMAND_MOVE;

         vector<UBI_WORLD_POS>pathNode;

         pathNode.push_back(pSCMoveHumanObjec->GetTargetPosition());

         command.m_apParam[0]= &pathNode;

         command.m_auParam[1]= (UBI_UINT)pathNode.size();

         command.m_anParam[2]= MOVE_GOAL_COMMAND_NULL;

         pObject->ProcessCharacterAICommand(&command, pSCMoveHumanObjec->GetMoveLogicCount());

}

客户端会调用ProcessCharacterAICommand,其中会根据具体命令类型,调用CreateObjectAICommand构造一个具体命令实例,例如对于“移动命令”,会构造出一个UBI_CObjectAICommandMove命令,之后把它放入m_listBaseAICommand命令队列中,之后就交由上面的(1)(2)处理。

 

2、  那么客户端的AI状态和服务器发过来的对象状态有啥关系呢?服务器同步过来的对象状态怎么利用的呢?

UBI_CPacketExecute::UpdateObjectStateExecute中,主要做了:

pCharacterData->SetState / ClearState(pObjectState->GetState());

pObject->UpdateSateEvent(pObjectState->GetState(),UBI_TRUE / UBI_FALSE);

客户端通过UBI_CObjectCharacterData::IsSetState来利用服务器发来的那些状态,进行一些逻辑判断操作,例如:

UBI_INT UBI_CObjectMyselfAI::GetActionIDByAIState(ObjectAIState actionType,UBI_INT nSkillID)//通过AIState得到ActionID

{

         UBI_INT nActionID= INVALID_VALUE;

         UBI_CObjectCharacterData* pCharacterData= m_pObject->GetCharacterData();

         if(OBJECT_AI_STATE_MOVE== actionType)

         {

            if(pCharacterData->IsSetState(HUMAN_STATE_WALK))

                            nActionID = ACTION_WALK;

            else

                            nActionID = ACTION_RUN;

         }

         else if(OBJECT_AI_STATE_IDLE==actionType)

         {

        //根据优先级播放状态动作(先假定:击倒>昏迷>恐惧>迷惑>战斗>站立)

        // 击倒

        if (UBI_TRUE == pCharacterData->IsSetState(HUMAN_STATE_DOWN))

                nActionID= ACTION_DOWN;

        // 昏迷

        else if (UBI_TRUE == pCharacterData->IsSetState(HUMAN_STATE_COMA))

                            nActionID = ACTION_COMA;

        // 恐惧

        else if (UBI_TRUE == pCharacterData->IsSetState(HUMAN_STATE_FEAR))

                            nActionID = ACTION_FEAR;

        // 迷惑

        else if (UBI_TRUE == pCharacterData->IsSetState(HUMAN_STATE_PUZZLE))

                            nActionID = ACTION_PUZZLE;

        // 战斗

                   else if (UBI_TRUE == pCharacterData->IsSetState(HUMAN_STATE_FIGHT))

                            nActionID = ACTION_COMBAT;

        // 站立

                   else

                            nActionID = ACTION_STAND;

         }

         else

                   nActionID = UBI_CObjectCharacterAI::GetActionIDByAIState(actionType,nSkillID);

         …. …. ….

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值