Zoomi的虚幻世界

我的邮箱:LFF3216689784@outlook.com

0%

UE5 调试 API-常用

UE5 调试 API-常用

基础调试输出

日志输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 基础日志
UE_LOG(LogTemp, Log, TEXT("This is a log message"));
UE_LOG(LogTemp, Warning, TEXT("This is a warning"));
UE_LOG(LogTemp, Error, TEXT("This is an error"));

// 带变量的输出
float Health = 75.0f;
UE_LOG(LogTemp, Log, TEXT("Player health: %f"), Health);

// 带分类的日志(需先在头文件声明)
DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, All);
DEFINE_LOG_CATEGORY(LogMyGame);
UE_LOG(LogMyGame, Log, TEXT("Custom category log"));
// 屏幕输出(默认显示2秒)
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::White, TEXT("Hello World!"));

// 带Key的屏幕输出(可更新)
static int32 MyDebugKey = 0;
GEngine->AddOnScreenDebugMessage(MyDebugKey, 5.0f, FColor::Green,
FString::Printf(TEXT("Health: %.2f"), Health));





// 绘制调试球体
DrawDebugSphere(GetWorld(), Location, Radius, Segments, Color, bPersistentLines, LifeTime, DepthPriority, Thickness);

// 绘制调试盒子
DrawDebugBox(GetWorld(), Center, Extent, Color, bPersistentLines, LifeTime, DepthPriority, Thickness);

// 绘制调试线条
DrawDebugLine(GetWorld(), Start, End, Color, bPersistentLines, LifeTime, DepthPriority, Thickness);

// 绘制调试点
DrawDebugPoint(GetWorld(), Location, Size, Color, bPersistentLines, LifeTime, DepthPriority);

Unreal Engine 5 常用宏速查表

1. 类声明宏

用途 示例
UCLASS() 声明一个 UE 反射类 UCLASS(Blueprintable, meta=(DisplayName="My Actor"))
UINTERFACE() 声明一个 UE 反射接口 UINTERFACE(MinimalAPI, Blueprintable)
UGENERATED_BODY() 自动生成类反射代码(必须放在类体内) GENERATED_BODY()

2. 属性宏 (UPROPERTY)

用途 示例
BlueprintReadOnly 蓝图只读 UPROPERTY(BlueprintReadOnly)
BlueprintReadWrite 蓝图可读写 UPROPERTY(BlueprintReadWrite)
EditAnywhere 在编辑器任意位置可编辑 UPROPERTY(EditAnywhere)
EditDefaultsOnly 仅可在默认值(CDO)编辑 UPROPERTY(EditDefaultsOnly)
VisibleAnywhere 在编辑器可见但不可编辑 UPROPERTY(VisibleAnywhere)
Category="CategoryName" 在编辑器分类 UPROPERTY(Category="Movement")
meta=(DisplayName="Nice Name") 显示更友好的名称 UPROPERTY(meta=(DisplayName="Health"))
Replicated 网络同步(需在 GetLifetimeReplicatedProps 处理) UPROPERTY(Replicated)
Transient 不保存到磁盘(临时变量) UPROPERTY(Transient)
SaveGame 可序列化到存档 UPROPERTY(SaveGame)

3. 函数宏 (UFUNCTION)

用途 示例
BlueprintCallable 可在蓝图中调用 UFUNCTION(BlueprintCallable)
BlueprintPure 纯函数(无副作用) UFUNCTION(BlueprintPure)
BlueprintImplementableEvent 蓝图可覆盖的虚函数(无 C++ 实现) UFUNCTION(BlueprintImplementableEvent)
BlueprintNativeEvent 蓝图可覆盖的虚函数(有默认 C++ 实现) UFUNCTION(BlueprintNativeEvent)
Server 仅在服务器执行(RPC) UFUNCTION(Server, Reliable)
Client 仅在客户端执行(RPC) UFUNCTION(Client, Reliable)
NetMulticast 多播(所有客户端执行) UFUNCTION(NetMulticast, Reliable)
WithValidation RPC 参数验证 UFUNCTION(Server, WithValidation)

4. 结构体宏 (USTRUCT)

用途 示例
USTRUCT() 声明反射结构体 USTRUCT(BlueprintType)
GENERATED_BODY() 生成结构体反射代码 GENERATED_BODY()
BlueprintType 可在蓝图中使用 USTRUCT(BlueprintType)
meta=(BlueprintInternalUseOnly) 仅限内部使用 USTRUCT(meta=(BlueprintInternalUseOnly))

5. 枚举宏 (UENUM)

用途 示例
UENUM() 声明反射枚举 UENUM(BlueprintType)
BlueprintType 可在蓝图中使用 UENUM(BlueprintType)
meta=(DisplayName="Nice Name") 显示友好名称 UENUM(meta=(DisplayName="Weapon Type"))

6. 其他常用宏

用途 示例
DECLARE_DYNAMIC_MULTICAST_DELEGATE 声明动态多播委托 DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyDelegate);
DECLARE_DELEGATE 声明单播委托 DECLARE_DELEGATE(FMySimpleDelegate);
UE_LOG 打印日志 UE_LOG(LogTemp, Warning, TEXT("Hello"));
ensure() 运行时检查(不崩溃) ensure(MyPointer != nullptr);
check() 运行时断言(崩溃) check(MyPointer != nullptr);
GEngine->AddOnScreenDebugMessage 屏幕打印调试信息 GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Hello"));

UCapsuleComponent 胶囊体 常用 API 速查表

1. 基础属性设置

API 说明 示例
SetCapsuleSize(float Radius, float HalfHeight, bool bUpdateOverlaps) 设置胶囊体的半径和半高 CapsuleComp->SetCapsuleSize(50.f, 100.f);
GetScaledCapsuleRadius() 获取当前缩放后的半径 float Radius = CapsuleComp->GetScaledCapsuleRadius();
GetScaledCapsuleHalfHeight() 获取当前缩放后的半高 float HalfHeight = CapsuleComp->GetScaledCapsuleHalfHeight();
GetCapsuleAxis() 获取胶囊体的朝向轴(默认 Z 轴) FVector Axis = CapsuleComp->GetCapsuleAxis();

2. 碰撞与物理

API 说明 示例
SetCollisionEnabled(ECollisionEnabled::Type NewType) 启用/禁用碰撞 CapsuleComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
SetCollisionResponseToChannel(ECollisionChannel Channel, ECollisionResponse Response) 设置对特定通道的碰撞响应 CapsuleComp->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);
SetGenerateOverlapEvents(bool bGenerate) 是否生成重叠事件 CapsuleComp->SetGenerateOverlapEvents(true);
OnComponentBeginOverlap.AddDynamic() 绑定重叠开始事件 CapsuleComp->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::OnCapsuleBeginOverlap);
OnComponentEndOverlap.AddDynamic() 绑定重叠结束事件 CapsuleComp->OnComponentEndOverlap.AddDynamic(this, &AMyActor::OnCapsuleEndOverlap);

3. 位置与变换

API 说明 示例
GetComponentLocation() 获取胶囊体中心位置 FVector Location = CapsuleComp->GetComponentLocation();
GetComponentRotation() 获取胶囊体旋转 FRotator Rotation = CapsuleComp->GetComponentRotation();
SetWorldLocation(FVector NewLocation) 设置世界坐标位置 CapsuleComp->SetWorldLocation(FVector(0, 0, 100));
AddLocalOffset(FVector DeltaLocation) 局部坐标偏移 CapsuleComp->AddLocalOffset(FVector(10, 0, 0));

4. 调试与可视化

API 说明 示例
SetHiddenInGame(bool bHidden) 隐藏/显示胶囊体 CapsuleComp->SetHiddenInGame(false);
ShapeColor 设置调试颜色(编辑器可见) CapsuleComp->ShapeColor = FColor::Green;
bDrawOnlyIfSelected 仅在选中时显示 CapsuleComp->bDrawOnlyIfSelected = true;
MarkRenderStateDirty() 强制刷新渲染状态 CapsuleComp->MarkRenderStateDirty();

5. 射线检测与几何查询

API 说明 示例
IsOverlappingComponent(UPrimitiveComponent* OtherComp) 检查是否与其他组件重叠 bool bOverlapping = CapsuleComp->IsOverlappingComponent(OtherActor->GetMesh());
SweepComponent(FHitResult& OutHit, FVector Start, FVector End, FRotator Rotation) 胶囊体扫描检测 CapsuleComp->SweepComponent(OutHit, Start, End, FQuat::Identity);
GetOverlappingActors(TArray<AActor*>& OutActors) 获取所有重叠的 Actor CapsuleComp->GetOverlappingActors(OverlappingActors);

零散重要api

**AutoPossessPlayer =EAutoReceiveInput::player0; 设置玩家控制器

UE5.5 增强输入系统使用指南 (C++ 实现)

以下是在 Unreal Engine 5.5 中使用增强输入系统(Enhanced Input System)的完整 C++ 实现方式
关键数据结构:输入映射上下文 输入映射

![[Pasted image 20250414102551.png]]

输入映射中的触发器和修改器

虚幻中的轴映射以X轴的右方为正方向!

一旦开启==拌合输入轴值==,则意味着可以交换坐标轴的位置
YXZ意味着交换X与Y轴的位置,交换后相当于在使用Y轴的正方向 比如W键前进

![[Pasted image 20250414102006.png]]
因为默认是X的右方是正方向,所以只需要否定X轴就是反方向

增强输入组件

​ 什么是增强输入组件?​

增强输入组件是虚幻引擎中用于处理玩家输入的组件,属于 ​​增强输入系统​​ 的一部分。它取代了传统的输入系统,提供了更强大的功能,如输入动作(Input Actions)、输入映射上下文(Input Mapping Contexts)、修饰器(Modifiers)等,使开发者能够更灵活地定义和管理输入逻辑。

. 增强输入组件的核心概念​

​输入动作(Input Actions)​

  • ​定义​​:输入动作代表具体的操作,如“跳跃”、“攻击”、“移动”等。
  • ​用途​​:用于绑定具体的游戏逻辑,便于在不同场景中复用和调整。

​ 输入映射上下文(Input Mapping Contexts)​**​

  • ​定义​​:输入映射上下文将物理输入(如键盘按键、鼠标按钮、手柄按键)映射到输入动作。
  • ​用途​​:允许为不同的玩家配置、设备或场景设置不同的输入映射,增强项目的适应性。

​ 修饰器(Modifiers)​

  • ​定义​​:修饰器用于修改输入行为,如按住时间、双击检测、输入反转等。
  • ​用途​​:增强输入的灵活性,实现复杂的输入逻辑。

​ 输入子系统(Input Subsystem)​

  • ​定义​​:管理所有输入组件和映射上下文,负责处理输入事件的分发和处理。
  • ​用途​​:确保输入系统的高效运行和统一管理。

​ 增强输入组件的优势​

  • ​模块化设计​​:通过输入动作和映射上下文分离输入逻辑与具体实现,便于管理和维护。
  • ​灵活性高​​:支持多种输入设备(键盘、鼠标、手柄等),并允许动态切换和组合输入方式。
  • ​丰富的功能​​:支持输入修饰器(如组合键、按住时间等)、轴映射(Axis Mappings)和动作映射(Action Mappings),满足复杂输入需求。
  • ​易于扩展​​:便于集成自定义逻辑,适应不同项目和平台的需求。

1. 基本设置

1.1 启用增强输入模块

1
2
3
4
5
6
7
8
首先在项目的 `Build.cs` 文件中添加依赖:
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput"
});

1.2 创建输入动作(Input Actions)

在内容浏览器中右键创建 Input Actions 资源:

  • IA_Jump

  • IA_Move

  • IA_Look

  • IA_Fire

2. C++ 利用增强输入系统实现输入映射

2.1 玩家控制器头文件 (MyPlayerController.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "InputActionValue.h"
#include "MyPlayerController.generated.h"

class UInputMappingContext;
class UInputAction;

UCLASS()
class MYPROJECT_API AMyPlayerController : public APlayerController
{
GENERATED_BODY()

protected:
virtual void BeginPlay() override;
virtual void SetupInputComponent() override;

// 输入映射上下文
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputMappingContext* DefaultMappingContext;

// 输入动作
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputAction* JumpAction;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputAction* MoveAction;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputAction* LookAction;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputAction* FireAction;

private:
// 输入回调函数
void OnJump(const FInputActionValue& Value);
void OnMove(const FInputActionValue& Value);
void OnLook(const FInputActionValue& Value);
void OnFire(const FInputActionValue& Value);
};

2.2 玩家控制器实现文件 (MyPlayerController.cpp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include "MyPlayerController.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"

void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();

// 获取本地玩家增强输入子系统并添加映射上下文
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}

void AMyPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();

// 设置增强输入组件
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent))
{
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &AMyPlayerController::OnJump);
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyPlayerController::OnMove);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyPlayerController::OnLook);
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &AMyPlayerController::OnFire);
}
}

