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


      迷迷糊糊的从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的内容。

  • 相关阅读:
    MVB设备分类
    MVB帧
    也说析构---C++
    oracle中以dba_、user_、v$_、all_、session_、index_开头
    查看Oracle的表中有哪些索引(用user_indexes和user_ind_columns)
    Spark_总结五
    Spring编程式和声明式事务实例讲解
    缓存穿透,缓存击穿,缓存雪崩解决方案分析
    redis持久化2
    redis的持久化方式
  • 原文地址:https://www.cnblogs.com/NEOCSL/p/2759363.html
Copyright © 2020-2023  润新知