UE4动画制作相关学习笔记4-AI场景设计

AI在UE4的示例里面主要用来做寻路、避障、探索等一些功能,在UE4的动画制作中,同样可以为角色设定一些AI动作,比如说对场景中一些物体的反应、听到不同对话声音的一些反应、空闲时随机做一些自由动作等。

AI的循环处理主要分为三个阶段:感知(Sense)、思考(Think)、行动(Act)。感知表示对AI当前状态进行记录,包括物理感觉(听觉视觉触觉等),可以对这些物理感觉定制优先级,还能添加一些抽象的感知;思考表示AI利用感知到的信息,和目标进行一起评估,然后为下步行动指明方向;行动是根据感知和思考阶段的信息,做出相应的行为,如跑步、跳跃、和其他AI通信等,当行动结束后,又回到感知的状态。

AI场景设计的参考流程如下:

1、新建一个CharacterBlueprint(角色蓝图),用于表示AI角色,并命名为AI_ThirdPersonCharacter;

2、新建一个AIController(AI控制器),并命名为AIC_ThirdpersonCharacter;

3、打开AI的角色蓝图,找到Pawn->AIControllerClass,选中之前创建的AI控制器,此处的AutoPossessPlayer是关闭的,表明控制方式是AI控制;

4、将AI角色蓝图放入到Level场景中;

5、使用AI寻路功能,在Level场景中添加“NavMeshBoneVolume”(寻路网格体),此网格体功能就是预先计算好寻路数据(默认是按照静态物体计算,如果要对移动的物体进行监测,要在设置里打开动态监测,会增加一定的计算机开销);

选中Level场景中的NavMeshBoneVolume,在Details->BrushSetting中将Mesh进行放大,覆盖整个地图(键盘的P键是打开/关闭显示导航路径,默认导航路径是自动更新的);

在Level场景中放入NavMeshBoneVolume后,会自动生成一个RecastNavMesh-Default对象,用于辅助寻路网格工作,如设置代理半径/高度(AgentRadius/Height)等,这样可以根据AI角色的实际大小,设置更为精确的导航路径,同时Agent的代理数量可以根据需求自行添加;

6、UE4的预置模块里,还有NavModifierVolume,它的可以设置一些AI寻路的权重,告诉AI哪些区域可以走,哪些区域比较难走,示例中不做过多介绍;

7、在蓝图中让AI角色自由行走的方法(此方法相对比较简单,处理的逻辑也不复杂,如果要使用复杂逻辑,后面会介绍使用行为树BehaviorTree的方法):

打开AI角色(AI_ThirdpersonCharacter)->EventGraph;

新建自定义事件并命名为“RandomWalk”,调用函数“SimpleMoveToLocation”,此函数中带两个输入参数Controller对象和Goal位置,获取AI角色蓝图里的Controller对象,通过调用Get Controller函数,它会自动返回一个Pawn对象,之前我们在Pawn里用的AIControllerClass就是我们自建的AIC_ThirdpersonCharacter;

调用GetRandomMoveToLocation函数,这个函数会返回一个随机的位置信息给SimpleMoveToLocation的Goal输入,此函数输入参数中的Origin表示初始点的位置信息,可以通过调用Get Actor Location获取AI角色的当前位置,Radius表示随机去的半径范围;

调用系统事件Event Begin Play,此事件在AI角色蓝图运行时自动触发,之后我们通过Set Timer by Event定时器函数,自动去触发之前设置的自定义事件Random Walk,注意在定时器函数中,把Loopping打开;

把所有环节串接起来,编译运行,AI角色就会在Level场景中随机移动,并且每隔固定的事件,重新寻找新的路径;

8、GamePlay调试器的使用,可以用键盘上的撇号调出调试器,调试器类似于编程里的Debugger,可以设置断点,可以查看Level场景中的各类参数;