void AMyPlayerController::OnJump(const FInputActionValue& Value)
{
if (ACharacter* Character = GetCharacter())
{
Character->Jump();
}
}

void AMyPlayerController::OnMove(const FInputActionValue& Value)
{
const FVector2D MovementVector = Value.Get<FVector2D>();

if (ACharacter* Character = GetCharacter())
{
const FRotator Rotation = GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);

const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

Character->AddMovementInput(ForwardDirection, MovementVector.Y);
Character->AddMovementInput(RightDirection, MovementVector.X);
}
}

void AMyPlayerController::OnLook(const FInputActionValue& Value)
{
const FVector2D LookAxisVector = Value.Get<FVector2D>();

if (IsLookInputIgnored())
{
return;
}

AddYawInput(LookAxisVector.X);
AddPitchInput(LookAxisVector.Y);
}

void AMyPlayerController::OnFire(const FInputActionValue& Value)
{
// 实现射击逻辑
UE_LOG(LogTemp, Warning, TEXT("Fire!"));
}

如何获取控制器的旋转角度并转换成向量-常用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void ABird::Move(float Value)
{
UE_LOG(LogTemp, Warning, TEXT("MoveForward"));
if (Value != 0.0f)
{
//根据控制器旋转获取移动方向
FRotator Rotation = GetControlRotation();
FRotator YawRotation(0, Rotation.Yaw, 0);//获取旋转的Yaw角度
FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);//通过旋转矩阵获取方向向量
AddMovementInput(Direction, Value);





}
}、

虚幻添加模块的办法-常用

1
2
3
4
5
6
7
8
PublicDependencyModuleNames.AddRange(
new string[] {
"Core",
"CoreUObject",
"Engine",
"GroomComponent" // 添加毛发组件模块
}
);

虚幻枚举

![[Pasted image 20250412151930.png]]
在虚幻引擎(Unreal Engine,简称UE)中,​​枚举(Enum)​​是一种非常有用的数据类型,用于定义一组命名的常量。使用枚举可以提高代码的可读性和可维护性,避免使用魔法数字(magic numbers),并简化逻辑判断。以下是关于虚幻引擎中枚举的详细介绍,包括其定义、使用方法、类型以及在蓝图和C++中的具体应用。


​1. 枚举的基本概念​

​枚举​​(Enumeration)是一种用户定义的数据类型,它包含了一组命名的整数常量。通过枚举,可以为这些常量赋予有意义的名字,使代码更加直观和易于理解。
​示例:​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
enum class EWeekDays : uint8
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
`
在上述示例中,`EWeekDays` 是一个枚举类型,包含了一周的七天。每个枚举值(如 `Monday`、`Tuesday` 等)都对应一个整数值,默认情况下从0开始递增。
---
虚幻引擎支持两种主要的枚举类型:

在C++中定义枚举时,推荐使用 `enum class`,因为它提供了更好的类型安全和作用域控制。
​**​定义枚举:​**​
```cpp
UENUM(BlueprintType)
enum class ECharacterClass : uint8
{
GUARDIAN UMETA(DisplayName = "Guardian"),
WARRIOR UMETA(DisplayName = "Warrior"),
MAGE UMETA(DisplayName = "Mage"),
ROGUE UMETA(DisplayName = "Rogue")
};

​说明:​

  • UENUM(BlueprintType) 宏用于将枚举暴露给蓝图系统,使其在蓝图中可用。
  • UMETA(DisplayName = "Guardian") 提供了枚举编辑器中的显示名称,增强可读性。
  • 使用 enum class 而不是传统的 enum,以避免命名冲突和类型隐式转换的问题。

​2.2 蓝图枚举(Blueprint Enum)​

蓝图枚举是专门在蓝图中创建和使用的枚举类型,适用于不需要复杂C++逻辑的场景。
​创建蓝图枚举的步骤:​

  1. 在内容浏览器中,右键点击空白区域,选择 Blueprint Class
  2. 在弹出的窗口中,选择 Enumeration 作为父类。
  3. 命名并创建蓝图枚举。
  4. 双击打开蓝图枚举,添加和命名各个枚举值。

​优点:​
- 易于在蓝图中创建和使用,无需编写C++代码。
- 适用于快速原型设计和逻辑简单的项目。
​缺点:​
- 功能上不如C++枚举灵活,特别是在需要复杂逻辑或与其他C++系统集成时。

​3. 枚举的使用方法​

​3.1 在C++中使用枚举​

​定义枚举变量​

1
E CharacterClass = ECharacterClass::Warrior;

​在函数中使用枚举​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void AMyCharacter::SetCharacterClass(ECharacterClass NewClass)
{
CharacterClass = NewClass;

switch (CharacterClass)
{
case ECharacterClass::Guardian:
// 守护者逻辑
break;
case ECharacterClass::Warrior:
// 战士逻辑
break;
case ECharacterClass::Mage:
// 法师逻辑
break;
case ECharacterClass::Rogue:
// 潜行侠逻辑
break;
default:
break;
}
}

​枚举与蓝图交互​

通过 UENUM(BlueprintType) 宏,可以在蓝图中访问和修改C++中定义的枚举变量。

​示例:在蓝图中设置角色的职业​

  1. 在角色类中定义一个公开的枚举变量:

    1
    2
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Character")
    ECharacterClass CharacterClass;
  2. 在蓝图中,可以通过细节面板直接选择或通过节点修改 CharacterClass 的值。

​3.2 在蓝图中使用枚举​

​创建和使用蓝图枚举​

  1. ​创建蓝图枚举​​:
    • 按照前述步骤在内容浏览器中创建一个蓝图枚举,例如 EBP_WeaponType,包含 PistolRifleShotgun 等枚举值。
  2. ​在蓝图中使用枚举变量​​:
    • 在蓝图中添加一个枚举类型的变量,选择刚刚创建的 EBP_WeaponType
    • 使用 ​​Switch on Enum​​ 节点根据枚举值执行不同的逻辑。
      ​示例:根据武器类型播放不同的声音​
  3. 添加一个 _WeaponType 类型的变量 CurrentWeapon
  4. 使用 ​​Switch on Enum​​ 节点,输入 CurrentWeapon
  5. 为每个枚举值(如 PistolRifleShotgun)添加对应的分支,并连接播放相应声音的节点。

​4. 枚举的高级用法​

​4.1 枚举与位掩码(Bitmask)​

有时需要将多个枚举值组合在一起使用,这时可以使用位掩码。虚幻引擎支持通过设置 Meta 参数来启用位掩码功能。
​定义位掩码枚举:​

1
2
3
4
5
6
7
8
9
UENUM(BlueprintType)
enum class EPermissions : uint8
{
None = 0 UMETA(DisplayName = "None"),
Read = 1 << 0 UMETA(DisplayName = "Read"),
Write = 1 << 1 UMETA(DisplayName = "Write"),
Execute = 1 << 2 UMETA(DisplayName = "Execute"),
All = Read | Write | Execute UMETA(DisplayName = "All")
};

​使用位掩码枚举:​

1
2
3
4
5
6
7
8
9
10
11
EPermissions UserPermissions = EPermissions::Read | EPermissions::Write;

if (UserPermissions & EPermissions::Read)
{
// 用户有读取权限
}

if ((UserPermissions & EPermissions::All) == EPermissions::All)
{
// 用户拥有所有权限
}

​说明:​

  • 使用位移运算符 << 定义每个权限的独立位。
  • 使用按位与 & 和按位或 | 运算符进行权限的组合和检查。

​4.2 枚举参数​

枚举常用于函数参数,以增强函数的灵活性和可读性。
​示例:​

1
2
UFUNCTION(BlueprintCallable, Category = "Combat")
void PerformAttack(EAttackType AttackType);

​调用方式:​
在蓝图中调用 PerformAttack 函数时,可以通过下拉菜单选择具体的攻击类型(如 MeleeRangedMagic),无需记住具体的数值。

​4.3 枚举与数据表(Data Tables)​

枚举可以与数据表结合使用,数据驱动的设计中管理大量相关数据。

​步骤:​

  1. 定义一个枚举,表示不同的数据类别。
  2. 创建一个结构体,包含该枚举作为成员变量。
  3. 在编辑器中创建并填充数据表,使用该结构体作为行类型。
  4. 在代码或蓝图中读取数据表,并根据枚举值处理相应的数据。

​示例:​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
USTRUCT(BlueprintType)
struct FWeaponData : public FTableRowBase
{
GENERATED_BODY()

UPROPERTY(EditAnywhere, BlueprintReadWrite)
EWeaponType WeaponType;

UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Damage;

UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Range;
};

​5. 常见问题与解决方案​

​5.1 枚举值在蓝图中不显示​

​原因:​

  • 枚举未正确暴露给蓝图,或者缺少必要的元数据。

​解决方法:​

  • 确保在C++中使用 UENUM(BlueprintType) 宏定义枚举。
  • 检查枚举值是否正确添加了 UMName),以增强在蓝图中的可读性。

​5.2 枚举值在不同平台上的兼容性​

​原因:​

  • 不同平台对枚举大小的默认处理可能不同,导致跨平台数据传输时出现问题。

​解决方法:​

  • 明确定义枚举的基础类型,如 uint8,以确保在所有平台上的一致性。

    1
    2
    3
    4
    5
    6
    7
    UENUM(BlueprintType)
    enum class EExampleEnum : uint8
    {
    Value1,
    Value2,
    // ...
    };

​5.3 枚举与序列化问题​

​原因:​

  • 在保存和加载过程中,枚举值可能因为版本变更或未正确处理而导致数据丢失或错误。
    ​解决方法:​
  • 确保在序列化相关的数据结构时,枚举值被正确处理。
  • 使用版本控制机制,以应对枚举值的变更。

​6. 最佳实践​

  1. ​使用 enum class 而非传统 enum​:
    • 提供更好的类型安全和作用域控制,避免命名冲突和隐式转换。
  2. ​为枚举值添加有意义的名称​​:
    • 使用描述性强的名称,增强代码的可读性和可维护性。
  3. ​暴露必要的枚举给蓝图​​:
    • 通过 UENUM(BlueprintType) 将需要在蓝图中使用的枚举暴露出来,并合理使用 UMETA 元数据增强其可用性。
  4. ​考虑枚举的可扩展性​​:
    • 在设计枚举时,预留一定的空间以备将来可能的扩展,避免频繁修改已有的枚举值。
  5. ​文档和注释​​:
    • 为枚举及其值添加详细的文档和注释,帮助团队成员理解其用途和含义。
  6. ​避免使用魔法数字​​:
    • 始终使用枚举替代硬编码的数字,提高代码的可读性和可维护性。

​7. 示例项目​

以下是一个简单的示例,展示如何在虚幻枚举来管理角色的状态。

​7.1 定义枚举​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// CharacterStateEnum.h
#pragma once

#include "CoreMinimal.h"
#include "CharacterStateEnum.generated.h"

UENUM(BlueprintType)
enum class ECharacterState : uint8
{
Idle UMETA(DisplayName = "Idle"),
Walking UMETA(DisplayName = "Walking"),
Running UMETA(DisplayName = "Running"),
Jumping UMETA(DisplayName = "Jumping"),
Attacking UMETA(DisplayName = "Attacking"),
Dead UMETA(DisplayName = "Dead")
};

​7.2 在角色类中使用枚举​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// MyCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "CharacterStateEnum.h"
#include "MyCharacter.generated.h"

UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()

public:
AMyCharacter();

protected:
virtual void BeginPlay() override;

public:
virtual void Tick(float DeltaTime) override;

// 角色当前状态
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "State")
ECharacterState CurrentState;

// 切换状态函数
UFUNCTION(BlueprintCallable, Category = "State")
void SetState(ECharacterState NewState);

// 状态处理函数
void HandleState();
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// MyCharacter.cpp
#include "MyCharacter.h"

AMyCharacter::AMyCharacter()
{
PrimaryActorTick.bCanEverTick = true;
CurrentState = ECharacterState::Idle;
}

void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
}

void AMyCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
HandleState();
}

void AMyCharacter::SetState(ECharacterState NewState)
{
CurrentState = NewState;
}

