提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
一、开火射击模块
1.制作开火蒙太奇动画
在制作开火射击系统之前,我们先将BP_FPSPlayer中的SpawnWeapon函数中动态创建Weapon对象的逻辑修改一下。我们现在是在SpawnWeapon函数中通过生成Actor节点中的Class参数,然后选择一个蓝图类来作为武器。但是后续我们会有多个武器,所以我们可以将这个生成Actor节点的Class参数设置为SpawnWeapon函数的输入参数,然后这样就能在调用SpawnWeapon函数时设置当前武器蓝图了。
然后我们在事件图表中,将SpawnWeapon函数的输入参数设置为BP_VIRTUS蓝图类。


我们还需要在SpawnWeapon函数中将生成Actor节点的返回值转换为BP_WeaponBase基础武器对象,然后将返回对象设置为变量CurrentWeapon表示当前武器。这样做的原因和C++中继承类似,因为后续所有武器都会继承自BP_WeaponBase蓝图类,所以我们将当前武器变量的类型也设置为BP_WeaponBase类型,因为我们不知道后续角色选中的武器到底是什么类型的,所以我们只能将当前武器类型设置为它们的统一父类BP_WeaponBase,这样不管武器是什么类型的,都不会出错。然后当我们在其它地方已经确认角色的武器类型时,想要拿到角色的当前武器对象,我们就可以先获取BP_FPSPlayer对象中的CurrentWeapon变量,然后将该变量转换为具体的武器类型。所以说这就和C++中的继承的使用方法类似了。
此时Weapon变量就不再使用了,我们直接将Weapon变量删除即可。因为Weapon变量之前是BP_VIRTUS类型的,它只能代表维克多这一个武器型号,后续其它武器型号就不能使用这个变量了。

然后我们在ABP_FPSPlayer动画蓝图中,将Weapon变量替换为CurrentWeapon变量。

下面我们就来制作开火功能。
我们在Input目录下新建一个输入操作,命名为IA_Fire,表示开火输入。然后设置它的值类型为布尔。


然后我们打开输入映射情境IMC_FPSInput,再添加一个数据映射IA_Fire,绑定到鼠标左键上。

然后我们在BP_FPSPlayer中将IA_Fire事件节点添加上,然后再添加两个变量IsFiring表示是否开火,IsDead表示角色是否死亡。然后判断当开火按键按下时,此时开火状态为false,并且角色没有死亡时,然后再判断当前角色并没有处于奔跑状态时,就先把IsFiring设置为true,表示进入开火状态,后面再开始执行开火逻辑。

然后我们在Code目录下新创建一个BluprintInterface文件夹,用来存储蓝图接口,然后我们创建一个蓝图接口命名为BI_Fire。

然后双击打开BI_Fire蓝图接口,设置函数名称为StartWeaponFire,然后添加一个函数输入变量Player,类型设置为BP_FPSPlayer。

然后我们在BP_WeaponBase蓝图中点击类设置,然后添加一个蓝图接口,并且选择为前面创建的BI_Fire,然后可以看到此时左侧就有一个StartWeaponFire接口了。我们双击一下接口就可以将它调取出来,然后我们将事件StartWeaponFire的Player参数提升为变量。

然后我们在BP_FPSPlayer蓝图中的开火逻辑后先创建一个StartWeaponFire节点,然后将它的目标设置为CurrentWeapon,因为我们在BP_WeaponBase蓝图中添加了这个StartWeaponFire蓝图接口,所以可以设置StartWeaponFire节点的目标为BP_WeaponBase类型的对象,而我们前面将CurrentWeapon变量的类型也设置为BP_WeaponBase类型了,所以可以直接将CurrentWeapon变量连接到StartWeaponFire节点的目标引脚上。
然后StartWeaponFire蓝图接口的输入参数Player我们设置为了BP_FPSPlayer类型,并且此时我们在BP_FPSPlayer蓝图的事件图表中,所以我们直接使用Self节点来设置StartWeaponFire节点的Player引脚即可。
经过上面的一系列操作,我们可以发现BP_FPSPlayer角色蓝图对象就传递到了BP_WeaponBase蓝图中了。即我们可以通过蓝图引脚来将两个没有联系的蓝图类联系到一起。例如我们在BI_Fire蓝图接口中实现了一个StartWeaponFire函数,然后我们在BP_WeaponBase蓝图中添加了BI_Fire接口,那么我们就能通过BP_WeaponBase蓝图对象来调用BI_Fire中的StartWeaponFire函数了,然后StartWeaponFire函数有一个类型为BP_FPSPlayer的输入参数Player,我们直接设置为Self。这样当点击鼠标左键时,会执行BP_FPSPlayer蓝图中的IA_Fire事件,然后就会执行该事件后面连接的StartWeaponFire函数,然后就会执行BP_WeaponBase蓝图中的StartWeaponFire事件后的逻辑了。