9、AI感知系统中有两个重要的组成部分:AI感知组件(AIPerception)和AI感知刺激源(AIPerceptionStimuliSource);系统自带事件OnTargetPerceptionUpdated,当感知发生变化时会自动触发,并返回一个刺激源(Stimulus)和感知对象(Actor),感知的框图如下:


图1.AI感知原理框图

10、在AI角色中(AI_ThirdpersonCharacter)中,添加感知组件(AIPerception),选中添加好的感知组件,在Details->AIPerception->SenseConfig中,添加感知源(AISightConfig视觉感知),在视觉感知源中,还能详细的设置视觉半径和视觉范围,在DetectionByAtilation中,勾选Neural,表示感知的监测对象为中立方(一般玩家角色设置为中立方才能被监测到);

11、将玩家角色(ThirdPersonCharacter)拖入到Level场景中,在蓝图中添加刺激源组件(AIPerceptionStimuli Source),选中刺激源,在Details->AIPerception,打开AutoRegisterSource,选择RegisterAsSourceForSense->AI_Sense_Sight,表示将刺激源放到玩家角色上,同时刺激源是视觉刺激;

默认ThirdPersonCharacter的类中自带SightStimuli,计算不加刺激源,它也能被AI感知到,此处加上只是为了说明原理;

12、在AI_Third personCharacter中,关闭Event Begin Play事件,不调用之前写的简单自动寻路蓝图,之后会用行为树BehaviorTree的方式让AI自动寻路;

13、在AI_Third personCharacter中,添加AI感知事件OnTargetPerceptionUpdated,捕获AI感知信息和对象,将感知到的对象存为内部变量并命名为“PerceivedActor”,将捕获到的刺激源,通过BreakAIStimulus函数打碎,这样可以返回感知到的所有内容和状态,这次例子中只用到了感知状态的真假SuccessfullySensed,然后把这个Bool值传递给Branch分支函数,分支的真表示AI感知到玩家,感知到玩家后回去追踪,分支的假表示丢失玩家,丢失后AI又会去自动寻路;

新建一个Target Lost的自定义事件;

在分支假后面,设定一个事件触发的计时器函数SetTimerByEvent,连接自定义事件Target Lost,并设定时间,表示分支进入假状态后,定时多少秒,自动去触发Target Lost事件;

在分支真后面,需要设定一个函数ClearAndInvalidateTimerByHandler,表示进入真以后,自动清除之前分支假里面的SetTimerByEvent函数,如果不清楚,可能会遇到Target被监测到,仍旧会被分支假里的计时器清空的情况,如果玩家一会进入AI感知范围,一会又出去的情况;

AI_Third personCharacter的蓝图中,我们只是定义的感知后的处理逻辑,具体怎么去处理,需要通过接下来的行为树来实现;

14、行为树(BehaviorTree)是将AI场景中的决策制定模型进行可视化操作的方法,相当于思考这个环节;行为树的执行顺序为从上到下,从左到右;

行为树的根节点(Root)下方有各种节点,其中任务节点(Task)是行为树分支的重点,有预置的任务(如Move to,Wait),也可以自定义任务;

选择合成器(Selector)节点会执行它下面的子节点,只要有子节点返回时True,那么Selector为True,会继续执行,只有当所有的子节点返回是False时,Selector才返回为False并退出;因此在Selector下的子节点,我们一般将优先级最高的放左边;类似于去上班的任务,不管是走路还是坐地铁还是公交车,最终我们都能到达公司;

Sequence合成器节点,它会遍历下方所有子节点,当都返回True时,它才返回True,这个节点的方式与人们处理障碍的方式类似;

同时行为树中还能定制一些服务(Service)和装饰器(Decarotar),放置到Selector和Sequence节点中,让处理逻辑更加灵活;

15、新建一个行为树,并命名为BT_EnemyAI;

16、新家一个黑板(BlackBoard),并命名为BB_EnemyAI,关联BT——EnemyAI,黑板的作用是为行为树提供参数和状态,帮助行为树来进行判定;

