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