posts - 72,  comments - 4,  trackbacks - 0
泰坦之旅的ai

titan quest的AI用的是切换式的状态机,而寻路用的是path engine第3方库,游戏中有一个任务编辑器主要是生成每一个任务,每个任务中可以生成多个触发器(trigger),每个触发器可以生成一系列条件(condition),并可生成条件成立时要触发的动作(action). 这个有点类似war3的事件编辑器。

以下是切换式状态机跟踪的一些记录,很乱,没写总结,只是用于备忘。。。
AI移动跟踪
WinMain消息循环中的Game::Run()中先
1. gGameEngine->GetFrustumForPlayer(updateFrustum, player->GetCoords().origin); 得到frustum

2. gEngine->Update(&updateFrustum);进行更新, 其中
   调用 world->Update(worldFrustumList);

   然后遍历每个frustum取得对应region进行更新

   a. 看看当前region是否与frustum相交,如果是则load,否则扩大一点再相交,这时如果相交则将添加到后台加载。
   b. 查看portal相关region进行更新
   3. 查看connectedRegions(在地图装载得到玩家出生位置后进行玩家所在region扩大后跟其它region判断相交所得)进行更新 

3. Region的更新中进行level更新:
  level->Update(frustumList, numFrustums, elapsedTime);
  在其中先取得在frustum中的Entity, 然后再遍历更新所有Entity,
  之后再更新Entity所在4叉树空间.

4. 角色的更新:
   Entity的更新中, 先UpdateSelf再UpdateAttachedEntities
   (Entity中有PhysicsRigidBody成员physicsObject)

5. UpdateSelf会跑到Character::PreAnimationUpdate中执行 baseController->Update(localTimeDelta);从而跳到ContrallerAI中执行GetExecutingState()->OnUpdate(deltaTime); 从而到达ControllerNpcStateIdle的更新中进行状态切换到SetState("Wander", ControllerAIStateData(0, 0, 0)); 
之后再跑到ControllerNpcStateWander::OnBegin()
处理:
    int closestPoint = GetClosest(GetController().GetWanderPoints());
    GetController().SetCurrentWanderPoint(closestPoint);
    if (!MoveToCurrentWanderPoint())
    {
        SetState("Idle", ControllerAIStateData());
        return;
    }
这个在MoveToCurrentWanderPoint()函数里从队列中取出当前目标点并ControllerAI::WalkTo
其中会GetController().WalkTo(location, target);即ControllerAI::WalkTo(。。), 这会执行:
 HandleAction(new WalkAction(GetParentId(), GetAI()->GetPathPosition(), location, target));
这个会执行:
        SetCurrentAction(action);
        GetCurrentAction()->Execute();从而运行了WalkAction::Execute(), 这其中又调用了Character::WalkTo
 这又会:
    movementMgr->SetNewPathTarget进行处理


最后在CharacterMovementManager::Update()中进行角色位置更新:

        CreateLocalPath(deltaTime, speed);

        if (!MoveDownPath(deltaTime, speed))
        {
            return false;
        }
        UpdateCharacterPosition(deltaTime, speed);

void UIDialogWindow::OnOpen()会调用 myNpc->AddSocialTarget( target );
在void ControllerNpcStateIdle::OnUpdate(Time deltaTime)中判断如果有SocialTarget则进入Chat状态处理

【状态处理例子】
Monster的初始状态是Idle,在Monster的更新函数里:
一)。进行搜敌,并切换成pursue状态
,并调追捕状态的OnBegin()函数处理,如果Monster不能行走则切回Idle状态,否则如果搜不到敌
人则切换到Return状态,否则根据当前技能id找出要移到的位置点.
------------------------------------------------------
【注意】找到要移到的位置点细节:
I. (Character::GetMoveToPoint)找出目标点:
  1. 目标是自己则直接return自己位置
  2. 没有目标则保存goalPoint=目标点,distance=技能施放范围,待后处理.
  3. 目标是FixedItem则return FixedItem->GetMoveToPoint(..)里进行处理
  4. 目标是StrategicMovementBase,则return sm->GetMoveToPoint(..)里进行处理
  5. 目标是Entity,则goalPoint = entity->GetCoords().origin;并且如果entity阻挡则让goalPoint回移一点以免浮点出错?否则distance=GetExtents() + entity-

