在教程的此部分中,你将创建一个敌人NPC来侦测、追逐并伤害玩家。 此外,敌人将能够受到伤害,并使用寻路网格体在环境中导航以避开障碍物。
首先,和之前的教程一样,你将使用蓝图可视化脚本创建敌人逻辑。 然后,你将创建寻路网格体,以定义被AI控制器控制的角色在游戏期间可以导航的区域。
开始之前
请确保你已掌握设计解谜冒险游戏前一节中介绍的以下主题:
蓝图和蓝图函数
你需要准备好在创建钥匙中创建的下列资产:
BPL_FPGame蓝图函数库
为敌人的导航创建寻路网格体
在将敌人添加到关卡并使其追逐玩家之前,你必须先构建寻路网格体,敌人AI将使用它在关卡中导航。
NavMesh是Navigation Mesh(寻路网格体)的简称,可用于定义关卡中AI可导航的区域。 通过在本教程中创建寻路网格体,你将定义一个区域,敌人可以在其中移动以追逐玩家。
要在你的关卡中添加寻路网格体,需执行以下步骤:
在虚幻编辑器的关卡编辑器中,点击主工具栏中的创建(Create)按钮,转到体积(Volumes)并选择寻路网格体边界体积(Nav Mesh Bounds Volume)。 这将为项目中的AI创建一个可导航区域。
使用变换工具,移动寻路网格体使其与关卡中的地面相交。
按下键盘上的P键以切换调试视图。 这可以用于在关卡视口中查看寻路网格体细节——包括关卡上的可导航区域,以及视口左侧基于文本的调试信息。
如果你在调试信息中看到“寻路网格体需要重新构建(NavMesh Needs To Be Rebuilt)”消息,请在菜单栏中导航到构建 > 构建路径。 这将重新构建你的寻路网格体。
继续移动和缩放(键盘上的W和R键)寻路网格体,以修改寻路网格体边界。 根据你的喜好放大此体积。
确保寻路网格体的底部足够低,能包含任何凹陷的地面部分,并且寻路网格体的顶部足够高,能容纳敌人的高度。
如果你使用的是教程示例关卡,请创建并缩放两个寻路网格体边界体积,使其填满Hallway 3和Room 3。
当你缩放和移动寻路网格体时,你会看到绿色区域随着边界盒体的大小而扩展——除非有一些障碍物,例如立方体。 任何被红色遮挡的部分表示游戏中的AI无法导航到这些部分。
如果虚幻编辑器在寻路网格体中包含不应包含的内容,请选择该网格体关卡对象并在细节面板中禁用“可影响导航(Can Ever Affect Navigation)”。
创建敌人
你将基于角色(Character)类创建敌人,因为它已经包含了适用于类人对手的移动和动画功能。 角色(Character)类实际上扩展自Pawn,后者是更通用的“可控Actor”,用于载具、摄像机或非类人生物等。 对于外形像人的敌人,角色类是更好的起点。
由于你是从头开始制作这个角色,你将添加一个网格体来直观地表示敌人,并使用动画蓝图来播放动画。 项目文件中提供了动画蓝图,因此你不需要从头创建,但需要了解如何将其与敌人角色的蓝图整合。
首先,创建敌人蓝图。 执行以下步骤:
在内容浏览器中,进入Content > AdventureGame > Designer > Blueprints > Characters。
在内容浏览器的空白区域中点击右键,然后点击蓝图类(Blueprint Class)。
在新窗口中,选择角色(Character)作为基类。
将此类命名为
BP_Enemy,并在蓝图编辑器中双击将其打开。
当你创建一个蓝图类时,你需要选择一个父类来构建。 该父类已经自带了内置功能。 例如,当你基于Actor类制作蓝图时,你起始就拥有了变换信息以及持有组件和运行逻辑的能力。 现在,通过基于Character类开始,你还获得了移动、碰撞和动画支持。 你使用基类的方式类似于模板。 当你添加功能时,你是在扩展该基类。
在BP_Enemy蓝图窗口打开的情况下,进入视口(Viewport)选项卡,查看组件(Components)面板。 你将在列表底部看到角色移动(CharMoveComp)组件。 所有角色类都带有一个角色移动(Character Movement)组件。 点击该组件并查看细节面板以查看其中的所有设置。 在你为项目创建敌人时,你将覆盖其中的一些设置。
为敌人添加3D模型
你的角色还带有一个网格体(Mesh)组件,但它不是静态网格体,而是一个骨骼网格体(skeletal mesh)。 你目前为止使用的静态网格体是固定不动、不会变形的3D模型,而骨骼网格体具有内部骨架,因此可以播放动画并产生变形。
首先,设置角色的网格体和动画。 执行以下步骤:
选择网格体(Mesh)组件,然后在细节面板下查看。 找到网格体类别,然后点击骨骼网格体资产旁边的下拉菜单。 在列表中搜索并选择
SKM_Manny_Simple模型。 这会将Manny模型分配给蓝图。在细节面板中,进入动画(Animation)类别。 点击动画类(Anim Class)旁边的下拉菜单,并分配
ABP_Unarmed动画蓝图。 现在,敌人角色应该会在视口(Viewport)选项卡中播放待机动画。修改动画蓝图,使其与敌人配合使用:
在敌人网格体的动画类(Anim Class)属性中,单击
ABP_Unarmed旁边的浏览至内容浏览器中的资产(Browse to Asset in Content Browser)。双击打开
ABP_Unarmed动画蓝图。转到位于Sequence(序列)节点的Then 1引脚之后的逻辑组。
你不希望此处的加速度检查阻止敌人角色移动,因此请移除And节点和Set Should Move节点之间的连线。
将Greater than (>)节点的输出引脚连接到Should Move引脚,绕过加速度检查。
编译、保存并关闭蓝图。
在视口中,你可以在敌人角色周围看到一个圆柱形的胶囊体形状。 这是胶囊体组件(Capsule Component)的直观表示,它用于检测与其他对象(包括地面)的碰撞。 角色的网格体是一个比较复杂的形状,因此简单的碰撞圆柱体可以平滑形状,从而获得更好的碰撞检测性能。
网格体发生了偏移,未与胶囊体对齐。 为了使角色的网格体(Mesh)与胶囊体组件(Capsule Component)对齐,请向下移动角色,使脚部与胶囊体组件的底部相接。
要使角色网格体与其碰撞体对齐,请执行以下步骤:
选择网格体组件,使3D模型高亮显示。
在细节面板中,使用变换(Transform)类别的位置(Location)字段来更改Z轴,就是最后一个蓝色的字段。 将值设置为
-96。
现在两个组件都以原点为中心,这样敌人在游戏中就不会漂浮在地面上方了。
最后,你会看到角色带有一个箭头组件(Arrow Component)。 箭头指示了蓝图的正面朝向。 确保此箭头与网格体面对的方向一致,以便角色能朝正确的方向奔跑。
要使角色网格体面朝前方,请按照以下步骤操作:
再次选择网格体(Mesh)组件。 使用旋转小工具或细节面板的变换(Transform)类别,将角色绕Z轴旋转90度。 其最后的旋转(Rotation)值应为-90。
更改3D模型的材质
在继续创建功能之前,先将敌人模型的颜色更改为红色。 这会将其与玩家区分开来,使其看起来更像敌人,而不是当前的白色。
要让敌人角色呈现不同的颜色,请按照以下步骤操作:
在细节面板的材质(Materials)分段下,有两个元素:
MI_Manny_01_New材质会影响敌人模型的各个部位,如上臂、腿部和头部。MI_Manny_02_New是影响前臂和躯干的材质。
在元素0(Element 0)旁边,点击在内容浏览器中浏览(Browse in Content Browser)按钮,看起来就像文件夹和放大镜。 这会在内容浏览器中打开包含该材质的文件夹。 第二个材质
MI_Manny_02_New也在这里。右键点击
MI_Manny_01_New资产,然后点击"创建材质实例(Create Material Instance)"。将材质实例命名为
MI_Enemy_01,然后双击打开。在细节面板中,展开01 - BaseColor,并启用绘制色调(Paint Tint)属性。
点击绘制色调(Paint Tint)属性旁边的色条,然后选择一种颜色。 本教程使用十六进制sRGB =
F60E6EFF。点击保存并关闭窗口。
回到内容浏览器,右键点击
MI_Manny_02_New,然后点击创建材质实例(Create Material Instance)。将此材质实例命名为
MI_Enemy_02,并重复前面的步骤来更改颜色并保存资产。
重复这些步骤后,你应该有两个新的材质实例:MI_Enemy_01和MI_Enemy_02。
接下来,要将这些材质指定给敌人角色,请按照以下步骤操作:
在
BP_Enemy蓝图中,在细节面板内,转至材质(Materials)分段。点击元素0(Element 0)的下拉菜单,并选择你创建的新材质
MI_Enemy_01。对于元素 1(Element 1),将其改为
MI_Enemy_02材质。
将敌人添加到关卡
将一个敌人添加到你的关卡中,看看目前的外观和行为。
从内容浏览器中,将敌人蓝图拖入关卡视口,并将其放置在玩家面前。
当你运行游戏时,你应该能看到敌人正在播放待机动画。
在Gameplay开始时配置敌人
敌人可视化完成后,你可以创建Gameplay功能。 首先,设置敌方生成时所需的属性。
要在游戏开始时设置敌人的移动速度,请执行以下步骤:
在
BP_Enemy蓝图编辑器窗口中,进入事件图表(EventGraph)选项卡。 默认情况下,有三个事件:Event BeginPlay、Event ActorBeginOverlap和Event Tick。 选择ActorBeginOverlap并将其删除,因为本教程不需要它。使用组件(Components)面板,将角色移动(Character Movement)组件拖到事件图表中。 这将创建一个角色移动(Character Movement)节点。 通过这个组件节点,你可以在图表中检查或更改该组件的属性。
拖动Character Movement节点的引脚并创建一个Set Max Walk Speed(设置最大行走速度)节点。
右键点击设置(Set)节点的绿色最大行走速度(Max Walk Speed)引脚,并选择提升为变量(Promote to Variable)。
这会在我的蓝图(My Blueprint)面板的变量列表下创建一个新变量。
将此变量重命名为
Max Speed。 点击变量旁边的眼睛图标,将其变成公共且可编辑的变量。将类别(Category)更改为Setup。
编译蓝图,在细节面板中,将最大速度(Max Speed)的默认值设置为
200。务必为你的可编辑变量分配一个默认值。 如果你忘了在关卡编辑器中更改其数值,而默认值又是0,敌人就无法移动了。
将Event BeginPlay连接到Set Max Walk Speed节点。
接下来,用两个变量来跟踪敌人的生命值。 TotalHP是一个实例可编辑变量,用于设置敌人的初始生命值。 CurrentHP用于跟踪游戏期间敌人受到玩家或环境伤害时的生命值。
要设置敌人的生命值,请执行以下步骤:
在变量(Variables)列表中,添加一个名为
CurrentHP且类型为Float(浮点)的新变量。将CurrentHP变量从变量(Variables)列表拖入事件图表,并选择Set Current HP选项。 拖动Set Max Speed节点的Exec引脚,并将其连接到Set Current HP节点的Exec引脚。
在Set Current HP节点上,右键点击Current HP浮点引脚,然后点击提升为变量(Promote to Variable)。
将此变量重命名为TotalHP。 确保其类型为Float(浮点),然后点击其眼睛图标将其设为公开且可编辑。 在细节面板中,将其分类设为Setup。 编译(Compile)蓝图,并将默认值设置为
100。在Set节点之间连接执行(exec)引脚。
你将在此事件图表中使用玩家角色执行许多操作,因此要将对玩家的引用保存为变量。
要为玩家角色设置一个变量,请执行以下步骤:
在变量(Variables)列表中,新建一个名为
PlayerRef的变量,并将其类型更改为Character(Object Reference)(角色(对象引用))。与每次需要引用玩家时调用Get Player Character函数相比,为角色对象创建引用变量对项目的性能而言更为有利。
在Set Current HP节点后连接Set PlayerRef节点。
对于它的Player Ref输入引脚,连接一个Get Player Character节点。 确保玩家索引(Player Index)值为0。 这是生成到关卡中第一个玩家角色的默认索引。
让敌人追逐玩家
接下来,你将创建让敌人移向玩家的逻辑。 你可以通过创建一个自定义事件来实现。 从蓝图中的任何位置调用自定义事件来触发并执行某些逻辑。
要创建一个触发移动操作的自定义事件,请执行以下步骤:
右键点击事件图表中的任意位置,然后在搜索字段中输入
Custom Event。 点击列表中的添加自定义事件(Add Custom Event)选项,并将此事件命名为MoveToPlayer。拖动MoveToPlayer事件节点的引脚,搜索并创建一个AI MoveTo节点。 此节点是一个拥有AI控制器的Pawn用来移动到特定位置的操作。 在本例中,你将使用它将敌人移向玩家。
在AI MoveTo节点上,拖动Pawn引脚并在搜索字段中输入
Self。 选择获取自身引用(Get a reference to self)来创建在游戏中引用此蓝图Actor的节点。拖动AI MoveTo节点的目标Actor引脚,然后创建一个Get PlayerRef节点。
将可接受半径(Acceptance Radius)设置为
10。 这是指敌人认为自己已经到达目的地时的距离(单位为厘米)。 它比Stop on Overlap(重叠时停止)能提供更多的控制。
在本例中,你正在使用Self节点将此敌人定义为应当移动的Pawn。 然后,你将玩家设置为目标Actor(Target Actor),即Pawn移动的目标。
AI MoveTo节点附带On Success和On Fail事件。 这些事件定义了当敌人到达玩家位置或失败时会发生什么。
要定义敌人移动后会发生什么,请执行以下步骤:
在AI MoveTo节点中,拖动On Success(成功时)引脚,并创建一个Delay(延迟)节点。
右键点击Delay节点的Duration引脚,然后选择提升为变量(Promote to Variable)。
将此变量命名为WaitAtDestination并确保其类型为Float(浮点)。
编译(Compile)蓝图并将WaitAtDestination的默认值改为
5。 点击眼睛图标使其可编辑,并将其分类更改为Setup。在事件图表中,拖动Delay节点的Exec引脚并创建Move To Player节点。 这能确保当玩家移开时,敌人会继续追逐,但在延迟之后会给玩家一个拉开距离的机会。
从AI MoveTo节点开始,拖动失败时(On Fail)引脚并创建新的Delay节点。
拖动Delay(延迟)的已完成(Completed)引脚,并创建一个Move To Player节点。 将时长(Duration)保持在0.2。 这能确保如果敌人还没有到达玩家那里,就会一直保持移动。
在Event BeginPlay逻辑组中。 在此序列的末尾,在Set Player Ref节点之后,连接Move To Player节点以触发此事件。 这会让敌人在游戏开始时移动,这样你就可以测试敌人的移动情况。
保存(Save)并 编译(Compile) 蓝图。
你的事件图表现在应该如下所示:
测试敌人移动
在你的关卡中,将你的敌人Actor移动到寻路网格体的边界内,即在绿色区域内。
在关卡视口或大纲视图中选择BP_Enemy Actor。 在细节面板中,你会在Setup分段看到你在蓝图中添加的变量。 你可以根据项目的需要更改这些数值,或打开Debug Detection以启用线条检测的调试。
再次运行你的关卡,看看敌人现在的行为。 它应该会朝玩家走去,在到达玩家位置时停止,等待5秒,然后再次尝试向玩家走去。
如果出现任何问题,请检查在蓝图中创建的变量是否在场景的BP_Enemy中设置了正确的数值。 如果你不设置最大速度(Max Speed)或最大检测距离(Max Detection Distance)的数值,敌方将不会移动。 如果值太低,也可能发生同样的情况。
对玩家造成伤害
既然敌人能够到达玩家那里,你可以添加一个功能,让敌人在与玩家碰撞时对其造成伤害。 在本教程中,你将创建一个在与玩家接触时自毁的敌人,因此你还将添加逻辑,在该敌人对玩家造成一次伤害后将其移除。
要检查敌人是否与玩家发生了碰撞,请执行以下步骤:
在组件(Components)面板中,右键点击胶囊体组件 (CollisionCylinder),转到添加事件(Add Event),然后选择添加OnComponentHit(Add OnComponentHit)。
这会在事件图表中创建一个新的事件节点,当胶囊体组件撞击到物体时会执行该节点。
拖动On Component Hit节点的引脚,并创建一个Branch节点。
将条件(Condition)引脚连接到On Component Hit节点的其他Actor(Other Actor)引脚,这会提示你创建一个新节点。
创建一个等于(Equal)节点。 拖出Equal节点的Select Asset引脚,并添加对PlayerRef变量的引用。
现在,当敌人的胶囊体组件击中游戏中的物体时,此逻辑将触发一个事件。 该事件会检查胶囊体组件击中的是否为玩家角色。
要对玩家造成一次伤害并移除敌人,请执行以下步骤:
在变量(Variables)列表中,创建一个名为Eliminated的新变量,并将其类型更改为Boolean(布尔)。 你将在敌人被打败并应当从游戏中移除时使用这个变量。
为确保它们只对玩家造成一次伤害,拖出Branch节点的True引脚并创建Do Once节点。
从Do Once节点的完成(Completed)引脚,创建一个Apply Damage节点。 拖动受损Actor(Damaged Actor)引脚,并连接一个对PlayerRef变量的引用。 这就是应当承受伤害的Actor。
右键点击基础伤害(Base Damage)引脚,并选择提升为变量(Promote to Variable)。
在变量列表中,点击基础伤害(Base Damage)的眼睛图标,使其可编辑,并将其类别更改为Setup。 编译(Compile)蓝图,并将默认值设置为
25。将Eliminated变量从变量(Variables)列表中拖到事件图表中,然后选择Set。 在节点中,勾选复选框以将Eliminated设置为true。
将Apply Damage节点的Exec引脚连接到Set Eliminated节点。
拖动Set Eliminated节点的Exec引脚并创建Delay节点。 将时长(Duration)设置为
2.0。拖动Delay节点的Exec引脚,创建Destroy Actor节点。 默认情况下,应将 Target属性设置为self。
当敌人与玩家发生碰撞时,它会向玩家应用伤害,并在2秒延迟后杀死敌人。
现在,敌人可以跑向玩家,然后在与玩家碰撞时应用伤害并自毁。 然而,它并没有考虑玩家与敌人之间的距离或障碍物。 因此无论距离远近或视野如何,敌人总是会追逐玩家。
BP_Enemy完成后的On Component Hit逻辑应该如下所示:
要将下面的蓝图片段复制到你的BP_Enemy事件图表中,请确保你的变量名称与示例项目中的一致。
添加距离和障碍物逻辑
接下来,添加一个最大检测距离,以便只有在玩家距离敌人足够近时,敌人才能检测到玩家。 然后,最后通过实现敌人的视野限制来收尾,使它们无法透过墙壁看到玩家。
为此,首先在蓝图库中创建新函数,用于执行敌人和玩家之间的线条检测。
使用线条检测计算视线和距离
线条检测(Line Trace)(或射线检测)是游戏开发中的常见做法。 线条检测从世界中的一个点(如源Actor)画出一条看不见的线,看看这条线撞到了什么。 它通常用于在运行时检测物体或表面,以便你可以根据检测结果触发逻辑。
在本教程中,你将从敌人角色发出一道线条检测,以检测玩家角色何时处于敌人的视野范围内。
如需详细了解线条检测,请参阅使用射线进行命中判定文档。
要设置一个使用线条检测来寻找玩家的函数,请执行以下步骤:
在内容浏览器中,转到你的Core文件夹并打开
BPL_FPGame蓝图库。添加名为
fnBPLFindPlayer的新函数。在新函数的细节面板内,在输入(Inputs)分段,点击添加 (+)来创建以下输入:
Player Reference (Pawn Object Reference)
Start Location (Vector)
Max Detection Distance (Float)
Debug (Boolean)
添加这些输入后,fnBPLFindPlayer函数条目节点现在针对每个变量都有了相应的引脚。
在函数的细节面板内,在输出(Outputs)分段,点击添加 (+)来创建一个名为Found的布尔(Boolean)类型输出值。
添加Found输出后,你的图表中会出现一个新的带有Found引脚的Return Node(返回节点)。
在我的蓝图(My Blueprint)面板中,在局部变量(Local Variables)分段,点击添加 (+)来创建一个名为PlayerFound且类型为Boolean(布尔)的局部变量。 确保其默认值为false。
玩家引用对于函数能否正常执行至关重要,因此当函数开始时,请检查有效的玩家引用以避免出错。
要检查输入是否有效,请执行以下步骤:
按住Alt键并点击连线或任一执行(exec)引脚,删除条目节点与Return Node(返回节点)之间的连线。
在函数条目节点之后,创建并连接Is Valid节点。
确保你创建的是节点动作列表底部附近带问号(?)图标的Is Valid节点。
将条目节点的Player Reference引脚连接到Is Valid节点的Input Object(输入对象)引脚。
将Is Not Valid(无效)执行(exec)引脚连接到Return节点。
对于Found返回值,连接一个对PlayerFound变量的引用。 这将退出函数,同时将PlayerFound=False返回到敌人的事件图表。
如果玩家有效,函数应执行线条检测对象,然后返回PlayerFound。
要设置线条轨迹,请按照以下步骤操作:
从Is Valid执行(exec)引脚处,添加一个Sequence(序列)节点来整理你的逻辑。
从Sequence节点的Then 0引脚处拖出并添加一个Line Trace By Channel(按通道进行线条检测)节点。
按通道进行线条检测(Line Trace By Channel)可检测特定碰撞通道上的物体。 要查看蓝图的碰撞通道设置,请选择该蓝图的碰撞组件,并在细节面板中转到Collision(碰撞) > Collision Presets(碰撞预设)属性。 Trace Responses(检测响应)分段具有Visibility(可见性)和Camera(摄像机)通道。
从函数条目节点开始,拖动Start Location引脚并将其连接到Line Trace By Channel节点的Start(起始)引脚。 这会将敌人角色的位置设置为线条检测的起点。
为了保持图表整洁,创建一个Get Player Reference节点。 该节点右上角的f图标表示它是一个函数输入。 你可以像添加其他任何变量一样添加对这些输入的引用。
将Player Reference引脚拖动到图表中的空白位置。 这会打开节点操作列表。 在列表中搜索并创建一个Get Actor Location节点。
将其返回值(Return Value)连接到按通道进行线条检测(Line Trace By Channel)节点的End(终点)引脚。 这将在玩家位置结束线条检测。
在按通道进行线条检测(Line Trace By Channel)节点上,使用下拉菜单将Trace Channel(追踪通道)属性设置为Camera(摄像机)。
既然你已经设置了线条检测的起点和终点,接着定义当检测击中物体时会发生什么。
要检查线条检测是否击中了玩家,请执行以下步骤:
拖出线条检测的Out Hit引脚并添加一个Break Hit Result(分解击中结果)节点。 当线条检测击中对象时,会收集大量信息。 你只需要使用几个特定数值,因此需要分解击中结果(Break Hit Result)节点将数据拆分为返回数据的个体组件。
在Break Hit Result节点中,点击底部的箭头查看更多选项。
你想要知道玩家是否在视野范围内且处于敌人的特定距离内,因此你将使用击中Actor(Hit Actor)引脚来检查玩家,并使用检测起始(Trace Start)和检测终点(Trace End)来测量到击中物体的距离。
拖出Hit Actor(击中Actor)引脚并添加一个等于(Equal)运算符节点。 对于底部输入,将引用连接到Player Reference函数输入。
要引用函数输入值,你可以像引用其他变量一样添加一个Get(获取)节点。
创建一个距离 (向量)(Distance (Vector))节点。 将V1连接到Trace Start,将V2连接到Trace End。
向量是表示Object或点在3D空间中位置的一组X、Y和Z数值。 距离(Distance)节点计算其中两个点之间的距离,这将产生一个浮点数值。
从距离(Distance)节点的返回值(Return Value)处拖出引脚并添加一个小于 (<)(Less (<))节点。 对于底部输入,连接一个对Max Detection Distance函数输入的引用。
创建一个与 (布尔)(AND (Boolean))节点。
将等于 (==)(Equals (==))和小于 (<)(Less (<))节点的布尔输出引脚连接到与(AND)节点的输入引脚。
利用此逻辑,如果线条检测击中的Actor等于玩家引用(Player Reference),且检测起点(敌人)与终点(击中的物体)之间的距离小于最大检测距离(Max Detection Distance),与(AND)节点就会返回true。
要使用检测结果来设置并返回PlayerFound的值,请执行以下步骤:
从与(AND)节点的输出引脚拖出并添加一个分支(Branch)节点。
连接其执行(exec)引脚,使其在按通道进行线条检测(Line Trace By Channel)之后执行。
从分支(Branch)节点的True执行引脚处,创建一个Set(设置)变量节点,将局部变量PlayerFound设置为 true。
从分支(Branch)节点的False引脚处,创建一个Set PlayerFound变量。 将该值保持为false。
线条检测逻辑已完成,所以请回到Sequence(序列)节点,将其Then 1引脚连接到Return Node(返回节点)以退出函数,并将PlayerFound结果发送回敌人蓝图。
添加线条检测调试选项
你希望能够在关卡编辑器中轻松打开和关闭线条检测/检测(碰撞)的视效。 但是,线条检测节点只允许你从绘制调试类型(Draw Debug Type)列表中选择一个选项。 你可以使用一个选择(Select)节点,使调试类型在关卡编辑器中可编辑。
要设置可自定义的调试选项到线条检测,请按以下步骤操作:
从按通道进行线条检测(Line Trace By Channel)节点的绘制调试类型(Draw Debug Type)引脚处拖出并创建一个选择(Select)节点。
将选择(Select)节点的索引(Index)引脚连接到函数Debug输入的引用。
选择(Select)节点的索引是一个通配符,所以当你连接布尔类型的Debug值时,选项会变为False和True。
将False选项设置为None(无)。 将True选项设置为For Duration(时长内)。
在按通道进行线条检测(Line Trace By Channel)节点底部,点击箭头按钮查看更多选项。 将绘制时间(Draw Time)改为
0.1秒。保存并编译你的蓝图函数库。
此逻辑使用Debug变量来确定线条检测调试绘图是否处于激活状态:
当Debug变量为false时,绘制调试类型(Draw Debug Type)被设置为None(无),意味着不会渲染调试绘图。
当Debug被设置为true时,选择(Select)节点会将绘制调试类型(Draw Debug Type)改为For Duration(时长内),这会在运行时将线条检测渲染一段设定的时长。
完成后的fnBPLFindPlayer函数应该如下所示:
如果你将此蓝图片段复制到你的项目中,你需要将函数条目节点的执行(Exec)和玩家引用(Player Reference)引脚连接到Is Valid节点的执行(Exec)和输入对象(Input Object)引脚。 为了使代码片段能够正确复制,请确保你的函数输入名称与示例项目中的一致。
更新追踪玩家逻辑
现在你可以调用一个在考虑距离和视线的情况下寻找玩家的函数,向敌人添加逻辑,使其仅在视野内或在线条检测成功找到玩家时才寻找并追逐玩家。
要使敌人在找到玩家后进行追逐,请执行以下步骤:
回到
BP_Enemy蓝图。 在事件图表中,找到提供的Event Tick节点。拖动Event Tick节点的执行(Exec)引脚,并创建一个分支(Branch)节点。
On Tick事件在游戏运行时每帧运行。 使用它可以让敌人不断检查玩家的位置并确定是否开始追逐。
拖动分支(Branch)节点的条件(Condition)引脚并创建一个非布尔(NOT Boolean)节点,该节点会返回布尔值的反值。
在本例中,你想要对Eliminated变量求反,因此请拖动非(NOT)节点的输入引脚并搜索Get Eliminated。
这会在使用分支(Branch)节点的True引脚继续执行之前,检查敌人是否未被消灭。
拖动分支(Branch)节点的True引脚,并创建一个FnBPLFindPlayer节点。 此节点执行一道线条检测来寻找世界中的玩家,并检查敌人与玩家之间的当前距离。
在Fn BPLFind玩家节点上,使用其引脚建立以下这些连接:
Player Reference:将引用连接到PlayerRef变量。
Start Location:创建一个Get Actor Location节点,并将目标(Target)属性设置为self(自身)。 这会将线条检测的起点指定给敌人在世界中的位置。
Max Detection Distance:右键点击该引脚并选择提升为变量(Promote to Variable)。 点击眼睛图标使其可编辑,并将其分类更改为Setup。 编译,并将它的默认值(Default Value)设置为
20000(200米)。 该数值越小,玩家就越容易被敌人检测。Debug:选择提升为变量(Promote to Variable)并将其命名为
DebugDetection。 还要将此变量设置为可编辑并显示在Setup类别中。
如果线条检测找到了玩家,则将敌人的最大移动速度设置为所需的数值,使敌人朝玩家移动。 如果不是,你应该将速度设置为0,这样敌人就不能向玩家移动。
要使敌人在找到玩家时移动,并在未找到玩家时停止,请执行以下步骤:
在FnBPLFindPlayer节点中,右键点击Found引脚并选择提升为变量(Promote to Variable)。 这会自动为该新变量连接一个Set(设置)节点。 现在你可以在图表的其他地方使用此Found结果了。
在Set节点之后,连接一个分支(Branch)节点。 将分支(Branch)节点的条件(Condition)引脚拖动到Set Found节点的输出引脚上。
从组件(Components)面板中,将CharMoveComp拖入事件图表。 拖动其引脚并创建一个Set Max Walk Speed(设置最大行走速度)节点。
拖动分支(Branch)节点的True引脚,将其连接到Set Max Walk Speed节点。
对于Set节点的最大行走速度(Max Walk Speed)引脚,连接一个对Max Speed变量的引用。
在Set节点之后,连接一个MoveToPlayer节点来调用该事件,使敌人仅在发现玩家时才移动。
复制Set Max Walk Speed和角色移动(Character Movement)节点。 将分支(Branch)节点的False引脚连接到新的Set节点。
在第二个设置最大行走速度(Set Max Walk Speed)节点中,确保行走速度的值为0。 这会使敌人停止移动,而无需取消其AI MoveTo任务并再次运行任务。
现在你已经让敌人每帧都在寻找玩家,删除你在事件BeginPlay逻辑末尾添加的Move to Player节点。 此节点用于测试部分完成的敌人,但你不再需要它了。
转到MoveToPlayer事件逻辑。 现在你只需要让敌人在找到玩家时执行以下代码:
从MoveToPlayer节点拖出并创建一个分支(Branch)节点。
对于分支(Branch)的条件(Condition),添加对Found变量的引用。
现在,如果玩家在与敌人的给定距离内,并且两者之间没有障碍物,敌人的移动速度将设置为MaxSpeed变量,他们将执行MoveToPlayer逻辑。 如果其中任一条件未满足,敌人的移动速度将设置为0,阻止敌人向玩家移动。
BP_Enemy最终的变量列表应当如下所示:
要将下面的蓝图片段复制到你的BP_Enemy事件图表中,请确保你的变量名称与示例项目中的一致。
BP_Enemy最终的Event BeginPlay逻辑应当如下所示:
BP_Enemy最终的MoveToPlayer逻辑应当如下所示:
BP_Enemy最终的Event Tick逻辑应当如下所示:
允许敌人受到伤害
在完成此蓝图之前,你还需要确保敌人也可以受到伤害。 如果你在本教程系列之外继续开发游戏,你可能希望敌人受到陷阱伤害,或者给玩家提供一个可以伤害并消灭敌人的可装备物品。
要让敌人承受伤害,请执行以下步骤:
在事件图表中的任意位置点击右键,然后创建一个Event AnyDamage节点。
从Event AnyDamage的执行(exec)引脚连出,创建一个Set CurrentHP节点。
要从CurrentHP中减去所受的伤害,拖动Current HP属性的引脚并创建一个减(Subtract)节点。
拖动减(Subtract)节点的上方引脚,并创建一个获取Current HP(Get Current HP)节点。
将减(Subtract)节点的底部引脚连接到Event AnyDamage节点的伤害(Damage)属性上。
当此Actor受到任何伤害时,此逻辑会获取 CurrentHP 变量的值并减去伤害(Damage),然后将 CurrentHP 设置为新数值。
接下来,检查CurrentHP是否小于或等于0,这应当会消灭敌人。
要让敌人在生命值为0时被移除,请执行以下步骤:
拖动Set节点的引脚,然后创建一个新的分支(Branch)节点。
拖动分支(Branch)节点的条件(Condition)引脚并创建一个小于等于 (<=)(Less Equal (<=))节点。
将Set Current HP节点的输出引脚连接到小于等于(Less Equal)节点的上方输入引脚。 确保下方引脚设置为0。
从分支(Branch)节点的True引脚拖出,并创建一个仅Do Once(执行一次)节点。
拖动其已完成(Completed)引脚并创建一个Set Eliminated节点。 在Set节点中,将Eliminated属性切换为true。
在Set节点后连接时长(Duration)设为
2的延迟(Delay)节点。拖动延迟(Delay)节点的已完成(Completed)引脚,并创建一个销毁Actor(Destroy Actor)节点,其目标(Target)属性设置为self(自身)。
保存(Save)并 编译(Compile) 蓝图。
BP_Enemy完成后的Event AnyDamage逻辑应当如下所示:
测试完成后的敌人角色
如果你现在运行游戏,根据敌人和玩家之间的距离,敌人可能不会移动。 如果你靠近敌人,它应该会开始追逐玩家。
让我们设置一个障碍物来试试。 退出运行模式,通过点击主工具栏中的创建(Create)按钮并选择形状(Shapes) > 立方体(Cube),在寻路网格体区域中间添加一个遮挡立方体或墙壁。 这个障碍物会切入寻路网格体。
如果玩家从这个障碍物后面出发,敌人将无法追逐玩家,因为线条检测会被立方体阻挡,导致敌人找不到玩家。 如果玩家绕着立方体行走,并且敌人可以直接看到玩家,那么它就会开始追逐玩家。 一旦敌人开启了AI MoveTo动作,躲在障碍物后面就无法停止敌人的追击了。
在项目中布置敌人的位置
在示例关卡中,我们在Hallway 3和Room 3放置了几个敌人。 第一个敌人被单独放置在Hallway 3中,以便给玩家一个简单的初次遭遇,并帮助他们了解敌人角色的行为和能力。 在走廊中遇到单个敌人后,我们在Room 3中放置了两个额外的敌人角色,为玩家带来更多的挑战。
你可以改变敌人的数量和类型,以便在整个项目中提供不同的难度级别。 向玩家引入新的敌人和挑战会慢慢形成更平衡的难度曲线,玩家也会随着时间的推移而习惯。 在让玩家学习如何导航和击败敌人之后,打造有一定难度的遭遇战,为玩家提供挑战,也让玩家有成就感。
下一步
在下一个模块中,你将学习如何为玩家的动作集添加冲刺机制。 这能帮助玩家穿过关卡中熟悉的地方,也为他们提供了一个工具来避开你刚刚添加到项目中的敌人。