遊戲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);

         …. …. ….

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章