Shoot Them Up Game

射击游戏项目学习

前提

我们先把ue4示例中的射击项目给拷贝过来这样我们就可以用里面的资源而不需要自己去找了

但是好像只能放在Content下面不能放在下面的下面~~不然会报错

1、创建ACharacter和APlayerController

每个Unreal Engine模块都需要一个Build.cs文件来定义其构建设置。Build.cs文件中包含了一些必要的函数和属性,例如PublicIncludePaths、PublicDependencyModuleNames等,这些可以用来定义编译模块时需要用到的头文件路径、依赖模块名称等。所有的模块文件都被收集到一个插件(Plugin)中,插件可以被添加到项目中

这里我们就将Player路径包含进来了

使用了PublicIncludePaths.AddRange函数将我们的路径包含进来,这样在我们的Module中使用include的时候有些就不需要包含Player/xxxx这种了

image-20230604150800342

因为我们需要在GameModeBase中改变其Character和PlayerController所以我们先加头文件

image-20230604151348614

然后在STUGameModeBase中将Pawn(Character也算是pawn中的一位)和PlayerController设置成默认值(当然需要先创一个构造函数捏)

image-20230604153348011

在Character中创建Component组件

我们需要创建一个摄像机这样我们就可以改变人物视角了

这是是创建了一个在蓝图中可读可写的且公开的组件

这这个class UCameraComponent只是声明一下这个组件而他具体是存在于下下个图的头文件#include “Camera/CameraComponent.h”

只有声明了你才能用瑟。

image-20230604153756711

这我们开始创建并将Camera组件放进Character中

“CameraComponent”是将其名字设为这个

image-20230604155129494

这样我们完成啦~

2、基本角色移动

这UE4中我们在输入中创建了2个映射(就是你按一下那个键就代表传了MoveForward/MoveRight)后面缩放代表正反~

image-20230604162636052

其实这个移动可以在Character和PlayerController中都可以实现,但目前考虑到PlayerController中有很多复杂的操作我们就不放到里面了别搞得更复杂我们放Character中

首先创两个函数用来实现移动image-20230604163953055

此处因为我们用了InputComponent(输入组件)组件所以要加头文件

image-20230604164001900

在输入功能中我们实现上下左右移动

其中BindAxis中第一个参数是绑定的事件,第二个是对象,最后一个是触发函数

image-20230604164050247

AddMovementInput() 函数会将 GetActorForwardVector() 方法返回的角色前向向量乘以输入的 Amount 值,然后将结果作为移动向量添加到角色的移动输入中。这样,当 ASTUBaseCharacter 类的蓝图实例中呈现在屏幕上时,它就会根据用户的输入沿着前进方向移动

这里这个AddMovementInput是每个pawn都有的函数

3、镜头移动

其实我们可以直接传LookUp函数里面的那个函数了就不需要单独创建函数了~这里只是教学意义

image-20230604191554360

都是控制角色旋转

我们使用AddControllerPitchInput函数来改变镜头的纵向角度

用AddControllerYawInput改变旋转角度

但记住要开启人物允许的纵向操作image-20230604192411432

但实际情况更多的是我们的视角是绕着人纵向变换的这样需要我们使用弹簧臂(SpirngArmComponent)组件

image-20230604194316257

这里我们创建了一个弹簧臂组件值得注意的是我们此处用代码打开了使用pawn旋转的设置并且我们要把Camera绑定到弹簧臂上(SetupAttachment(SpirngArmComponent))这样只需要旋转弹簧臂就可以改变摄像机位置

image-20230604194337398

image-20230604194534563

最终效果,我们在把动画换成资产中的向前跑就不那么呆了

image-20230604200840571

4、创建动作蓝图

首先我们需要使用动画蓝图~哦

image-20230604211913774

像上述所做过后虽然人物有动作但是太死板(没走也在做动作)现在我们让他从没走的时候IDLE,走的时候Run_Fwd

首先先创一个动画蓝图叫做ABP_BaseCharacter和另一个混合空间1D(只有一个轴所以是1维另一个是个二维)

image-20230604211327326

image-20230604211130668

这里我们转到混合动作这里,这个东西是可以让我们人物从一个动作到另一个动作我们在这把idle放在水平坐标(取名叫Velocity)为0的地方

把Run_Fwd动作放在600处(这个600可以去取值)

这样就做出了连贯的动作

image-20230604211419509

然后我们转到ABP_BaseCharacter原蓝图那,我们把混合动作放进去然后创一个变量叫做Velocity去跟其相连(Velocity可以获得实时速度)

最后输出动作

image-20230604211409075

我们把动作做出来了,但是我们需要把Velocity设置一下

在原动画蓝图中可以获取速度并将其转化为float给Velocity设置

image-20230604211804370

这样就闭环了

~~

ok啦

5、实现Jump功能

首先我们先在输入中创建一个操作映射Jump绑定空格键

image-20230605125109471

在输入中实现这个功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7AiAbKjz-1686743830888)(image-20230605125139246.png)]

其中IE_Pressed是按下还有个叫IE_Realsed释放(放开),Jump是Character自带的函数,可以实现跳跃

但是没有动作哦

现在我们需要用动画蓝图给我们的跳跃绑定动作

首先我们在动画蓝图中添加了一个变量叫做IsFalling的东西,这个东西可以判断我们是否跳跃我们使用正在掉落这个功能来判断是否,如果正在的话IsFalling就是true

image-20230605132920320

而为了使我们的动作连贯,就是走的时候也可以跳那我们使用一个叫做

New State Machine的动画状态机来保存我们的动作我们取名为Locomotion(运动)image-20230605132356424

我们在Locomotion中从Entry拉出一个添加状态我们命名为walk

image-20230605132522498

我们把行走的部分给弄进来

image-20230605132619341

其他3个Start loop end就像下图一样搞进去,在这里我们的跳跃动作其实是一个连贯的动作,loop是双脚悬空的一个循环状态,end就是一个落地的动作(注意loop不是缩腿啥的,start才是)

image-20230605132652220

然后每个状态转换的中间不是有一个叫做过度规则嘛image-20230605133114068

我们点开在walk到start就判断是否起跳image-20230605133123833

start到loop就使用一个叫做剩余时间(这个可以统计从上个动作到完成还有多少s)后面用的less小于0.1秒这个意思就是从上个动作还有<0.1s要结束的时候开启下一个动作

image-20230605133153636

注意:这个start和end在细节面板里要关闭其loop哦~,因为我们的跳跃和结束只进行一次哦

不然会抽搐~

6、实现奔跑