void AMyCharacter::HandleState()
{
switch (CurrentState)
{
case ECharacterState::Idle:
// 执行闲置逻辑
break;
case ECharacterState::Walking:
// 执行行走逻辑
break;
case ECharacterState::Running:
// 执行奔跑逻辑
break;
case ECharacterState::Jumping:
// 执行跳跃逻辑
break;
case ECharacterState::Attacking:
// 执行攻击逻辑
break;
case ECharacterState::Dead:
// 执行死亡逻辑
break;
default:
break;
}
}

​7.3 在蓝图中使用枚举​

  1. ​创建角色蓝图​​:

    • 基于 AMyCharacter 创建一个蓝图类 BP_MyCharacter
  2. ​设置状态变量​​:

    • 在蓝图的细节面板中,可以看到 CurrentState 变量,可以直接选择不同的状态。
  3. ​调用状态切换函数​​:

    • 使用事件图表中的节点调用 SetState 函数,动态改变角色的状态。
  4. ​扩展状态逻辑​​:

    • 在蓝图中,可以进一步扩展 HandleState 的逻辑,使用 ​​Switch on Enum​​ 节点根据 CurrentState 执行不同的行为。

硬引用和软引用- 性能相关

在虚幻引擎(Unreal Engine)中,软引用(Soft Reference)硬引用(Hard Reference)是资源加载和管理的核心概念,直接影响内存使用、加载性能和项目维护。以下是它们的详细对比和用法:


1. 硬引用(Hard Reference)

定义
硬引用表示资源在运行时强制加载到内存中。只要引用者存在,被引用的资源会一直驻留内存。
特点

  • 自动加载:资源随引用它的对象(如蓝图、关卡)一起加载。

  • 内存占用高:可能导致不必要的内存消耗,尤其是引用大型资源(如纹理、模型)。

  • 强依赖性:若被引用的资源丢失,会导致运行时错误(如红色错误提示)。
    常见场景

  • 直接拖放资源到蓝图或关卡中(例如:在蓝图中直接指定一个静态网格或材质)。

  • 使用 UPROPERTY 声明时未指定特殊修饰符:

    UPROPERTY()
    UTexture2D* HardTextureRef; // 硬引用

==硬引用== 这也是一个硬引用
![[Pasted image 20250412160904.png]]
==创建软引用==
软引用的好处是资产仅在需要时加载,这可以节省内存。我们对暂时不需要的不关键资产用软引用
但是不适合![[Pasted image 20250412161333.png]]

![[Pasted image 20250412160946.png]]

虚幻智能指针

TObjectPtr

支持访问跟踪和延迟加载
访问跟踪:知道对象何时被加载或者访问
延迟加载:只在需要时加载资源
缺点:主要适用于成员变量,仅仅在编辑器中有效,在发布版本中会被转换成原始指针,所以不会让游戏变得更快,仅仅提供一种编辑体验

动画蓝图

要获取父类的数据要使用强制转换,将子类对象转换为父类对象 这样就能在动画蓝图中获取到父类角色等蓝图的数据,比如角色移动组件,组件中的速度等数据。

tips:==使用C++指定动画蓝图父类==时一定要关闭编辑器!==Animinstance==是所有动画蓝图的基础蓝图类!可以重写它以实现动画蓝图的功能!
![[Pasted image 20250331162306.png]]
上图所示表示动画开始时获取角色组件,==TryGetPawnOwner()== 用于尝试获取当前动画蓝图实例所关联的Pawn(角色)对象。因为character是Pawn的子类,所以也能获取,但是需要Cast<>()进行类型转换
另外,NativeThreadSafeUpdateAnimatio()是在线程中执行的函数,性能更佳!

使用多个动画蓝图控制动画

创建一个新蓝图,在另一个类中使用链接图表将蓝图链接。intputpose节点设置在需要姿势的动画蓝图里可以链接另一个蓝图的姿势

动画蒙太奇-一种动画分组容器

使用标签指定动画播放的顺序,创建插槽将蒙太奇动画关联到插槽
![[Pasted image 20250402141713.png]]
网格体可以获取到动画示例,并使用动画蒙太奇。

****** ==要想使用蒙太奇,必须在动画蓝图中启用插槽!==

UE5 C++动画蓝图关键API:重写动画开始与每帧更新

以下是Unreal Engine 5中动画系统两个重要API的C++实现方式,分别用于动画开始执行和每帧执行逻辑,和动画蓝图一致,需要重写Animainstace

![[Pasted image 20250402142248.png]]在角色的.h文件中申明一个攻击蒙太奇变量,通过网格体访问动画实例,然后使用其中的蒙太奇播放函数
![[Pasted image 20250402142750.png]]

关键api: Montage_JumpToSection() Montage_Play()

如何在 C++ 中接收动画通知​

要在 C++ 中接收动画通知,需要以下步骤:

​(1) 创建自定义 AnimNotify 类​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// MyAnimNotify.h
#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "MyAnimNotify.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMyAnimNotify, USkeletalMeshComponent*, MeshComp);

UCLASS()
class YOURGAME_API UMyAnimNotify : public UAnimNotify
{
GENERATED_BODY()

public:
// 重写 Notify 函数
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;

// 可选:声明一个动态多播委托,用于在蓝图中绑定回调
UPROPERTY(BlueprintAssignable, Category = "Animation")
FOnMyAnimNotify OnNotifyTriggered;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MyAnimNotify.cpp
#include "MyAnimNotify.h"
#include "Components/SkeletalMeshComponent.h"

void UMyAnimNotify::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::Notify(MeshComp, Animation);

// 在这里执行你的逻辑(如播放音效、生成特效等)
UE_LOG(LogTemp, Warning, TEXT("MyAnimNotify Triggered!"));

// 如果需要,可以触发委托
if (OnNotifyTriggered.IsBound())
{
OnNotifyTriggered.Broadcast(MeshComp);
}
}

​(2) 在动画序列中添加自定义通知​

  1. 打开 ​​动画序列(Animation Sequence)​​。
  2. 在时间轴上右键点击,选择 ​​Add Notify​​ → ​​Add Custom​​。
  3. 选择你的自定义通知类(如 MyAnimNotify)。
  4. 调整通知的时间点,动画序列执行到通知点即可执行你需要的逻辑
    一般来说简单逻辑直接在编辑器中实现就好了

Kismet数学库

具有向量运算等强大功能,详情查询UE帮助手册

![[Pasted image 20250331162234.png]]

反向动力学-IK inverse kinematics

球体追踪-一种定位距离办法

控制骨架-animation动画

可以根据末端执行器移动骨骼位置,指定末端骨骼,IK骨骼,或者添加虚拟骨骼,
一:导入骨架
二:创建足部追踪函数
三:添加新参数:rig element key
四:添加get transform函数
![[Pasted image 20250331180247.png]]
![[Pasted image 20250331180347.png]]
![[Pasted image 20250331180436.png]]

添加球追踪通道-具有起点和终点两个向量,会返回一个命中位置

经常使用插值来使变量平滑过渡

全身IK-UE5新增(难点)

对身体的任意一串骨骼指定末端执行器

碰撞

所有网格体都具有碰撞属性
1.无碰撞
2.仅仅查询-无物理模拟(用于射线检测之类…)
3.物理模拟
4.开启碰撞-支持物理模拟和射线检测,性能成本最高

简单碰撞与复杂碰撞

支持重力与推力….需要手动启用

重叠事件

使用前必须将角色和角色身上的组件重叠事件设为true

球体组件

当两个物体重叠时就会触发这个事件,可以

组件开始重叠(用得最多)

虚幻5中的委托

观察者模式-

委托是一种特殊的类,组件对象旗下有许多的委托,比如组件重叠事件,组件开始重叠,组件结束重叠,可以储存观察者列表,可以为某一个特定对象创造委托。并广播这个委托。
![[Pasted image 20250401165837.png]]
组件继承关系↑

C++实现:
需要创建一个回调函数,这个回调函数可以提供许多关于回调函数的信息,绑定到动态多播委托,要绑定必须对反射系统可见,需要加上UFUNCTION(),因为这种类型的委托可以被蓝图访问

访问==组件对象==下面的oncomponentBeginOverlap函数下的AddDynamic(用户对象,回调函数的地址)

然后回调函数就会在组件重叠的时候被触发。
Tips:在虚幻5中要重写继承的虚函数,需要移除UFUCTION等宏,因为会隐式继承一个宏。super常用于保留原函数的功能

骨骼插槽

动画重定向-反向目标定位

1.根据导入的骨骼创建IK骨架,设置反相目标根节点(是所有骨骼中联系其它骨骼的核心节点),
根据设置的根节点新建链条,指定起始骨骼。从选定骨骼创建新的重新定位链。
2.根据需要替换的骨骼创建IK骨架
3.创建IK反向目标

tips:强制内联,FORCEINLINE

虚幻引擎拾取系统实现方案总结

基于重叠事件的拾取系统实现