17、打开AI控制器AIC_ThiredpersonCharacter,在EventGraph中,使用系统自带事件EventOnPossess(表示AI控制器被执行的时候触发),然后调用RunBehaviorTree函数,选择之前创建的行为树并执行行为树;

18、在黑板(BB_EnemyAI)中新建一个Key,命名为Target Location,类型设置为Vector,我们后面其实就是要把AI角色蓝图里的数据,传递给黑板,黑板再告知行为树进行判定;

19、在行为树中使用自动寻路Task的方法如下:

在行为树中,新建一个自动寻路的任务(Task),来用作自动寻路,并命名为“BTT_FindNavigationLocation”,之前再AI角色蓝图中(AI_ThirdpersonCharacter)中已经去掉的自动寻路事件触发,因此在AI角色蓝图中,不会执行自动寻路,我们把自动寻路的功能改成的Task的方式,供行为树使用;

进入BTT_FindNavigationLocation任务,添加系统事件ReceiveExecuteAI,当行为树执行该任务时,会自动触发此事件,ReceiveExecuteAI事件执行后返回两个对象Owener Controller和ControlledPawn,我们可以通过将Owener Controller对象输出给GetActorLocation函数,来返回一个AI对象的位置;

把这个AI对象的位置传递给Get Random Reachable PointInRadius函数,并输出一个随机位置信息;

将随机的位置信息通过SetBlackBoardValueAsVector函数传递给BlackBoard,SetBlackBoardValueAsVector中还有一个Key的输入变量,我们在Task中新建一个BlackBoardKey类型的变量,并设置成公有变量命名为“Target Location Key”,将此共有变量传递给SetBlackBoardValueAsVector;之后只要在行为树的黑板中,把这个Key对应上,就能准确传递随机位置;

在Task任务的最后,加上FinishExecute,并勾选success,或者将之前Get Random Reachable PointInRadius的状态发给它;所有Task任务中,一定要加上FinishExecute,表示任务已经完成,不然在行为树中,不会去执行接下来的任务;

20、注意Task任务如果失败,那么它的父节点Sequence也会失败并终止;将BTT_FindNavigationLocation任务加入到行为树中,此时公共变量Target Location Key暴露给了行为树,将此变量绑定到黑板中的Target Location啊,这样就能将Key对应的随机位置数值,传递给黑板,具体行为树中流程图如下:


图2.行为树中的逻辑图

21、在行为树中设置追踪玩家角色的逻辑,在黑板中(BB_EnemyAI)中,新建一个Key命名为TargetActor,类型为Actor,新建一个Key,命名为HasLineOfSight,类型为Bool;

22、打开AI控制器(AIC_ThirdpersonCharacter),新建两个函数UpdateTargetActorKey和UpdateHasLineOfSightKey,这两个函数之后会在AI角色蓝图中被调用,它们的主要作用是,把AI角色蓝图中感知到的对象以及感知状态,发送给黑板,这两个函数里面主要调用的一个函数是SetValueAsObject,SetValueAsObject函数的输入包含Value、Target和Key,其中Target对象通过通过GetBlackboard来直接获取,Key的值通过自定义,并跟实际行为树中的黑板值对的上,Value值就是AI角色蓝图中送过来的;

23、下图列出了整个AI搭建中的框图,供参考和理解:


图3.AI流程图

24、在行为树(BT_EnemyAI)中创建一个Sequence用于追踪玩家并命名为ChasePlayer,并把这个Sequence放到之前随机走动Sequence的左侧,子节点使用自带的任务MoveTo和Wait,其中Move指向之前BB_EnemyAI中建立的Key-TargetActor;

25、在ChasePlayer的Sequence上添加装饰器(Decarotar),用于判定目标对象是否被锁定;添加完后选择装饰器,在Details->BlackBoard->KeyQuery(选择is Set)->BlackBoardKey(选择TargetActor);在Details->FlowControl(流控制)->NotifyObserver(通知观察者)->OnResualt->ObserverAborts(选择Both);