当然我们要现在输入部分创建两个映射一个使用shift代表奔跑

首先我们创建了三个函数和2个变量,其中只有两个On函数设置是否奔跑,值得注意的是IE_Released代表释放(松开)shift~

其中WantsToRun代表是否奔跑,IsMovingForward代表是否按了W键~,

image-20230605162707916image-20230605162419434

image-20230605162403091

IsRunning他这个东东就有点讲究了,UFUNCTION(BlueprintCallable)代表这个函数可以在蓝图中使用

我们要在蓝图中用

这代表是否奔跑(按shift),是否移动(按W),速度是否为0

这是为了避免bug比如(撞墙了v=0,但这是肯定不能做出奔跑的状态瑟,又比如如果没有是否移动,那按s也会有奔跑动画,但是我们暂时没做向后奔跑的动画

image-20230605162409237

我们单独创建了一个混合动作这是为了区分走路和奔跑的

image-20230605162215024

然后事件图表中我们先转换成我们的BP_STUBaseCharacter对象,然后设置is Running(C++中的我们上面创建的函数)

image-20230605162254847

​ 然后在状态机中创个Run,再设定触发条件啥的image-20230605163321212

image-20230605163239063

这是Run里面我们通过与walk一样的构造完善其动作image-20230605163350413

这个时候是不是感觉缺少了点啥~

没错就是速度,我们奔跑的时候是会改变速度的~

这里我们用创个新的component的方法来改变速度(这样这一类的东西都可以用这个component组件来改变速度了)

在C++类中创建一个CharacterMovementComponent组件再取个名image-20230605163847734

image-20230605163804358

首先我们在.h文件中重载GetMaxSpeed函数并且创个RunModifier变量(用于速度的变换在UE4中你想要冲刺速度更大就把RunModifier调大点)

这里限制1.5-10.0

image-20230605164037273

这是cpp的实现部分,先继承基类函数,然后创一个Character类并取得目前掌控的角色,然后进行判断,自己目前有角色吗?并且是否出发了IsRunning了?如果都触发了就得到MaxSpeed*RunModifier值image-20230605164043931

FObjectInitializer 是一个现有的类,被用于初始化很多UE4引擎中的对象

这里其实就是把MoveComponent设为其子类image-20230605165926903image-20230605165937881

其目的:image-20230605170327934

单独放的哦~,跟根组件啥的不一样

image-20230605170305388

8、实现左右前后的动作

我们创了一个混合空间walk2

image-20230605201752999

在里面有两个轴一个水平座标我们将他取名:Velocity,另一个叫做Direction

代表一个轴是根据速度来变化的,另一个轴代表根据方向变化的最小轴值和最大轴值分别为-180-+180。把Fwd放到velocity:600,direction:0处,Bwd放到600,-180和180处,left放到600,-90

right600,90处这样我们只需要传输direction和velocity就可以实现前后左右运动了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VT0OxhuX-1686743830900)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230605201824442.png)]

这里就是来算角度啦~

image-20230605202220757

GetSafeNormal() 函数将其归一化为单位向量

FMath::Acos反余弦,GetActorForwardVector是得到对象前方向量

image-20230605202856514

可以求到a的弧度值~

使用 FVector::CrossProduct 计算速度向量和对象前方向量的叉积,用于判断速度向量是否在对象的左边或右边。叉积结果的Z值的符号即为速度向量相对于对象的位置关系,取其符号并乘以角度,即可获得一个正负两种不同方向的角度值(算方向的)|向量c|=|向量a×向量b|=|a||b|sin<a,b>

image-20230605205144053

FMath::RadiansToDegrees() 函数将计算得到的弧度转换为角度

最后sin(z)就是把方向单位化了1/-1这种速度和方向的夹角0-180°是1 180-360为-1

image-20230605210204599

蓝图中的使用~

image-20230605210218436

最后我们优化了一下函数,因为原来那样的化如果进游戏直接按s键我们会发现我们的运动是Fwd的这是因为当Velocity与Direction同向或者刚好反向的时候CrossProduct的值就会为0(sin的问题),这就导致我们直接后退的时候做的是0°的动作,所以优化一下直接返回Degrees

9、显示Health

首先我们要达到这种目的

image-20230605214004112

我们先创建一个HealthActorComponent组件

里面创建Health和MaxHealth与一个GetHealth函数

image-20230605214053206

image-20230605214106959

然后就是老样子创建组件并绑定(我们这好像没有绑定HealthComponent组件只绑定了那个TextRenderComponent)

那个Text是用来显示文本的组件

image-20230605214202737image-20230605214209842

image-20230605214218925

这里第二局是固定说法,这就是固定用法image-20230605214229898

这个是可以检查是否创建成功组件的调试函数,报错的化就会跳到这就知道那有问题啦~image-20230605214233649

10、伤害机制

我们暂时先做一个按tick减少0.1血量的功能

首先有个函数我们学习一哈

TakeDamage()函数

TakeDamage() 是一个常用的虚函数,可用于在游戏中受到伤害的角色类中。该函数通常在游戏中用于从攻击方造成伤害到受攻击方,如减少生命值等。

函数通常接受的参数包括受到的伤害值 Damage,伤害事件 DamageEvent,造成伤害的控制器 Instigator,造成伤害的实体 Causer 等。

这里TakeDamage中我们传输了Damage为0.1f,事件默认,造成伤害的控制器是controller,造成实体是本体

image-20230606155438428

这里是TakeDamage中的FDamageEvent总会调用的事件我们将其重载一下

image-20230606155706999

要用到获得角色的话需要加上这个头文件image-20230606155730141

这里就是Damage就是前面的TakeDamge中的0.1f

这是实现OnTakeAntDamage的重载

并且我们获得了角色并且使用角色的自带的函数OnTakeDamage

使用了 AddDynamic 函数将当前类的 OnTakeAnyDamage 函数添加到 USTUHealthComponent OnTakeAnyDamage 委托中

image-20230606155718432

其实这里需要理解

TakeDamage(0.1f, FDamageEvent{}, Controller, this);

这句函数,因为你F12可以知道其中有3个伤害事件分别是

OnTakeAnyDamage此事件是在任何情况下都会调用的我们就是用这个来订阅委托实现减少Health的

FPointDamageEvent(子弹伤害)

RadialDamageEvent(爆炸伤害)

其实伤害机制只是一个已有的功能,我们现在需要把其中的伤害转化成我们想要表达的健康Health值,所以Damage不会减少Health(只是有这个东西)我们需要的是用return Health-Damage~~

11、伤害叠加机制

实现这种image-20230606164032560

