原文:How to Create a Simple FPS in Unreal Engine 4
作者:Tommy Tran
译者:Shuchang Liu
在本篇教程中,将学习创建一个简单的第一人称视角射击游戏。你将学会如何创建一个持枪的第一人称角色,并实现射击其他Actor。
第一人称视角射击游戏(FPS)是一类玩家以游戏角色视角进行射击体验的游戏。FPS游戏非常热门,不乏使命召唤和战地等大作。
Unreal引擎最开始就是为FPS游戏量身打造的引擎,所以用Unreal引擎制作FPS游戏也是理所当然的事。在本篇教程中,你将学会:
- 创建能够四处移动的第一人称角色
- 创建一把枪,绑定在角色身上
- 使用直线追踪(大家熟知的射线追踪)发射子弹
- 对Actor扣除伤害
注意:本篇教程只是Unreal Engine 4系列教程的其中一篇:
- Part 1:入门
- Part 2:蓝图
- Part 3:材质
- Part 4:UI
- Part 5:制作简单游戏
- Part 6:动画
- Part 7:音频
- Part 8:粒子系统
- Part 9:AI
- Part 10:制作简单FPS游戏
起步入门
下载示例项目并解压。进入项目文件夹,双击BlockBreaker.uproject打开项目,我们能看到以下场景:

绿色墙上包含着多个目标,当目标受到伤害时会变红。一旦血量值降为零,目标就会消失。红色按钮可以重置所有的目标。
首先,我们要创建玩家角色。
创建玩家角色
打开Blueprints文件夹并创建一个新的Blueprint Class类,选择Character作为父类,并将其命名为BP_Player。

Character本身是Pawn的一种,额外多了一些其他功能,比如CharacterMovement组件。

该组件会自动处理如走动跑跳等移动功能,我们只要简单调用对应函数就可以移动角色。我们也可以在该组件设置走路速度,起跳速度等变量。
在实现移动功能前,Character需要知道玩家的按键情况,因为我们先将移动映射到W,A,S和D键上。
注意:如果你还不熟悉关于键位映射的有关内容,请查看蓝图教程。键位映射是一种定义键位执行特定行为的方法。
创建移动映射
选择Edit\Project Settings,打开Input设置。
创建两个名为MoveForward和MoveRight的轴映射。MoveForward控制前后移动,MoveRight控制左右移动。

对于MoveForward,将按键改为W,随后,创建多一个键位插槽,将其设置为S,并将Scale改为-1.0。

随后,我们会将Scale值跟角色朝向向量相乘,当Scale值是正数时,向量方向朝前,当Scale值是负数时,向量方向朝后。通过得出的向量结果,我们就可以让角色朝前朝后移动了。

接着,我们要对左右移动做同样的设置,将MoveRight设为D,新建键位插槽设为A,Scale值设为-1.0。

现在我们设置好了键位映射,就可以用它们来进行移动了。
实现移动
打开BP_Player并打开Event Graph,添加MoveForward事件节点(在Axis Events分类下)。即使没有按任何按键,该事件也会每帧调用。

该事件会输出Axis Value,也即刚才所设置的Scale值。当按下W时,输出1,当按下S时,输出-1。如果不按任何按键,输出0。
接着,我们要让角色进行移动,添加Add Movement Input,进行如下连接:

Add Movement Input节点会用一个向量与Scale Value字段相乘,这样就能将向量转换到对应方向。由于我们用了Character类,CharacterMovement组件会将Pawn往对应方向移动向量距离。
现在,我们需要指定移动方向。我们希望角色往正面朝向移动,所以可以使用Get Actor Forward Vector节点,该节点返回一个正面朝向向量,创建节点如图下一样连接:

小结:
- MoveForward节点会每帧输出Axis Value,当按下W时输出1,当按下S时输出-1,什么都不按,输出0
- Add Movement Input节点将玩家朝向向量与Scale Value相乘,使得不同按键控制输出不同方向的向量。什么都不按,意味着向量并没有方向,角色原地不动
- CharacterMovement组件获得Add Movement Input节点的输出,驱动角色朝指定方向移动
MoveRight按以上步骤操作,不过记得将Get Actor Forward Vector节点改为Get Actor Right Vector节点。

在测试移动功能前,我们还要设置下Game Mode里的默认Pawn。
设置默认Pawn
点击Compile并回到主编辑器,打开World Settings面板并找到Game Mode设置,将Default Pawn Class改为BP_Player。