然后我们在BP_WeaponBase蓝图中新创建几个对象。我们将之前创建的Ammo对象重命名为CurrentAmmo,表示当前弹夹中子弹数量;然后新建一个TotalAmmo整型变量表示当前剩余多少子弹;然后创建一个MagSize整型变量表示弹夹容量;然后创建一个BaseDamage浮点型变量表示武器伤害;然后创建一个FireRate浮点型变量表示武器射速。

然后我们在BP_WeaponBase基础武器蓝图中写开火逻辑,这样的话其它继承自BP_WeaponBase蓝图类的武器类就可以使用这个开火逻辑了。
我们在开火之前需要先判断当前弹夹中是否还有子弹,

然后当弹夹中有子弹时,我们就通过以事件设置定时器节点设置一个定时器,然后该节点的Time引脚就是定时器执行速率,我们设置为武器射速FireRate即可。然后该节点的Event引脚我们连接上新创建的FireInternal事件。
当弹夹中没有子弹时,我们需要播放一个类似三角洲中的弹夹没有子弹的音效。

然后我们在FireInternal事件中添加两个播放蒙太奇节点,一个表示开火时角色播放的开火动画,一个表示开火时武器播放的开火动画。
然后我们先设置第一个武器开火蒙太奇动画。播放蒙太奇节点的InSkeletalMeshComponent(传入一个骨骼网格体组件作为输入参数)引脚我们就连接Weapon骨骼网格体组件,这表示当前武器的骨骼网格体。然后MontagetoPlay(待播放动画蒙太奇)引脚是要播放的动画资产,因为角色每个状态的开火动画不一样,所以我们先通过BP_FPSPlayer类型对象Player得到当前角色状态EPlayerState,然后根据角色不同的状态设置相对应的开火动画。并且因为每个武器的开火动画都是不一样的,所以我们这里不能设置角色每个状态的开火动画是定死的,我们需要将每个状态的开火动画都提升为变量,然后在外部根据具体武器来设置它的不同状态的开火动画。因为Run状态下没有办法开火,所以这个状态我们就不设置开火动画了。


然后我们来设置第二个角色开火蒙太奇动画,我们先通过Player获取到角色手臂骨骼网格体Arm,然后设置给播放蒙太奇的InSkeletalMeshComponent(传入一个骨骼网格体组件作为输入参数)引脚。然后MontagetoPlay(待播放动画蒙太奇)引脚的值和前面武器蒙太奇节点的同理,都需要根据角色的状态使用不同的角色开火动画。然后我们将新创建的动画蒙太奇类型的变量名称都修改规范,以便后续使用。

然后此时我们来到BP_VIRTUS蓝图中,点击类默认值,然后在右侧细节面板中可以设置我们在BP_WeaponBase蓝图中创建的一些变量的值,例如武器弹夹容量、武器伤害、武器射速等,并且还可以设置当前维克多武器对应的角色不同状态下的开火动画等。

然后我们在资产文件夹下的Player文件夹下面搜索Fire,找到和Fire相关的动画序列。然后第一个ADS_Fire就是角色在瞄准状态下的开火动画,第二个Fire就是角色在非瞄准状态下的开火动画,即腰射。
然后我们选中ADS_Fire动画序列,右键创建动画蒙太奇,名称不需要改,这样就创建出了角色瞄准状态下的开火动画蒙太奇了。同理创建出非瞄准状态下的。