我们单独创一个Actor叫做STUDevDamageActor

当然我们还是先创一个SceneComponent的根组件

image-20230606164137598

然后我们有两个事情做,一个是画一个圆另是个是造成伤害

image-20230606164228077

其中DrawDebugSphere这是一个在场景中绘制调试信息的代码示例,用于在运行时显示一个以当前 Actor 位置为中心,半径为 Radius 的球形。具体来说,它会调用 DrawDebugSphere 函数,并传入一个 GetWorld() 的实例表示当前所在的世界、GetActorLocation() 函数返回当前 actor 的位置作为球形中心、Radius(float)作为球形半径,24 作为球面的三角形数量,SphereColor 是球形的颜色

第二个函数就是GameplayStatic头文件中的ApplyRadialDamage函数啦

这个函数会在 GetActorLocation() 函数返回的位置为中心,半径为 Radius 的范围内对周围的敌人造成 Damage 点伤害。ApplyRadialDamage 函数还包括一些额外的参数,例如:nullptr表示不排除任何一个 actor;{}表示不使用任何轨迹(trace);this表示伤害源是调用该函数的 actor;nullptr表示使用默认的伤害类型;最后一个参数 DoFullDamage 指示是否对半径范围内的每个 actor 造成完整伤害(true)或者衰减伤害(false)

我们使用不衰减

image-20230606164148879

12、伤害类型

我们新创建了两个DamageType 一个是火另一个是冰image-20230606172009405

在DamageActor中我们创建一个TSubclassOf<UDamageType> 是一个模板类,它表示一个可以引用任何 UDamageType 子类的类型。在代码中使用 TSubclassOf 可以方便地获取一个子类的引用image-20230606172108150

image-20230606172037796

我们可以在ue4中设置Fire或者ice

我们最后会在HealthComponent中体现我们的DamageType

像这样

image-20230606172313347

image-20230606172514117

13、角色生命值为0时动画

此处我们需要用到我们的DELEGATE委托(在HealthComponent中)

这个委托声明了一种事件叫做FOnDeath,并创建了OnDeath这个事件

image-20230606202524635

而且声明了一个IsDead函数来判断是否Health是否<=0image-20230606202535486

这里我们就在Character这里使用这个HealthComponent中的OnDeath事件与下面声明的ASTUBaseCharacter事件绑定在一起

image-20230606202624605

这里是用日志来体现是否死亡image-20230606202612898

这里我们就转到了伤害机制那,在里面我们先用一个Clamp方法来限制Health的值让他在一个正常范围内变换

并且用判断Health值的方法启动委托事件(用Broadcast())OnDeath中的显示日志image-20230606203113377

当然这只是初级的,我们需要调用动画来使我们死亡更加完美~

动画蒙太奇(Animation Montage):是一种以叠加形式组合多个动画片段的方式来制作复杂动作的工具。它是通过将多个动画片段按序列组合起来来形成一个新的动画

这里我们先创建一个Death的蒙太奇的版本

image-20230606205625164

后面我们用c++方法搞,因为不想在每个动作后面连线(因为随时可能Death)

我们在UE4中BaseCharacter中创一个叫做Animation的区域用来放动画的

image-20230606205909538image-20230606210029547

最后效果就是可以在这有一个AM_Death选项

image-20230606210145006

最后为了播放动画,我们要从Locomotion中创建一个slot插槽

一个状态机通常由多个状态节点(State Node)以及各种控制节点(Blend Node、Seq Node 等)组成。Slot 节点的作用是允许我们在状态机中插入多个动画,并在状态转换时根据需要添加和移除不同的 Slot,以达到不同状态之间的动画混合效果

这个slot应该就是一个触发函数类似与,当满足C++中蒙太奇动画的触发条件就触发slot中的蒙太奇动画

并且这个自动混出要取消勾选(不取消的话满足死亡时就会一直触发死亡的动画(鬼畜,一直死))

image-20230606210811750

image-20230606210323626

但是我们仍然有问题,这个问题就是我们触发死亡的动画了但是我们的人人可以移动并且转向

我们后面再来完善一下

这个里面GetCharacterMovement是Character类自带的函数可以得到角色的控制移动,让其不在移动,SetLifeSpan是在5秒后销毁实体~~

image-20230606213500673

现在我们在HealthComponent中又加入了一个委托

image-20230606212601155

并在开始和收到伤害是实时跟新其Health值

在 Unreal Engine 中,引发伤害事件指的是从一个游戏对象(Actor)向其他游戏对象造成一次伤害事件。这种伤害事件可以通过调用 UGameplayStatics::ApplyDamage 函数来实现

其中因为有DevDamageActor中的每帧引发伤害是件导致OnTakenDamage每一帧也会触发

我们只是把以前的在每个Tick中显示Health的值放在了HealthComponent组件里这样从DevDamageActor的每一帧都会刷新从而显示血量

我们先要GetHealth开始的时候血量就是文本里显示的那个(有可能还是字母呢)

image-20230606212627898

image-20230606212721678image-20230606212727564

14、旁观者类

image-20230606213851377

我们让我们角色死亡后我们进入旁观者类image-20230606213927317

image-20230606213936823image-20230606213943847

加个头文件,并且死完后改变Controller状态变为NAME_Spectating(旁观者)

还有一个控制者类的

15、恢复健康

我们实现设置一个自动恢复血量的功能

原理我们运用这个Update函数根据定时器中的时间频率开始恢复

在HealthComponent组件中

image-20230607102110217FTimerHandle HealTimerHandle 是一个用于管理定时器的 Unreal Engine 类型。它是一个结构体,包含一些属性和方法,可以用于安排和取消计时回调函数。

image-20230607102142954

这里为啥要把定时器绑定到OnTakeAnyDamage中呢

因为下图中的DevDamageActor有个每一帧一直添加伤害的函数

image-20230607102556334

我的理解是因为我们是先收到了伤害,这个函数就每一帧触发我们的OnTakeAnyDamage函数并且只要没死就一直在执行HealUpdate功能虽然离开伤害区域过后就不会执行但是我们是设置了时间延迟的,所以其实我们的所有恢复都是在伤害区域里先触发了,只是时间延后而已

image-20230607102318205

最后我们再用这个来优化一下我们的Health设置image-20230607102858068

其中SetTimer是属于定时器的定时器又是是与GetWorld的

这一行代码创建一个定时器,定时调用 USTUHealthComponent 类的 HealUpdate 函数,来执行治疗逻辑。