核心实现步骤

  1. 物品基类(AItem)创建

    • 继承自AActor
    • 包含USphereComponent用于碰撞检测
    • 实现PickUp虚函数供子类重写
  2. 关键代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    // Item.h关键部分
    UPROPERTY(VisibleAnywhere)
    USphereComponent* CollisionSphere;

    UFUNCTION()
    void OnBeginOverlap(...);

    // Item.cpp关键部分
    CollisionSphere->OnComponentBeginOverlap.AddDynamic(this, &AItem::OnBeginOverlap);

    void AItem::OnBeginOverlap(...)
    {
    if(APawn* Picker = Cast<APawn>(OtherActor))
    {
    PickUp(Picker);
    }
    }```
    # 虚幻引擎枚举
    在普通枚举中加E,枚举变量前加ECS_ ,枚举后面限定枚举变量的数据结构大小以避免额外开销

    ![[Pasted image 20250402083301.png]]
    在蓝图中指定显示属性
    ![[Pasted image 20250402083446.png]]

    # 在动画实例中更新角色持有武器的状态
    ![[Pasted image 20250402084241.png]]


    ![[Pasted image 20250402084722.png]]利用枚举选择动画状态


    # 音效

    sound_cue功能可以修改音效,而不改动原本的音频资源

    虚幻5中mate-sounds也可以,优于sound_cue。SFX格式,可以制作随机音效等程序化音效
    ![[Pasted image 20250402160539.png]]![[Pasted image 20250402161158.png]]混洗功能

    # UE5的接口类-实际上是一个虚基类
    * 将自定义的函数暴露给作用类,而不用考虑类型,使用时作用类继承接口类重写虚函数即可,接口可以实现各个类进行通信,下面是个例子,
    ![[Pasted image 20250404092303.png]]
    如果检测碰撞返回的类被成功转换为接口类,则执行接口函数,因为接口类本身就是传递对象的父类,所以能转换成功。然后调用其中的函数将数据传输给另一个类,实现通信。
    ![[Pasted image 20250404092750.png]]

    # 向量点积-求两个向量之间的夹角-返回的是标量无方向
    ![[Pasted image 20250404094522.png]]



    ## 向量归一化
    ![[Pasted image 20250404094726.png]]

    打击点位置-物体位置=指向打击点位置的向量,下面使用UE5的点积式求解角度,此时DotProduct为Cosθ
    ![[Pasted image 20250404095327.png]]
    然后计算反余弦即可求出角度θ![[Pasted image 20250404110709.png]]


    ![[Pasted image 20250404113610.png]]



    ## 向量叉积

    ![[Pasted image 20250404114917.png]]
    ![[Pasted image 20250404115350.png]]

    虚幻采用左手坐标系

    # Cascade特效与Niagara


    Cascade特效系统
    ![[Pasted image 20250404144744.png]]![[Pasted image 20250404145002.png]]
    Niagara:
    至少需要一个发射器,可以直接挂载在物体上
    1.发射器
    2.精灵图:会自动面向玩家
    3.

    在角色身上生成特效:


    C++方法:




    # 可破坏网格体
    场系统:是一个ACtor类
    ![[Pasted image 20250404151132.png]]![[Pasted image 20250404151214.png]]
    径向衰减,

    ![[Pasted image 20250404151704.png]]
    线性力能打飞物体![[Pasted image 20250404151742.png]]
    需要一个向量来获取

    C++可以创建一个临时立场,并暴露给蓝图


    创建一个可破坏物体,并将其挂载在ACTOR上
    ![[Pasted image 20250404153252.png]]

    ![[Pasted image 20250404153622.png]]![[Pasted image 20250404153729.png]]
    必须包含此模块



    # 蓝图原生事件
    ![[Pasted image 20250404154338.png]]

    一旦使用,可以在蓝图和C++中同时使用接口类的接口函数,但是你的虚函数会加上特殊后缀,这样就能将接口暴露给策划或者美术使用![[Pasted image 20250404154538.png]]


    下面一行是加上后缀后的新方法![[Pasted image 20250404154728.png]]

    从指定位置生成物体

    ![[Pasted image 20250404160134.png]]

    ![[Pasted image 20250404160412.png]]一旦发生事件重叠,则消除物品

    C++实现生成物品:
    ![[Pasted image 20250404160912.png]]


    先声明一个UClass类暴露给编辑器,然后去编辑器指定蓝图类,则可以用C++生成一个蓝图类(非常重要!)

    ![[Pasted image 20250404161558.png]]

    ![[Pasted image 20250404161755.png]]
    限定修饰器,取代UClass,可以限定继承蓝图类。最好使用它。

    tips:蓝图也可以继承

    # 自定义组件





    # 伤害


    可以在任何角色类中重写伤害函数,这是虚幻自带的伤害函数
    ![[Pasted image 20250405132812.png]]

    ![[Pasted image 20250405133835.png]]
    将健康值限制到0和最大生命之间
    ![[Pasted image 20250405134525.png]]![[Pasted image 20250405134850.png]]
    ![[Pasted image 20250405140837.png]]



    # 追踪角色
    利用takedamage函数可以获取攻击对象,将攻击对象存入指针,这样就你能访问攻击对象的属性和方法
    ![[Pasted image 20250406102654.png]]



    ![[Pasted image 20250406102830.png]]
    例如判断攻击角色与被攻击角色的距离
    ![[Pasted image 20250406102949.png]]
    设置默认半径
    ![[Pasted image 20250406103010.png]]
    如果距离大于攻击半径,将攻击对象置空


    # AI-导航网格

    ## 首先需要创建导航网格体网格![[Pasted image 20250406113807.png]]
    show navigation控制台开启显示导航命令





    # 混合空间


    ![[Pasted image 20250406121727.png]]
    这个是动画蓝图中的状态



    混合空间可以组合多种动画,并根据变量大小执行相应过渡动画。

    ![[Pasted image 20250406122450.png]]
    设置这个之后可以将移动交给AI托管,
    ![[Pasted image 20250406122537.png]]
    必须包含这个头文件


    ## 导航目标
    ![[Pasted image 20250406123934.png]]

    可以利用一个角色指针数组将导航点存在里面,需要开启AI模块,并包含相关头文件
    ![[Pasted image 20250406124514.png]]![[Pasted image 20250406124901.png]]

    ![[Pasted image 20250406124934.png]]
    创建一个AI控制器,并将目标设为导航点


    ![[Pasted image 20250406125423.png]]
    # UE5 AI移动请求代码总结

    ## 代码功能
    实现AI导航移动并在路径点上绘制调试球体
    前面将导航点暴露给了蓝图,所以能直接用蓝图添加导航点
    ## 代码解析

    ```cpp
    FAIMoveRequest MoveRequest;
    MoveRequest.SetGoalActor(PatrolTarget);
    MoveRequest.SetAcceptanceRadius(15.f);
    FNavPathSharedPtr NavPath;
    EnemyController->MoveTo(MoveRequest, &NavPath);
    TArray<FNavPathPoint>& PathPoints = NavPath->GetPathPoints();
    for (auto& Point : PathPoints)
    {
    const FVector& Location = Point.Location;
    DrawDebugSphere(GetWorld(), Location, 12.f, 12, FColor::Green, false, 10.f);
    }```
    ### 1. 创建移动请求

    - `FAIMoveRequest` - AI移动请求类
    ### 2. 设置移动目标
    - `SetGoalActor(PatrolTarget)` - 设置目标Actor
    - `PatrolTarget` - 要移动到的目标(AActor指针)
    ### 3. 设置接受半径
    - `SetAcceptanceRadius(15.f)` - 到达判定半径(15厘米)
    ### 4.执行移动命令

    - `FNavPathSharedPtr` - 导航路径共享指针
    - `EnemyController->MoveTo()` - 执行移动
    - 参数1: 配置好的移动请求
    - 参数2: 输出导航路径信息
    ### 5. 获取路径点
    - `GetPathPoints()` - 获取路径点数组
    - `TArray<FNavPathPoint>` - 路径点动态数组
    ### 6. 绘制调试球体

    - `DrawDebugSphere()`参数:
    1. 世界上下文
    2. 球体位置(FVector)
    3. 半径(12厘米)
    4. 细分段数(12)
    5. 颜色(FColor::Green)
    6. 是否持久化(false)
    7. 显示时间(10

    这段代码用于在Unreal Engine 5中可视化显示AI的导航路径点,通过在每一个路径点位置绘制绿色球体来辅助调试。
    ```cpp
    for (auto& Point : PathPoints) // 遍历导航路径中的所有点
    {
    const FVector& Location = Point.Location; // 获取当前路径点的3D位置坐标
    DrawDebugSphere( // 绘制调试球体函数
    GetWorld(), // 参数1:获取当前游戏世界上下文
    Location, // 参数2:球体中心位置(使用路径点坐标)
    12.f, // 参数3:球体半径(12厘米)
    12, // 参数4:球体细分精度(12个分段)
    FColor::Green, // 参数5:球体颜色(绿色)
    false, // 参数6:是否持久化(false=仅当前帧显示)
    10.f // 参数7:显示持续时间(10秒)
    );
    }```

    ![[Pasted image 20250406173827.png]]
    如果距离小于等于半径返回true

    ![[Pasted image 20250406175408.png]]




    # 定时器

    ```cpp
    GetWorldTimerManager().SetTimer(TimerHandle_PrimaryAttack, this, &ASCharacter::PrimaryAttack_TimeElapsed, 0.2f);

    这段代码是基于虚幻引擎(Unreal Engine)的C++ 代码,主要功能是设置一个定时器,以下是对各部分的详细解释:

  3. GetWorldTimerManager()​:

    • 这是虚幻引擎中用于获取当前世界上下文(UWorld)的定时器管理器的函数调用。UWorld 代表游戏世界,定时器管理器则是用来管理和控制各种定时任务的对象。通过调用这个函数,我们可以访问到与当前游戏世界相关的定时器相关功能。
  4. .SetTimer()​:

    • 这是定时器管理器的一个成员函数,用于设置一个新的定时器。它接受多个参数来配置定时器的行为。
  5. TimerHandle_PrimaryAttack​:

    • 这是一个 FTimerHandle 类型的变量,作为定时器的句柄。句柄就像是定时器的一个唯一标识符,后续可以通过这个句柄来对定时器进行操作,比如取消定时器等。通常在类的成员变量中声明这个句柄,以便在不同的函数中都能引用到同一个定时器。
  6. this​:

    • this 指针指向当前对象实例。在这段代码中,它表示当前拥有这个定时器设置代码的对象(一般是某个继承自 ASCharacter 的角色类的实例),定时器相关的回调函数将绑定到这个对象上执行。
  7. &ASCharacter::PrimaryAttack_TimeElapsed​:

    • 这是一个函数指针,指向 ASCharacter 类中的 PrimaryAttack_TimeElapsed 成员函数。当定时器到期时,虚幻引擎会调用这个指定的成员函数。也就是说,PrimaryAttack_TimeElapsed 函数是定时器触发时要执行的回调逻辑。
  8. 0.2f​:

    • 这是一个浮点数参数,表示定时器的延迟时间,单位是秒。这里设置为 0.2f 意味着定时器将在 0.2 秒后触发,调用 PrimaryAttack_TimeElapsed 函数。

==这段代码的作用是在当前游戏世界中设置一个定时器,0.2 秒后触发 ASCharacter 类中的 PrimaryAttack_TimeElapsed 函数==

HUD

AI行为树

需要重写Beginplay()
头文件中也需要创建AI行为树对象,在beginpaly中运行
![[Pasted image 20250411161843.png]]

![[Pasted image 20250411162416.png]]

行为树节点状态预览

![[Pasted image 20250411164703.png]]

![[Pasted image 20250411164444.png]]
这段代码是一个行为树服务(UBTService)的 TickNode 函数实现,主要用于在行为树执行过程中检查AI控制的棋子(Pawn)与目标Actor之间的距离,并将是否在攻击范围内的结果存储到黑板(Blackboard)组件中。

以下是在虚幻引擎蓝图中使用上述C++代码逻辑的步骤:

1. 创建行为树

在虚幻编辑器中,打开内容浏览器,右键点击并选择 AI -> 行为树 来创建一个新的行为树资产。命名为例如 MyAITree

2. 创建黑板

同样在内容浏览器中,右键点击并选择 AI -> 黑板 来创建一个新的黑板资产。命名为例如 MyBlackboard

3. 在行为树中使用黑板

打开刚才创建的行为树 MyAITree,在行为树的根节点属性中,将 黑板 属性指定为刚刚创建的 MyBlackboard

4. 创建AI控制器类

在内容浏览器中,右键点击并选择 C++类 -> 选择或创建一个合适的父类(比如 AIController),命名为例如 MyAIController。编译项目,确保C++代码编译通过。

5. 在AI控制器类中关联黑板

打开 MyAIController 的头文件(.h 文件),添加以下代码来声明黑板组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "CoreMinimal.h"
#include "AIController.h"
#include "MyAIController.generated.h"

UCLASS()
class YOURGAMENAME_API AMyAIController : public AAIController
{
GENERATED_BODY()

public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
UBlackboardComponent* BlackBoardComp;
};

然后在 MyAIController 的源文件(.cpp 文件)的构造函数或 BeginPlay 函数中初始化黑板组件:

1
2
3
4
5
6
7
8
#include "MyAIController.h"

AMyAIController::AMyAIController()
{
PrimaryActorTick.bCanEverTick = false;

BlackBoardComp = CreateDefaultSubobject<UBlackboardComponent>(TEXT("BlackBoardComp"));
}

6. 创建行为树服务类

在内容浏览器中,右键点击并选择 C++类 -> 选择或创建一个合适的父类(比如 BTService),命名为例如 S8TService。编译项目。

7. 在行为树服务类中关联黑板键

打开 S8TService 的头文件,确保 AttackRangeKey 是一个已声明的成员变量,并且类型合适(比如 FName 类型),例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "CoreMinimal.h"
#include "BehaviorTree/BTService.h"
#include "S8TService.generated.h"

UCLASS()
class YOURGAMENAME_API US8TService : public UBTService
{
GENERATED_BODY()

protected:
UPROPERTY(EditAnywhere, Category = "AI")
FName AttackRangeKey;

virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};

在源文件中实现 TickNode 函数,就是你提供的那段C++代码(注意修正代码中的语法错误,比如一些乱码和不规范的写法)。

8. 在行为树中使用服务

回到行为树 MyAITree,右键点击行为树的节点区域,搜索并添加 S8TService 服务节点。将其放置在合适的位置(比如在根节点下作为子节点)。

9. 设置黑板键

选中添加的 S8TService 节点,在细节面板中找到 AttackRangeKey 属性,设置其值为黑板中用于存储是否在攻击范围内的布尔键的名称(例如命名为 bIsInAttackRange)。

10. 在黑板中设置初始值

打开 MyBlackboard,创建一个新的布尔类型的键,命名为刚才在服务节点中设置的名称(如 bIsInAttackRange),并设置其初始值为 false

11. 在AI控制器中使用行为树

打开 MyAIController 的源文件,在 Possess 函数中启动行为树:

1
2
3
4
5
void AMyAIController::Possess(APawn* InPawn)
{
Super::Possess(InPawn);
RunBehaviorTree(MyBTTree); // 假设MyBTTree是行为树资产的引用,在头文件中声明并赋值
}

在头文件中添加相应的声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "CoreMinimal.h"
#include "AIController.h"
#include "MyAIController.generated.h"
#include "MyBTTree.h" // 引入行为树资产的头文件

UCLASS()
class YOURGAMENAME_API AMyAIController : public AAIController
{
GENERATED_BODY()

public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
UBlackboardComponent* BlackBoardComp;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
UBehaviorTree* MyBTTree;

virtual void Possess(APawn* InPawn) override;
};

完成以上步骤后,在游戏运行时,AI控制器会按照行为树的逻辑,通过 S8TService 服务节点定期检查AI控制的Pawn与目标Actor之间的距离,并将结果存储在黑板的指定布尔键中,供后续的行为树节点使用。

代码详细解析

  1. ​调用父类的 TickNode 函数​​:
1
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);

这一步确保行为树服务的父类逻辑也能正常执行,维持行为树系统的基本运行流程。

  1. ​获取黑板组件​​:
1
2
UBlackboardComponent* BlackBoardComp = OwnerComp.GetBlackboardComponent();
if (ensure(BlackBoardComp))

通过 OwnerComp 获取与之关联的黑板组件,并使用 ensure 宏进行有效性检查。如果黑板组件不存在,ensure 宏会触发断言(在开发环境下)或进行适当的错误处理(在发布环境下)。

  1. ​获取目标Actor​​:
1
2
AActor* TargetActor = Cast<AActor>(BlackBoardComp->GetValueAsObject("TargetActor"));
if (TargetActor)

从黑板组件中获取键为 "TargetActor" 的值,并尝试将其转换为 AActor 类型。如果转换成功且目标Actor存在,则继续后续操作。
4. ​​获取AI控制器和其控制的Pawn​​:

1
2
3
4
5
6
AAIController* MyController = OwnerComp.GetAIOwner();
if (ensure(MyController))
{
APawn* AIPawn = MyController->GetPawn();
if (ensure(AIPawn))
{

首先从 OwnerComp 获取AI的所有者(即AI控制器),然后确保控制器存在。接着通过控制器获取其控制的Pawn,并对Pawn的存在性进行有效性检查。

  1. ​计算距离并判断是否在攻击范围内​​:
1
2
float DistanceTo = FVector::Distance(TargetActor->GetActorLocation(), AIPawn->GetActorLocation());
bool bWithinRange = DistanceTo < 2000.0f;

使用 FVector::Distance 函数计算目标Actor和AI控制的Pawn之间的距离,并将距离与设定的阈值 2000.0f 进行比较,判断目标是否在攻击范围内。

  1. ​将结果存储到黑板组件​​:
1
BlackBoardComp->SetValueAsBool(AttackRangeKey.SelectedKeyName, bWithinRange);

将判断结果(是否在攻击范围内)以布尔值的形式存储到黑板组件中,使用 AttackRangeKey.SelectedKeyName 作为键。

![[Pasted image 20250411170301.png]]
这段代码是一个行为树任务(UBTTask)的ExecuteTask函数实现,用于在虚幻引擎(Unreal Engine)中执行一个范围攻击任务。其主要目的是让AI角色从枪口位置向目标角色发射一个投射物(如子弹等)。

代码详细解析

  1. ​获取AI控制器和所属角色​
1
2
3
4
5
6
7
8
AAIController* MyController = OwnerComp.GetAIOwner();
if (ensure(MyController))
{
ACharacter* MyPawn = Cast<ACharacter>(MyController->GetPawn());
if (MyPawn == nullptr)
{
return EBTNodeResult::Failed;
}
1
2
- 首先通过`OwnerComp.GetAIOwner()`获取行为树组件的AI控制器。
- 使用`ensure`宏检查AI控制器是否有效。如果有效,再通过`MyController->GetPawn()`获取AI角色所控制的角色(`ACharacter`类型),并检查该角色是否存在。如果角色不存在,直接返回任务失败。
  1. ​获取枪口位置和目标角色​

cpp

复制

1
2
3
4
5
6
FVector MuzzleLocation = MyPawn->GetMesh()->GetSocketLocation("Muzzle_01");
AActor* TargetActor = Cast<AActor>(OwnerComp.GetBlackboardComponent()->GetValueAsObject("TargetActor"));
if (TargetActor == nullptr)
{
return EBTNodeResult::Failed;
}
1
2
- 通过`MyPawn->GetMesh()->GetSocketLocation("Muzzle_01")`获取角色模型上名为`"Muzzle_01"`的插槽位置,即枪口位置。
- 从黑板组件(`BlackboardComponent`)中获取名为`"TargetActor"`的对象,并尝试将其转换为`AActor`类型,得到目标角色。如果目标角色不存在,返回任务失败。
  1. ​计算发射方向和旋转​

cpp

复制

1
2
FVector Direction = TargetActor->GetActorLocation() - MuzzleLocation;
FRotator MuzzleRotation = Direction.Rotation();
1
2
- 计算从枪口位置到目标角色位置的方向向量`Direction`。
- 根据该方向向量获取对应的旋转`MuzzleRotation`。
  1. ​生成投射物​

cpp

复制

1
2
3
4
FActorSpawnParameters Params;
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AActor* NewProj = GetWorld()->SpawnActor<AActor>(ProjectileClass, MuzzleLocation, MuzzleRotation, Params);
return NewProj? EBTNodeResult::Succeeded : EBTNodeResult::Failed;
1
2
- 创建一个`FActorSpawnParameters`对象`Params`,并设置其碰撞处理方式为总是生成(`ESpawnActorCollisionHandlingMethod::AlwaysSpawn`)。
- 使用`GetWorld()->SpawnActor<AActor>`在世界中生成一个指定类(`ProjectileClass`)的投射物,位置为枪口位置,旋转为计算得到的枪口旋转,同时传入生成参数`Params`。如果生成成功,返回任务成功;否则返回任务失败。
  1. ​默认返回失败​

cpp

复制

1
return EBTNodeResult::Failed;

如果在上述过程中没有提前返回成功或失败,最后返回任务失败。

C++与UMG案例

在Unreal Engine中使用C++创建一个简单的3D UI伤害数字,通常涉及以下几个步骤:

  1. ​创建UI组件​​:
    • 使用UMG(Unreal Motion Graphics)创建一个UI小部件,用于显示伤害数字。UMG允许你设计UI元素并在游戏中使用它们。
  2. ​绑定UI事件​​:
    • 在C++中,你需要创建一个UI控件类,并将其与UMG小部件绑定。这通常涉及到创建一个继承自UUserWidget的C++类。
  3. ​处理伤害事件​​:
    • 在游戏逻辑中,当角色受到伤害时,触发一个事件来显示伤害数字。
  4. ​动画和显示​​:
    • 实现伤害数字的动画效果,例如从屏幕外飞入并逐渐消失。

下面是一个简化的示例,展示如何在C++中创建一个基本的伤害数字UI:

步骤1:创建UMG小部件

  1. 打开Unreal Editor,创建一个新的UMG小部件。
  2. 添加一个TextBlock控件,并设置其属性,如字体大小、颜色和位置。
  3. 将UMG小部件保存为一个蓝图类(例如UDamageNumberWidget)。

步骤2:创建C++ UI控件类

在你的C++项目中,创建一个新的C++类,继承自UUserWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "Runtime/UMG/Public/UMG.h"
#include "Runtime/UMG/Public/Slate/SObjectWidget.h"
#include "Runtime/UMG/Public/IUMGModule.h"
#include "Runtime/UMG/Public/UMGStyle.h"
#include "Runtime/Core/Public/Misc/Paths.h"
#include "Runtime/Core/Public/Misc/FileHelper.h"
#include "Runtime/Core/Public/GenericPlatform/GenericPlatformFile.h"

UCLASS()
class YOURPROJECT_API UDamageNumberWidget : public UUserWidget
{
GENERATED_BODY()

public:
UPROPERTY(meta = (BindWidget))
UTextBlock* DamageText;

UFUNCTION(BlueprintCallable, Category = "Damage UI")
void SetDamageText(const FString& Text);
};

步骤3:实现C++ UI控件类

.cpp文件中实现SetDamageText函数:

1
2
3
4
5
6
7
8
9
#include "DamageNumberWidget.h"

void UDamageNumberWidget::SetDamageText(const FString& Text)
{
if (DamageText)
{
DamageText->SetText(FText::FromString(Text));
}
}

步骤4:在游戏中使用UI控件

在你的游戏逻辑中,创建并显示UI控件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "DamageNumberWidget.h"

void AYourCharacter::TakeDamage(float DamageAmount)
{
// 创建UI控件实例
UDamageNumberWidget* DamageWidget = CreateWidget<UDamageNumberWidget>(GetWorld(), UDamageNumberWidget::StaticClass());

if (DamageWidget)
{
// 设置伤害文本
DamageWidget->SetDamageText(FString::Printf(TEXT("%d"), FMath::RoundToInt(DamageAmount)));

// 将UI控件添加到视口
if (GEngine && GEngine->GameViewport)
{
DamageWidget->AddToViewport();
}

// 可以添加动画效果,例如淡出或移动
// ...
}
}

虚幻5自定义控制台函数

在Unreal Engine(UE)中,你可以通过使用控制台命令(Console Commands)将函数暴露给控制台,从而便于在游戏运行时调用这些函数。这在调试、测试或实现作弊功能时非常有用。以下是详细的步骤和示例,帮助你实现这一目标:

步骤一:创建一个控制台命令

1. 在C++中创建控制台命令

如果你使用的是C++,可以通过以下步骤创建一个控制台命令:

a. 在头文件中声明函数和命令

假设你有一个类 MyGameInstance,你希望在其中添加一个控制台命令来调用某个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MyGameInstance.h

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"

UCLASS()
class YOURPROJECT_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()

public:
// 构造函数
UMyGameInstance();

protected:
// 初始化时注册控制台命令
virtual void Init() override;

public:
// 控制台命令调用的函数
UFUNCTION(Exec)
void CheatFunction();
};

b. 在源文件中实现函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// MyGameInstance.cpp

#include "MyGameInstance.h"
#include "Engine/Engine.h"

UMyGameInstance::UMyGameInstance()
{
// 设置此GameInstance在游戏开始时自动创建
bAutoInitialize = true;
}

void UMyGameInstance::Init()
{
Super::Init();

// 这里可以添加初始化代码
UE_LOG(LogTemp, Warning, TEXT("MyGameInstance Initialized"));
}

void UMyGameInstance::CheatFunction()
{
// 在这里编写你希望执行的作弊逻辑
UE_LOG(LogTemp, Warning, TEXT("Cheat Function Called"));

// 示例:给玩家增加生命值(假设玩家有一个SetHealth的方法)
APlayerController* PlayerController = GetFirstPlayerController();
if (PlayerController)
{
ACharacter* Character = PlayerController->GetCharacter();
if (Character)
{
// 假设Character有一个SetHealth方法
// Character->SetHealth(Character->GetHealth() + 100);
UE_LOG(LogTemp, Warning, TEXT("Player Health Increased by 100"));
}
}
}

​说明:​

  • UFUNCTION(Exec) 宏用于将函数暴露给控制台,使其可以通过控制台命令调用。
  • 确保你的 GameInstance 类在项目设置中被正确设置为默认的 GameInstance 类。

步骤二:确保控制台命令可用

  1. ​启用控制台:​
    • 默认情况下,UE的控制台可以通过按下 ~ 键(位于 Tab 键上方)打开。如果无法打开,可以在项目设置中检查输入设置,确保没有禁用控制台。
  2. ​注册控制台命令:​
    • 使用 UFUNCTION(Exec) 宏声明的函数会自动注册为控制台命令。确保你的类(如 GameInstance)在游戏启动时被正确初始化。

步骤三:测试控制台命令

  1. ​运行游戏:​
    • 启动你的游戏,确保 GameInstance 已正确初始化。
  2. ​打开控制台:​
    • 按下 ~ 键打开控制台。
  3. ​输入命令:​
    • 输入 CheatFunction(假设你的函数名为 CheatFunction),然后按下 Enter 键。
  4. ​查看结果:​
    • 如果一切设置正确,你应该在输出日志中看到 Cheat Function Called 的日志信息,并且作弊逻辑(如增加生命值)应被执行。

示例:完整实现增加玩家生命值的作弊功能

假设你有一个玩家角色类 MyCharacter,其中包含一个 SetHealth 方法:

1. 在 MyCharacter.h 中定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// MyCharacter.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()
class YOURPROJECT_API AMyCharacter : public ACharacter
{
GENERATED_BODY()

public:
AMyCharacter();

protected:
virtual void BeginPlay() override;

public:
virtual void Tick(float DeltaTime) override;

UFUNCTION(BlueprintCallable, Category = "Health")
void SetHealth(float NewHealth);

UPROPERTY(BlueprintReadOnly, Category = "Health")
float CurrentHealth;
};

2. 在 MyCharacter.cpp 中实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MyCharacter.cpp

#include "MyCharacter.h"

AMyCharacter::AMyCharacter()
{
PrimaryActorTick.bCanEverTick = true;
CurrentHealth = 100.0f;
}

void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
}

void AMyCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}

void AMyCharacter::SetHealth(float NewHealth)
{
CurrentHealth = FMath::Clamp(NewHealth, 0.0f, 100.0f);
UE_LOG(LogTemp, Warning, TEXT("Health set to %f"), CurrentHealth);
// 这里可以添加更新UI或其他逻辑
}

3. 修改 MyGameInstance::CheatFunction 来调用 SetHealth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MyGameInstance.cpp

#include "MyGameInstance.h"
#include "Engine/Engine.h"
#include "MyCharacter.h"