>GetExtents();待后处理
  6. 目标是Character,则,
     a. 如果是朋友
       1)如果当前是移动状态,则要求目标给出DefenseSlot(防御位置点)作为goalPoint直接返回.
       2)否则如果能直线通路到目标点的话就直接返回离目标比较近的一点(去掉半径),不能直通则返回0点  
     b. 如果是敌人
       1)如果没有技能,则goalPoint=目标点,distance=GetExtents() + target->GetExtents();待后处理
       2)如果技能不需要AttackSlot或者this是Player, 则
        goalPoint=目标点,distance = GetExtents() + target->GetExtents() + skill->GetRange();待后处理
       3)否则直接返回目标算出的AttackSlot攻击点位置.
  上述2和5以及6中的b.的1)和2)需要待后处理的最后通过 
    WorldVec3 finalPoint = movementMgr->GetPointAwayFromGoal(goalPoint, distance);
  得到最终位置, 这个位置还要特殊判断一下如果不在Region中或者路径不能到达的话,则直接用TranslateToFloor到goalPoint.

【说明】:
  1. 什么是AttackSlot/DefenseSlot:
每个角色可以有n个x距离的AttackSlot/DefenseSlot,它会在周围x半径的圆上平分出n个位置点,当有其它人要攻击它或者要来帮助(防御)它时,它就会在旁边找一个较近的还

没其它人用过的slot分给这个其它人。
  2. movementMgr->GetPointAwayFromGoal()函数细节:
    先是FindPath(目标)得出path,再用path->Advance(pathLength - distance)得到回退一点的位置。

II. 找到目标点后,还要调用movementGoalManager->GetClosestMovePoint(目标点) 进行处理:
    这个函数主要是给范围武器用的,如果不是使用范围武器的Monster则不会调整目标点。
    如果是的话则遍历全局对象movementGoalManager中的m_MoveGoalMap目标点映射表,求出其它在同一region中的Monster
    所在目标点跟当前Monster所在目标点的距离,如果距离比较近则调用GetPointAwayFromGoal(目标点, 3.0);调整当前Monster的目标点回退一点,并将些处理后的位置及些

Monsterid映射到m_MoveGoalMap目标点映射表中。这样遍历过所有其它Monster的目标点进行一一检测处理后就会尽量避免与其它Monster挤到一起。
------------------------------------------------------

找到要移到的位置点之后,
1. 用(CloseEnoughToUseSkill(GetCurrentEnemy(), GetCurrentSkillID()))判断是否在技能攻击范围内,
如果在则用IsPathClear(GetCurrentEnemy())来判断是不是到直通目标,是则切换成Attack状态后返回,否则切换成NavigateObstacle状态后返回。

2. 否则敌人不在攻击范围内就看当前是否已站在目标点,是则切回Idle状态后返回

3. 不在目标点则看是不是能够移到目标点,不能则切回Idle状态后返回。

4. 能移到目标点则MoveTo到目标点.
   这个MoveTo会调用
   HandleAction(new MoveToAction(GetParentId(), GetAI()->GetPathPosition(), location, target, GetAI()->GetSkillReferenceNumber(skill), 1.0, animType));
   这个会执行到MoveToAction, 其中会转调:
        monseter->SetCurrentAttackTarget(targetId, location, skillNumber);
        monseter->SkillWarmUp( skillNumber, false );
        monseter->MoveTo(location, GetBlendTime(), animType);
        monseter->PlayLoopingRunningSound();
  而monseter->MoveTo又会调用 movementMgr->SetNewPathTarget(movementMgr->GetPathPosition(), surfacePoint, alreadyThere))
  然后再用SetActionState(Character_ActionState_Move);设置Action的状态为Move,并通过PlayAnimation播放run动作(即调用GetAnimationBase( type ).PlayAnimation( 

actor, selection, speedModifier, loop, iteration ),这个可以参考我另一个动画跟踪文档看细节)

二)。搜敌后会接着调用ControllerAI::Update()更新函数:
   1. 先进行当前状态更新()
      由于前面Monster切换到了pursue追捕状态,所以执行到
      ControllerMonsterStatePursue::OnUpdate(),其中:
      a. 追捕所用时间过了,则切换回return状态
      b. 重新选择技能时间到了则重新找出一个bestSkill.(这也避免了万一当前技能是melee,而玩家总是绕着Monster转,怪就会不停地追不上而没法肉搏攻击)
      c. 用CloseEnoughToUseSkill判断是否够技能施放距离,够的话用IsPathClear判断攻击方向是否可通,是则转Attack状态,不通则转NavigateObstacle状态.

   2. 再遍历执行m_PreloadQuestActionList中action.

上述都在【更祥细一点】中1。Character::UpdateSelf()中进行
接着会到【更祥细一点】中2。 Update subsystems:中的FollowPath()进行真正的移动处理.
posted on 2012-08-13 10:09 flipcode 阅读(325) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理