其中:

  • HealTimerHandle 是一个定时器句柄,用于识别和管理这个定时器。
  • this 是一个指向当前 USTUHealthComponent 类实例的指针。
  • &USTUHealthComponent::HealUpdate 是一个函数指针,指向 HealUpdate 函数。
  • HealUpdateTime 是定时器的间隔时间,即每隔多少时间调用一次 HealUpdate 函数。
  • true 表示这是一个重复执行的定时器。
  • HealDelay 是定时器的延迟时间,即在多少时间后开始执行第一次 HealUpdate 函数。

其中清除定时器这是因为如果之前存在的定时器还没有被停止,那么它会继续定时执行 HealUpdate 函数,执行治愈逻辑。如果此时游戏对象已经死亡,那么执行治愈逻辑就没有意义了,而且会浪费资源。

16、坠落伤害

在Character中自己有一个LandedDelegate和Landed

image-20230607111649961

这个是在原文件中的(不是我们创的)

image-20230607111753018

所以现在明朗了我们目前只需要创个新函数绑定这个委托,结果是当跳跃发生时自己就会触发委托自然也会触发与我们绑定的函数

我们绑定的函数是通过两个参数一个LandedDamageVelocity和LandedDamage

一个是指定跳落时会受到伤害的速度范围,小于900不受伤,大于1200就直接死亡

第二个就是收到的伤害从10-100

image-20230607111959859

这里我们需要获得当前的掉落速度(我们要将其反过来加个-号,因为我们设定的是正的~)

我们这就通过一个数学方法(在范围值映射中很好用)FMath::GetMappedRangeValueClamped 是一个用于线性插值的函数,在范围映射时非常有用。它接受一个输入值,将其从一个范围映射到另一个范围,并通过线性插值来计算最终的映射值

就是把当前速度放到输入的速度范围去映射,最后从LandedDamage中输出映射

image-20230607111940448

16、ABaseWeapon类

我们当前的目的是为了使人物拿起武器

我们先创一个ABaseWeapon基类

并在里面声明了一个蓝图组件

image-20230607141521825

创建这个蓝图组件并且将其作为根节点image-20230607141526556

然后根据这个BaseWeapon创建其蓝图类image-20230607155957603

这样我们到时候就可以用Character使用这个BP_STUBaseWeapon

随后我们在Character中创建一个class其类属于ASTUBaseWeapon

image-20230607160115386

然后我们在character中声明并创造了一个函数image-20230607170101524

我们先用weapon来判断我们的Weaponclass类创建没

第一个参数 GetMesh() 表示要挂载到角色模板的 Mesh 组件上,也即表示要利用角色本身的位置朝向对武器进行一次转换和定位。第二个参数 AttachmentRules 则表示挂载方式所遵循的规则,即前面定义的 FAttachmentTransformRules AttachmentRules 变量。最后一个参数 "WeaponSocket" 表示目标点的名称,这个名称通常在角色的模板文件中预先设定,在这里用来表示武器要绑定到角色的哪个位置上

而if中的第一句就是设置挂载的规则EAttachmentRule::SnapToTarget 参数,表示将Weapon挂载到目标上时使用快速嵌入的方式,在场景中快速调整目标与其他Actor之间的距离和方向。而第二个参数为 false 表示这种方式只考虑角色的本地坐标系而不是绝对世界坐标系,也即武器会跟随角色哪里走哪里的运动轨迹走,不会固定在空间中的一个位置不动

image-20230607170116464

最后在Character开始的时候实现这个武器(函数)image-20230607170129299

17、AHUD类

我们要实现这个东西

image-20230607172640879

其实这就是UI

​ 我们现在C++下创建一个HUD类image-20230607173015710

这里我们使用两个函数一个是DrawHUD另一个是DrawCrossHair

DrawHUD() 函数是UE4游戏开发中常用的一个函数,该函数属于HUD(Head-up display,即游戏中头顶/角落的信息显示框)类,在实例化HUD类之后由UE4引擎调用,用于在屏幕上绘制游戏中的UI元素。可以在HUD类的派生类中重写此函数,实现自定义的UI绘制逻辑

DrawCrossHair() 函数实现了一个简单的绘制准心的功能。首先

先计算中心中心点使用Interval<float>是UE4游戏开发中的一个模板类,表示类型为float的区间范围。它定义了一个有两个参数的模板类型,第一个参数是区间的较小值,第二个参数是区间的较大值

其中image-20230607173533740Canvas 是UE4中表示HUD绘制的画布,也是绘制2D/UI元素的主要对象之一

DrawLine() 的基本语法如下:

void DrawLine(
	float StartScreenX,
	float StartScreenY,
	float EndScreenX,
	float EndScreenY,
	const FLinearColor& Color,
	float Thickness = 1.0f,
	float DepthPriorityGruop = 0,
	float StippleFactor = 1.0f,
	uint32 StipplePattern = 0xFFFFFFFF,
	bool bAntialiased = true
)

参数解析:

  • StartScreenX: 直线的起点X坐标(屏幕坐标系,以左上角为原点)
  • StartScreenY: 直线的起点Y坐标(屏幕坐标系,以左上角为原点)
  • EndScreenX: 直线的终点X坐标(屏幕坐标系,以左上角为原点)
  • EndScreenY: 直线的终点Y坐标(屏幕坐标系,以左上角为原点)
  • Color: 直线的颜色
  • Thickness: 直线的宽度(以画布的像素为单位)
  • DepthPriorityGruop: Depth Priority组,用来指定在多个HUD元素同时出现时的层级关系(即HUD元素的渲染顺序)。一般情况下可以保留默认值0。
  • StippleFactor: 虚线的间距。值越大,线段之间的距离越远,虚线的线段也就越短。
  • StipplePattern:虚线的格式。用一个32位无符号整数表示,二进制位上每一位的值表示实线或虚线,为0表示虚线,为1表示实线。
  • bAntialiased: 是否抗锯齿

image-20230607173034724

这里把这个勾上就更像游戏了

image-20230607172943243image-20230607173924465

18、WeaponComponent

我们现在创了一个WeaponComponent将BaseWeapon中的Fire函数绑定在Character中的鼠标左键

这里我们Fire原函数放在BaseWeapon中

image-20230607212438273

image-20230607212508961

在Component中得到世界中的WeaponClass将那个武器跟人物的WeaponAttachPointName帮定到一起其实就是17条讲的

这个Fire就是调用了BaseWeapon中的fireimage-20230607212532672

这里就是在Character中将WeaponComponent创建并且在输入Component中将鼠标左键与WeaponComponent中的Fire函数绑定

image-20230607212850461image-20230607212903591

19、发射轨迹

实现线条并且碰到物体会有一个红点~

image-20230608142450064

