定时器(Timer) 可用于执行延迟类型的操作,或让某些操作在一段时间内重复执行。 例如,你可能希望让玩家角色在获得道具后变更无敌状态,然后在几秒后让角色恢复到正常状态。或者,你可能希望让站在火堆中的玩家每隔几秒就受到一次伤害。 在本指南中,你需要为玩家角色实现一个定时器,使得玩家在走进火焰特效Actor的体积内后,着火并持续受到伤害。 如果玩家在火堆中站得太久,就会发生爆炸并被摧毁。
项目设置
- 首先点击 新建(New) > 游戏(Games) > 第三人称(Third-Person) > 蓝图项目(Blueprint Project) 创建名为 Timers 的项目,并启用 初学者内容包(Starter Content) 。
第三人称模板项目提供了已映射为接收输入的第三人称角色。此外,使用初学者内容包设置项目后,你就可以获得不同的声音和粒子效果,例如火焰。
用于切换项目类型的下拉菜单 (1),用于启用初学者内容包的下拉菜单 (2),项目的名称字段 (4),用于初始化项目的"创建项目(Create Project)"按钮
创建火焰效果Actor
火焰效果Actor作为 蓝图(Blueprint) 类存在于 初学者内容包(Starter Content) 文件夹中。它包含用于火焰效果的ParticleSystemComponent和用于火焰声音的AudioComponent。你还需要设置火焰效果的 初始生命周期(Initial Lifespan) 的值。Actor的生命周期到期后,它将被销毁。
有关额外的文档,请参阅Actor生命周期
-
找到 内容(Content) > 初学者内容包(Starter Content) > 蓝图(Blueprints) ,然后双击 Blueprint_Effect_Fire 打开其 类默认值(Class Defaults) 。
-
在 细节(Details) 面板中,找到 Actor 类别,然后将 初始生命周期(Initial Lifespan) 变量设置为值 1.5 。
-
点击 编译(Compile) 和 保存(Save) 。
完成的蓝图
创建爆炸效果
爆炸效果Actor(Explosion Effect Actor) 作为 蓝图(Blueprint) 类存在于 初学者内容包(Starter Content) 文件夹中。它包含用于爆炸效果的粒子系统组件,以及用于爆炸声音的音频组件。你还需要设置爆炸效果的 初始生命周期(Initial Lifespan) 变量的值。Actor的生命周期到期后,它将从Gameplay中销毁。
-
找到 内容(Content) > 初学者内容包(Starter Content) > 蓝图(Blueprints) ,然后双击 Blueprint_Effect_Fire 打开其 类默认值(Class Defaults) 。
-
在 细节(Details) 面板中,找到 Actor 类别 ,然后将 初始生命周期(Initial Lifespan) 变量设置为值 1.5 。
-
点击 编译(Compile) 和 保存(Save) 。
完成的蓝图
创建火焰效果Actor
火焰效果Actor用作存在于关卡中的物理位置的粒子效果。你会将该功能用于玩家角色与火焰效果Actor重叠的情况,这会导致角色着火并在一段时间后受到伤害。 为此,你需要创建以下类成员:
| 类成员 | 说明 |
|---|---|
| BoxComponent | 用作火焰效果Actor的碰撞体积的盒体。另一个Actor与其边界重叠时,将调用NotifyActorBeginOverlap函数。 |
| ParticleSystemComponent | 提供视觉火焰效果的粒子发射器。 |
| NotifyActorBeginOverlap | 此Actor与另一个Actor重叠时触发的事件。例如,玩家走进用于火焰效果Actor的重叠边界的盒体组件时。 |
| NotifyEndActorOverlap | Actor不再与另一个Actor重叠并且已分开时触发的事件。例如,玩家离开用于火焰效果Actor的重叠边界时。 |
-
找到 内容浏览器(Content Browser) ,然后点击 添加/导入(Add/Import) 按钮。从下拉菜单选择 创建(Create a) 蓝图类(Blueprint Class) ,然后从 选取父类(Pick Parent Class) 窗口选择Actor。
-
从 类默认值(Class Defaults) ,找到 组件(Components) 面板并点击 添加组件(Add Component) 按钮,然后搜索并选择 盒体碰撞(Box Collision) 。将此盒体碰撞形态命名为 OverlapVolume 。
-
点击 添加组件(Add Component) 按钮,然后搜索并选择 粒子系统(Particle System) 。
-
在粒子系统的 细节(Details) 面板中,找到 粒子(Particles) 类别。选择 模板(Template) 变量旁边的箭头,以显示下拉资产窗口。搜索并选择 P_Fire 粒子资产。
-
在 事件图表(Event Graph) 中,右键点击,然后从"操作(Actions)"下拉菜单搜索并选择 Actor Begin Overlap 。
用于创建新函数的"添加(+)(Add (+))"按钮。(2) 三个新创建的函数:TakeFireDamage、IgniteFireTimer和ClearFireTimer。
实现TakeFireDamage函数。
-
打开 TakeFireDamage 函数选项卡后,找到 我的蓝图(My Blueprint) 面板,然后将 生命值(Health) 变量拖移到 事件图表(Event Graph) 以创建参考节点。
-
从 生命值(Health) 变量引脚拖移,然后从"操作(Actions)"菜单搜索并选择 Subtract 节点。
-
将扣减值设置为你想对 玩家角色(Player Character) 造成的伤害量,在本示例中,我们选择了值 -20.0 。
-
回到 我的蓝图(My Blueprint) 面板,然后将 生命值(Health) 变量拖移到 事件图表(Event Graph) 以创建参考节点。
-
从 生命值(Health) 引脚拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 小于等于 (小于或等于)运算符。
-
从 TakeFireDamage 节点的 执行引脚 拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 分支(Branch) 。
-
回到 我的蓝图(My Blueprint) 面板,然后将 生命值(Health) 变量的副本拖移到 float - float 节点的返回值上。 这会将 生命值(Health) 变量设置为此节点的运算的结果。
-
将返回值引脚从 Health <= 0 Operator 节点连接到 Branch 节点的条件引脚。然后,将 False 输出引脚连接到 Set Health 节点的 设置(Set) 输入引脚。
-
从 Branch 节点的 True 执行引脚拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 从类生成Actor(Spawn Actor From Class) 。然后,从类引脚搜索并选择 Blueprint_Explosion_Effect 。
-
从 SpawnActor Blueprint Effect Explosion 节点的 生成变换(Spawn Transform) 引脚拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 获取Actor变换(Get Actor Transform) 。
-
从 SpawnActor Blueprint Effect Explosion 节点的 执行引脚 拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 DestroyActor 。
-
点击 编译(Compile) 和 保存(Save) 。
半成品蓝图
实现IgniteFireTimer函数
-
打开 IgniteFireTimer 函数图表。从 IgniteFireTimer 节点的 执行引脚 拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 从类生成Actor(Spawn Actor From Class) 。然后,从类引脚搜索并选择 Blueprint_Fire_Effect 。
-
从 SpawnActor Blueprint Fire Effect 节点的 生成变换(Spawn Transform) 引脚拖移。然后,从 可执行操作(Executable actions) 菜单搜索并选择 获取Actor变换(Get Actor Transform) 。
-
从 SpawnActor Blueprint Fire Effect 节点的返回值引脚拖移。然后,从 可执行操作(Executable actions) 菜单搜索并选择 AttachActorToComponent 。
-
在 AttachActorToComponent 节点中,找到 插槽名称(Socket Name) 引脚并将其设置为 spine_01 。接下来,将 位置规则(Location Rule) 引脚设置为 对齐目标(Snap to Target) ,并设置 旋转规则(Rotation Rule) 和 缩放规则以保持相对(Scale Rule to Keep Relative) 。然后,点击选中相应复选框,以将 结合模拟的形体(Weld Simulated Bodies) 布尔值启用为 true 。
-
从 AttachActorToComponent 节点的父引脚拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 获取网格体(Get Mesh) 。
-
从 AttachToActorComponent 节点的执行引脚拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 Set Timer By Function Name 。接下来,将 函数名称(Function Name) 引脚设置为 TakeFireDamage ,并将"时间(Time)"引脚设置为 0.75 。最后,点击选中相应复选框,以将 循环(Looping) 布尔值启用为 true 。
-
从 Set Timer By Function Name 节点的 计时器句柄返回值(Timer Handle Return Value) 引脚拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 设置火焰计时器(Set Fire Timer) 。
-
点击 编译(Compile) 和 保存(Save) 。
半成品蓝图
实现ClearFireTimer函数
-
打开 ClearFireTimer 函数图表。从 我的蓝图(My Blueprint) 面板,将FireTimer变量拖移到事件图表(Event Graph)。
-
从 火焰计时器(Fire Timer) 引脚拖移,然后从 可执行操作(Executable actions) 菜单搜索并选择 按句柄清除计时器并使其失效(Clear and Invalidate Timer by Handle) 。
-
点击 编译(Compile) 和 保存(Save) 。
半成品蓝图
创建火焰效果Actor
火焰效果Actor用作存在于关卡中的物理位置的粒子效果。你会将该功能用于玩家角色与火焰效果Actor重叠的情况,这会导致角色着火并在一段时间后受到伤害。 为此,你需要创建以下类成员:
| 类成员 | 说明 |
|---|---|
| BoxComponent | 用作火焰效果Actor的碰撞体积的盒体。另一个Actor与其边界重叠时,将调用NotifyActorBeginOverlap函数。 |
| ParticleSystemComponent | 提供视觉火焰效果的粒子发射器。 |
| NotifyActorBeginOverlap | 此Actor与另一个Actor重叠时触发的事件。例如,玩家走进用于火焰效果Actor的重叠边界的盒体组件时。 |
| NotifyEndActorOverlap | Actor不再与另一个Actor重叠并且已分开时触发的事件。例如,玩家离开用于火焰效果Actor的重叠边界时。 |
-
找到 内容浏览器(Content Browser) ,然后点击 添加/导入(Add/Import) 按钮。从下拉菜单选择 创建(Create a) 蓝图类(Blueprint Class) ,然后从 选取父类(Pick Parent Class) 窗口选择Actor。
-
从 类默认值(Class Defaults) ,找到 组件(Components) 面板并点击 添加组件(Add Component) 按钮,然后搜索并选择 盒体碰撞(Box Collision) 。将此盒体碰撞形态命名为 OverlapVolume 。
-
点击 添加组件(Add Component) 按钮,然后搜索并选择 粒子系统(Particle System) 。
-
在粒子系统的 细节(Details) 面板中,找到 粒子(Particles) 类别。选择 模板(Template) 变量旁边的箭头,以显示下拉资产窗口。 搜索并选择 P_Fire 粒子资产。
-
在 事件图表(Event Graph) 中,右键点击,然后从"操作(Actions)"下拉菜单搜索并选择 Actor Begin Overlap 。
-
从 Event ActorBeginOverlap 节点的 其他Actor(Other Actor) 输出引脚拖移,然后从下拉菜单搜索并选择 Cast To Third Person Bp Character 。
-
从 Cast To ThirdPersonCharacter 节点的 作为第三人称角色(As Third Person Character) 输出引脚拖移,然后从下拉菜单搜索并选择 Ignite Fire Timer 函数。
-
在 事件图表(Event Graph) 中,右键点击,然后从"操作(Actions)"下拉菜单搜索并选择 ActorEndOverlap 。
-
从 Event ActorEndOverlap 节点的 其他Actor(Other Actor) 输出引脚拖移,然后从下拉菜单搜索并选择 Cast To Third Person Character 。
-
从 Cast To ThirdPersonCharacter 节点的 作为第三人称角色(As Third Person Character) 输出引脚拖移,然后从下拉菜单搜索并选择 Clear Fire Timer 函数。
-
点击 编译(Compile) 和 保存(Save) 。
完成的蓝图
最终结果
-
从内容浏览器,将Bp_FireActor的实例拖移到关卡中。
-
找到编辑器工具栏,然后点击 运行(PIE)(Play (PIE)) 以在编辑器中预览你的项目。
使用方向键或WASD键,将你的角色移入火焰效果Actor的体积中。如果你在体积中逗留太久,角色的生命值最终会耗尽为0,此时角色将爆炸。
与计时器相关的额外节点
Set Timer By Function Name 和 Clear Timer by Handle 节点用于启动和停止计时器,而下面的节点还可能适合你的需要。
获取计时器耗时和获取计时器剩余时间
Get Timer Elapsed by Handle 和 Get Timer Remaining by Handle 节点分别返回自启动以来的耗时,以及获取指定计时器(由句柄指示)的剩余时间。它们返回浮点值,可用于多种不同的用途,例如随时间更改效果时,或在效果即将到期前显示消息时。
暂停计时器和取消暂停计时器
按句柄暂停计时器(Pause Timer by Handle) 会在当前时间暂停计时器,而 按句柄清除计时器(Clear Timer by Handle) 会完全停止计时器并将其重置。 Unpause Timer by Handle 节点可用于恢复已暂停的计时器,并从之前停止的位置继续。例如,如果玩家应用了会在计时器之后到期的效果,当玩家进入菜单或物品栏界面时,你可能需要暂停该效果,并在玩家返回Gameplay时恢复。
计时器存在、计时器是否已激活,以及计时器是否已暂停
Does Timer Exist by Handle 、 Is Timer Active by Handle 和 Is Timer Paused by Handle 节点都会返回 True 或 False 值,分别可用于确定计时器是否存在、计时器当前是否已激活或者计时器当前是否已暂停。
按事件设置计时器
Set Timer by Event 节点的功能类似于 Set Timer By Function Name 节点,但不是用于指定函数,而是可以连接将触发的 Custom Event 节点。你还可以将它设置为循环,就像 Set Timer By Function Name 节点那样。
项目设置
-
首先创建 新建(New)> 游戏(Games)>第三人称(Third-Person)> C++项目(C++ project),并启用 初学者内容包(Starter Content)。将你的项目命名为 Timers。
第三人称模板项目提供了已映射为接收输入的第三人称角色。此外,使用初学者内容包设置项目后,你就可以获得不同的声音和粒子效果,例如火焰。
用于切换项目类型的下拉菜单 (1),用于启用初学者内容包的下拉菜单 (2),项目的名称字段 (4),用于初始化项目的"创建项目(Create Project)"按钮
创建火焰效果Actor
火焰效果Actor是 初学者内容包(Starter Content) 文件夹中的蓝图类。它包含用于火焰效果的UParticleSystemComponent和用于火焰音频的UAudioComponent。你也需要创建该类的C++版本,以在玩家角色与火焰效果Actor重叠时在TimerCharacter类上生成火焰效果。
- 从 内容浏览器(Content Browser) 点击 添加/导入(Add/Import) 按钮,然后选择 新建C++类(New C++ Class) 以创建名为 FireEffect 的新 Actor 类。
-
在
Fire Effect.h文件的类默认值中,声明以下类:protected: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) class UParticleSystemComponent* FireParticle; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) class UAudioComponent* FireAudio; -
找到
FireEffect.cpp 文件,然后包括以下类库以实例化组件类成员:#include "Particles/ParticleSystemComponent.h" #include "Components/AudioComponent.h" -
在
FireEffect::FireEffect构造函数中,实例化并附加以下类成员:// 将此Actor设置为每帧调用更新函数()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; // 实例化我们的Actor组件 FireParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("FireParticle")); FireAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("FireAudio")); // 将音频组件设置为附加到FireParticle效果 FireAudio->SetupAttachment(FireParticle); static ConstructorHelpers::FObjectFinder<UParticleSystem>ParticleAsset(TEXT("ParticleSystem'/Game/StarterContent/Particles/P_Fire.P_Fire'")); FireParticle->SetTemplate(ParticleAsset.Object); static ConstructorHelpers::FObjectFinder<USoundBase>AudioAsset(TEXT("SoundCue'/Game/StarterContent/Audio/Fire01_Cue.Fire01_Cue'")); FireAudio->SetSound(AudioAsset.Object); }ConstructorHelpers这个类提供了用于将Cpp代码中的引用分配到虚幻编辑器中找到的Object。
-
找到
FireEffect::BeginPlay方法,并实现以下代码:{ Super::BeginPlay(); SetLifeSpan(1.5f); }SetLifeSpan是从Actor类继承的方法,接受一个浮点值,以设置"初始生命周期(Initial Lifespan)"变量。Actor的生命周期到期后,它将被销毁。有关额外的文档,请参阅Actor生命周期
-
编译你的代码。
已完成代码
FireEffectActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FireEffect.generated.h"
UCLASS()
class TIMERS_API AFireEffect : public AActor
{
GENERATED_BODY()
public:
// 为此Actor的属性设置默认值
AFireEffect();
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
class UParticleSystemComponent* FireParticle;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
class UAudioComponent* FireAudio;
protected:
// 当游戏开始或重生时被调用
virtual void BeginPlay() override;
public:
// 每一帧都调用
virtual void Tick(float DeltaTime) override;
};
FireEffectActor.cpp
#include "FireEffect.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/AudioComponent.h"
// 设置默认值
AFireEffect::AFireEffect()
{
// 将此Actor设置为每帧调用更新函数()。 如果不需要此特性,可以关闭以提升性能。
PrimaryActorTick.bCanEverTick = true;
FireParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("FireParticle"));
FireAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("FireAudio"));
FireAudio->SetupAttachment(FireParticle);
static ConstructorHelpers::FObjectFinder<UParticleSystem>ParticleAsset(TEXT("ParticleSystem'/Game/StarterContent/Particles/P_Fire.P_Fire'"));
FireParticle->SetTemplate(ParticleAsset.Object);
static ConstructorHelpers::FObjectFinder<USoundBase>AudioAsset(TEXT("SoundCue'/Game/StarterContent/Audio/Fire01_Cue.Fire01_Cue'"));
FireAudio->SetSound(AudioAsset.Object);
}
// 当游戏开始或重生时被调用
void AFireEffect::BeginPlay()
{
Super::BeginPlay();
SetLifeSpan(1.5f);
}
// 每一帧都调用
void AFireEffect::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
创建爆炸效果Actor
爆炸效果Actor(Explosion Effect Actor) 作为蓝图类存在于 初学者内容包(Starter Content) 文件夹中。它包含用于爆炸效果的粒子系统组件和用于爆炸声音的音频组件。你也需要创建该类的C++版本,以实现当玩家角色的生命值降至0时在 TimerCharacter 类上生成爆炸效果的预期Gameplay功能。
-
在 内容浏览器(Content Browser) 中点击 添加/导入(Add/Import) 按钮,然后选择 新建(New) C++类(C++ Class) 以创建名为 ExplosionEffect 的新 Actor 类。
-
在
ExplosionEffect.h文件的类默认值中声明以下内容:protected: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) class UParticleSystemComponent* ExplosionParticle; UPROPERTY(EditDefaultsOnly,BlueprintReadOnly) class UAudioComponent* ExplosionAudio; -
找到
ExplosionEffect.cpp文件,然后包括以下类库以实例化类成员。#include "Particles/ParticleSystemComponent.h" #include "Components/AudioComponent.h" -
在
ExplosionEffect::ExplosionEffect构造函数中,实现以下类成员。// 将此Actor设置为每帧调用更新函数()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; // 初始化Actor组件 ExplosionParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ExplosionParticle")); ExplosionAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("ExplosionAudio")); // 将音频组件设置为ExplosionParticle效果 ExplosionAudio->SetupAttachment(ExplosionParticle); static ConstructorHelpers::FObjectFinder<UParticleSystem>ParticleAsset(TEXT("ParticleSystem'/Game/StarterContent/Particles/P_Explosion.P_Explosion'")); ExplosionParticle->SetTemplate(ParticleAsset.Object); static ConstructorHelpers::FObjectFinder<USoundBase>AudioAsset(TEXT("SoundCue'/Game/StarterContent/Audio/Explosion_Cue.Explosion_Cue'")); ExplosionAudio->SetSound(AudioAsset.Object); } -
接下来,找到
ExplosionEfect::BeginPlay方法,并实现以下代码:void AExplosionEffect::BeginPlay() { Super::BeginPlay(); SetLifeSpan(1.5f); } -
编译你的代码。
已完成代码
ExplosionEffect.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ExplosionEffect.generated.h"
UCLASS()
class TIMERS_API AExplosionEffect : public AActor
{
GENERATED_BODY()
public:
// 为此Actor的属性设置默认值
AExplosionEffect();
protected:
// 当游戏开始或重生时被调用
virtual void BeginPlay() override;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
class UParticleSystemComponent* ExplosionParticle;
UPROPERTY(EditDefaultsOnly,BlueprintReadOnly)
class UAudioComponent* ExplosionAudio;
public:
// 每一帧都调用
virtual void Tick(float DeltaTime) override;
};
ExplosionEffect.cpp
#include "ExplosionEffect.h"
#include "Components/AudioComponent.h"
#include "Particles/ParticleSystemComponent.h"
// 设置默认值
AExplosionEffect::AExplosionEffect()
{
// 将此Actor设置为每帧调用更新函数()。 如果不需要此特性,可以关闭以提升性能。
PrimaryActorTick.bCanEverTick = true;
ExplosionParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("FireParticle"));
ExplosionAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("FireAudio"));
ExplosionAudio->SetupAttachment(ExplosionParticle);
static ConstructorHelpers::FObjectFinder<UParticleSystem>ParticleAsset(TEXT("ParticleSystem'/Game/StarterContent/Particles/P_Explosion.P_Explosion'"));
ExplosionParticle->SetTemplate(ParticleAsset.Object);
static ConstructorHelpers::FObjectFinder<USoundBase>AudioAsset(TEXT("SoundCue'/Game/StarterContent/Audio/Explosion_Cue.Explosion_Cue'"));
ExplosionAudio->SetSound(AudioAsset.Object);
}
// 当游戏开始或重生时被调用
void AExplosionEffect::BeginPlay()
{
Super::BeginPlay();
SetLifeSpan(1.5);
}
// 每一帧都调用
void AExplosionEffect::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
实现玩家角色类
第三人称模板包括了玩家角色类。该类设计为在按下其输入键时使用移动逻辑。将角色移动到火焰效果Actor的重叠边界上时,这很有用。你还将负责使用以下类成员创建额外的功能。
| 类成员 | 说明 |
|---|---|
| float Health | 表示玩家生命值的浮点变量。 |
| FTimerHandle | 带有用于区分委托的唯一句柄的结构体。计时器句柄将管理要在指定时间段内调用的TakeFireDamage函数。 |
| void TakeFireDamage() | 该函数将按指定伤害量消耗玩家的生命值,如果玩家的生命值降至0,则会生成爆炸Actor,并销毁玩家角色 |
| void IgniteFireTimer() | 该函数会在玩家角色身上生成火焰效果Actor,并将计时器句柄设置为调用TakeFireDamage函数。 |
| void ClearFireTimer() | 该函数将清除计时器句柄,防止它调用TakeFireDamage函数。 |
| FTimerDelegate | 一种委托模板,能以通用、类型安全的方式对C++ Object调用成员函数。 |
-
首先找到C++类文件夹,然后双击TimerCharacter类打开其
TimerCharacter.h文件。 -
在类定义中,声明以下内容:
protected: FTimerHandle FireTimer; FTimerDelegate FireDelegate; UPROPERTY(BlueprintReadOnly) float Health; // 消耗玩家生命值 UFUNCTION() void TakeFireDamage(); public: // 调用Firetimer并生成火焰效果Actor void IgniteFireTimer(); // 清除FireTimer句柄 void ClearFireTimer(); -
找到
TimerCharacter.cpp文件并添加以下类库:#include "Kismet/GameplayStatics.h" #include "FireEffect.h" #include "ExplosionEffect.h" -
在
TimerCharacter::FireActor类的构造函数中,声明以下代码:// 设置默认值 AFireActor::AFireActor() { // 实例化Actor组件。 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent")); OverlapVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("OverlapVolume")); FireParticleComp = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("FireParticleComp")); // 将Actor组件附加到根组件。 OverlapVolume->SetupAttachment(RootComponent); FireParticleComp->SetupAttachment(OverlapVolume); // 设置重叠体积的大小。 OverlapVolume->SetBoxExtent(FVector(256, 256, 128)); } -
声明以下代码,以实现
ATimersCharacter::TakeFireDamage函数:void ATimersCharacter::TakeFireDamage() { if (Health <= 0) { // Actor的生命值已降至0或更低。Spawn Explosion Effect GetWorld()->SpawnActor<AExplosionEffect>(GetActorLocation(), GetActorRotation()); // 销毁Actor Destroy(); } else { // Actor着火,执行火伤。 Health = (Health - 20.0f); } }如果计时器附加到的Object被销毁,这些计时器会自动取消。在本例中,玩家角色被销毁时,计时器句柄将变为无效,并且不会调用该函数。
-
为
ATimersCharacter::IgniteFireTimer函数实现以下代码逻辑:void ATimersCharacter::IgniteFireTimer() { GetWorldTimerManager().SetTimer(FireTimer, FireDelegate, 0.75f, true); AActor* TempFireEffect = GetWorld()->SpawnActor<AFireEffect>(GetActorLocation(), GetActorRotation()); TempFireEffect->AttachToActor(this, FAttachmentTransformRules::SnapToTargetIncludingScale); }FTimerManager类的SetTimer函数将设置一个计时器,以在延迟之后调用函数或委托,并且可以设置为无限重复该函数调用。这些函数将创建 计时器句柄(Timer Handle)(FTimerHandle),可用于暂停和恢复倒计时,查询或更改剩余时间,以及取消计时器。在计时器调用的函数中设置计时器是安全的,包括复用用于调用函数的计时器句柄。 此外,tTimers可以在下一个帧上运行,而不是在规定的时间间隔内运行。这通过调用SetTimerForNextTick来完成,但此函数不会填充计时器句柄。
7.最后,为 ATimersCharacter::ClearFireTimer 函数实现以下定义:
void ATimersCharacter::ClearFireTimer()
{
GetWorldTimerManager().ClearTimer(FireTimer);
}
但是,火焰计时器句柄现在无效,你可以复用该句柄来管理新的计时器。若使用现有计时器句柄调用SetTimer,将清除该计时器句柄所引用的计时器,并将其替换为新的计时器。
示例:
void AMyActor::BeginPlay()
{
Super::BeginPlay();
// 每秒调用一次RepeatingFunction,从现在起两秒后开始。
GetWorldTimerManager().SetTimer(MemberTimerHandle, this, &AMyActor::RepeatingFunction, 1.0f, true, 2.0f);
}
void AMyActor::RepeatingFunction()
{
// 调用此函数足够次数后,清除计时器。
if (--RepeatingCallsRemaining <= 0)
{
GetWorldTimerManager().ClearTimer(MemberTimerHandle);
// MemberTimerHandle现在可以复用于其他计时器。
}
// 在这里执行某个操作...
}
已完成代码
TimersCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "TimersCharacter.generated.h"
UCLASS(config=Game)
class ATimersCharacter : public ACharacter
{
GENERATED_BODY()
/** 用于将摄像机放置在角色身后的摄像机升降臂 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class USpringArmComponent* CameraBoom;
/** 跟随摄像机 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class UCameraComponent* FollowCamera;
public:
ATimersCharacter();
/** 基础旋转速度,以度/秒为单位。其他单位可能会影响最终旋转速度。*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
float BaseTurnRate;
/** 基础向上看/向下看速度,以度/秒为单位。其他单位可能会影响最终速度。*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
float BaseLookUpRate;
protected:
virtual void BeginPlay() override;
/** 重置VR中的HMD方向。*/
void OnResetVR();
/** 为向前/向后输入而调用*/
void MoveForward(float Value);
/** 为侧面到侧面输入而调用 */
void MoveRight(float Value);
/*** 通过输入进行调用,从而以给定的速度旋转。
*@param 速度 这是规范化速度,即1.0表示100%的所需旋转速度
*/
void TurnAtRate(float Rate);
/**
* 通过输入进行调用,从而以给定的速度向上看/向下看
* @param 速度 这是规范化速度,即1.0表示100%的所需旋转速度
*/
void LookUpAtRate(float Rate);
/** 在触摸输入开始时使用的处理程序。*/
void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);
/** 在触摸输入停止时使用的处理程序。*/
void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);
FTimerHandle FireTimer;
FTimerDelegate FireDelegate;
UPROPERTY(BlueprintReadOnly)
float Health;
UFUNCTION()
void TakeFireDamage();
protected:
// APawn接口
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// APawn末端接口
public:
/** 返回CameraBoom子对象 **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** 返回FollowCamera子对象 **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
// 设置FireTimer以调用TakeFireDamage函数
void IgniteFireTimer();
// 清除所调用的FireTimer。
void ClearFireTimer();
};
TimersCharacter.cpp
#include "TimersCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "Kismet/GameplayStatics.h"
#include "FireEffect.h"
#include "ExplosionEffect.h"
// ATimersCharacter
ATimersCharacter::ATimersCharacter()
{
// 设置碰撞胶囊体的大小
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
// 为输入设置我们的旋转速度
BaseTurnRate = 45.f;
BaseLookUpRate = 45.f;
// 当控制器旋转时不旋转使其仅影响摄像机。
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// 配置角色移动
GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...
GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...以此旋转速度
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.2f;
// 创建摄像机升降臂(发生碰撞则朝着玩家推进)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 300.0f; // 摄像机按此距离跟随角色
CameraBoom->bUsePawnControlRotation = true; // 基于控制器旋转升降臂
// 创建一个跟随摄像机
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // 将摄像机连接到升降臂末端,对升降臂进行调整以便与控制器方向保持一致
FollowCamera->bUsePawnControlRotation = false; // 摄像机不相对于升降臂旋转
Health = 100.0f;
// 注意:网格体组件上的骨骼网格体和动画蓝图引用(从角色继承)
// 是在名为MyCharacter的推导蓝图资产中设置的(以避免在C++中直接引用内容)
}
void ATimersCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
// 设置Gameplay键绑定
check(PlayerInputComponent);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
PlayerInputComponent->BindAxis("MoveForward", this, &ATimersCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &ATimersCharacter::MoveRight);
// 我们提供了两个版本的旋转绑定来处理不同类型的设备,
// "旋转"负责处理可以提供绝对增量的设备,例如鼠标。
// "旋转速度"适用于我们选择作为更改速度来处理的设备,例如模拟摇杆
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("TurnRate", this, &ATimersCharacter::TurnAtRate);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAxis("LookUpRate", this, &ATimersCharacter::LookUpAtRate);
// 处理触摸设备
PlayerInputComponent->BindTouch(IE_Pressed, this, &ATimersCharacter::TouchStarted);
PlayerInputComponent->BindTouch(IE_Released, this, &ATimersCharacter::TouchStopped);
// VR头戴设备功能
PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &ATimersCharacter::OnResetVR);
}
void ATimersCharacter::BeginPlay()
{
Super::BeginPlay();
FireDelegate.BindUFunction(this, FName("TakeFireDamage"));
}
void ATimersCharacter::OnResetVR()
{
UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}
void ATimersCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
{
Jump();
}
void ATimersCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
{
StopJumping();
}
void ATimersCharacter::TakeFireDamage()
{
if (Health <= 0)
{
GetWorld()->SpawnActor<AExplosionEffect>(GetActorLocation(), GetActorRotation());
Destroy();
}
else
{
Health = (Health - 20.0f);
}
}
void ATimersCharacter::IgniteFireTimer()
{
GetWorldTimerManager().SetTimer(FireTimer, FireDelegate, 0.75f, true);
AActor* TempFireEffect = GetWorld()->SpawnActor<AFireEffect>(GetActorLocation(), GetActorRotation());
TempFireEffect->AttachToActor(this, FAttachmentTransformRules::SnapToTargetIncludingScale);
}
void ATimersCharacter::ClearFireTimer()
{
GetWorldTimerManager().ClearTimer(FireTimer);
}
void ATimersCharacter::TurnAtRate(float Rate)
{
// 根据速度信息计算此帧的增量
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}
void ATimersCharacter::LookUpAtRate(float Rate)
{
// 根据速度信息计算此帧的增量
AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}
void ATimersCharacter::MoveForward(float Value)
{
if ((Controller != nullptr) && (Value != 0.0f))
{
// 找出向前方向
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 获取向前向量
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}
void ATimersCharacter::MoveRight(float Value)
{
if( (Controller != nullptr) && (Value != 0.0f))
{
// 找出向右方向
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 获取向右向量
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// 添加该方向动作
AddMovementInput(Direction, Value);
}
}
创建火焰效果Actor
火焰效果Actor用作存在于关卡中的物理位置的粒子效果。你会将该功能用于玩家角色与火焰效果Actor重叠的情况,这会导致角色着火并在一段时间后受到伤害。
| 类成员 | 说明 |
|---|---|
| UBoxComponent | 用作火焰效果Actor的碰撞体积的盒体组件。另一个Actor与其边界重叠时,将调用NotifyActorBeginOverlap函数。 |
| UParticleSystemComponent | 提供视觉火焰效果的粒子发射器。 |
| void NotifyActorBeginOverlap | 此Actor与另一个Actor重叠时触发的事件。例如,玩家走进用于火焰效果Actor的重叠边界的盒体组件时。 |
| void NotifyEndActorOverlap | Actor不再与另一个Actor重叠时触发的事件。例如,玩家离开用于火焰效果Actor的重叠边界的盒体组件时。 |
-
找到 C++类(C++ Classes) 文件夹,然后右键点击 内容浏览器(Content Browser) 以创建名为 FireActor 的 新C++Actor类(New C++ Actor class)。
-
在AFireActor的类定义中,声明以下内容:
UPROPERTY(EditDefaultsOnly,BlueprintReadOnly) class UBoxComponent* OverlapVolume; UPROPERTY(EditDefaultsOnly,BlueprintReadOnly) class UParticleSystemComponent* FireParticleComp; // Actor重叠时调用 virtual void NotifyActorBeginOverlap(AActor* OtherActor); virtual void NotifyActorEndOverlap(AActor* OtherActor); -
接下来,找到FireActor.cpp文件并包括以下类库:
#include "Particles/ParticleSystemComponent.h" #include "Components/BoxComponent.h" #include "TimersCharacter.h" -
在火焰Actor的构造函数中,初始化以下类默认值:
AFireActor::AFireActor() { // 将此Actor设置为每帧调用更新函数()。 如果不需要此特性,可以关闭以提升性能。 PrimaryActorTick.bCanEverTick = true; // 实例化Actor组件。 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent")); OverlapVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("OverlapVolume")); FireParticleComp = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("FireParticleComp")); // 将Actor组件附加到根组件。 OverlapVolume->SetupAttachment(RootComponent); FireParticleComp->SetupAttachment(OverlapVolume); //将FireParticleComp效果设置到初学者内容包文件夹中的资产 static ConstructorHelpers::FObjectFinder<UParticleSystem>ParticleAsset(TEXT("ParticleSystem'/Game/StarterContent/Particles/P_Fire.P_Fire'")); FireParticleComp->SetTemplate(ParticleAsset.Object); } -
玩家角色进入火焰效果Actor的体积时,计时器句柄应该调用TakeFireDamage函数。声明以下代码以实现
FireActor::NotifyActorBeginOverlap函数:void AFireActor::NotifyActorBeginOverlap(AActor* OtherActor) { ATimersCharacter* TimersCharacterRef = Cast<ATimersCharacter>(OtherActor); if (TimersCharacterRef) { TimersCharacterRef->IgniteFireTimer(); } } -
如果玩家角色在生命值降至0之前离开火焰效果Actor的体积,则计时器句柄将自行清除,并停止调用TakeFireDamage函数。为FireActor::NotifyActorEndOverlap函数实现类似的代码定义:
void AFireActor::NotifyActorEndOverlap(AActor* OtherActor) { ATimersCharacter* TimersCharacterRef = Cast<ATimersCharacter>(OtherActor); if (TimersCharacterRef) { TimersCharacterRef->ClearFireTimer(); } }
已完成代码
FireActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FireActor.generated.h"
UCLASS()
class TIMERS_API AFireActor : public AActor
{
GENERATED_BODY()
public:
// 为此Actor的属性设置默认值
AFireActor();
protected:
// 当游戏开始或重生时被调用
virtual void BeginPlay() override;
UPROPERTY(EditDefaultsOnly,BlueprintReadOnly)
class UBoxComponent* OverlapVolume;
UPROPERTY(EditDefaultsOnly,BlueprintReadOnly)
class UParticleSystemComponent* FireParticleComp;
virtual void NotifyActorBeginOverlap(AActor* OtherActor);
virtual void NotifyActorEndOverlap(AActor* OtherActor);
public:
// 每一帧都调用
virtual void Tick(float DeltaTime) override;
};
FireActor.Cpp
#include "FireActor.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/BoxComponent.h"
#include "TimersCharacter.h"
// 设置默认值
AFireActor::AFireActor()
{
// 将此Actor设置为每帧调用更新函数()。 如果不需要此特性,可以关闭以提升性能。
PrimaryActorTick.bCanEverTick = true;
// 实例化Actor组件。
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
OverlapVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("OverlapVolume"));
FireParticleComp = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("FireParticleComp"));
// 将Actor组件附加到根组件。
OverlapVolume->SetupAttachment(RootComponent);
FireParticleComp->SetupAttachment(OverlapVolume);
//将FireParticleComp效果设置到初学者内容包文件夹中的资产
static ConstructorHelpers::FObjectFinder<UParticleSystem>ParticleAsset(TEXT("ParticleSystem'/Game/StarterContent/Particles/P_Fire.P_Fire'"));
FireParticleComp->SetTemplate(ParticleAsset.Object);
}
// 当游戏开始或重生时被调用
void AFireActor::BeginPlay()
{
Super::BeginPlay();
}
void AFireActor::NotifyActorBeginOverlap(AActor* OtherActor)
{
ATimersCharacter* TimersCharacterRef = Cast<ATimersCharacter>(OtherActor);
if (TimersCharacterRef)
{
TimersCharacterRef->IgniteFireTimer();
}
}
void AFireActor::NotifyActorEndOverlap(AActor* OtherActor)
{
ATimersCharacter* TimersCharacterRef = Cast<ATimersCharacter>(OtherActor);
if (TimersCharacterRef)
{
TimersCharacterRef->ClearFireTimer();
}
}
// 每一帧都调用
void AFireActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
最终结果
-
从内容浏览器,找到 C++类(C++ Classes) 文件夹,右键点击FireActor类,然后选择 基于FireActor创建蓝图类(Create a Blueprint Class based on FireActor)。
-
将Bp_FireActor的实例拖移到关卡中。
-
找到编辑器工具栏,然后点击"运行(PIE)(Play (PIE))"以在编辑器中预览你的项目。
使用方向键或WASD键,将你的角色移入火焰效果Actor的体积中。如果你在体积中逗留太久,角色的生命值最终会耗尽为0,此时角色将爆炸。
与计时器相关的额外说明
计时器由全局 计时器管理器(Timer Manager)(类型为FTimerManager)管理。全局计时器管理器存在于 游戏实例(Game Instance) Object 中以及每个 世界(World) 中。 两个主函数用于通过计时器管理器设置计时器:SetTimer和SetTimerForNextTick,两者都有几个重载。每一个都可以附加到所有类型的Object或函数委托,并且SetTimer可以设置为定期重复。有关更多类方法,请参阅计时器管理器API页面文档 计时器可以用于标准C++函数指针TFunctionObject或委托。
暂停和恢复计时器
FTimerManager函数PauseTimer使用计时器句柄来暂停运行中的计时器。这可防止计时器执行其函数调用,但耗时和剩余时间会在暂停时停止递增。UnPauseTimer会导致已暂停的计时器恢复运行。
计时器信息
计时器管理器还提供了函数来获取特定计时器的信息,例如时间速度、耗时和剩余时间。
IsTimerActive
FTimerManager类的IsTimerActive函数用于确定指定计时器当前是否已激活并且未暂停。
示例:
// 该武器是否在等待再次能够开火?
GetWorldTimerManager().IsTimerActive(**this**, &AUTWeapon::RefireCheckTimer);
GetTimerRate
FTimerManager有一个名为GetTimerRate的函数,用于从计时器句柄返回计时器的当前时间速度(相邻两次激活之间的时间)。计时器的速度无法直接更改,但是SetTimer可以通过其计时器句柄进行调用,以将其清除并使用不同的速度创建复制的计时器。如果计时器句柄无效,GetTimerRate将返回值-1。
示例:
// 该武器的开火速度会随着预热而更改。它当前是否在等待开火,如果是,相邻两次射击之间的当前延迟是多久?
GetWorldTimerManager().GetTimerRate(**this**, &AUTWeapon::RefireCheckTimer);
GetTimerElapsed和GetTimerRemaining
FTimermanager通过GetTimerElapsed和GetTimerRemaining提供功能,它们分别返回与所提供计时器句柄关联的计时器的耗时和剩余时间。与GetTimerRate一样,这些函数会在计时器句柄无效时返回-1。
示例:
// 该武器多久后可以再次开火?如果收到的回答是-1,则现已就绪。
GetWorldTimerManager().GetTimerElapsed(**this**, &AUTWeapon::RefireCheckTimer);
计时器的耗时和剩余时间之和应该等于该计时器的速度。