void UMyGameInstance::CheatFunction()
{
UE_LOG(LogTemp, Warning, TEXT("Cheat Function Called"));

APlayerController* PlayerController = GetFirstPlayerController();
if (PlayerController)
{
ACharacter* Character = PlayerController->GetCharacter();
if (Character && Character->IsA<AMyCharacter>())
{
AMyCharacter* MyCharacter = Cast<AMyCharacter>(Character);
if (MyCharacter)
{
MyCharacter->SetHealth(200.0f); // 设置生命值为200
}
}
}
}

​注意:​

  • 确保你的玩家角色类是 AMyCharacter 或者根据实际情况调整类型转换。
  • 你可以根据需要扩展作弊功能,如添加更多控制台命令,调用不同的函数等。

额外提示

  • ​安全性考虑:​​ 控制台命令可以用于调试和开发,但在发布版本中应谨慎使用,避免被恶意利用。你可以通过编译条件(如 UE_BUILD_SHIPPING)来移除或禁用作弊相关的控制台命令。

    1
    2
    3
    4
    #if !UE_BUILD_SHIPPING
    UFUNCTION(Exec)
    void CheatFunction();
    #endif
  • ​命令参数:​​ 你可以为控制台命令添加参数,以增加灵活性。例如,允许玩家输入要增加的生命值数量。

    1
    2
    3
    4
    // MyGameInstance.h

    UFUNCTION(Exec)
    void CheatAddHealth(float Amount);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // MyGameInstance.cpp

    void UMyGameInstance::CheatAddHealth(float Amount)
    {
    UE_LOG(LogTemp, Warning, TEXT("Cheat Add Health Called with Amount: %f"), Amount);

    APlayerController* PlayerController = GetFirstPlayerController();
    if (PlayerController)
    {
    ACharacter* Character = PlayerController->GetCharacter();
    if (Character && Character->IsA<AMyCharacter>())
    {
    AMyCharacter* MyCharacter = Cast<AMyCharacter>(Character);
    if (MyCharacter)
    {
    float NewHealth = MyCharacter->GetCurrentHealth() + Amount;
    MyCharacter->SetHealth(NewHealth);
    }
    }
    }
    }

    这样,你可以在控制台中输入 CheatAddHealth 50 来增加50点生命值。

  • ​文档与调试:​​ 为你的控制台命令编写文档,确保团队成员了解可用的作弊命令及其用途。同时,利用UE的日志系统记录命令调用,便于调试和监控。

继承gamemode并管理角色死亡等事件

![[Pasted image 20250411174105.png]]
这段C++ 代码是基于虚幻引擎(Unreal Engine)的,主要实现了游戏中玩家死亡后的重生逻辑以及记录玩家被击杀的相关日志信息。

具体函数功能分析

  1. ASGameModeBase::RespawnPlayerElapsed 函数​
    • ​功能​​:负责在合适的时机让玩家角色重新生成。
    • ​代码细节​​:
      • 首先使用 ensure(controller) 来确保传入的 controller(玩家控制器指针)是有效的。如果有效,调用 Controller->UnPossess() 方法,这会解除玩家控制器与当前所控制的角色之间的关联。
      • 然后调用 RestartPlayer(controller) 方法,该方法会重新生成玩家角色,让玩家可以重新回到游戏中。
  2. ASGameModeBase::OnActorKilled 函数​
    • ​功能​​:当一个 Actor(这里主要关注玩家角色被击杀的情况)被击杀时被调用,处理玩家被击杀后的重生延迟以及记录击杀日志等操作。
    • ​代码细节​​:
      • 首先将 VictimActor 尝试转换为 ASCharacter 类型的指针 player,因为只有玩家角色才需要进行后续的重生等处理。如果转换成功(即 player 不为空):
        • 声明一个 FTimerHandle 类型的变量 TimerHandle_RespawnDelay,用于管理重生延迟定时器。
        • 声明一个 FTimerDelegate 类型的变量 Delegate,并通过 BindUFunction 将当前对象的 RespawnPlayerElapsed 函数绑定到该委托上,同时传入被击杀玩家的控制器指针作为参数。这样在定时器触发时就会调用 RespawnPlayerElapsed 函数来重生玩家。
        • 定义一个 float 类型的变量 RespawnDelay 并赋值为 2.0f,表示重生延迟时间为2秒。
        • 最后通过 GetWorldTimerManager().SetTimer 方法设置一个定时器,当指定的延迟时间(RespawnDelay)到达时,会触发绑定的委托函数 Delegate,并且设置 false 表示该定时器只触发一次。
      • 使用 UE_LOG 宏记录玩家被击杀的日志信息,包括被击杀玩家的名字和击杀者的名字。

可能的改进或扩展方向

  • ​可配置的重生时间​​:目前重生延迟时间 RespawnDelay 是硬编码为 2.0f,可以考虑从配置文件或游戏设置中读取该值,以便在不同场景或游戏模式下灵活调整。
  • ​更多的击杀日志信息​​:除了记录玩家名字,还可以记录击杀发生的地点、使用的武器等信息,以提供更详细的击杀分析数据。
  • ​错误处理优化​​:对于 Cast 操作,目前只是简单判断是否成功,可以考虑在转换失败时进行更详细的错误记录或处理,比如记录一个警告日志说明转换失败的原因。

继承蓝图函数库添加自定义静态函数

![[Pasted image 20250411174833.png]]

  1. ApplyDamage函数​​:

    • ​功能​​:对目标角色(TargetActor)应用伤害,伤害由伤害施加者(DamageCauser)造成,伤害量为DamageAmount
    • ​实现逻辑​​:
      • 首先通过USAttributeComponent::GetAttributes(TargetActor)获取目标角色的属性组件(AttributeComp)。
      • 如果成功获取到属性组件(即AttributeComp不为空),则调用属性组件的ApplyHealthChange方法,传入伤害施加者和伤害量,来应用健康值的变化,并返回该操作的结果(布尔值)。
      • 如果没有获取到属性组件,则直接返回false,表示伤害应用失败。
  2. ApplyDirectionalDamage函数​​:

    • ​功能​​:对目标角色应用带有方向性的伤害,除了伤害施加者、目标角色和伤害量外,还考虑了碰撞结果(HitResult),用于在特定方向上施加冲力。
    • ​实现逻辑​​:
      • 首先调用ApplyDamage函数尝试对目标角色应用伤害。如果ApplyDamage返回true(即伤害应用成功):
        • 从碰撞结果(HitResult)中获取对应的组件(HitComp)。
        • 检查该组件是否正在模拟物理(通过HitComp->IsSimulatingPhysics(HitResult.BoneName))。
        • 如果组件正在模拟物理,则通过HitComp->AddImpulseAtLocation方法在碰撞点(HitResult.ImpactPoint)施加一个冲力,冲力的方向由碰撞法线(HitResult.ImpactNormal)和大小(30000.0f)决定,同时指定了受影响的骨骼名称(HitResult.BoneName)。
      • 最后返回true,表示带有方向性的伤害应用成功。如果ApplyDamage返回false,则整个函数也返回false

可能的改进或扩展点

  1. ​错误处理和日志记录​​:目前代码在获取属性组件失败或组件不模拟物理时只是简单地返回结果,可以考虑添加一些日志记录,方便调试时了解具体发生了什么情况。例如使用UE_LOG宏(如果是虚幻引擎项目)来记录相关信息。
  2. ​伤害类型和效果扩展​​:目前只考虑了简单的健康值变化和方向性冲力,实际游戏中可能需要考虑更多伤害类型(如火焰伤害、毒素伤害等)以及不同的伤害效果(如减速、眩晕等),可以在AttributeCompApplyHealthChange方法中进一步扩展相关逻辑。
  3. ​性能优化​​:如果频繁调用这些函数,可能需要考虑性能优化,比如减少不必要的组件获取操作等。

在虚幻引擎(Unreal Engine)中继承UBlueprintFunctionLibrary有以下几个重要思想和实际用途:

提供可在蓝图中使用的自定义函数

UBlueprintFunctionLibrary是一个基类,用于创建可以在虚幻蓝图系统中调用的自定义函数集合。通过继承它,开发者能够将一些通用的、复杂的或者需要在蓝图层面复用的逻辑封装成函数。这样,关卡设计师、策划人员等非程序员也可以方便地在蓝图节点中使用这些函数,而无需编写实际的C++代码,极大地提高了开发效率和灵活性。例如,在一个RPG游戏中,可以创建一个继承自UBlueprintFunctionLibrary的类,里面包含计算角色经验值升级所需经验的函数,蓝图设计师在设置角色升级逻辑时就可以直接调用这个函数。

代码组织和模块化

将相关的功能函数集中放在一个继承自UBlueprintFunctionLibrary的类中,有助于对代码进行良好的组织和管理。不同的功能模块可以分别封装在不同的函数库类里,使得代码结构更加清晰,易于维护和扩展。比如,在一个开放世界游戏中,可能会有专门处理环境交互的函数库类,包含检测角色与场景物体碰撞、获取附近资源点等函数;还有处理角色战斗的函数库类,包含计算伤害、应用状态效果等函数。

复用性和一致性

一旦在UBlueprintFunctionLibrary的派生类中实现了某个函数,就可以在整个项目的多个蓝图中重复使用。这保证了在不同地方调用相同功能时逻辑的一致性,避免了重复编写相同代码导致的潜在错误。例如,计算两点之间距离的函数,在游戏中的寻路系统、物体交互判定等多个场景都可能用到,通过封装在函数库类中,就可以确保在各个地方计算的结果都是准确一致的。

与虚幻引擎生态集成

虚幻引擎的蓝图系统是其重要的可视化编程工具,UBlueprintFunctionLibrary作为与蓝图系统紧密集成的基类,使得自定义函数能够无缝地融入到引擎的生态系统当中。开发者可以利用蓝图的可视化连线、变量传递等特性,方便地与这些自定义函数进行交互,充分发挥虚幻引擎强大的可视化开发优势。

总之,继承UBlueprintFunctionLibrary主要是为了在虚幻引擎项目中更好地组织、复用代码,并增强代码的可访问性和易用性,以适应不同角色在游戏开发过程中的需求。

meta标识符

在虚幻引擎(Unreal Engine)中,Meta 是一个非常重要的概念,主要用于 属性(UPROPERTY)函数(UFUNCTION) 和 类(UCLASS) 的元数据(Metadata)标记,以控制编辑器行为、序列化、蓝图交互等。以下是 Meta 的详细解析:


1. Meta 的作用

Meta 用于提供额外的信息或指令,主要影响:

  • 编辑器 UI 行为(如分类、显示名称、工具提示)

  • 序列化规则(如是否保存到磁盘)

  • 蓝图交互(如是否在蓝图中可见)

  • 运行时行为(如网络复制条件)


2. 常见使用场景

(1) 在 UPROPERTY 中使用 Meta

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = “Character”,
meta = (DisplayName = “生命值”, ToolTip = “角色的生命值,范围为0-100”))
float Health;

常用 Meta 参数