然后我们在Weapon文件夹下面同理创建出维克多武器的瞄准状态下和非瞄准状态下的动画蒙太奇。

然后我们在BP_VIRTUS蓝图中配置相对应的动画蒙太奇和维克多武器的相关参数。


然后我们在ABP_FPSPlayer动画蓝图和ABP_VIRTUS动画蓝图中的状态窗口,在输出姿势节点前面添加一个插槽节点,这样动画蒙太奇才能正常播放。



此时运行项目,点击鼠标左键开枪时,可以看到枪在抖动,这就说明动画蒙太奇已经可以正常播放了。

前面运行项目时,点击鼠标左键一下枪就会一直抖动,这是因为我们当前只做了开火逻辑,没有做停火逻辑,所以点击一下鼠标左键,就会一直处于开火状态了。
下面我们在BP_WeaponBase蓝图中将定时器提升为变量,重命名为FireTimerHandle。

然后我们新创建一个StopFire事件,在该事件中通过FireTimerHandle变量调用定时器的以句柄清除定时器并使之无效节点来让开火定时器停止Looping循环,这样就不会一直播放开火动画了。

然后我们在BP_FPSPalyer蓝图中,在IA_Fire事件的Completed后面通过CurrentWeapon变量调用BP_WeaponBase蓝图对象的StopFire事件来执行停火逻辑,然后再设置IsFiring变量为false,表示当前为停火状态。

此时我们运行项目,当前还存在两个问题,一是开火动画的幅度很小,所以开火时好像是枪在一直抖动。二是当长按鼠标左键时才可以播放开火动画,松开鼠标左键时开火动画停止,并且点击鼠标左键后开火动画是有延迟的。

下面我们先解决开火动画延迟问题。
这个延迟是因为在BP_WeaponBase中,我们设置开火定时器时,第一次点击鼠标左键开火后,它也需要等待FireRate时间才会开火,所以就有了延迟。所以我们只需要在每次点击鼠标左键时先执行一次开火事件FireInternal,然后再设置开火定时器,这样就可以解决开火延迟问题了。

然后我们来解决开火时枪的动画幅度小的问题。
我们找到前面创建的动画蒙太奇并打开,然后将混入时的混合时间设置为0,混合选项设置为线性。然后将混出时的混合时间设置为0.25,混合选项设置为线性。前面创建的四个动画蒙太奇都需要进行这一步修改。




此时运行项目,可以看到开枪时,动画的幅度就大一些了。

2.使用MetaSound实现音效
下面我们来制作开枪音效。
在UE5里做音效逻辑一般有两种方式,一种是传统的SoundCue,另一种就是现在最新、最强大的MetaSound,它可以实现更加复杂的音效效果。而SoundCue以前用的比较多,它的逻辑比较固定,扩展性也很有限。所以我们下面会使用更加先进的MetaSound来制作枪械音效。
我们先在Weapon目录下创建一个MetaSound文件夹,然后在MetaSound文件夹下面创建一个MetaSound源文件,命名为MS_VIRTUS。

然后打开MS_VIRTUS文件,其中播放时节点就是只要这个MetaSound被触发了,播放时节点后面的内容就会被执行。完成时节点就是告诉引擎什么时候这个音频能播放完,然后引擎就会将这个音频文件给释放掉,不再占用资源。输出单声道节点就是具体播放什么声音。

我们添加一个随机获取(Float:Array)节点,这个节点可以从输入数组中随机选取一个数组中的值然后输出。然后我们右键让输入数组提升为图表输入,然后给输入数组添加两个索引,分别设置为0.9,1.1。这样的话该节点就会随机输出0.9或1.1了。我们让该节点输出的值来作为后续枪声音效的音高,这样枪声音高就不是一直不变的了,而是随机的0.9或1.1了,这样显得枪声更真实。