注意:如果你的主编辑器面板还没有World Settings面板,在Toolbar选择Settings\World Settings调出面板。
现在运行游戏你就能控制BP_Player了,按下Play并使用W,S,A和D来进行移动。

我们接着创建输入映射来观察四周。
创建观察映射
打开Project Settings,再创建两个轴映射,分别命名为LookHorizontal和LookVertical。

将LookHorizontal的键位改为Mouse X。

这样当鼠标向右滑动时会输出正数,反之亦然。
接着,将LookVertical的键位改为Mouse Y。

这样当鼠标向上滑动时会输出正数,反之亦然。
现在,我们要写点逻辑来实现转动视角。
实现转动视角
如果一个Pawn上没有Camera组件,Unreal会自动为你创建一个摄像机。默认情况下,摄像机会使用控制器的旋转。
注意:如果你想了解更多关于控制器的内容,可以查看AI部分教程。
虽然控制器并没有物理实体,它仍旧有自己的旋转。这意味着我们可以让角色和摄像机面向不同方向。比如,在第三人称游戏里,角色和摄像机并不总是处于同一方向。

要在第一人称视角里转动摄像机,我们所要做的就是修改控制器的旋转。
打开BP_Player并创建LookHorizontal事件。

要让摄像机看向左边或右边,我们需要调整控制器的偏航角(Yaw),创建Add Controller Yaw Input节点并进行如下连接:

现在,当你水平移动鼠标时,控制器会向左或向右转动,由于摄像机使用了控制器的旋转,摄像机也会跟着转动。
重复以上步骤实现LookVertical,不过记得将Add Controller Yaw Input改为Add Controller Pitch Input节点。

如果我们现在就运行测试游戏,会发现上下转动的反转的,也就是说,当我们鼠标向上滑动,摄像机是向下转动的。
如果想改成非反转控制,将Axis Value剩余-1,这样就能对Axis Value取反,控制器的转动也会反转过来。

点击Compile并按下Play运行游戏,使用鼠标来转动视角吧。

现在移动和视角转动都实现了,是时候搞把枪了!
创建枪支
你还记得当创建蓝图类时,我们可以指定一个父类吧?好吧,其实也可以指定自己的蓝图类作为父类。当我们有多个不同类型的物体,拥有同样的函数或属性时,就会发现很有用处。
举例来说,当我们有多种类型的汽车。我们可以创建一个Car类,包含比如速度和颜色等变量。然后可以再创建其他类(子类),使用Car类作为父类。每个子类都会包含同样的变量。现在我们就能轻易地创建不同速度和颜色变量的汽车了。

我们可以使用相同方式来创建枪支。因为我们首先要创建一个基类。
创建枪支基类
回到主编辑器并创建Actor类型的Blueprint Class,将其命名为BP_BaseGun并打开。
接着,我们要创建一些定义枪械参数的变量,创建如下float类型变量:
- MaxBulletDistance:子弹最远飞行距离
- Damage:子弹伤害
- FireRate:子弹发射间隔(秒)

注意:每个变量的默认值都是0,对本例来说没什么问题。然而,如果你希望新的枪支类有别的默认值,你需要在BP_BaseGun设置下。
现在,我们需要给枪支一个物理外观,添加Static Mesh组件并命名为GunMesh。

先别急着给Static Mesh组件设置网格,我们会在创建枪械子类时再做这件事。
创建枪械子类
点击Compile并返回主编辑器。要创建子类,我们要在右键点击BP_BaseGun,从弹出菜单选中Create Child Blueprint Class。

将其命名为BP_Rifle并双击打开,然后打开Class Default设置以下变量:
- MaxBulletDistance: 5000
- Damage: 2
- FireRate: 0.1

这意味着每颗子弹能最远飞行5000单位的距离。如果子弹命中Actor,能对其造成2点伤害。当持续开火射击时,射击间隔不少于0.1秒。
接着,我们需要指定这把枪的网格,选中GunMesh组件,并将其Static Mesh设置为SM_Rifle。

这把枪现在就完成了,点击Compile并关闭BP_Rifle。
接着,我们要创建自己的摄像机组件了。这样能够更好地控制摄像机位置,我们还可以将枪支跟摄像机绑定在一起,这样枪支就能始终保持在摄像机的正面了。
创建摄像机
打开BP_Player并创建摄像机组件,将其命名为FpsCamera。