头文件(第一个是获得GetWorld,第二个是DrawDebugLine)

image-20230608142519267

这里我们创建了一个叫MakeShot的函数,并在每次Fire的时候触发它

这个时候我们先要去在BaseWeapon先去找到Rifle的骨骼网格体

(目的是添加Socket)在游戏开发中,Socket一般指的是Actor或Component上的一个特殊点位,可以用来定位或附加其他的东西。

我们在武器的头头上创了一个叫MuzzleSocket用来后面作为线条的出发点

image-20230608142658121

image-20230608142615253

这里我们就用到了得到Socket的Transform(位置、旋转和缩放信息)

是Start作为开始,End作为结束(start+direction*1500),ShootDirection作为方向

DrawDebugLine(GetWorld(), TraceStart, TranceEnd, FColor::Red, false, 3.0f, 0, 3.0f);

false是是否永久存在,第一个3.0f是线条宽度,0是深度一般为0,最后一个3.0f是持续时间

具体来说,该代码使用LineTraceSingleByChannel函数绘制一条来自TraceStart,到TraceEnd的射线,并且将结果存在HitResult变量中。如果这条射线与某个物体碰撞并且阻挡了该射线,则bBlockingHit为true。如果出现了阻挡,将在ImpactPoint位置处画一个红色的10.0f半径的球体,持续5秒

  • HitResult:用于保存射线碰撞的结果,包括碰撞位置、碰撞法线、被碰撞Actor等等信息;
  • TraceStart:发射射线的起始点;
  • TranceEnd:射线终点;
  • ECollisionChannel::ECC_Visibility:所使用的碰撞通道。ECC_Visibility是UE4预置的一个常用通道,表示射线可以碰撞Actor,但不包括在Character类的Capsule Collider碰撞盒以内;
  • bBlockingHit:表示是否有物体阻挡了射线;
  • ImpactPoint:射线与物体的碰撞点在世界空间中的坐标;
  • DrawDebugSphere:在ImpactPoint处绘制一个Debug球体;
  • FColor::Red:球体的颜色为红色;
  • false:是否持久化,即这个Debug信息是否一直存在;
  • 5.0f:球体的持续时间为5秒。
  • 24:它控制了绘制球体时球体表面(网格)网格的数量

image-20230608142533912

并且这个要设为Block才会有碰撞才会有球球

并且我们把胶囊体得这个关掉,在Mesh中打开,你以后射击就只会打到人物模型上不会打到碰撞胶囊体积上了(Niceeeeeeee!)image-20230608143818913

这个时候我们完成了红点的绘制但是对于射击来说还是有些缺陷

我们要将子弹到我们瞄准的地方

实现这种效果

其实就是从摄像头(我们真实人物视角)到目标点

image-20230608150032403

头文件

image-20230608150051592

我们先要在Component中给Weapon传递人物(为了后面传摄像头)

image-20230608150141819

我们先判定一下武器的所有者是不是人物,然后再判定一下人物有人掌控没

然后通过Controller->GetPlayerViewPoint(ViewLocation, ViewRotation)这个函数我们该函数通过Controller指针获取当前玩家的控制器,并从控制器中获取玩家视角的位置ViewLocation和旋转ViewRotation

image-20230608150253442

这里划线就是从玩家视角到圆圈处

如果没碰撞到就玩家视角的直线image-20230608150627519

这是设置我们的自己不受攻击

image-20230608151637347

但是目前我们还有个致命的bug就是我们会打到身后的物体~,这是因为我们的摄像机在很后面,目前我们不修改其BUG

image-20230608151624936

20、子弹造成伤害

首先我们要获得遭受伤害的ACTOR这样我们才能使用其得TakeDamage并传伤害给他

然后我们在MakeShot中使用这个函数

image-20230608170248066image-20230608170255110

但是有点不足的是死亡后其胶囊体还在(原地任然有碰撞),所以我们需要关闭其碰撞

胶囊提的头文件

image-20230608170949773image-20230608171000548

关闭碰撞~

21、动画偏移、瞄准

这个其实跟19章有点相似只不过是直接在ABP蓝图中搞得,就是先获得rotation再得到其forward*1200+start的位置

这其实就是获得的我们玩家视角然后画的线image-20230608210034568

image-20230608205716634

然后我们让我们的瞄准各个地方都有动画我们需要创建一个叫做

image-20230608211512774

其中纵轴叫做Pitch横轴叫做Yaw我们只需要把右边的Aim动画拉到对应的位置就可以有连贯的动画了

image-20230608211543872

但是其中要注意的是8个动画都要设置一下附加设置中用网格体空间

这样就可以在某个动作比如Idle的基础上做出这些动作了

image-20230608211637093

就是有这种动作了

image-20230608211920427

22、动画逆运动学

可以完成这种东西~~0.0

其实目的是因为手跟枪会有空闲距离(手有animation)

所以我们为了让手跟枪固定才做了这中image-20230608212430634

这里有个叫做FABRIK的组件

先点右上角不要公开引脚(这样可以自己调位置)

第一个目标就是根据RightHand的移动改变移动

顶端骨骼就是我们要移动的最外端,根骨骼就是最里端

根据RightHand的变化计算顶端骨骼与根骨骼的变化

为啥这样我们的手就可以跟着武器一起了?

因为我们的武器是根RightHand贴着的,右手动武器也动

image-20230608213217742

image-20230608213152820

但是这也有点问题

就是死了的时候左手任然跟着右手走~

image-20230608213807953改一下位置就好啦~

23、定时拍摄

形成扫射,而不是点射,并且增加子弹散射

image-20230609170209549

首先要实现连发的话我们需要

要搞一个开始开火和停止开火然后在开始开火中搞一个定时器一直循环我们的makeshot函数image-20230609170302472

这样就可以实现连发了

image-20230609170424158

然后我们要实现子弹散射就修改我们的得到Trace函数

我们的想法是用一个圆锥我们枪口就是头子弹就是底,在底上触发散射

VRandCone是Unreal Engine中的一个函数,用于在一个锥形内随机生成一个向量

第一个参数是方向向量,第二个参数是锥形的半角度数,表示从锥形中心轴向两侧的张角,单位为弧度

所以我们要搞个弧度

DegreesToRadians就是将度数转化成弧度的函数,而BulletSpread是我们可以自己定的度数此处我们定为1.5

image-20230609170457425

24、两个武器,一个Rife另一个Launcher

我们要从BaseWeaponC++类中创建两个子c++类,然后将几个需要区分的功能设置为virtual让两个类可以自己实行多态

image-20230610105124821