然后我们再添加一个随机获取(WaveAsset:Array)节点,这个节点可以从输入数组中随机选取一个数组中的音效文件,然后输出该音效,即达到枪械音效随机选择的效果。我们复制粘贴三个随机获取(WaveAsset:Array)节点,因为我们有三重音效,即子弹发射音、子弹爆发音,环境回响等,然后让这三种音效叠加来组成最后的开枪声音,所以我们添加三个节点。然后将引脚连接上,然后将这三个随机获取(WaveAsset:Array)节点的输入数组参数都提升为图表输入。


然后我们设置第一个随机获取(WaveAsset:Array)节点的输入数组,我们添加6个索引,这里每一个索引就表示一个枪的音效,然后开枪时随机播放这里面的6个音效。然后我们在资源文件夹中找到开枪音效文件,将编号为1-6的音效文件分别设置给6个索引。


然后同理设置第二个随机获取(WaveAsset:Array)节点的输入数组,也是添加6个索引,然后将编号为7-12的音频文件分别设置给6个索引,这6个声音属于一次枪声中的另一个组成部分。


然后同理设置第三个随机获取(WaveAsset:Array)节点的输入数组,这个数组添加8个索引,然后将剩下的8个音频文件设置给这8个索引。


然后我们添加声波播放器(1.0,单声道)节点,然后复制三个声波播放器(1.0,单声道)节点。前面我们使用随机获取(WaveAsset:Array)节点获取到音频文件,然后再使用声波播放器(1.0,单声道)节点来播放音频文件。为什么我们需要三个声波播放器(1.0,单声道)节点呢?这是因为枪声并不是一个单独的音频组成的,它是这三个音频同时播放叠加组成的。


然后我们还需要添加一个单声道混音器(3)节点来将前面的三个单声道音频混合在一起,因为输出单声道节点只能输出一个音频,所以我们需要通过单声道混音器(3)节点来将3个单声道音频混合为一个。当然也可以通过单声道混音器(5)节点来将5个单声道音频混合为一个,如果我们有5个单声道音频的话。


然后我们将第一个声波播放器(1.0,单声道)节点的完成时引脚连接到完成时节点上,因为第一个是枪声的回响音效,它的时间比较长,所以当第一个声波播放器(1.0,单声道)节点播放完成时,其它两个声波播放器(1.0,单声道)节点肯定已经播放完成了,所以我们只需将第一个节点的完成时引脚连接即可。我们可以通过音频资源文件看到每个音频的时长。这样当枪声的回响音效播放完成后,UE5就会将MS_VIRTUS音频对象销毁了,这样就能节省性能开销了。


然后我们将随机获取(Float:Array)节点的随机音高输出值提升为图表变量。然后将该变量给声波播放器(1.0,单声道)节点的音高频移偏移引脚。声波播放器(1.0,单声道)节点的这个引脚是调整音频是偏明亮还是偏低沉的,值越小声音越低沉,值越大声音越明亮。以此我们来让枪声更加的随机性。
即我们先通过随机获取(Float:Array)节点来随机获取到0.9或1.1,然后将这个值设置为声波播放器(1.0,单声道)节点的音高频移偏移值,以此来达到枪声音效随机高频或低频,这样能显得枪声更真实。


我们可以点击绿色播放按钮来听我们制作的音效。

然后我们在BP_WeaponBase蓝图的开火事件FireInternal后面添加一个播放音效2D节点,然后设置资产为我们前面创建的MS_VIRTUS。这样每当FireInternal事件执行时,就会通过播放音效2D节点来播放MS_VIRTUS中的音频。


此时运行程序,我们点击开火按键,就可以听到有开枪声音了。

3.添加开火Niagara特效
下面我们来实现枪的开火特效。
我们先将特效资源复制到项目根目录下的Content目录下。

然后我们将BP_VIRTUS蓝图中Weapon骨骼网格体组件下的5个枪组件都剪切到BP_WeaponBase蓝图中的Weapon骨骼网格体组件下。因为这5个组件一般的枪械都会有,所以我们直接在BP_WeaponBase蓝图基类中实现,然后在继承自BP_WeaponBase蓝图基类的蓝图类中手动设置这些静态网格体组件所需要的静态网格体资源即可。
然后选中BP_WeaponBase中的5个静态网格体组件,将它们的静态网格体设为默认值。还需要注意这5个组件和Weapon组件的碰撞预设都设置为无碰撞。


