Chicken的代码解剖 :2 ChickenAIController(寻路部分)

Posted on 2012-11-07 19:46  neocsl  阅读(488)  评论(0编辑  收藏  举报

 

  迷迷糊糊的从AchievementManager过来,是不是有点瞌睡。来来来!!!给你提供一顿美味的AIController。

  首先学习这一章能给你带来好几项能力:

1.掌握让AI巡逻的技能。做潜入游戏是非常需要的哦!

2.赋予你iOS拖曳一件物品的强大技能。在PC平台这可是让你望尘莫及的哦!  

  

  因此我想将这一节分两部分讲。那么先从寻路开始吧。

  你先得在场景中放置pylon然后编译路径。在一个叫InitNavigationHandle的函数中做这些事。

simulated function InitNavigationHandle()
{
 if(NavigationHandleClass!=none&&NavigationHandle==none)     //NavigationHandleClass引擎中定义了这个类但是没有引用
 {
     NavigationHandle=new(self) NavigationHandleClass;
 }
}

    这是很简单的内容,差不多UDK的寻路都得有这一句,就当公式记吧。
  另外还有两个路径函数得填充,GeneratePathToLocation和GeneratePathToActor一个是朝location走,一个是朝actor走,两个都返回bool。

  两者的参数基本上都一样,除了vector goal和actor goal。

  NavigationHandle下面有两个东西,PathConstraintList和PathGoalList一个是限制路径,一个是目标。就像流水一样规范行为才能达到目的。

event bool GeneratePathToLocation(vector goal,optional float WithinDistance,optional bool bAllowPartialPath)
{
 if(NavigationHandle==none)
 {
  InitNavigationHandle();
  return false;
 }

 NavigationHandle.PathConstraintList=none;
 NavigationHandle.PathGoalList=none;

 class'NavMeshPath_Toward'.Static.TowardPoint(NavigationHandle,Goal);
 class'NavMeshGoal_At'.Static.AtLocation(NavigationHandle,Goal,WithinDistance,bAllowPartialPath);

 return NavigationHandle.FindPath();
}

event bool GeneratePathToActor(Actor goal,optional float WithinDistance,optional bool bAllowPartialPath)
{
 if(NavigationHandle==none)
 {
  InitNavigationHandle();
  return false;
 }


 NavigationHandle.PathConstraintList=none;
 NavigationHandle.PathGoalList=none;

 class'NavMeshPath_Toward'.Static.TowardGoal(NavigationHandle,Goal);
 class'NavMeshGoal_At'.Static.AtActor(NavigationHandle,Goal,WithinDistance,bAllowPartialPath);

 return NavigationHandle.FindPath();
}

上面两个函数分别返回了到达地点和Actor的路径。

下面这个函数是巡逻系统的重心,它定义了pawn是反转180°还是随机转身。

var float  previousRotationChange;

var bool bFroceTurnAround;

function float GetNewWanderRotation()

{

  if(bForceTurnAround)

  return 180;

  return (PreviousRotationChange+(Rand(8)*45))%360;

}

 返回一定的角度,或是直接转身。这里面发现直接用的float。怎么样将float转化为角度呢?很简单,你只需要将(数值*DegToUnrRot)即可。

  

  接下来的函数是非常重要的,这也是巡逻的核心部分,竖起耳朵听好!let's begin.

  当你看到全局变量的时候,脑海中肯定想到的是这个变量肯定会在别的地方运用。下边这个变量我们可以调节自动巡逻的下一个目的点的距离。可能会有性能影响,同时还会影响到这个pawn的随机巡逻的每段路程长度。

var(chicken) float MinMovementDistance;

  LookDir其实就是预备到下一点的矢量,计算了角色的转身方向和距离。

Local vector LookDir;

LookDir=normal(vector(pawn.rotation))*MinMovementDistance;

  这里面当然有一个转身方向我们得要获取,这非常重要。我们当然想的是从现有的身体转向转到wander的方向变化。

local Rotation DirectionChange;

PreviousRotationChange=GetNewWanderRotation();

DirectionChange.pitch=0;
DirectionChange.roll=0;
DirectionChange.yaw=PreviousRotationChange*DegToUnrRot;

获取新的转身方向,然后将转身的变化角度传给DirectionChange,注意人物的pitch和roll方向不能乱飘,在现实中除非见到没喝脉动的人会成为这个样子;)。

给你偷偷介绍一个独门绝招,>>符号是非常牛逼的,通常vector>>rotation也就是改变了一下坐标系,比如你的方式是PlayerViewOffset>>Pawn.Rotation也就是变成以你的角色转向为参考系。

同时这个方法有另外一个函数TransformVectorByRotation(PlayerViewOffset,Pawn.Rotation);

LookDir =normal(TransformVectorByRotation(DirectionChange,LookDir))*MinMovementDistance;

 

获取系统时间是巡逻系统中一个很关键的因素。因为利用角色的随机时间才能确定他什么时候转身巡逻。

var float TimeSinceLastRotate;