这些就是需要单独覆盖的函数

因为榴弹不需要连发所以StartFire中的定时器需要改变哦~

image-20230610105154250

当然创建了c++类后还要从其创建蓝图类然后把WeaponComponent选择新的东东

image-20230610105420582

25、Launcher中的火箭创造

首先实现有火箭

image-20230610134901045

这样我们就需要创建一个火箭的ACTOR

我们称之为STUProjectile

然后要在这个里面创建一个CollisionComponent并且设定其半径为5.0image-20230610135026490

这样我们的ACTOR类就完成了,然后就需要我们将这个火箭放在枪口

这里我们创建了一个叫做Projectileclass的ASTUPrijectile类

首先用SpawnTransform变量储存枪口的socket位置与rotation信息(我们可以像以前那样去lancher武器材质里面去创建socket同样也叫做MuzzleSocket)

然后Deferred Actor Spawn 是一种 Unreal Engine 中的概念,它可以延迟 Actor 的生成,即在某个时候再执行 Actor 的生成操作。这通常用于在启动关卡时生成Actor,而不是在关卡中实时生成它们,以提高性能或优化游戏逻辑等。调用 BeginDeferredActorSpawnFromClass 不会立即生成 Actor,而是返回一个 Actor 引用,该引用可以在稍后(通过 UGameplayStatics::FinishSpawningActor 函数调用)用于完成 Actor 的生成过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YhcB2dVH-1686743883202)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230610135128919.png)]

这样当年下左键就会触发makeshot就会将火箭弄在枪口处了

或者用这种是一样的

ASTUProjectile* Projectile =GetWorld()->SpawnActorDeferred(ProjectileClass, SpawnTransform);

Projectile->FinishSpawning(SpawnTransform)

26、火箭发射

​ 我们使用其ue4上有的component叫做MovemenetComponent

在Projectile中创建此Component

在 Unreal Engine 中,移动组件是一种可以被添加到 Actor 中的特殊组件。它可以控制 Actor 对象主体的物理运动和旋转行为。移动组件的使用方式类似于其他组件

image-20230610143809794

image-20230610143825620

image-20230610144131410

MovementComponent->Velocity = ShotDirection*MovementComponent->InitialSpeed;

这个就是发射轨迹

在ue4中MovementComponent中还可以改变重力,和初始速度啥的

这就是先将方将传给MovementCompoent然后创建actor然后自动启动其MovementComponent组件然后就可以飞出去了

SetLifeSpan就是将其单位5秒后消灭(不然会一直创建,就太占资源了)

image-20230610144442135

27、火箭造成伤害

首先我们先创建两个参数一个叫做伤害范围另一个叫做伤害总量

image-20230610183625847

SetCollisionEnabled 函数指定了 CollisionComponent 的碰撞模式,即 ECollisionEnabled::QueryOnly。这种模式下,CollisionComponent 仅用于查询物理碰撞,不发生实际的情况。也就是说,CollisionComponent 不会与物体发生碰撞,而只用于查询物理碰撞。

SetCollisionResponseToAllChannels 函数指定了 CollisionComponent 与其他物体的碰撞响应类型,即 ECollisionResponse::ECR_Block。这意味着 CollisionComponent 会阻止其他物体穿过它,而不是允许它们通过。因此,当其他物体与 CollisionComponent 发生碰撞时,它们将被阻止并不会穿过

image-20230610183722079

这是使用了自带的delegate委托,当 CollisionComponent 与其他物体相撞时,就会调用 OnProjectileHit 函数

image-20230610183914272

下面是这个函数的的构造image-20230610184221543

首先先将速度清零

然后使用自带的爆炸Damage触发机制(前面讲过)

最后画一个圈可以看见炸弹范围

最后,执行 Destroy() 命令,将 Projectile 销毁并从游戏世界中删除

image-20230610183853569

我们在榴弹枪里先将其owner设置为了人物

然后返回人物,就是谁打的

image-20230610184512111

image-20230610184451512

28、实现按键切换枪

首先先创建一个数组储存我们需要的枪到时候去ue4蓝图去将这些枪加进去

image-20230610212801706

这个时候取两个名第一个WeaponSocket就是手上的点

ArmorySocket就是背着的枪的名[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HYAXG0RC-1686743886257)(null)]

image-20230610212901137

修改我们最初的函数(多个Weapon哦~)

前面是将Weapon的所有权给加到Character上并且给Weapons初始化

后面

我们使用 AttachWeaponToSocket 函数将生成的武器对象附加到角色蒙皮网格的指定插槽上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8C3nXibg-1686743883206)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230610213408483.png)]

image-20230610213801619

上述函数就是在启动时将武器信息录入并且放到该放的位置

这个函数才是交换武器的关键

现在就是将Current枪给放在背上并且将背上的枪放在手上

image-20230610213905408

后面的看看就知道啦不需要讲了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yeo05nrG-1686743883207)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230610214058237.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DP5Cwccv-1686743883207)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230610214109061.png)]

image-20230610214119624

29、装备动画与动画通知

首先我们现在Component组件上创建一个蒙太奇动画选项

image-20230611131446808

创建一个函数就是将选项中的蒙太奇在Character中播放 image-20230611131456580

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gkR3CNtn-1686743883208)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611131503947.png)]

这样只要选项选好了那个蒙太奇了那我们的人物就可以播放动画了

我们在UE4中,动画通知(Animation Notification)主要用于在动画播放期间执行自定义操作或触发事件。通知可以在动画的任何时间点上触发

我们先创建一个叫做Notify的类在C++中[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OvypIIQK-1686743883208)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611141731167.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KATJjm1F-1686743883208)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611141644199.png)]

并且在其中创建了一个委托叫做OnNotified

我们在Notify函数中触发这个委托

image-20230611141806114image-20230611141811133

然后我们在WeaponComponent中创建了两个函数

InitAnimations就是在动画里找动画通知并给每个动画通知绑定OnEquipFinished函数(break用来通知只给一个)

这个函数OnEquipFinished就是动画通知触发到这个Notify事件时发生的事我们在日志打印文字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X5SD2UWT-1686743883209)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611141918818.png)]image-20230611141932973

然后我们需要在AnimMontage中创建动画通知

这样在每次触发到动画通知时就会发生我们想要做的事(比如人物受伤动作,或声音啥的)

30、在动画结束后才能更换武器和开火

创了个变量和两个函数

image-20230611144236556

image-20230611144224192这里我们在装备武器开始时将其设为true然后当动画完成后设为falseimage-20230611144440725

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ve5t3Y6E-1686743883210)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611144340658.png)]