然后我们需要给BP_WeaponBase蓝图中的Weapon骨骼网格体组件设置维克多枪的骨骼网格体模型。这里我们只需要它的骨骼来进行一个占位即可,因为没有骨骼的话,我们就没有办法设置Weapon组件下的5个静态网格体组件的父项套接字了。

设置好Weapon组件的骨骼网格体后,我们将5个静态网格体的父项套接字还设置为和前面一样。这样当我们新创建一个武器蓝图时,我们只需要在新蓝图中设置Weapon组件的骨骼网格体,设置5个子组件的静态网格体即可。如果有的武器没有枪托或者瞄准镜时,我们只需要不设置该子组件的静态网格体模型即可。
在设置父项套接字时,前面三角形图标表示插槽,骨头图标表示骨骼。






然后我们在BP_VIRTUS中设置Weapon组件的骨骼网格体模型和5个子组件的静态网格体模型。






然后我们在BP_WeaponBase蓝图的开火事件FireInternal后面添加一个生成系统已附件节点,然后资产选择VIRTUS开火特效。然后开火特效要附件在枪管组件上,所以我们将Barrel组件连接到生成系统已附加节点的Attach to Component引脚上,然后Attach Point Name插槽名称我们需要选择Barrel组件上的插槽名称。我们可以点击进入到维克多武器的枪管模型DMR-X_Barrel模型中,然后看到该模型中有两个插槽,Laser插槽是用来装镭射或者手电筒的,Muzzle是枪口插槽。我们将开火特效附加到Muzzle插槽上即可,所以我们在生成系统已附件节点的Attach Point Name参数中填写Muzzle。因为我们是在BP_WeaponBase蓝图中设置的生成系统已附件节点的Attach Point Name参数为Muzzle,所以后续枪械的枪管模型中都需要有名称为Muzzle的插槽,不然的话开枪特效就会绑定错误。




然后运行项目,我们就可以看到有开火特效了。如果我们想仔细观察开火特效位置对不对等,我们可以一直按着开火按键,然后按F8按键进入自由视角,此时可以更好的观察开火特效等。

如果我们感觉开火特效大小不合适,烟雾浓度不合适等,我们可以在特效中进行修改。



4.实现开火时生成弹壳
下面我们来实现每开火一次,在抛弹口抛出一枚弹壳的效果。
我们打开SK_MCX_VIRTUS骨骼网格体,然后在body下添加一个插槽,重命名为Casing,将插槽位置移动到抛弹口附近,确保红色轴线向外。

然后我们在Weapon文件夹下面创建一个Actor蓝图类,命名为BP_Casing,这个就是弹壳蓝图。

然后我们进入到BP_Casing蓝图,添加一个静态网格体组件命名为Casing,然后让Casing组件替代默认根组件。然后我们设置Casing组件的静态网格体为7.62x39类型子弹的模型。
然后设置Casing组件的碰撞预设为Custom自定义,然后将Casing与Pawn的碰撞设置为忽略,即弹壳模型和角色模型不进行碰撞,然后将模拟生成命中事件勾选上,后续判断弹壳落地时要用到。


然后我们添加一个发射物移动组件,通过该组件来让弹壳发射出去。然后我们设置该组件的一些属性。发射物移动组件是UE内置专门用于子弹、箭矢、炮弹、投掷物这类抛射物体的移动组件,该组件自动计算抛射体的飞行轨迹,帮你处理:初速度、重力下落、空气阻力、碰撞反弹、飞行朝向旋转,甚至原生支持追踪导弹;移动过程做扫描碰撞,撞击物体后抛出OnProjectileHit命中事件,用来写扣血、爆炸、销毁子弹逻辑。这里我们只让该组件简单的让弹壳以500初始速度发射出即可。


然后我们再添加一个旋转移动组件,后续通过该组件让弹壳抛出去时进行随机旋转。


