Unreal Engine 4 系列教程 Part 10:制作简单FPS游戏

原文: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系列教程的其中一篇:

起步入门

下载示例项目并解压。进入项目文件夹,双击BlockBreaker.uproject打开项目,我们能看到以下场景:

绿色墙上包含着多个目标,当目标受到伤害时会变红。一旦血量值降为零,目标就会消失。红色按钮可以重置所有的目标。

首先,我们要创建玩家角色。

创建玩家角色

打开Blueprints文件夹并创建一个新的Blueprint Class类,选择Character作为父类,并将其命名为BP_Player

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

该组件会自动处理如走动跑跳等移动功能,我们只要简单调用对应函数就可以移动角色。我们也可以在该组件设置走路速度,起跳速度等变量。

在实现移动功能前,Character需要知道玩家的按键情况,因为我们先将移动映射到WASD键上。

注意:如果你还不熟悉关于键位映射的有关内容,请查看蓝图教程。键位映射是一种定义键位执行特定行为的方法。

创建移动映射

选择Edit\Project Settings,打开Input设置。

创建两个名为MoveForwardMoveRight轴映射。MoveForward控制前后移动,MoveRight控制左右移动。

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

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

接着,我们要对左右移动做同样的设置,将MoveRight设为D,新建键位插槽设为AScale值设为-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节点,该节点返回一个正面朝向向量,创建节点如图下一样连接:

小结:

  1. MoveForward节点会每帧输出Axis Value,当按下W时输出1,当按下S时输出-1,什么都不按,输出0
  2. Add Movement Input节点将玩家朝向向量Scale Value相乘,使得不同按键控制输出不同方向的向量。什么都不按,意味着向量并没有方向,角色原地不动
  3. 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并使用WSAD来进行移动。

我们接着创建输入映射来观察四周。

创建观察映射

打开Project Settings,再创建两个轴映射,分别命名为LookHorizontalLookVertical

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 ClassReturn Value引脚设为EquippedGun

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

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

小结:

  1. BP_Player生成时,它会连带生成BP_Rifle实例
  2. EquippedGun字段会持有BP_Rifle引用
  3. AttachToComponent节点会将枪支设置在GunLocation位置。

点击Compile并按下Play运行游戏。现在当玩家生成时,枪支也会一同生成,显示在摄像机的前面。

现在有趣的地方来了:射击子弹!要检测子弹是否打中东西,我们要用上射线检测(line trace)

射击子弹

射线检测是一个包含开始点和结束点(两点成线)的函数,它会检测这条线上的每个点,看是否碰到其他物体。在游戏中,这是用于检测子弹是否打中东西的最普遍做法。

由于射击是属于枪支的特性,射击函数应该设计在枪支类里,而不是角色类。打开BP_BaseGun并创建名为Shoot的函数。

随后,创建两个Vector输入,分别命名为StartLocationEndLocation。它们分别为射线检测的开始点和结束点(通过BP_Player传入)。

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

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

如果检测到碰撞,Return Value会输出true,反之亦然。

为了在子弹击中物体时给予玩家视觉反馈,我们可以使用粒子特效。

生成子弹撞击粒子

首先,我们需要获得射线碰撞的位置。拖拽Out Hit引脚到图表空白处,从弹出菜单中,选择Break Hit Result

这样我们能得到一个跟射线检测结果相关的,具有多种引脚的节点。

创建Spawn Emitter at Location节点,并将Emitter Template设为PS_BulletImpact。随后,连接其LocationBreak Hit ResultLocation

以下是目前的函数连线:

小结:

  1. Shoot函数执行时,它首先会执行一个射线检测
  2. 如果检测到碰撞,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单位远的地方。因此要计算出结束点位置,添加如下高亮节点:

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

小结:

  1. 当玩家按下或按住鼠标左键时,枪支会从摄像机处开始发射子弹。
  2. 子弹会向前飞行MaxBulletDistance距离

点击Compile并按下Play运行游戏,按住鼠标左键开始发射子弹吧!

现在,枪支是每帧都在射击的,射速实在是有点太快了,所以下一步要降低枪支的开火速度。

降低开火速度

首先,我们需要一个变量检测玩家是否正在射击。打开BP_Player创建boolean类型变量,命名为CanShoot,将其默认值设为true。如果CanShoot等于true,说明玩家正在射击。

下面将Branch部分图表改成下图:

现在,玩家只有在按下Shoot键且CanShoot等于true时才能进行射击了。

接着,添加如下高亮节点:

调整修改小结:

  1. 玩家只有在按住鼠标左键CanShoot等于true时才能进行射击
  2. 一旦玩家射出一颗子弹,CanShoot就会设为false,防止马上射出第二颗子弹
  3. CanShoot会在隔FireRate秒时间后重置为true

点击Compile并关闭BP_Player,按下Play运行游戏测试下枪支的射速吧!

实现受击

在Unreal里,每个Actor都能受击。然而,Actor要对受击伤害做出什么处理是可以自由定义的。

比如,当战斗中的游戏角色当受击时,会扣除血量。然而,像气球一类物体是没有血量概念的。取而代之的,我们会编写逻辑让气球在受击时爆炸。

在我们处理Actor受击时,我们首先要施加一个“伤害”。打开BP_BaseGun并在Shoot函数后添加Apply Damage节点。

接着,我们需要指定要施加伤害的Actor,在本例中,就是射线检测到的Actor。连接Damaged ActorBreak Hit ResultHit 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系列教程。希望到时可以看到你们的身影!:]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容