16、测试行为树
在播放游戏前,重新调整玩家的位置让它不会立刻被AI发现。开始游戏后,在内容浏览器中打开行为树,找到工作栏中的下拉菜单,它当前显示“没有选中调试对象“,点击它并选中”AIC Third PersonCharacter“,现在我们可以看到行为树的执行流程。
选中随即漫游事件的等待节点,然后按下F9设置断点,注意下次这个节点执行时行为树会暂停,现在我们可以i通过右下角的黑板框查看“TargetLocation“的数值,它会停止更新。我们还可以点击工具栏中的”返回:进入“按钮,返回行为树中的上一个执行节点。
17、创建第一个EQS查询
我们的AI现在可以使用感知系统找到玩家并向他们靠近。下一步我们需要让AI将多种环境因素考虑在内。对于这种复杂情况,我们需要用到环境查询系统。我们先从创建一个AI感兴趣的对象——一个食物来源Actor,把它布置在关卡,然后我们会启用EQS系统并创建一个EQS查询来帮助AI定位关卡内的食物来源。我们从新建食物来源开始。在蓝图目录中新建一个目录,将它命名为”EQS”,打开目录,右键点击内容浏览器,选择“蓝图类“,然后选择”Actor“,命名为”foodsource“。双击食物来源,打开蓝图编辑器,找到左边的组件面板,点击”添加组件“按钮,选择立方体,为蓝图添加立方体组件。在组件面板中选中立方体组件,找到细节面板中的”碰撞“分段,其中”碰撞预设“下拉菜单将它从默认设置改为”无碰撞“,这么做是为了避免这个对象的碰撞对导航网格体产生干扰进而影响AI的运动。接着把立方体相对于根节点的Z坐标设置成正70,这将有助于我们把它直接放到关卡地面上,修改完成后,我们编译并保存蓝图。
现在我们要在关卡内放置多个食物。为此我们可以直接将Actor从内容浏览器中拖动到视口中。接下来,按下P键显示导航网格体调试器,确认导航网格体没有因此被破坏。
在创建第一个EQS查询之前我们要启用EQS系统,其仍处于试验阶段。为了使用它,在编辑器偏好设置中找到通用,实验性功能,在右侧找到“AI“分段,启用”环境查询系统“,然后关闭”编辑器偏好设置“窗口。打开”EQS”,右键选择”人工智能“>”环境查询“,将新资源命名为”EQS find CloseFoodSource“,双击打开查询编辑器。拖动根节点,在生成器分段中,选择”Actorsofclass“,在细节面板中找到”生成器“分段。首先,我们要指定这个查询要查找哪个Actor,在”搜索的Actor类“中,点击下拉菜单,找到我们的食物来源Actor,取消勾选”仅在半径内生成Actor“,这样做是因为没有必要将搜索结果限定在一个特定半径内。右键点击生成器,点击”添加测试“,然后选择”距离“。点击左键选择距离修改器,找到细节面板查看第一个分段,查看”测试目的“,我们要把测试目的设置为”仅计分“,把积分因子设为”-1“,现在我们保存并退出。
是时候在行为树上使用我们的新功能了。首先要创建一个黑板键,保存最近的食物来源,稍后我们将使用这个键来控制装饰器以便于区分看到玩家、追逐玩家和搜寻食物。首先在内容浏览器中打开行为树,点击黑板键。新建“object“,命名为”targetfoodsource“,在右侧找到”键类型“将基类从”obiect“改为”foodsource“actor。现在回到行为树界面,我们要新建一个序列,将它放置于”追逐玩家“和”随机漫游“之间,并命名为”Find Food“序列。拖动”Find Food“序列,选择”Run Query“,在右侧细节面板中的”EQS“分段,展开”EQS任务“分段,选择刚才创建的查询作为查询面板,在下拉菜单中选中它,确保”运行模式“是”单个最佳项“。
注意,我们之前把查询设置成对项目仅进行计分,这意味着它当前会返回多个项目,将“运行模式“设置为”单个最佳项“,会返回得分量高的项目,也就是距离最近的对象。最后将黑板键设置为我们新建的”targetfoodsource“。
现在EQS就完成了。他会运行“EQS find CloseFoodSource“,并将结果保存在”targetfoodsource“黑板键。
接下里让AI去做的就是移向目标食物来源Actor,我们可以使用“Move to task“,拖动”Find Food“序列,选择“Move to task“,在细节面板中找到”黑板“分段,将黑板键改为”TargetFoodSource“,最后我们要在序列末尾添加一个简短的等待任务。最后回顾一下”Find Food“序列,首先是EQS查询,它会运行我们的查找最近食物来源查询,这个查询随后会返回最近的食物来源,并将结果保存在”TargetFoodSource“黑板框中,假设它成功了,AI然后会移向目标食物来源。
18、根据饥饿程度制定策略
在这节我们会赋予AI寻找食物的理由并避免它一直寻找食物。
我们要在行为树中创建一个服务,用它修改表示饥饿的黑板键,然后我们会通过这个服务让饥饿水平逐步上升,最后我们会新建一个装饰器让它根据饥饿程度来控制当前执行哪个程序。我们将学习如何编写这类逻辑。通过重新排列当前行为树中的节点来快速重建复杂行为。
为此,我们首先会新建一个黑板键,用来保存饥饿值,然后通过服务对其进行操作,首先打开当前的行为树,在右侧打开黑板,在黑板中添加一个浮点类型的黑板键,并命名为“hungerKey“。添加新键后,在右上角选择”行为树“,现在我们添加一个服务,点击工具栏中的”服务“按钮,这样会自动新建服务并打开编辑器,回到内容浏览器将之命名为”BTS_Hunger“,在我的蓝图面板新建一个变量,将它命名为”HungerKey”,把变量类型设置为”key“,确保它是公共变量所以其他行为书中可修改它。现在在事件图表中添加一个”tick”事件,每当行为树中的服务更新时,这个事件都会触发,右键点击事件表,选择”Event Recevie Tick AI“,现在我们需要从创建的”HungerKey“中获取一些信息。按住Ctrl键和左键,将”变量“分段中的“hungerKey“拖动到事件图表,拖动”HungerKey“然后输入”get Blackboard value as float“。现在我们要做的是让饥饿值与浮点数相加用于表示在这一帧中饥饿发生了变化,拖动”get Blackboard value as float“的返回值搜索”float+float“的第二个输入,将它提升至变量,把这个变量命名为”Hunger increase Rate“,确保它也是公共变量。这样每当服务更新时,”Hunger Increase Rate“都会与当前的饥饿值相加。我们要让饥饿值介于0和1的范围之间,我们可以通过限制函数来实现这点,拖动”float+float“节点,然后输入”clamp float“,限制函数会获取输入值,然后根据函数定义中的最小值和最大值返回结果。现在要将新的饥饿值保存到黑板中,在事件图表中选中”Hunger Key“,复制后拖动并选择”set blackboardvalueasfloat“。现在拖动限制函数的返回值连上”set Blackboard value as float“的输入值。最后要做的是将”Tick AI“事件的执行引脚连到”set Blackboard value as float“,编译并关闭。
现在我们将新增加的行为添加到行为树上了,打开行为树。我们希望服务一直运行,由于服务只在那个节点执行时运行,我们需要将它放置在选择器中,位于所有分支之上。右键点击选择器,选择“添加服务“”BTS_Hunger“。选中新建的服务,如果查看细节面板的”默认“分段,我们能调整在服务中创建的变量,我们可以让”Hunger Key“指向”Hunger“黑板键,可以把增长率从1改为0.1,此外,在细节面板的”服务“分段中,我们可以调整服务运行的间隔和它的随机偏差,在这个事件中,我们要尽量避免它的不确定性,所以去除随机偏差。
接下来我们要做的是根据这个饥饿值来改变行为树的执行顺序,为此我们要再添加一个装饰器。右键点击“Find Food“序列,选择”添加装饰器“然后选择”黑板“,将新的装饰器的”黑板“分段中的黑板键改为”Hunger“。注意上面会多出一个”键值“选项,把它改为1,这样这个装饰器会阻止运行”Find Food“序列,除非”Hunger“达到最大值1。为了避免忘记这个装饰器的作用,将节点名改为”Is Hunger at Maximum“,这样行为树就修改好了。
现在我们来运行游戏并观察行为树,会发现当饥饿值到达1时,装饰器就会始终执行寻找食物序列。
接下来要做的是新建一个任务,这个新任务会把“Hunger“黑板键的值重置到0,我们会有意让这个任务变成一个泛型,这样我们就可以在任何时间用它来控制浮点类型的黑板键。这样把数值改为0。首先点击工具栏中的”新建“按钮,新建一个任务,选择”BTTTask_Blueprintbase“作为新任务的基类,将此任务重命名为”BTT_setkeyfloat“。首先我们添加需要的黑板键,在”我的蓝图“面板的”变量“分段中,新建一个变量并命名为”FloatKey“,确保是公共变量,在右侧细节面板中将变量类型改为黑板键选择器。再新添一个变量并命名为”FloatValue“,在细节面板将变量类型改为浮点数,确保这个变量是公有变量,编译。
接下来,右键点击事件图表,然后输入“Event Receive Execute AI“,我们要用变量将黑板键设置成需要的值,按住Ctrl键,将” FloatKey “拖动到主窗口,拖动其引脚,找到”set Blackboard value as float“。现在将”FloatValue“连到”set Blackboard value as float“的数值输入,按住Ctrl键,将”FloatValue“拖动到主窗口,将浮点数值连到”set Blackboard value as float“的数值输入,现在我们可以将事件的执行引脚连到函数上。
接下来我们要确保结果返回真,否则行为树就会卡在这里等待返回值,右键事件图表“Finish Execute“函数,务必勾选布尔值”成功“,然后将”set Blackboard value as float “的执行引脚连到”Finished Execute“,保存并编译。
现在我们回到行为树,开始使用我们新建的任务,我们将该任务放置在搜寻食物序列的等待任务的后面。拖动搜寻食物序列找到我们的新任务 ,找到细节面板将“Float Value“设为0,将”Float Key“设为”Hunger“,之后保存。