然后我们在BP_WeaponBase蓝图中的开火事件FireInternal后面添加一个从类生成Actor节点,然后将Class类型选择为BP_Casing蓝图。这样的话每次执行FireInternal事件后,就会通过从类生成Actor节点来动态生成一个BP_Casing弹壳对象。


然后我们通过Weapon组件创建一个获取插槽变换节点来获取Weapon组件中的Casing插槽位置,然后赋值给生成Actor BP_Casing节点的Spawn Transform引脚,因为我们不需要调整缩放,所以缩放引脚不需要连接。然后将插槽名称设置为前面在SK_MCX_VIRTUS中创建的Casing插槽的名称。这样的话通过从类生成Actor节点动态生成的BP_Casing弹壳对象的初始生成位置就是SK_MCX_VIRTUS骨骼网格体的Casing插槽的位置,即就是维克多枪的出弹口位置。


然后此时运行项目,可以看到有弹壳生成并飞出去了,但是弹壳是直挺挺的飞出去的。

然后我们先在BP_Casing蓝图中选中Casing组件,设置它的缩放为0.6,以此来让弹壳缩小一些。
然后我们通过RotatingMovement组件创建一个设置RotationRate节点,用来设置弹壳对象旋转速率,我们需要将这个旋转速率设置为随机值,所以我们可以创建一个范围内随机浮点节点,用来生成范围内的一个随机值。然后我们将这个随机值设置为弹壳对象各轴的旋转速率。
这样的话当BP_Casing弹壳对象生成时,就会通过RotatingMovement组件的RotationRate节点来设置该弹壳对象的旋转速率为一个-1800到1800之间的随机值。这样每个动态生成的弹壳对象就都会以随机旋转速率旋转了。



此时运行项目,再次点击开火,此时可以看到弹壳就随机旋转了。

然后我们选中BP_Casing蓝图,然后设置它的初始生命周期为2.0,这是设置BP_Casing蓝图对象的生命周期。因为弹壳生成后并且落地后不能一直在地上,所以我们设置弹壳对象的生命周期为2.0,即弹壳对象2秒后就自动销毁。这样就能减少性能消耗。

然后我们选择Casing组件,然后点击组件命中时,添加一个组件命中时事件,这个事件就是当弹壳与其它物体发生碰撞时就会被触发。

然后我们创建一个在位置处生成音效的节点,然后我们选择Bullet_Impact音效资产。然后我们再创建一个获取Actor位置节点来获取到BP_Casing对象的位置,然后将位置赋值给在位置处生成音效节点的位置引脚。
然后因为子弹落地时有反弹,所以它会一直触发组件命中时事件,那么每个弹壳对象就会一直播放弹壳落地音效,这是不行的。所以我们通过Do Once节点让每个弹壳对象落地音效只执行一次即可。




然后我们还需要设置当弹壳对象落地时,让它的旋转速率设置为0,即弹壳落地后就不需要再旋转了。

此时运行项目可以看到每次开枪都会有弹壳生成,并且弹壳落地后会发出响声,而且弹壳对象2秒后就自动销毁了。

如果我们感觉弹壳落地的声音太大的话,我们可以通过在位置处生成音效节点的Volume Multiplier参数来修改音效的声音大小。

5.实现武器后坐力效果
下面我们来制作后坐力系统,后坐力系统其实就是开枪时让角色的视角向上抬。
我们在BP_WeaponBase蓝图的FireInternal事件后通过Player变量创建一个添加控制器Pitch输入节点,通过该节点可以设置角色控制器的俯仰值,即可以让角色视角的俯仰值进行改变,我们通过让角色俯仰值向上抬,以此来模拟开枪时武器垂直后坐力。制器Pitch输入节点的值设置为负数时表示向上抬。然后我们再创建一个添加控制器Yaw输入节点用来模拟开枪时武器水平后坐力,这个后坐力我们使用范围内随机浮点节点来生成一个随机值作为水平后坐力。这样我们就做好了一个简单的后坐力系统。




此时运行项目,如果一直开枪的话,可以看到角色视角会越来越向上抬。