TimeSinceLastRotate=worldInfo.TimeSeconds; 

 

  bForceRotationChange是控制标志位。在这个标志位里面来确定是否自转。而这些所有内容是由时间因素控制的。是不是很酷,我来给你看看代码。

if(bForceRotationChange)
{
 PreviousRotationChange=GetNewWanderRotation();
 DirectionChange.pitch=0;
 DirectionChange.roll=0;l
 DirectionChange.yaw=PreviousRotationChange*DegToUnrRot;

 LookDir=(normal(DirectionChangeDir>>LookDir)*MinMovementDistance);
 bForceRotationChange=false;
 bForceTurnAround=false;
 TimeSinceLastRotate=WorldInfo.TimeSeconds;
}

//在该条件中,降低一半的比率fRand()>0.5
if(WorldInfo.TimeSeconds-TimeSinceLastRotate)>RandRoateTime&&fRand()>=0.5)
{
 bForceRotationChange=true;
}

 

 第一个if语句的最后代码记录了上一次的时间,第二个if里面就判断50的概率经过足够的时间是否进行转身。我们可以看到小鸡和狐狸运行的很好。这让我想到了《Warp》这款游戏中的巡逻士兵,在我的AntGame中是否可以将所有的机器人都变成这种巡逻敌人。(⊙_⊙)应该是个不错的主意。

逻辑层面的工作告一段落,我们获取了角色的下一个方向,现在我们将尝试让角色朝那个方向去走。

TestPoint=Pawn.Location+LookDir;

 

if(GeneratePathToLocation(TestPoint))
{
 currentWanderDestination=TestPoint;
 if(NavigationHandle.PointReachable(TestPoint))
 {
   currentWanderPoint=TestPoint;      //我们知道currentWanderPoint才是寻路要获取的地方
   return true;
 }
 else
 {
 if(NavigationHandle.GetNextMoveLocation(TestPoint,Pawn.GetCollisionRadius()))
 {
  if(NavigationHandle.SuggestMovePreparation(TestPoint,self)
  {
   currentWanderPoint=TestPoint;
   return true;
  }
 }
 }
}

 

 当这两个if执行完的时候还没有找到路径就只能返回false,并且将bForceRotationChange=true这样就可以再次返回。

人的直觉非常强,当有点点违和的事物出现就会不舒服。如果我们的pawn卡在一个地方将会让人们觉得很傻,所以StuckTimer是很有必要处理的内容。

 function CheckStuckTimer()
 {
          if(VSize(Pawn.Location-PreviousStandLocation)<15)       //PreviousStandLocation还不得而知,在哪里获取?后来知道在状态中就获得了,PreviousStandLocation=pawn.location
          {
           StopLatentExecution();         //取消寻路执行
           bHasWanderPoint=false;
           bForceRotationChange=true;
          } 
 }

 

方法已经罗列完了,该进入状态中去处理了。

auto state Wander
{
  simulated event BeginState(name PreviousStateName)
    {
          TimeSinceLastRotate=Pawn.Location;
          SetTimer(StuckTimerCheckDelay,true,'CheckStuckTimer');
    }  
}    

 

这句中主要解决的问题便是调用检测是否卡住,CheckStuckTimer函数写的非常巧妙,不仅仅是处理是否卡住,主要是为了激活各种标志位。

simulated function EndState(name NextStateName)
 {
  StopLatentExecution();
  bHasWanderPoint=false;
 }

   检测两个pawn撞在一起的处理方式,如果转身标志位或者检测时间很短就返回false。

 event bool NotifyBump(Actor Other,Vector HitNormal)
 {
  if(bForceRotationChange||(WorldInfo.TimeSeconds-TimeSinceLastBump)<2.0)
  return false;

  bForceRotationChange=true;
  StopLatentExecution();

  TimeSinceLastBump=WorldInfo.TimeSeconds;
  return false;
 }

  看了前边这个状态中定义的函数,该来一点货真价实的东西了。那么开始运行这个状态吧。

//如果当前没有徘徊目标,并且能计算出来一个,那么就将bWanderPoint设为true
 if(!bHasWanderPoint&&GenerateWanderPoint())
 {
  bHasWanderPoint=true;
 }
if(bHasWanderPoint)
 {
  MoveTo(currentWanderPoint,,Pawn.GetCollisionRadius()*0.45f);
  bHasWanderPoint=false;
 }

然后Sleep(0.1);在state中这是个非常有用的函数。然后不断地goto'Begin';

  在Defaultproperties中有一些变量得标出来。

defaultproperties
{
  RotationRate=(Pitch=0,Yaw=0,Roll=0)
  NavigationHandleClass=class'NavigationHandle'
  MinMovementDistance=20
  PreviousRotationChange=0

 RandRotateTime=15
 StuckTimerCheckDelay=0.5f
}

  至此,寻路的动作基本上做完了。写到这里我突然不想继续写AIController中的其他部分,我想写写关于Pawn狐狸和Chicken的内容,毕竟弄完寻路人们更想知道狐狸和chicken是否能动弹。好吧我换一章循序渐进的走pawn的内容。