摄像机的默认位置有点偏低,会让玩家感觉太矮,将FpsCamera的位置改为(0, 0, 90)。

默认情况下,摄像机组件并不使用控制器的旋转。要修正这点,在Details面板启用Camera Settings\Use Pawn Control Rotation。

接着,我们需要定义枪支的位置。
定义枪支位置
要定义枪支位置,我们可以使用Scene组件。这个组件非常适合用来定义位置,因为它只包含一个Transform。首先确保选中了FpsCamera,然后再创建Scene组件,组件就会附着在摄像机节点下,将组件命名为GunLocation。

通过在FpsCamera放置GunLocation,枪支就会相对于摄像机保持同一位置,这样枪支就能始终保持在镜头前方。
接着,将GunLocation的位置改为(30, 14, -12),让其处于相对摄像机靠前一侧位置。

随后,将旋转设为(0, 0, -95)。当枪支在这个位置时,看起来就像在瞄准屏幕中间。

现在,我们需要生成枪支并将其绑定在GunLocation位置上。
生成并绑定枪支
找到Event BeginPlay并创建Spawn Actor From Class节点,将Class设为BP_Rifle。

由于我们需要用到枪支,先创建变量存储其引用。创建BP_BaseGun类型变量,将其命名为EquippedGun。
这里要注意新建变量不要设为BP_Rifle类型的,因为玩家应该能够使用各种类型的枪支,而不单只是来福枪,否则如果生成了其他种类的枪支,就不能存储在BP_Rifle类型变量上了。
接着,将Spawn Actor From Class的Return Value引脚设为EquippedGun。

要绑定枪支的位置,我们需要用上AttachToComponent组件。创建组件并将Location Rule和Rotation Rule设为Snap to Target。这样就能让枪支拥有跟其父类一样的位置和旋转。

接着,创建GunLocation引用并进行如下连线:

小结:
- 当BP_Player生成时,它会连带生成BP_Rifle实例
- EquippedGun字段会持有BP_Rifle引用
- AttachToComponent节点会将枪支设置在GunLocation位置。
点击Compile并按下Play运行游戏。现在当玩家生成时,枪支也会一同生成,显示在摄像机的前面。

现在有趣的地方来了:射击子弹!要检测子弹是否打中东西,我们要用上射线检测(line trace)。
射击子弹
射线检测是一个包含开始点和结束点(两点成线)的函数,它会检测这条线上的每个点,看是否碰到其他物体。在游戏中,这是用于检测子弹是否打中东西的最普遍做法。
由于射击是属于枪支的特性,射击函数应该设计在枪支类里,而不是角色类。打开BP_BaseGun并创建名为Shoot的函数。
随后,创建两个Vector输入,分别命名为StartLocation和EndLocation。它们分别为射线检测的开始点和结束点(通过BP_Player传入)。

我们可以使用LineTraceByChannel节点来执行射线检测。这个节点会使用可视力(Visibility)或者摄像机(Camera)碰撞通道来进行碰撞检测。创建节点,进行如下连线:

接着,我们需要检测射线是否碰撞到任何东西。创建Branch并进行如下连线:

如果检测到碰撞,Return Value会输出true,反之亦然。
为了在子弹击中物体时给予玩家视觉反馈,我们可以使用粒子特效。
生成子弹撞击粒子
首先,我们需要获得射线碰撞的位置。拖拽Out Hit引脚到图表空白处,从弹出菜单中,选择Break Hit Result。

这样我们能得到一个跟射线检测结果相关的,具有多种引脚的节点。
创建Spawn Emitter at Location节点,并将Emitter Template设为PS_BulletImpact。随后,连接其Location与Break Hit Result的Location。

以下是目前的函数连线:

小结:
- 当Shoot函数执行时,它首先会执行一个射线检测
- 如果检测到碰撞,Spawn Emitter at Location节点会在碰撞位置生成粒子特效PS_BulletImpact。
现在射击逻辑已经完成了,再写下调用逻辑即可。
调用射击函数
首先,我们需要创建射击的按键映射。点击Compile并打开Project Settings。创建一个新的Axis Mapping并命名为Shoot,将其按键设为Left Mouse Button,然后关闭Project Settings。