6.添加击中特效和音效
下面我们使用射线检测功能来制作武器子弹击中效果,即当开枪时,我们就发射一个射线来模拟子弹。现在的大部分FPS游戏点击开枪时其实并没有发射子弹模型,而是通过UE5的射线检测机制从屏幕中间发射了一条射线,然后通过检测射线和哪里发生了碰撞,然后就表示子弹击中了哪里,然后就在碰撞的地方生成击中特效,以此来到达子弹击中物体的效果和体验。
我们在BP_WeaponBase蓝图的FireInternal事件后面添加一个按通道进行线条追踪节点,该节点可以从Start位置到End位置之间形成一条检测射线。


然后我们通过Player变量获取到角色摄像机,然后再获取到摄像机的世界位置赋值给按通道进行线条追踪节点的Start引脚,即将射线的起始位置设置为角色当前视角的中间,即屏幕中间。
我们现在的大多数FPS游戏的发射子弹其实都是从屏幕的中间发射的,并不是从枪口发射的,所以这里我们也从屏幕中间发射一条射线。


然后射线的End参数需要结合子弹的射程,地形等来计算。
我们先通过摄像机对象获取当前角色向前向量,然后再让角色向前向量乘一个整数,这个整数其实就是子弹射程,这里我们先设置为10000,这样就得到子弹的向前向量了。然后我们让射线起始位置加上子弹向前的向量,这样就得到射线的结束位置了。


按通道进行线条追踪节点会在Start和End位置间生成一条射线,当这条线段与其它物体产生碰撞时,就会通过OutHit命中结果输出。我们可以通过Break Hit Result节点展开OutHit命中结果的值,其中Location就是射线在什么位置发生了碰撞,Phys Mat就是与什么材质发生了碰撞。


下面我们创建一个在位置处生成系统节点,该节点会在指定场景位置处生成Niagara系统,即粒子特效。然后连接到按通道进行线条追踪节点的后面,然后我们将OutHit的Location连接到在位置处生成系统节点的Location引脚,然后我们选择一个特效Dirt,Dirt特效就是子弹击中泥土时的特效,这样当射线与物体产生碰撞时,就会在碰撞位置生成一个Dirt特效了。



此时运行项目,当我们向下开枪时,可以看到地面上就会有Dirt特效生成了。

上面的Dirt是子弹击中泥土的特效,当子弹击中金属时就不能使用这个特效了,此时就需要通过OutHit的PhysMat物理材质对象来得到碰撞物体的物理材质,然后再生成相对应的特效了。
我们在项目设置的物理中设置物理材质的名称,我们这里就先设置4个物理材质,即Dirt泥土、Metal金属、Wood木头、Enemy敌人。当然我们自己还可以设置更多的材质,我们的特效资源中还有击中玻璃的特效,击中雪的特效,击中水的特效,这些物理材质我们可以后期自己添加上去。


然后我们通过OutHit的PhysMat物理材质对象创建一个获取SurfaceType节点,该节点获取了物理材质对象表面类型,然后通过SurfaceType节点创建一个Switch节点,可以看到Switch后面就有我们创建的每个物理材质的引脚了,我们在对应的引脚后面连接对应的物理材质击中效果。




然后我们通过OutHit的Normal结果创建一个利用X创建旋转节点,然后连接到在位置处生成系统节点的Rotation引脚上,这样就能保证特效一定是垂直于墙面进行发射的。


然后我们在Code目录下创建一个Physics文件夹,在该文件夹下面创建一个物理材质,然后命名为PM_Dirt,即泥土材质。


然后我们双击进入PM_Dirt材质中,将它的表面类型设置为Dirt。

然后同理我们添加PM_Metal物理材质、PM_Wood物理材质、PM_Enemy物理材质,然后将材质的表面类型设置为它们所对应的物理材质。



然后我们在关卡中添加四个立方体,将这四个立方体的物理材质重载分别设置为前面创建的PM_Dirt、PM_Metal、PM_Wood和PM_Enemy。


此时运行项目,然后将枪对准这四个立方体开枪时,可以看到会生成不同的击中特效。