参数 作用
DisplayName 在编辑器中的显示名称(覆盖变量名)
ToolTip 鼠标悬停时的提示文本
ClampMin/ClampMax 限制输入范围(如 ClampMin="0"
UIMin/UIMax 在 Slider 控件中的范围
EditCondition 动态控制属性是否可编辑(如 EditCondition="bIsEditable"
BlueprintPrivate 对蓝图隐藏该属性

(2) 在 UFUNCTION 中使用 Meta

UFUNCTION(BlueprintCallable, Category = “AI”,
meta = (DisplayName = “计算伤害”, Keywords = “Damage|Attack”))
void CalculateDamage(float BaseDamage);

常用 Meta 参数

参数 作用
Keywords 在蓝图搜索时的关键字(用 `
DefaultToSelf 自动传递 self 对象(如 DefaultToSelf="Target"
HidePin 隐藏蓝图中函数的某些引脚
AdvancedDisplay 将参数标记为“高级”(默认折叠)

(3) 在 UCLASS 中使用 Meta

UCLASS(Blueprintable, meta = (DisplayName = “玩家角色”, ShortToolTip = “可控制的玩家实体”))
class APlayerCharacter : public ACharacter;

常用 Meta 参数

参数 作用
BlueprintType 允许该类在蓝图中作为变量类型
IsBlueprintBase 允许蓝图继承该类
ChildCanTick 子类是否可以重写 Tick 事件

3. 高级用法

(1) 动态编辑条件

通过 meta = (EditCondition) 实现属性联动的显隐/禁用逻辑:

UPROPERTY(EditAnywhere)
bool bUseCustomSpeed;

UPROPERTY(EditAnywhere, meta = (EditCondition = “bUseCustomSpeed”))
float CustomSpeed; // 仅当bUseCustomSpeed为true时可编辑

(2) 网络同步控制

UPROPERTY(Replicated, meta = (ReplicateCondition = “bIsAlive”))
float Health; // 仅在bIsAlive为true时同步

(3) 资产引用规则

UPROPERTY(EditAnywhere, meta = (AllowedClasses = “Texture2D,Material”))
UObject* VisualAsset; // 限制只能选择纹理或材质


4. 实际案例

案例1:属性分类与工具提示

UPROPERTY(EditDefaultsOnly, Category = “Weapon”,
meta = (DisplayName = “伤害值”, ToolTip = “武器的基础伤害值”))
float BaseDamage;

案例2:动态滑块控制

UPROPERTY(EditAnywhere, meta = (UIMin = 0, UIMax = 100, ClampMin = 0, ClampMax = 100))
int32 AccuracyPercent;


5. 注意事项

  1. 区分 Meta 和 Specifiers
    • BlueprintReadWrite 是 说明符(Specifier),直接控制基础行为。
    • DisplayName 是 元数据(Meta),提供附加信息。
  2. 编辑器刷新问题
    修改 Meta 后可能需要重启编辑器或重新编译才能生效。
  3. C++ 与蓝图的差异
    部分 Meta 参数(如 BlueprintPrivate)仅影响蓝图,不影响 C++ 代码。

FgameplayTag游戏标签

在游戏开发(特别是使用虚幻引擎等引擎开发时),Play Tag(玩法标签)是一种用于标识和分类游戏玩法相关元素的机制。

  1. ​功能特性​
    • ​分类与组织​
      • FGamePlayTag可以用来对游戏中的各种玩法相关的内容进行分类,比如不同的角色能力类型(近战型、远程型等)、任务类型(主线任务、支线任务、日常任务等)、游戏模式(单人模式、多人合作模式、多人对战模式等)。通过这种分类方式,开发团队可以更高效地管理和组织游戏内容。
    • ​筛选与查询​
      • 在游戏的开发工具或编辑器中,开发者可以使用这些标签来筛选特定的游戏元素。例如,当想要查找所有与多人对战模式相关的游戏逻辑、资产(如地图、角色等)时,可以通过“多人对战模式”这个FGamePlayTag进行快速筛选。
    • ​游戏逻辑关联​
      • 在游戏的运行时逻辑中,FGamePlayTag也起到重要作用。比如,根据玩家当前的任务类型标签(如支线任务标签),游戏可以动态调整界面显示、NPC交互行为或者任务提示等相关逻辑。
  2. ​实现方式(以虚幻引擎为例)​
    • 在虚幻引擎中,FGamePlayTag是一种轻量级的数据结构。开发人员可以在项目的代码和蓝图中定义和使用这些标签。
    • 可以通过C++代码创建自定义的FGamePlayTag,并将其应用到相应的游戏对象、组件或者函数中。在蓝图中,也有直观的节点来处理FGamePlayTag,例如检查某个对象是否具有特定的FGamePlayTag,或者根据FGamePlayTag执行不同的分支逻辑等。![[Pasted image 20250413161403.png]]
    • FGameplayTag 是一种用于标识和分类游戏内容的强大工具。具体到你提供的代码片段中的 FindNativeInputActionByTag 函数,FGameplayTag 的作用主要体现在以下几个方面:

1. ​​输入动作的分类与标识​

FGameplayTag 在这里用于对不同的输入动作进行分类和标识。通过为每个输入动作分配一个或多个标签,开发者可以方便地管理和引用这些动作。例如:

  • ​动作类型​​:如“攻击”、“防御”、“跳跃”、“移动”等。
  • ​上下文​​:如“近战”、“远程”、“特殊”等。
  • ​状态​​:如“按下”、“释放”、“长按”等。

2. ​​简化输入系统的管理​

使用 FGameplayTag 可以简化输入系统的管理,特别是在处理复杂的输入逻辑时。例如,你可以通过标签来动态绑定和解绑输入动作,而无需硬编码每个输入的具体关联。这在需要频繁更改或扩展输入系统时尤为有用。

3. ​​提高代码的可读性和可维护性​

通过使用标签而不是直接引用具体的输入动作,代码变得更加抽象和通用。这不仅提高了代码性,还使得在修改或扩展输入动作时更加方便。例如,添加一个新的输入动作只需要为其分配一个合适的标签,而不需要修改大量的查找逻辑。

4. ​​支持运行时的动态配置​

FGameplayTag 允许在运行时动态地查询和管理输入动作。这意味着你可以根据游戏的状态或玩家的设置,动态地决定哪些输入动作是可用的。例如,某些技能可能只在特定条件下可用,通过标签可以轻松实现这种条件判断。

5. ​​与其他系统的集成​

FGameplayTag 不仅限于输入系统,它还可以与其他虚幻引擎的系统(如 Gameplay Ability System, GAS)无缝集成。例如,你可以使用相同的标签来控制角色的能力激活、动画播放等,从而实现跨系统的一致性和协同工作。

具体到你的代码

游戏标签关键宏:

  • UE_DECLARE_GAMEPLAY_TAG_EXTERN(TagName):在头文件声明一个外部标签变量。

  • UE_DEFINE_GAMEPLAY_TAG_EXTERN(TagName, TagString):在CPP文件定义并初始化这个变量。

  • UE_DECLARE_GAMEPLAY_TAG_EXTERN(TagName)展开后类似:

  • extern FGameplayTag TAG_Weapon_Rifle;

  • UE_DEFINE_GAMEPLAY_TAG_EXTERN(TagName, TagString)展开后类似

  • FGameplayTag TAG_Weapon_Rifle =FGameplayTag::RequestGameplayTag(FName(“Weapon.Rifle”));

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    UInputAction* UDataAsset_input::FindNativeInputActionByTag(const FGameplayTag& inTags) {
    // 遍历这个数组,如果标签等于动作
    for (const FWarriorInput& InputActionConfig : NativeInput) {
    if (InputActionConfig.Tags == inTags && InputActionConfig.Input_Action)
    {
    return InputActionConfig.Input_Action;
    }
    }
    return nullptr;
    }
  • ​功能​​:该函数通过传入的 FGameplayTag (inTags) 来查找对应的 UInputAction 对象。

  • ​用途​​:
    查找特定动作​**​:根据标签快速找到需要的输入动作,例如“跳跃”或“攻击”。

  • ​动态绑定​​:在运行时根据不同的标签动态绑定输入动作,增强游戏的灵活性。

  • ​条件控制​​:结合游戏状态,通过标签控制哪些输入动作可用。

输入配置映射集合-DataAsset

用与将输入标签映射到唯一的输入操作

GENERATED_BODY()宏

GENERATED_BODY() 宏是一个非常重要的部分,它通常出现在由 Unreal Engine 的 Unreal Header Tool (UHT) 自动生成代码的类中。这个宏用于处理 Unreal Engine 的反射系统,它使得类能够在编辑器中被识别和使用,同时也支持序列化、网络复制等功能。
==tips:看见类与结构体就放在里面!==

USTRUCT

USTRUCT(BlueprintType) 是一个用于声明结构的宏,它告诉 Unreal Engine 这个结构可以在蓝图中被使用

TArray

TArray 是 Unreal Engine 4 和 Unreal Engine 5 中用于存储动态数组的类模板。它提供了一系列的方法和属性来管理数组中的元素,包括添加、删除、插入、查找等操作。TArray 可以存储任何类型的数据,包括基本数据类型、结构体、类实例等。


TSubclassOf ​

TSubclassOf 是 Unreal Engine 提供的一个模板类,用于安全地存储和引用特定类型的类(必须是 UObject 的子类)。它的核心功能包括:

  • ​类型安全​​:确保只能赋值为指定父类的子类(例如 TSubclassOf<AActor> 只能接受 AActor 或其子类)。
  • ​编辑器集成​​:在 UE 编辑器中提供友好的下拉菜单,方便选择类。
  • ​蓝图兼容​​:允许在蓝图中动态选择和引用类。

虚幻接口

作为父类被指定被继承对象,即可调用其中的功能。实现功能复用(接口用于定义一组方法,但不实现它们。任何类都可以实现该接口并覆盖这些方法——虚基类)

使用流程​

  1. ​定义接口​​(C++ 或蓝图)。
  2. ​实现接口​​(在 Actor 类中继承并覆盖方法)。
  3. ​调用接口​​(通过 Cast<IInterface> 检查并调用方法)。
    代码示例:

定义接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Enemy.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "HealthInterface.h" // 包含接口头文件
#include "Enemy.generated.h"

UCLASS()
class YOURGAME_API AEnemy : public AActor, public IHealthInterface
{
GENERATED_BODY()

public:
AEnemy();

// 实现接口方法
virtual float GetHealth_Implementation() const override;

private:
UPROPERTY(EditAnywhere, Category = "Health")
float Health = 100.0f;
};
1
2
3
4
5
6
7
8
9
10
11
12
// Enemy.cpp
#include "Enemy.h"

AEnemy::AEnemy()
{
PrimaryActorTick.bCanEverTick = true;
}

float AEnemy::GetHealth_Implementation() const
{
return Health;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// PlayerCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "HealthInterface.h" // 包含接口头文件
#include "PlayerCharacter.generated.h"

UCLASS()
class YOURGAME_API APlayerCharacter : public ACharacter
{
GENERATED_BODY()

public:
APlayerCharacter();


UFUNCTION(BlueprintCallable, Category = "Combat")
void AttackEnemy(AActor* Enemy);
};

使用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// PlayerCharacter.cpp
#include "PlayerCharacter.h"

void APlayerCharacter::AttackEnemy(AActor* Enemy)
{
if (!Enemy) return;

// 检查 Enemy 是否实现了 IHealthInterface
if (IHealthInterface* HealthInterface = Cast<IHealthInterface>(Enemy))
{
float Health = HealthInterface->GetHealth(); // 调用接口方法
UE_LOG(LogTemp, Warning, TEXT("Enemy Health: %f"), Health);

// 如果血量 <= 0,消灭敌人
if (Health <= 0.0f)
{
Enemy->Destroy();
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Enemy does not implement IHealthInterface!"));
}
}

TSubclassOf 与蓝图的关系​

​(1) 在 C++ 中使用 TSubclassOf

  • ​声明方式​​:
    1
    2
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Abilities")
    TSubclassOf<UGameplayAbility> AbilityClass;
  • ​用途​​:
    • 在 C++ 中定义一个变量,用于存储某个类的引用(如技能、武器、道具的类)。
    • 在编辑器中,该变量会显示为一个下拉菜单,列出所有符合条件的类(如 UGameplayAbility 的子类)。

​(2) 在蓝图中使用 TSubclassOf

  • ​蓝图兼容性​​:
    • 如果 C++ 中声明了 UPROPERTY(EditAnywhere) 的 TSubclassOf 变量,蓝图可以直接访问并修改它。
    • 在蓝图中,该变量会显示为一个 ​​类选择器​​(Class Picker),允许从符合条件的类中选择。
  • ​动态赋值​​:
    • 蓝图可以通过 ​​“Make” 节点​​ 或 ​​“Set” 节点​​ 动态修改 TSubclassOf 变量的值。
    • 例如:在蓝图中选择一个技能类并赋值给 AbilityClass

​3. TSubclassOf 是否意味着“由蓝图添加数据”?​

​(1) 不一定​

  • ​C++ 也可以直接赋值​​:
    1
    AbilityClass = UMyFireballAbility::StaticClass(); // 直接在 C++ 中硬编码
    此时数据完全由 C++ 控制,与蓝图无关。
  • ​蓝图可以覆盖​​:
    • 如果变量标记为 EditAnywhere 或 BlueprintReadWrite,蓝图可以修改它的值。
    • 但如果没有暴露给蓝图(如仅标记为 VisibleAnywhere),则蓝图无法修改。

​(2) 蓝图的主要作用​

  • ​动态选择类​​:
    • 蓝图可以通过类选择器动态选择类(如根据玩家选择切换技能)。
  • ​扩展功能​​:
    • 蓝图可以基于 TSubclassOf 变量动态生成对象(如 SpawnActorFromClass)。

​4. 实际应用示例​

​(1) C++ 中定义技能类变量​

1
2
3
4
5
6
7
8
9
10
// MyCharacter.h
UCLASS()
class AMyCharacter : public ACharacter
{
GENERATED_BODY()

public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Abilities")
TSubclassOf<UGameplayAbility> FireballAbilityClass; // 可在蓝图中修改
};

​5. 关键特性总结​

特性 TSubclassOf 的作用
​类型安全​ 确保只能赋值为指定父类的子类
​编辑器下拉菜单​ 在 C++ 编辑器和蓝图中显示类选择器
​动态赋值​ 允许在运行时修改引用的类
​数据来源​ 可硬编码(C++)、蓝图或数据资产

​6. 总结​

  • TSubclassOf 的核心功能​​:安全地引用类,支持编辑器集成和蓝图交互。
  • ​是否依赖蓝图​​:
    • ​否​​:C++ 可以完全控制类的赋值(硬编码或数据资产)。
    • ​是​​:如果希望动态选择类(如技能切换),蓝图可以通过类选择器实现。
  • ​典型用途​​:技能系统、武器/道具管理、动态生成对象等。

如果需要完全由蓝图添加数据,通常需要结合 ​​数据资产(Data Asset)​​ 或 ​​蓝图变量​​ 使用,而 TSubclassOf 本身只是一个类型安全的类引用工具。

数据资产

-用于存储和管理游戏中的配置数据,如输入动作、角色属性等。通过数据资产,可以将配置与代码分离,便于非程序员进行编辑和管理。

同步加载与异步加载

一、同步加载(Synchronous Loading)

1. 工作原理

同步加载是指游戏主线程在执行加载操作时,会暂停并等待资源完全加载完毕后再继续执行后续代码。这意味着在资源加载期间,游戏可能会出现短暂的卡顿或“冻结”现象。

2. 使用方法

在虚幻引擎中,同步加载通常通过调用 LoadObject 或 StaticLoadObject 等函数实现。例如:

1
2
3
4
5
// 同步加载一个Actor类
AActor* LoadedActor = Cast<AActor>(StaticLoadObject(AActor::StaticClass(), NULL, TEXT("/Game/Path/To/Actor.Actor")));

// 同步加载一个关卡
UGameplayStatics::OpenLevel(this, FName("LevelName"), true);

3. 优缺点

​优点:​

  • ​实现简单​​:代码逻辑直观,易于理解和实现。
  • ​确保资源可用​​:加载完成后,资源立即可用,无需担心异步加载的回调问题。
    ​缺点:​
  • ​性能影响​​:加载过程中主线程被阻塞,可能导致帧率下降和用户体验不佳。
  • ​卡顿现象​​:特别是在加载大型资源时,用户可能会明显感受到游戏的停顿。

4. 适用场景

  • ​开发阶段​​:在编辑器中进行快速迭代和测试时,同步加载可以简化流程。
  • ​小型项目​​:资源较少且加载时间短的项目,同步加载对用户体验影响较小。
  • ​特定需求​​:某些需要确保资源立即可用的特定场景,如加载菜单界面所需资源。

二、异步加载(Asynchronous Loading)

1. 工作原理

异步加载允许游戏主线程在资源加载的同时继续执行其他任务,不会因为加载操作而阻塞。加载过程在后台进行,通过回调函数或事件通知主线程资源已加载完毕。

2. 使用方法

虚幻引擎提供了多种异步加载的方法,包括 FStreamableManagerAsyncLoading 命名空间下的函数以及蓝图中的异步节点。

​使用 FStreamableManager 加载资产:​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义一个委托用于处理加载完成事件
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAssetLoaded, UObject*, LoadedAsset);

UCLASS()
class MYGAME_API UMyAssetLoader : public UObject
{
GENERATED_BODY()

public:
UPROPERTY(BlueprintAssignable, Category = "AssetLoader")
FOnAssetLoaded OnAssetLoaded;

void LoadAssetAsync(const FString& AssetPath)
{
FSoftObjectPath SoftPath(AssetPath);
FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
Streamable.RequestAsyncLoad(SoftPath, [this, SoftPath]()
{
UObject* LoadedAsset = SoftPath.TryLoad();
OnAssetLoaded.Broadcast(LoadedAsset);
});
}
};

​使用 UGameplayStatics::LoadStreamLevelAsync 加载关卡异步:​

1
2
// 异步加载一个子关卡
UGameplayStatics::LoadStreamLevelAsync(this, FName("SubLevelName"), true, true, FLatentActionInfo());

​蓝图中的异步加载节点:​

虚幻引擎的蓝图系统也提供了异步加载的节点,如 Async Load Assets,方便非编程人员使用。

3. 优缺点

​优点:​

  • ​提升性能​​:避免主线程阻塞,保持游戏的流畅性和响应性。
  • ​优化用户体验​​:尤其在加载大型资源或关卡时,用户不会感受到明显的卡顿。
  • ​更好的资源管理​​:可以更灵活地控制加载顺序和优先级,优化内存使用。

​缺点:​

  • ​实现复杂​​:需要处理回调函数或事件,代码逻辑相对复杂。
  • ​资源可用性延迟​​:资源在加载完成前不可用,可能需要额外的处理来管理资源状态。

4. 适用场景

  • ​大型项目​​:资源丰富且加载时间较长的项目,异步加载能显著提升用户体验。
  • ​开放世界游戏​​:需要动态加载和卸载远距离区域的资源,减少初始加载时间。
  • ​多平台优化​​:在不同性能的设备上保持一致的性能表现,特别是在移动设备上尤为重要。

三、同步加载与异步加载的对比

特性 同步加载 异步加载
​主线程阻塞​
​性能影响​ 可能导致帧率下降和卡顿 更高的性能,保持流畅的游戏体验
​实现复杂度​ 简单直观 较复杂,需要处理回调或事件
​用户体验​ 可能有明显的加载停顿 加载过程平滑,用户体验更好
​适用场景​ 小型项目、开发阶段、特定需求 大型项目、开放世界、多平台优化

资源导入相关问题

1.所有外部资源必须导入至content文件夹内
2.你如果是自己从网上鼓捣下载下来的uesset文件,这种文件虽然可以直接导入至content,但是如果路径与原项目路径名不一致则会出现无法定向贴图材质骨骼等问题。
所以一定要确定你这个资源原来的路径。在你自己的项目中同样使用这个路径。

常用代码—使用这些你就能实现游戏的绝大多数功能

生成在指定坐标生成一个类(特效、声音等)

1
2
3
4
5
6
7
void ASCharacter::PrimaryAttack()
{
FTransform SpawnTM = FTransform(GetControlRotation(), GetActorLocation());
FActorSpawnParams SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
GetWorld()->SpawnActor<AActor>(ProjectileClass, SpawnTM, SpawnParams);
}

获取插槽socket位置

1
FVector HandLocation = GetMesh()->GetSocketLocation("Muzzle");

添加并使用径向力组件

1
2
3
4
5
6
7
8
9
10
11
12
13
// MyActor.h
UCLASS()
class YOURGAME_API AMyActor : public AActor
{
GENERATED_BODY()

public:
AMyActor();

protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Effects")
URadialForceComponent* RadialForce;//径向力组件
};
1
2
3
4
5
6
7
8
9
10
// MyActor.cpp
AMyActor::AMyActor()
{
RadialForce = CreateDefaultSubobject<URadialForceComponent>(TEXT("RadialForce"));
RadialForce->SetupAttachment(RootComponent);
RadialForce->Radius = 500.0f; // 力的作用范围
RadialForce->Strength = 1000.0f; // 力的大小
RadialForce->bImpulseVelChange = true; // 是否直接改变速度(true=瞬时速度变化)
RadialForce->bIgnoreOwningActor = true; // 是否忽略自身
}

在需要施加力的地方调用 FireImpulse()

1
RadialForce->FireImpulse(); // 触发径向力

如果 Actor 数量很多,建议使用 ​​Sphere Trace​​ 或 ​​Overlap Sphere​​ 来筛选范围内的 Actor,而不是遍历所有 Actor。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
TArray<FOverlapResult> Overlaps;
FCollisionShape Sphere = FCollisionShape::MakeSphere(Radius);
GetWorld()->OverlapMultiByChannel(Overlaps, Origin, FQuat::Identity, ECC_PhysicsBody, Sphere);

for (const FOverlapResult& Overlap : Overlaps)
{
AActor* Actor = Overlap.GetActor();
if (Actor)
{
// 施加力...
}
} ```

## 射线检测
```cpp
void USInteractionComponent::PrimaryInteract()
{
FCollisionObjectQueryParams ObjectQueryParams;
ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldDynamic);

AActor* MyOwner = GetOwner();

FVector EyeLocation;
FRotator EyeRotation;
MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation);

FVector End = EyeLocation + (EyeRotation.Vector() * 1000);

FHitResult Hit;
GetWorld()->LineTraceSingleByObjectType(Hit, EyeLocation, End, ObjectQueryParams);
}
````
`FHitResult` 结构体(射线检测 `Hit` 的返回结果)保存了多种数据类型,用于描述碰撞检测的详细信息。以下是 `FHitResult` 中包含的主要数据类型及其用途:

---

### ​**​1. 碰撞位置与方向​**​

|数据类型|成员变量|说明|
|---|---|---|
|​**​`FVector`​**​|`ImpactPoint`|碰撞发生的精确位置(世界坐标系)。|
|​**​`FVector`​**​|`Location`|与 `ImpactPoint` 类似,但可能因碰撞规则不同(如射线起点偏移)。|
|​**​`FVector`​**​|`Normal`|碰撞表面的法线方向(单位向量)。|
|​**​`FVector`​**​|`TraceStart` / `TraceEnd`|射线检测的起点和终点(需手动记录,`FHitResult` 本身不存储)。|

---

### ​**​2. 碰撞对象信息​**​

|数据类型|成员变量|说明|
|---|---|---|
|​**​`AActor*`​**​|`Actor`|被碰撞的 Actor 对象指针。|
|​**​`UPrimitiveComponent*`​**​|`Component`|被碰撞的组件(如静态网格体、骨骼网格体等)。|
|​**​`FName`​**​|`BoneName`|如果碰撞的是骨骼网格体,返回命中的骨骼名称。|

---

### ​**​3. 物理与材质信息​**​

|数据类型|成员变量|说明|
|---|---|---|
|​**​`float`​**​|`Distance`|从射线起点到碰撞点的距离。|
|​**​`float`​**​|`Time`|碰撞发生的时间(通常用于运动学模拟)。|
|​**​`UPhysicalMaterial*`​**​|`PhysMat`|碰撞表面的物理材质(如金属、木材等)。|
|​**​`FMaterialRenderProxy*`​**​|`Material`|碰撞表面的渲染材质(用于视觉效果)。|

---

### ​**​4. 调试与扩展信息​**​

|数据类型|成员变量|说明|
|---|---|---|
|​**​`uint8`​**​|`FaceIndex`|命中多边形的索引(适用于静态网格体)。|
|​**​`FName`​**​|`Item`|自定义标识符(需手动设置)。|
|​**​`FHitResult::HitFlags`​**​|`Flags`|标记碰撞类型(如是否为阻挡碰撞)。|

---

### ​**​5. 其他数据​**​

|数据类型|成员变量|说明|
|---|---|---|
|​**​`FVector`​**​|`Impulse`|如果碰撞触发了物理响应,返回施加的冲量。|
|​**​`FHitResult::HitGroup`​**​|`HitGroup`|命中的碰撞组(需自定义设置)。|



射线击中后需要判断被击中类是否存在接口


以下是代码片段示例
```cpp
FHitResult Hit;
if (GetWorld()->LineTraceSingleByObjectType(Hit, EyeLocation, End, ObjectQueryParams))
{
// 尝试将击中的Actor转换为实现了指定接口的类型
IMyInterface* InterfaceActor = Cast<IMyInterface>(Hit.GetActor());
if (InterfaceActor)
{
// 这里表示击中的Actor实现了IMyInterface接口,可以进行相关操作
InterfaceActor->SomeInterfaceFunction(); // 假设接口有一个这样的函数
}

也可以这样判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void USInteractionComponent::PrimaryInteract()
{
FCollisionObjectQueryParams ObjectQueryParams;
ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldDynamic);
AActor* MyOwner = GetOwner();
FVector EyeLocation;
FRotator EyeRotation;
MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
FVector End = EyeLocation + (EyeRotation.Vector() * 1000);
FHitResult Hit;
GetWorld()->LineTraceSingleByObjectType(Hit, EyeLocation, End, ObjectQueryParams);
AActor* HitActor = Hit.GetActor();
if (HitActor)
{
if (HitActor->Implements<ISGameplayInterface>())//检查被击中类是否实现接口
{
APawn* MyPawn = Cast<APawn>(MyOwner);
ISGameplayInterface::Execute_Interact(HitActor, MyPawn);//调用类上的接口
}
}
}

球体横扫检测==SweepMultiByObjectType==

1
2
3
4
TArray<FHitResult> Hits;
FCollisionShape Shape;
Shape.SetSphere(10.0f);
GetWorld()->SweepMultiByObjectType(Hits, EyeLocation, End, FQuat::Identity, objectQueryParams, Shape);

可以保存一个半径为10的球体横扫碰撞到的所有数据,返回一个布尔值用于检测是否碰撞到物体。