此处的设定表示AI在随机行走时,如果发现目标,则会停止随机行走,转而追踪目标;如果在追踪目标时,目标丢失后,当目标状态发生变化,AI会立即终止追踪,改为随机行走;

26、使用EQS环境查询(首先需要在编辑配置中将其打开),操作方法如下:

新建EQS查询文件EnvironmentQuery,命名为EQS_FindClosestFoodSource,双击进入编辑页面,拖动其根节点(Root),并在生成器里选择生成ActorOfClass(之前需要建一个FoodSource,模拟食物,取消碰撞机制,然后把这些Actor托到Level场景中),取消GenerateOnlyActorInRadius(此处表示场地小,AI完全可以在场地里寻找,不需要设置额外的寻找食物半径);

右击ActorOfClass生成器,添加一个条件-DistanceToQuery,在对应的Details中,将TestPurpose设置为ScoreOnly,然后在ScoreFactor中设置因子为-1;(这样表示偏好距离更短的选择,也就是说距离越短,权重越大)

在BlackBoard中(BB_EnemyAI),新建一个Key,类型选择Food Source(之前建的Actor),并命名为TargetFoodSource;

回到行为树中(BT_EnemyAI),在Selector下方新建一个Sequence,放在追踪和随机行走的Sequence中间,并命名为FindFood;

在FindFood的Sequence下,新建EQS查询任务,选择EQS查询,在Details->EQSRequest->选择之前建立的EQS文件;在RunMode中,选择SingleBestItem(即最优的选项);在BlackBoard->BlackBoardKey中,选择要查询的对象FoodSource;

在FindFood的Sequence下,EQS查询任务的旁边,新建Move To任务,选择对象时Target Food Source,再建立Wait任务,设置等待时间;

根据饥饿值来判断是否是找食物,还是自由行走,还是追踪玩家(默认追踪玩家的优先级最高,其次是找食物,最后是自由行走);

在黑板中(BB_EnemyAI),新建一个Float类型的Key,并命名为Hunger;

在行为树中(BT_EnemyAI),新建一个Service服务,命名为BTS_Hunger,打开Service文件,在其内部新建一个公共变量,命名为HungerKey,类型是BlackBoardKey;

在Service中添加一个系统事件Event Receive Tick AI(表示每次服务更新时会触发,服务和任务的区别在于,服务会一直循环运行,而Task运行完会结束,下次要使用需要重新调用);

在Service中,内部新建一个公共变量,命名为HungerIncreaseRate,类型是Float;每次服务更新时,通过HungerIncreaseRate增加饥饿值,并把饥饿值传递给黑板(BB_EnemyAI,通过函数SetBlackBoardValueAsFloat实现);

将Service服务放进行为树的Selector中,让它一直运行,然后将HunggerKey绑定到黑板中的Hunger;在Selector中,选择Service后,还能调整Service的更新时间间隔,以及随机偏差(RandomDerivative,此处我们不加偏差);

在FindFood的Sequence中,添加一个装饰器,类型是BlackBoard,在装饰器的Details->BlackBoardKey,选择Hunger,并设置Key的值是1,此处表示当Key值到1时,装饰器会运行FindFood序列;

再建立一个任务,命名为BTT_SetKeyFloat,其目的是当Hunger值到1时,自动清零;

打开BTT_SetKeyFloat任务,在里面新建一个Float Key公共变量,类型是BlackBoard Key,再新建一个FloatValue公共变量,类型是Float,设置其默认值是0,使用SetBlackBoardValueAsFloat设置黑板上的Hunger值;

最后将BTT_SetKeyFloat任务放到FindFood的Sequence下,并放到最后;

你可能感兴趣的:(UE4动画制作相关学习笔记4-AI场景设计)