这样在我们开枪时如果动画没完成就参数就是true canfire就是false

同理在换武器那也是一样

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxVyNycM-1686743883210)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611144355473.png)]image-20230611144404234

31、解决一个小bug(跑步时更换武器会把跑步动作卡掉)

因为以前的Equip蒙太奇动画是放在以前默认的slot中

所以我们现在新创一个slot来保存我们的equip动画

image-20230611170735714

然后创建了一个动作缓存image-20230611170852866

并取名LocomotionCache

image-20230611170809224

然后使用一个混合就可以将缓存动作和换装动作混合到一起

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TTTEbYOo-1686743883212)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611170948522.png)]我们把Posses0中的骨骼名称给粘贴进去这样就把Spine有关的运动划分为用新的Equip中的Spine运动和原运动混合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ftjUAng-1686743883212)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611171030589.png)]

32、弹药模型和弹夹

我们这里先实现其逻辑

我们创建了一个子弹类

其中有3个参数子弹数量,弹夹数量,和无线模式(子弹无限)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xL53Zm2k-1686743883213)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611192454339.png)]

然后创造了4个函数

分别是LogAmmo(显示子弹数量),IsAmmoEmpty(返回所有子弹包括弹夹是否耗尽),IsClipEmpty(返回此弹夹子弹是否耗尽),ChangeClip(将子弹重塑为原本数),DecreaseAmmo(消耗子弹并且当本弹夹空了就换弹夹)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qo2IKJPN-1686743883213)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230611192546229.png)]

然后单独在每种武器的MakeShot函数中改一下

当总子弹没了就shot不了了,然后最后一行DecreaseAmmo

image-20230611192932486

33、为换弹夹加动作

我们先在WeaponComponent中创一个结构体叫做FWeaponData这样的话我们就可以将蒙太奇动画和武器类给放到里面

image-20230611204655391

然后到SpawnWeapons中修改一下

image-20230611205230834

然后再把EquipWeapon改一下

其中我们搞了个FindByPredicate函数这个函数的功能就是判断我们目前的武器的类到底是枪还是炮

然后就将CurrentReloadAnimMontage初始化然后PlayAnimMontage来绘制其对应的动画

image-20230611205329796

34、优化一下换弹时不能射击换武器时不能射击

我们现在先创建1个叫做基类的STUAnimNotifynotify其他2个notify都继承它

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hZHTRKGk-1686744023859)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612134728492.png)]

基类notify(跟我们之前讲的动画通知一样)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B6nDJxTL-1686744023860)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612134908797.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIO1TpYT-1686744023860)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612135031744.png)]

现在就是将以前的EquipFinished来代表换装备

Reload代表换弹的动画通知

再加了个能否Reload和能否Equip

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-14GJU20d-1686744023861)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612135203112.png)]

和一个Reload

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fP3TRkAm-1686744023861)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612135508555.png)]

其实就是当可以换的时候就做调用这个函数然后做这个动画

这俩函数是Character与键盘绑定的函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mkgOI1A0-1686744023861)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612135547134.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ua9JNuks-1686744023862)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612135557646.png)]

这俩函数就是传递是当动画放完后才可以播放下一个动画的要求函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWWv5kDs-1686744023862)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612135755720.png)]

这个函数是为每个动画中的每个通知绑定相应的函数,当到了动画通知的时候才会Finished这个时候你再按键盘才会播放下一个动画

为啥下面有for循环,因为有两种枪两种是不同的动作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rS8hpxZB-1686744023862)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612140047891.png)]

这个是我们自己定义的FindNotifyByClass模板

这个动画就是创建对应通知类的通知实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sK8DP2Pl-1686744023863)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612140230016.png)]

与这个相似

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5nRG6Jjo-1686744023863)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612140907797.png)]

35、这节课就是把所有struct和方法分开放到了两个头文件中一个是core另一个是Utills

并且加了一些安全检查

自己去看吧65集

36、角色健康栏

这个叫做进度条(在Widget中)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FuOflIiq-1686744023863)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612164227213.png)]

首先我们在c++ui中创建了这个Widget类(都是显示组件)

Widget通常表示为按钮、文本框、下拉列表、滑块等可与用户进行交互的控件。它们用于在屏幕上显示信息、响应用户动作、控制程序行为等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKot6el0-1686744023863)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612155848611.png)]

然后在其中声明并创建了GetHealthPercent函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDlb2esO-1686744023864)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612160047617.png)]

首先我们要先得到角色然后

第一个component是获取基类Actor的UActorComponent指针

第二个是将其强制转化为USTUHealthComponent

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JG4ZACXV-1686744023864)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612160114166.png)]

然后我们在STUGameHUD(才是UI的显示的地方)中

创建了UUserWidget类

和重载了BeginPlay类

到时候就可以在UE4中将我们创建好了的Widget类给选上就ok啦[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XMvyAVHh-1686744023864)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612161005002.png)]

先创一个Widget组件并且将其组件展示开来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xjrtERxX-1686744023864)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612161112683.png)]

这里第一个HUD是从以前那个继承过来的第二个是自己创建的Widget组件然后去里面去[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oeo4bE2d-1686744023865)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612164326525.png)]

然后在里面创一个进度条将其绑定为我们创建的GetHealth函数

然后在基类HUD中我们先前创建了Widget选项选择WBP_PlayerHUD就好啦[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r5t0SUVb-1686744023865)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612164245665.png)]

37、准心

(这个在Widget中叫做image(图像))

实现这俩可以切换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lle2FBmI-1686744023865)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612205554892.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-392JgVft-1686744023865)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612205544487.png)]

我们在CoreTypes中创建了一个叫FWeaponUIData的结构体

其中储存了2个数据一个是武器图标,另一个是准信图标

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EndrwAbm-1686744023866)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612205626797.png)]

然后在武器基类中创建了这么一个函数到时候就可以在ue4中将武器的图标配置好了然后可以通过这个函数传送俩个数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I0VmFTYc-1686744023866)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612205854844.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KTheSfGp-1686744023866)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612205749416.png)]

然后我们传到哪了呢

当然是传到WeaponComponent中了

这个函数把从Weapon中的传来的俩数据储存到UIData中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R0wiaycV-1686744023867)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612210019822.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xr6DZyuL-1686744023867)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612210039057.png)]

然后在我们的Widget中我们创造了一个函数叫做GetWeaponUIData

这个就是把Widget上传输UIData数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbnxrZ8t-1686744023867)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612210210433.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-odQFQttB-1686744023867)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612210215957.png)]