随后,打开BP_Player并创建Shoot事件。

为了检测玩家是否按下Shoot键,我们需要检测Axis Value是否等于1。创建如下高亮节点:

接着,创建EquippedGun引用节点,并调用它的Shoot函数。

现在,我们需要计算下射线检测的起始点和结束点。
计算射线检测的位置
在很多FPS游戏里,子弹其实是从摄像机而不是从枪里发射出来的,这是因为摄像机天然就是对准射击准心的。所以如果从摄像机发射子弹,就可以保证其一定会往准心飞行。
注意:也有些游戏确实是从枪里发射子弹的。然而,它要求一些额外计算来保证子弹向准心方向射击。
创建FpsCamera引用并将其与GetWorldLocation连接。

现在我们还需要结束点位置。记得枪支有个MaxBulletDistance变量吧,这意味着结束点位置应该在距离摄像机MaxBulletDistance单位远的地方。因此要计算出结束点位置,添加如下高亮节点:

随后,像下图一样连接所有节点:

小结:
- 当玩家按下或按住鼠标左键时,枪支会从摄像机处开始发射子弹。
- 子弹会向前飞行MaxBulletDistance距离
点击Compile并按下Play运行游戏,按住鼠标左键开始发射子弹吧!

现在,枪支是每帧都在射击的,射速实在是有点太快了,所以下一步要降低枪支的开火速度。
降低开火速度
首先,我们需要一个变量检测玩家是否正在射击。打开BP_Player创建boolean类型变量,命名为CanShoot,将其默认值设为true。如果CanShoot等于true,说明玩家正在射击。
下面将Branch部分图表改成下图:

现在,玩家只有在按下Shoot键且CanShoot等于true时才能进行射击了。
接着,添加如下高亮节点:

调整修改小结:
- 玩家只有在按住鼠标左键且CanShoot等于true时才能进行射击
- 一旦玩家射出一颗子弹,CanShoot就会设为false,防止马上射出第二颗子弹
- CanShoot会在隔FireRate秒时间后重置为true
点击Compile并关闭BP_Player,按下Play运行游戏测试下枪支的射速吧!

实现受击
在Unreal里,每个Actor都能受击。然而,Actor要对受击伤害做出什么处理是可以自由定义的。
比如,当战斗中的游戏角色当受击时,会扣除血量。然而,像气球一类物体是没有血量概念的。取而代之的,我们会编写逻辑让气球在受击时爆炸。
在我们处理Actor受击时,我们首先要施加一个“伤害”。打开BP_BaseGun并在Shoot函数后添加Apply Damage节点。

接着,我们需要指定要施加伤害的Actor,在本例中,就是射线检测到的Actor。连接Damaged Actor与Break Hit Result的Hit Actor引脚。

最后,我们需要指定要受击伤害数值,创建Damage变量引用,并与Base Damage相连。

现在,当我们调用Shoot函数时,它就会对射线检测到的物体进行伤害。点击Compile并关闭BP_BaseGun。
现在我们需要处理每种Actor对于受击伤害的反馈。
处理受击
首先,我们需要处理目标获得伤害数据,打开BP_Target并创建Event AnyDamage事件节点,这个节点会在受到伤害且其数值不为零时触发执行。

随后,调用TakeDamage函数并连接Damage引脚。这个函数会将目标的Health变量减去Damage数值,并更新目标的颜色。

现在,当目标受到伤害时,它就会扣除血量了。点击Compile并关闭BP_Target。
接着,我们需要处理按钮对伤害的反馈。打开BP_ResetButton并创建Event AnyDamage。随后,调用ResetTargets函数。

这个函数会在按钮受击时调用并重置所有目标的状态。点击Compile并关闭BP_ResetButton。
按下Play运行游戏开始射击目标。如果你想要重置所有目标,就朝按钮射击。

后续学习
你可以在这里下载完整项目。
虽然本篇教程中所制作是一个非常简单的FPS游戏,你可以在此基础上进一步扩展,试着创建更多具有不用射速和伤害的枪械,也可以尝试添加装弹功能!
以上就是Unreal Engine 4 系列教程的全部内容了。不过别担心,我们后续还会制作更多关于Unreal Engine 4的教程。现在我们开始在招募新的Unreal Engine教程团队了,希望可以为社区带来更多精彩的Unreal Engine系列教程。希望到时可以看到你们的身影!:]