然后我们运行结束后可以看到出现了错误,然后点击错误后定位到Pyhs Mat这里,这个错误就是当朝天空开枪时,我们并没有设置天空这个物理材质,所以当射线检测到和天空产生碰撞时,此时获取到的Pyhs Mat物理材质对象是无效的,所以就出现错误了。

我们通过PhysMat物理材质对象来创建一个IsValid节点,当判断PhysMat物理材质对象有效时,才会执行后面的生成击中特效的逻辑。这样错误就解决了。

然后我们来添加击中物体时的音效。我们在生成Dirt击中特效的节点后面创建一个在位置处生成音效的节点,然后资产选择Dirt音效,位置就是生成Dirt击中特效节点的位置,即产生碰撞的位置。这样当射线检测到碰撞时,在生成特效的同时还会生成击中音效。
同理我们分别在Metal、Wood、Enemy击中特效节点的后面也添加一个在位置处生成音效节点,资产选择相对应的Metal音效、Wood音效、Enemy音效,位置就是生成击中特效节点的位置。






如果我们感觉连线有些乱的话,我们可以将Location提升为变量,然后在需要位置的地方,使用变量连线。

7.优化生成机制
前面我们通过判断PhysMat对象是否有效来决定是否生成击中特效和音效。但是这样的话当我们击中那些没有物理材质的模型时,就不会生成击中特效了。这显然不是我们想要的,我们想要的是当射线击中那些没有物理材质的模型时,会触发一个默认的击中特效。
这时我们就需要使用到OutHit的BlockingHit这个变量值了,这个变量表示是否击中物体,如果击中物体的话就为true。然后我们修改逻辑当击中物体时执行击中特效生成和音效生成。如果射线击中的是那些没有物理材质的模型时,就生成默认的击中特效和音效。这里默认的击中特效和音效我们设置的是击中混凝土的特效和音效。




我们上面使用了混凝土的击中特效和音效,那么我们就将混凝土物理材质Concrete也添加到项目物理材质中,然后在PyhsMat对象的Switch节点后面将默认引脚和Concrete引脚都连接到混凝土击中特效和音效节点上。这样的话当射线击中混凝土物理材质的物体或者没有物理材质的物体时,都会生成混凝土击中特效和音效了。


此时运行项目,然后对着地面开枪,因为地面我们没有设置物理材质,此时对地面开枪时可以看到生成的是击中混凝土的特效和音效。

8.实现弹孔痕迹
下面我们来实现弹孔贴图的效果。
我们先将Decals资源文件拷贝到项目根目录下的Content目录下。

然后在BP_WeaponBase蓝图的FireInternal事件后添加一个从类生成Actor节点,即在每个物理材质的生成击中音效节点的后面连接从类生成Actor节点。然后我们选择类的类型为BP_BulletDecal,这个就是普通弹孔的痕迹,然后我们将弹孔对象位置也设置为击中特效生成的位置,旋转也和击中特效的一致,因为弹孔对象也需要垂直于墙面。



此时我们运行项目,可以看到击中物体后,物体上就会有弹孔了。

我们看到前面设置的弹孔类BP_BulletDetal其实是一个蓝图类,打开这个蓝图类,可以看到里面就创建了一个贴图对象BulletDecal,然后设置了贴图对象的随机旋转和随机大小。


我们在弹孔类BP_BulletDetal中选中BulletDetal贴图对象,然后可以看到它的贴花材质是Concrete混凝土材质弹孔。


如果我们击中的是Metal金属或者Wood木头的话,那么弹孔对象的贴花就不能是混凝土贴花了,我们可以通过生成Actor BP_BulletDecal节点的返回值获取到BP_BulletDecal蓝图中的贴图对象BulletDecal,然后通过设置贴花材质节点来设置Metal贴花或者Wood贴花。
因为Dirt泥土击中时弹孔不明显,击中敌人时没有弹孔,所以这两个物理材质我们先不设置弹孔贴花。





此时运行项目,可以看到击中混凝土、木头和金属材质的物体,生成的弹孔是不一样的。