Self传给了这个Get函数数据然后创建SlateBrush

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fDH3pAft-1686744023868)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230612210524818.png)]

38、子弹数量显示和武器图标

UE4中的特殊宏

1、UPROPERTY

  1. 作用
    将变量公开到编辑器或蓝图

  2. 参数

VisableAnywhere:公开变量到编辑器和蓝图(仅显示)

​ EditAnywhere:显示可编辑变量到编辑器和蓝图

​ Category:将变量分组,便于查看(就是蓝图中一层又一层)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NXO8w7XC-1686744023868)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230604154120989.png)]

其中|表示二级分组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y0v3g62n-1686744023868)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230604154143925.png)]

meta:限制变量大小
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xGrar7zq-1686744023868)(https://gitee.com/ljx99999/picture/raw/master/java-imgs/image-20230604154152393.png)]

​ 其中ClampMin/Max表示键盘所能输入的极限值,UIMin/Max表示滑动条所能拖动的极限值

VisibleDefaultsOnly:在编辑器中不显示,在蓝图中显示(不可编辑)

EditDefaultsOnly:在蓝图中可编辑,在编辑器中不显示

EditInstanceOnly:在蓝图中可见不可修改,只有实例化蓝图后才可修改

  1. 访问权限
  2. BlueprintReadWrite:在蓝图中可读可写

BlueprintReadOnly:在蓝图中只读

2、UFUNCTION(对函数的宏)

函数说明符:
*BlueprintAuthorityOnly*
如果在具有网络权限的计算机(服务器,专用服务器或单人游戏)上运行,此功能只能从Blueprint代码执行,如无网络权限,则该函数将不会从蓝图代码中执行

*BlueprintCallable*
该函数可以在蓝图或关卡蓝图图表中执行

*BlueprintCosmetic*
此函数为修饰函数而且无法运行在专属服务器上

*BlueprintGetter* 修饰自定义的Getter函数专用【例子见UE4入门-常见的宏-UPROPERTY】
该函数将用作Blueprint暴露属性的访问器。这个说明符意味着BlueprintPure和BlueprintCallable

*BlueprintSetter* 修饰自定义的Setter函数专用【例子见UE4入门-常见的宏-UPROPERTY】
此函数将用作Blueprint暴露属性的增变器。这个说明符意味着BlueprintCallable

*BlueprintImplementableEvent*
此函数可以在蓝图或关卡蓝图图表内进行重载
不能修饰private级别的函数,函数在C++代码中不需要实现定义

*BlueprintInternalUseOnly*
表示该函数不应该暴露给最终用户

*BlueprintNativeEvent*
此函数将由蓝图进行重载,但同时也包含native类的执行。提供一个名称为[FunctionName]_Implementation的函数本体而非[FunctionName];自动生成的代码将包含转换程序,此程序在需要时会调用实施方式

BlueprintPure
该函数不会以任何方式影响拥有对象,并且可以在蓝图或级别蓝图图表中执行

CallInEditor
该函数可以在编辑器中通过详细信息面板中的按钮在选定实例中调用

Category = TopCategory|SubCategory|Etc
指定函数在编辑器中的显示分类层级,| 是分层级的符号

*Client*
此函数仅在该函数从属对象所从属的客户端上执行。提供一个名称为[FunctionName]_Implementation的函数主体,而不是[FunctionName]; 自动生成的代码将包含一个转换程序来在需要时调用实现方法

*CustomThunk*
UnrealHeaderTool(虚幻头文件工具)的代码生成器将不会为此函数生成execFoo转换程序; 可由用户来提供

*Exec*
此函数可从游戏中的控制台中执行。Exec命令仅在特定类中声明时才产生作用
此标记修饰的函数应在可以接受输入的类中,才能正常接受命令

*NetMilticast*
无论角色的NetOwner如何,该函数都在服务器上本地执行并复制到所有客户端

*Reliable / UnReliable*
Reliable函数在网络间进行复制,并会忽略带宽或网络错误而被确保送达。仅在与客户端或服务器共同使用时可用
UnReliable函数在网络间复制,但可能会由于带宽限制或网络错误而传送失败。仅在与客户端或服务器一起使用时有效

*SealeEvent*
这个函数不能在子类中重写。 SealedEvent关键字只能用于事件。对于非事件函数,声明它们是static的还是final的来封闭它们

*ServiceRequest / ServiceResponse*
ServiceRequest函数是一个RPC服务请求
ServiceResponse函数是一个RPC服务响应

*Server*
此函数仅在服务器上执行。提供一个名称为[FunctionName]_Implementation的函数主体,而不是[FunctionName]; 自动生成的代码将包含一个转换程序来在需要时调用实现方法

*WithValidation*
声明一个名为与main函数相同的附加函数,但将_Validation添加到最后。该函数采用相同的参数,并返回一个布尔值来指示是否应该继续调用主函数

3、DELEGATE宏

DECLARE_DELEGATE()是UE4中用于声明委托的宏。

其中有很多种委托宏DELEGATE

委托是一种类型,它是一组函数指针,可以用来传递函数指针和订阅/取消订阅事件,允许在不同对象之间进行通信。

带有 DYNAMIC 关键字的宏表示该委托类型将能够通过蓝图编辑器进行编写和调用,可以在蓝图中绑定委托并添加响应函数。

MULTICAST 关键字则表示该委托可以同时调用多个响应函数

eg:

DECLARE_DELEGATE_TwoParams( DelegateName, Param1Type, Param2Type )
返回void的名为DelegateName的两个参数的委托
DECLARE_DELEGATE_RetVal_OneParam( RetValType, DelegateName, Param1Type )
返回值为RetValType类型,有一个参数,类型为Param1Type

意义:(1)将实时检测逻辑转换为触发式逻辑

请进行以下思考:

如果要实现物体从A移动到B点,在到达B点时打印一句"到达目标点!".

如果使用检测式逻辑:或许你会在使用Timer或者Tick没隔一段时间判断一下是否到达B点,这样会导致判断逻辑会在到达B点之前一直在重复执行,如果判断逻辑较为复杂,则会导致程序会高频率执行一段运算复杂逻辑,导致性能降低.并且如果使用的Timer间隔比较长,可能会导致物体到达时,Timer倒计时还没到,导致判定失准!

如果使用触发式逻辑:在B点放置一个BoxTrigger,当物体到达B点时触发Overlap回调时执行打印,这么做避免了高频率执行判断逻辑,降低了性能的损耗,并且精确度比检测式要更高!

4、TSubclassOf

其实应该是比如创建一个类

使用TSubclassOf< xxxx > xxclass

可以指定这个类继承于哪个

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值