import*asUEfrom'ue'importTS_BaseGunfrom'./TS_BaseGun'import{$ref,$unref}from'puerts'//和本节无关,但后面要用到import'./ObjectExt'classTS_PlayerextendsUE.Character{FpsCamera: UE.CameraComponent;EquippedGun:TS_BaseGun;GunLocation:UE.SceneComponent;// other code...}exportdefaultTS_Player;
这几个变量的含义分别是
FpsCamera: 摄像机;
EquippedGun:枪的引用;
GunLocation:枪的位置;
添加构造函数,初始化FpsCamera,GunLocation:
classTS_PlayerextendsUE.Character{// other code...Constructor(){letFpsCamera=this.CreateDefaultSubobjectGeneric<UE.CameraComponent>("FpsCamera",UE.CameraComponent.StaticClass());FpsCamera.SetupAttachment(this.CapsuleComponent,"FpsCamera");FpsCamera.K2_SetRelativeLocationAndRotation(newUE.Vector(0,0,90),undefined,false,$ref<UE.HitResult>(undefined),false);FpsCamera.bUsePawnControlRotation=true;this.FpsCamera=FpsCamera;this.GunLocation=this.CreateDefaultSubobjectGeneric<UE.SceneComponent>("GunLocation",UE.SceneComponent.StaticClass());this.GunLocation.SetupAttachment(this.FpsCamera,"GunLocation");this.GunLocation.K2_SetRelativeLocationAndRotation(newUE.Vector(30,14,-12),newUE.Rotator(0,95,0),false,$ref<UE.HitResult>(undefined),false);}// other code...}
classTS_PlayerextendsUE.Character{// other code...ReceiveBeginPlay(): void{letucls=UE.Class.Load("/Game/Blueprints/TypeScript/TS_Rifle.TS_Rifle_C");this.EquippedGun=UE.GameplayStatics.BeginDeferredActorSpawnFromClass(this,ucls,undefined,UE.ESpawnActorCollisionHandlingMethod.Undefined,this)asTS_BaseGun;UE.GameplayStatics.FinishSpawningActor(this.EquippedGun,undefined);this.EquippedGun.K2_AttachToComponent(this.GunLocation,undefined,UE.EAttachmentRule.SnapToTarget,UE.EAttachmentRule.SnapToTarget,UE.EAttachmentRule.SnapToTarget,true);}}
chexiongsheng/puerts_fps_demo
这篇文章《制作简单FPS游戏》介绍了如何在UE下用蓝图制作一个简单的FPS游戏,本文按其步骤,用TypeScript实现了一遍,熟悉蓝图的同学可以通过两边对照,找到蓝图怎么换成TypeScript的感觉;不熟悉蓝图的同学也可以直接看本文。
起步入门
下载示例项目并解压。进入项目文件夹(BlockBreakerStarter),双击BlockBreaker.uproject打开项目,我们能看到以下场景:
绿色墙上包含着多个目标,当目标受到伤害时会变红。一旦血量值降为零,目标就会消失。红色按钮可以重置所有的目标。
TypeScript编程环境搭建
创建玩家角色
vscode打开BlockBreakerStarter目录,在TypeScript目录新建TS_Player.ts文件,输入如下代码:
这样就新建了个能在UE编辑器下使用的TypeScript类。
注意:要满足以下三点,一个类才能被UE编辑器使用:
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**。
现在我们设置好了键位映射,就可以用它们来进行移动了。
实现移动
在TS_Player输入MoveForward和MoveRight的处理代码:
代码解释:
设置默认Pawn
保存TS_Player.ts,打开World Settings面板并找到Game Mode设置,将Default Pawn Class改为TS_Player。
现在运行游戏你就能控制TS_Player了,按下Play并使用W,S,A和D来进行移动。
我们接着创建输入映射来观察四周。
创建观察映射
打开Project Settings,再创建两个轴映射,分别命名为LookHorizontal和LookVertical。
将LookHorizontal的键位改为Mouse X。
这样当鼠标向右滑动时会输出正数,反之亦然。
接着,将LookVertical的键位改为Mouse Y。
这样当鼠标向上滑动时会输出正数,反之亦然。
现在,我们要写点逻辑来实现转动视角。
实现转动视角
如果一个Pawn上没有Camera组件,Unreal会自动为你创建一个摄像机。默认情况下,摄像机会使用控制器的旋转。
虽然控制器并没有物理实体,它仍旧有自己的旋转。这意味着我们可以让角色和摄像机面向不同方向。比如,在第三人称游戏里,角色和摄像机并不总是处于同一方向。
要在第一人称视角里转动摄像机,我们所要做的就是修改控制器的旋转。
在TS_Player输入LookHorizontal和LookVertical的处理代码:
和之前左右移动类似,有点差异的是LookVertical会将axisValue乘以-1,如果不这么处理,视角上下移动和大多数人的习惯不太一致。
保存文件,按下Play运行游戏,使用鼠标来转动视角吧。
现在移动和视角转动都实现了,是时候搞把枪了!
创建枪支
创建枪支基类
在TypeScript目录下新建TS_BaseGun.ts,输入如下代码:
我们在TS_BaseGun类里头定义了几个number类型的变量,它们含义分别是:
GunMesh是StaticMeshComponent类型的变量,是枪支的外形,我们会在创建枪械子类时初始化它。
创建枪械子类
保存后,添加TS_Rifle.ts,输入如下代码:
代码多起来了,别慌,听我一一道来:
注:Object的CreateDefaultSubobject方法用于创建子对象,但鉴于该方法参数较多,而且返回的是基类Object,不便于使用,我们稍稍封装了一下(CreateDefaultSubobjectGeneric),封装的实现在ObjectExt.ts,代码如下
这把枪现在就完成了。
接着,我们要创建自己的摄像机组件了。这样能够更好地控制摄像机位置,我们还可以将枪支跟摄像机绑定在一起,这样枪支就能始终保持在摄像机的正面了。
创建摄像机
打开TS_Player.ts并新增几个变量
这几个变量的含义分别是
添加构造函数,初始化FpsCamera,GunLocation:
代码解释
生成并绑定枪支
TS_Player下添加ReceiveBeginPlay方法,这个函数会在游戏开始的时候被引擎调用,在该方法添加来复枪的生成和绑定逻辑
保存后点击运行,这时可以看到我们有了枪。
现在有趣的地方来了:射击子弹!要检测子弹是否打中东西,我们要用上射线检测(line trace)。
射击子弹
射线检测是一个包含开始点和结束点(两点成线)的函数,它会检测这条线上的每个点,看是否碰到其他物体。在游戏中,这是用于检测子弹是否打中东西的最普遍做法。
由于射击是属于枪支的特性,射击函数应该设计在枪支类里,而不是角色类。在TS_BaseGun类中添加创建名为Shoot的函数。
代码解释
调用射击函数
首先,我们需要创建射击的按键映射。点击Compile并打开Project Settings。创建一个新的Axis Mapping并命名为Shoot,将其按键设为Left Mouse Button,然后关闭Project Settings。
打开TS_Player.ts,添加Shoot事件处理
代码解释
保存文件,按下Play运行游戏,按住鼠标左键开始发射子弹吧!
现在,枪支是每帧都在射击的,射速实在是有点太快了,所以下一步要降低枪支的开火速度。
降低开火速度
首先,我们需要一个变量检测玩家是否正在射击。打开TS**_Player创建boolean类型变量,命名为CanShoot**,将其默认值设为true。如果CanShoot等于true,说明玩家正在射击。
另外,将Shoot逻辑稍作改造
代码解释:
里头用到的delay函数时用setTimeout的简单封装,熟悉TypeScript的同学应该都知道怎么写。
保存后,按下Play运行游戏测试下枪支的射速吧!
实现受击
在Unreal里,每个Actor都能受击。然而,Actor要对受击伤害做出什么处理是可以自由定义的。
比如,当战斗中的游戏角色当受击时,会扣除血量。然而,像气球一类物体是没有血量概念的。取而代之的,我们会编写逻辑让气球在受击时爆炸。
打开TS_BaseGun.ts,在Shoot函数添加受击逻辑
新增的只是if (hitResult.Actor) {}这段,简单的对碰撞到的Actor调用下ApplyDamage,把枪支的伤害值传过去。
现在我们需要处理每种Actor对于受击伤害的反馈。这部分内容原来的蓝图教程很简单,只是简单调用了下封装好的逻辑,我就不改造成TypeScript了,保留原文,有兴趣的同学可以继续实现;要改造需要用TypeScript实现其例子已经封装好的逻辑,而且要把地图里头的绿墙上方块,红色按钮都换成TypeScript模块。
处理受击
首先,我们需要处理目标获得伤害数据,打开BP_Target并创建Event AnyDamage事件节点,这个节点会在受到伤害且其数值不为零时触发执行。
随后,调用TakeDamage函数并连接Damage引脚。这个函数会将目标的Health变量减去Damage数值,并更新目标的颜色。
现在,当目标受到伤害时,它就会扣除血量了。点击Compile并关闭BP_Target。
接着,我们需要处理按钮对伤害的反馈。打开BP_ResetButton并创建Event AnyDamage。随后,调用ResetTargets函数。
这个函数会在按钮受击时调用并重置所有目标的状态。点击Compile并关闭BP_ResetButton。
按下Play运行游戏开始射击目标。如果你想要重置所有目标,就朝按钮射击。
后续学习
虽然本篇教程中所制作是一个非常简单的FPS游戏,你可以在此基础上进一步扩展,试着创建更多具有不用射速和伤害的枪械,也可以尝试添加装弹功能!
相关代码