Unreal Engine——《Aura》Gameplay Ability System学习报告

Unreal Engine——《Aura》Gameplay Ability System学习报告

ELecmark VIP

虚幻引擎5 - 游戏技能系统(GAS) - 俯视角RPG

Create a multiplayer RPG with Unreal Engine’s Gameplay Ability System (GAS)!

0.Introduction / 介绍

  • 1个讲座·12 分钟 / 1 Lecture · 12 Minutes

  • Introduction

    12:07

1.Project Creation / 项目创建

  • 14 个讲座·2 小时 26 分钟 / 14 Lectures · 2 Hours 26 Minutes

  • Jetbrains Rider - Now Free for Non-Commercial Use!

    00:26

  • Project Creation

    04:56

  • Setting up Version Control (Optional)

    07:26

  • The Base Character Class

    10:00

🧙‍♀️ AuraCharacterBase 基类分析

类定义特性

1
UCLASS(Abstract)  // 标记为抽象类,不能被实例化
  • 抽象基类:只作为父类被继承,不能直接放置到关卡中
  • 设计目的:提供通用功能给英雄和敌人角色共享

构造函数核心实现

1. Tick 系统禁用
1
PrimaryActorTick.bCanEverTick = false;
  • 性能优化:角色不需要每帧更新时禁用 Tick
  • 典型场景:AI 控制或简单动画的角色
2. 武器组件创建
1
Weapon = CreateDefaultSubobject<USkeletalMeshComponent>("Weapon");
  • **CreateDefaultSubobject**:在构造函数中创建组件
  • 命名规范:给组件指定唯一名称便于调试
3. 武器附着系统
1
Weapon->SetupAttachment(GetMesh(), FName("WeaponHandSocket"));
  • 附着到骨骼:绑定到网格体的指定骨骼插槽
  • **FName("WeaponHandSocket")**:使用骨骼插槽名称
  • 动画同步:武器随角色动画自然移动
4. 碰撞设置
1
Weapon->SetCollisionEnabled(ECollisionEnabled::NoCollision);
  • **NoCollision**:禁用武器所有碰撞
  • 设计考虑
    • 避免武器与环境的意外碰撞
    • 伤害检测通过其他系统实现(如伤害盒)

UPROPERTY 配置

1
2
UPROPERTY(EditAnywhere, Category = "Combat")
TObjectPtr<USkeletalMeshComponent> Weapon;
  • **EditAnywhere**:可在蓝图实例和类默认值中编辑
  • **Category = "Combat"**:在编辑器”Combat”分类下显示
  • **TObjectPtr**:UE5 安全指针,自动处理垃圾回收

架构设计意义

继承结构
1
2
3
4
5
ACharacter (UE基类)

AAuraCharacterBase (项目基类)
├── AHeroCharacter (玩家英雄)
└── AEnemyCharacter (敌人)
职责分离
1. **基类**:通用组件(武器、基础属性)
2. **派生类**:特定逻辑(玩家控制、AI行为)

扩展建议

武器系统增强
1
2
3
4
// 可添加的扩展功能
virtual void EquipWeapon(USkeletalMesh* NewWeaponMesh);
virtual void Attack();
virtual void TakeDamage(float DamageAmount);
GAS 集成预留
1
2
3
// 为后续游戏技能系统准备
class UAbilitySystemComponent;
class UAttributeSet;

最佳实践

  • 抽象基类:提取公共功能,减少代码重复

  • 组件化设计:武器作为独立组件便于更换

  • 性能优化:无Tick需求时及时禁用

  • 碰撞分离:攻击检测与渲染碰撞分开处理

  • Player and Enemy Characters

    06:09

  • Character Blueprint Setup

    12:13

  • Animation Blueprints

    14:30

  • Enhanced Input

    09:43

  • Aura Player Controller

    11:14

🎮 AuraPlayerController 分析

  • 自定义玩家控制器,管理输入系统和光标控制
  • 使用 UE5 增强输入系统(Enhanced Input)
  • 支持 多人游戏网络复制

🔑 核心特性

1. 构造函数设置
1
bReplicates = true;  // 启用网络复制
  • 作用:让服务器同步控制器状态到所有客户端
2. 输入系统初始化
1
Subsystem->AddMappingContext(AuraContext, 0);
  • 优先级 0:基础输入上下文
  • 使用 EditAnywhere 可在编辑器中灵活设置
3. 光标控制配置
1
2
bShowMouseCursor = true;
DefaultMouseCursor = EMouseCursor::Default;
  • 显示鼠标光标
  • 使用默认光标样式
4. 输入模式设置
1
FInputModeGameAndUI InputModeData;
  • 游戏+UI混合模式:同时处理游戏输入和UI交互
  • 不锁定鼠标:可自由移动出视口
  • 输入时不隐藏光标:保持可见性

⚙️ 技术要点

  • **check()**:开发时断言验证资源有效性
  • **TObjectPtr**:UE5 推荐的对象指针(自动垃圾回收)
  • **UEnhancedInputLocalPlayerSubsystem**:管理输入上下文的核心系统

🎯 应用场景

  • 俯视角 RPG 游戏

  • 需要鼠标交互的游戏

  • 多人网络游戏

  • Movement Input

    16:14

🔄 AuraPlayerController 输入系统升级

新增输入动作支持

1
2
UPROPERTY(EditAnywhere, Category = "Input")
TObjectPtr<UInputAction> MoveAction; // 移动输入动作资源
  • **UInputAction**:增强输入系统的原子输入单位
  • 可配置性:在编辑器中指定具体的移动输入动作

输入组件重写

1
virtual void SetupInputComponent() override;  // 重写输入绑定
  • 扩展点:在父类基础上添加增强输入绑定
  • 最佳实践:将输入绑定逻辑集中在此函数

SetupInputComponent 实现

1. 获取增强输入组件
1
2
UEnhancedInputComponent* EnhancedInputComponent = 
CastChecked<UEnhancedInputComponent>(InputComponent);
  • **CastChecked**:安全转换,失败时断言崩溃
  • 类型转换:将基础输入组件转为增强输入组件
2. 输入动作绑定
1
2
3
4
5
6
EnhancedInputComponent->BindAction(
MoveAction, // 输入动作资源
ETriggerEvent::Triggered, // 触发事件类型
this, // 绑定的对象
&AAuraPlayerController::Move // 回调函数
);
  • **ETriggerEvent::Triggered**:输入持续触发(如按住按键)
  • 函数指针绑定:将输入事件连接到成员函数

Move 函数实现

1. 输入值解析
1
const FVector2D InputAxisVector = InputActionValue.Get<FVector2D>();
  • **FVector2D**:二维输入向量(WASD/摇杆)
  • 泛型获取:安全获取指定类型的输入值
2. 方向计算
1
const FRotator YawRotation(0.f, Rotation.Yaw, 0.f);  // 仅保留偏航角
  • 简化旋转:俯视角游戏中忽略俯仰和滚转
  • 偏航角:控制角色左右旋转的方向
3. 坐标系转换
1
2
3
4
const FVector ForwardDirection = 
FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); // 前方向
const FVector RightDirection =
FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); // 右方向
  • 旋转矩阵:将局部方向转换为世界方向
  • 坐标系
    • X轴:前方向(前进/后退)
    • Y轴:右方向(左移/右移)
4. 应用移动输入
1
2
ControlledPawn->AddMovementInput(ForwardDirection, InputAxisVector.Y);
ControlledPawn->AddMovementInput(RightDirection, InputAxisVector.X);
  • 向量分解
    • Y分量:控制前后移动(W/S)
    • X分量:控制左右移动(A/D)
  • 叠加效果:同时按下两个方向键产生对角线移动

输入流程总结

1
输入动作 → 绑定到函数 → 解析向量 → 计算方向 → 应用移动

设计优势

1. 灵活性
  • 输入映射可在编辑器中配置
  • 支持多种输入设备(键盘、手柄、鼠标)
2. 模块化
  • 输入动作与逻辑分离
  • 便于扩展新输入功能
3. 维护性
  • 所有输入绑定集中管理

  • 清晰的输入处理流程

  • Game Mode

    13:23

🤺 AuraCharacter 玩家角色配置

角色运动组件设置

1. 朝向运动方向
1
GetCharacterMovement()->bOrientRotationToMovement = true;
  • 自动朝向:角色自动转向移动方向
  • 俯视角特性:视觉上更自然的移动反馈
  • 替代方案:如果设为 false,需要手动控制旋转
2. 旋转速度配置
1
GetCharacterMovement()->RotationRate = FRotator(0.f, 400.f, 0.f);
  • **FRotator(Pitch, Yaw, Roll)**:三维旋转速度
  • Yaw 400:适中的偏航旋转速度(每秒度数)
  • Pitch/Roll 为 0:俯视角游戏通常不需要俯仰和滚转
3. 平面约束
1
2
GetCharacterMovement()->bConstrainToPlane = true;
GetCharacterMovement()->bSnapToPlaneAtStart = true;
  • 平面锁定:将角色运动限制在水平面(X-Y平面)
  • 初始对齐:游戏开始时自动对齐到平面
  • 俯视角必要性:防止意外的Z轴移动

控制器旋转配置

禁用控制器旋转继承
1
2
3
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
  • 完全解耦:角色旋转与控制器旋转完全独立
  • 设计目的
    • 允许控制器单独控制相机
    • 角色根据移动方向自动旋转
    • 俯视角游戏的典型配置

配置效果总结

运动行为
1
输入处理 → 方向计算 → 自动旋转 → 应用移动
旋转关系
1
2
3
控制器(控制相机)
↓ 独立
角色(根据运动方向自动旋转)

对比分析

设置 开启效果 关闭效果
bOrientRotationToMovement 自动面向移动方向 保持当前方向
bUseControllerRotationYaw 跟随控制器偏转 旋转独立
bConstrainToPlane 锁定水平移动 允许垂直移动

扩展应用

动画适配
  • 自动朝向确保动画播放方向正确
  • 移动停止时保持最后朝向
网络同步
  • 旋转变化会通过网络复制
  • 确保所有客户端看到一致的角色朝向
后续扩展点
1
2
3
4
// 可添加的额外运动配置
GetCharacterMovement()->MaxWalkSpeed = 600.f; // 最大速度
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f; // 刹车减速度
GetCharacterMovement()->GroundFriction = 8.f; // 地面摩擦力

最佳实践建议

1. **俯视角游戏**:保持这些设置不变
2. **第一/第三人称**:需要调整 `bUseControllerRotationYaw = true`
3. **特殊移动**:可能需要禁用平面约束
4. **性能考量**:合理的旋转速率避免视觉不适

-

Enemy Interface

06:38

🎯 Unreal Engine 接口系统深入解析

双类接口架构

1. UInterface(反射类)
1
2
3
4
5
UINTERFACE(MinimalAPI)
class UEnemyInterface : public UInterface
{
GENERATED_BODY()
};
  • **UINTERFACE**:UE宏,声明反射接口类
  • **MinimalAPI**:仅导出必要函数,减少编译依赖
  • **继承自 UInterface**:UE接口基类
  • 作用:为反射系统提供类型信息,可在蓝图中使用
2. IInterface(功能类)
1
2
3
4
5
6
7
class GAS_AURA_API IEnemyInterface
{
GENERATED_BODY()
public:
virtual void HighlightActor() = 0; // 纯虚函数
virtual void UnHighlightActor() = 0; // 纯虚函数
};
  • 功能承载:实际定义接口方法
  • **GENERATED_BODY**:允许使用UE反射功能
  • 核心:纯虚函数声明,强制派生类实现

纯虚函数详解

语法特征
1
virtual void FunctionName() = 0;
  • **= 0**:纯虚函数标记
  • 抽象性:没有默认实现
  • 强制性:派生类必须实现
纯虚函数 vs 普通虚函数
特性 纯虚函数 普通虚函数
定义 = 0 有默认实现
实现要求 必须在派生类实现 可选覆盖
类类型 使类成为抽象类 不强制
实例化 不能直接实例化 可以直接实例化
UE中用途 接口定义 基类提供默认行为

🏗️ 接口设计模式

1. 角色高亮接口
1
2
3
// 敌人交互可视化
virtual void HighlightActor() = 0; // 鼠标悬停时高亮
virtual void UnHighlightActor() = 0; // 鼠标离开时取消高亮
2. 实现示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在敌人角色类中实现
class AEnemyCharacter : public ACharacter, public IEnemyInterface
{
public:
virtual void HighlightActor() override
{
// 实现高亮逻辑:改变材质、添加轮廓等
}

virtual void UnHighlightActor() override
{
// 恢复默认状态
}
};

🔧 UE接口系统工作机制

反射系统集成
1
2
3
4
5
6
7
8
9
// 接口检查
if (Actor->Implements<UEnemyInterface>())
{
IEnemyInterface* Enemy = Cast<IEnemyInterface>(Actor);
if (Enemy)
{
Enemy->HighlightActor();
}
}
蓝图支持
  • 可在蓝图中实现接口
  • 编辑器可识别接口类型
  • 支持接口事件调度

🎮 游戏中的应用场景

敌人交互系统
flowchart TD
    Player[玩家鼠标悬停] --> Check[检查接口]
    Check -->|有接口| Highlight[调用高亮]
    Check -->|无接口| Ignore[忽略]
    PlayerLeave[鼠标离开] --> Unhighlight[取消高亮]
多态调用
1
2
3
4
5
6
7
8
9
// 统一处理不同类型的敌人
TArray<AActor*> Enemies;
for (AActor* Enemy : Enemies)
{
if (Enemy->Implements<UEnemyInterface>())
{
IEnemyInterface::Execute_HighlightActor(Enemy);
}
}

💡 设计优势

1. 松耦合设计
  • 调用者不关心具体实现类
  • 只需知道接口协议
2. 扩展性
1
2
3
4
5
6
7
8
9
10
11
12
// 可轻松添加新接口
class IInteractable
{
public:
virtual void Interact() = 0;
};

// 类实现多个接口
class AEnemy : public IEnemyInterface, public IInteractable
{
// 实现两个接口的方法...
};
3. 类型安全
  • 编译时检查接口实现
  • 避免运行时错误

🚀 最佳实践

接口命名规范
  • I 前缀:功能接口类(IEnemyInterface
  • U 前缀:反射接口类(UEnemyInterface
  • 清晰语义:接口名描述能力而非身份
纯虚函数设计
1
2
3
4
5
// 好的设计:职责单一
virtual void TakeDamage(float Damage) = 0;

// 避免:功能过于复杂
virtual void HandleCombat(float Damage, FVector Location, AActor* Instigator) = 0;
UE特定技巧
1
2
3
4
5
6
7
// 蓝图可调用版本
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void HighlightActor();
virtual void HighlightActor_Implementation(); // C++实现

// 纯C++版本
virtual void HighlightActor() = 0; // 强制实现

📊 总结对比

方案 接口 继承 组件
适用场景 跨类共享能力 类层次关系 功能模块化
耦合度
灵活性
UE集成 蓝图友好 标准继承 编辑器友好
性能 虚函数开销 虚函数开销 可能更高

接口最适合:定义”能做什么”而不是”是什么”,特别是需要跨不同类层次共享功能的场景。

-

Highlight Enemies

19:25

🎯 鼠标光标追踪与敌人高亮系统

新增核心成员变量
1
2
3
void CursorTrace();  // 光标追踪方法
TObjectPtr<IEnemyInterface> LastActor; // 上一帧的敌人接口
TObjectPtr<IEnemyInterface> ThisActor; // 当前帧的敌人接口

🔍 PlayerTick 集成

1
virtual void PlayerTick(float DeltaTime) override;
  • 每帧调用:启用 Actor Tick 系统
  • 实时检测:持续追踪鼠标下方的敌人
  • 性能考虑:简单的射线检测开销较小

🎮 CursorTrace 方法详解

1. 光线投射检测
1
2
FHitResult CursorHit;
GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);
  • **GetHitResultUnderCursor**:从光标位置发射射线
  • **ECC_Visibility**:可见性碰撞通道
  • **false**:不启用复杂碰撞检测
  • 返回值:包含击中信息的 FHitResult
2. 接口指针转换
1
2
LastActor = ThisActor;  // 保存上一帧结果
ThisActor = Cast<IEnemyInterface>(CursorHit.GetActor());
  • **Cast<IEnemyInterface>**:尝试将 Actor 转换为敌人接口
  • 自动转换:如果 Actor 实现了接口,返回有效指针;否则返回 nullptr
  • 指针传递:使用 TObjectPtr 安全存储接口指针

🧠 智能状态管理逻辑

状态转移矩阵
1
2
3
4
5
6
上一帧 (LastActor)  当前帧 (ThisActor)  操作
null null 无操作 (A)
null 有效 高亮当前 (B)
有效 null 取消高亮上一帧 (C)
有效 有效(不同) 取消高亮上一帧,高亮当前 (D)
有效 有效(相同) 无操作 (E)
代码实现逻辑
1
2
3
4
5
6
7
8
9
10
11
// 情况 C 和 D:需要取消上一帧的高亮
if (LastActor != nullptr && LastActor != ThisActor)
{
LastActor->UnHighlightActor(); // 调用接口方法
}

// 情况 B 和 D:需要高亮当前帧
if (ThisActor != nullptr && ThisActor != LastActor)
{
ThisActor->HighlightActor(); // 调用接口方法
}

💡 设计亮点分析

1. 无状态切换优化
1
2
3
// 避免了重复调用
if (ThisActor == LastActor) return; // 显式优化
// 但代码中通过条件判断隐式实现
2. 接口安全调用
1
2
// 空指针安全检查已包含在条件判断中
LastActor->UnHighlightActor(); // 仅在 LastActor 非空时调用
3. 帧间连贯性
  • 平滑过渡:从高亮到非高亮状态自然切换
  • 无闪烁:避免同一物体反复高亮/取消

🚀 扩展可能性

性能优化
1
2
3
4
// 可添加距离检测
float Distance = FVector::Distance(GetPawn()->GetActorLocation(),
CursorHit.Location);
if (Distance > MaxHighlightDistance) return;
视觉效果增强
1
2
3
4
5
6
7
8
9
10
11
12
// 可添加淡入淡出效果
void CursorTrace()
{
// 当前实现...

// 扩展:根据距离调整高亮强度
if (ThisActor)
{
float Intensity = CalculateHighlightIntensity(CursorHit.Distance);
ThisActor->HighlightWithIntensity(Intensity);
}
}
多目标支持
1
2
3
// 未来可扩展为区域选择
TArray<TObjectPtr<IEnemyInterface>> HighlightedActors;
void HighlightArea(FVector Center, float Radius);

📊 与其他系统集成

flowchart TD
    Input[鼠标移动事件] --> Tick[PlayerTick调用]
    Tick --> Trace[CursorTrace光线检测]
    Trace --> Interface[获取敌人接口]
    Interface --> Check[状态检查]
    
    Check -->|新敌人| Highlight[调用高亮接口]
    Check -->|离开敌人| Unhighlight[调用取消高亮接口]
    Check -->|同一敌人| NoAction[无操作]
    
    Highlight -->|触发| Enemy[敌人视觉效果]
    Unhighlight -->|触发| Enemy

⚠️ 注意事项

性能考虑
  • 每帧调用:确保 CursorTrace 逻辑轻量
  • 射线检测:使用合适的碰撞通道和复杂度
  • 接口调用:虚函数调用有一定开销
网络同步
1
2
3
// 如果是多人游戏,需要考虑
bReplicates = true; // 已启用复制
// 但高亮效果可能需要服务器验证
用户体验
  • 响应速度:立即反馈鼠标悬停
  • 视觉清晰:高亮效果明显但不刺眼
  • 逻辑一致:确保不会同时高亮多个敌人

核心改进:通过每帧的鼠标射线检测,实现了智能的敌人高亮系统,为后续的敌人选择和战斗交互奠定了基础。

-

Post Process Highlight

13:19

👾 AuraEnemy 敌人实现类

多重继承结构

1
class AAuraEnemy : public AAuraCharacterBase, public IEnemyInterface
  • 角色基类:继承通用的角色功能(武器、移动等)
  • 敌人接口:实现高亮交互能力

碰撞通道配置

1
2
GetMesh()->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
GetMesh()->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);
  • 可见性通道阻挡:确保光标射线能检测到敌人
  • 相机通道忽略:防止相机与敌人网格体碰撞

自定义深度高亮实现

1
2
GetMesh()->SetRenderCustomDepth(true);
GetMesh()->SetCustomDepthStencilValue(CUSTOM_DEPTH_RED);
  • 渲染通道:使用自定义深度渲染实现轮廓高亮
  • 模板值CUSTOM_DEPTH_RED 定义高亮颜色(通常在项目头文件中定义)
  • 武器同步:武器组件也应用相同的高亮效果

核心:通过UE的渲染系统实现视觉反馈,将接口逻辑转化为具体的视觉效果。

📚 第一章关键方法总结

基础类定义

1
2
3
4
5
UCLASS(Abstract)                     // 声明为抽象类,不可直接实例化
class GAS_AURA_API AAuraCharacterBase : public ACharacter
GENERATED_BODY() // UE反射系统必需宏
UPROPERTY(EditAnywhere, Category="Input") // 编辑器可见属性
TObjectPtr<USkeletalMeshComponent> Weapon // UE5安全指针

组件创建与设置

1
2
3
4
PrimaryActorTick.bCanEverTick = false;   // 禁用Actor Tick
CreateDefaultSubobject<USkeletalMeshComponent>("Weapon"); // 创建组件
SetupAttachment(GetMesh(), FName("WeaponHandSocket")); // 绑定到骨骼
SetCollisionEnabled(ECollisionEnabled::NoCollision); // 禁用碰撞

玩家控制器配置

1
2
3
4
bReplicates = true;                              // 启用网络复制
Subsystem->AddMappingContext(AuraContext, 0); // 添加输入映射(0=优先级)
bShowMouseCursor = true; // 显示鼠标光标
SetInputMode(InputModeData); // 设置输入模式

增强输入系统

1
2
3
4
5
6
7
8
UEnhancedInputLocalPlayerSubsystem* Subsystem = 
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
EnhancedInputComponent->BindAction(
MoveAction, // 输入动作资源
ETriggerEvent::Triggered, // 触发事件类型
this, // 目标对象
&AAuraPlayerController::Move // 回调函数指针
);

角色移动处理

1
2
3
4
const FVector2D InputAxisVector = InputActionValue.Get<FVector2D>();  // 获取2D输入
FRotator YawRotation(0.f, Rotation.Yaw, 0.f); // 仅保留偏航角
FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); // 获取前向向量
AddMovementInput(ForwardDirection, InputAxisVector.Y); // 应用移动输入

角色运动组件配置

1
2
3
4
GetCharacterMovement()->bOrientRotationToMovement = true;  // 朝向移动方向
GetCharacterMovement()->RotationRate = FRotator(0.f, 400.f, 0.f); // 旋转速度
GetCharacterMovement()->bConstrainToPlane = true; // 平面约束
bUseControllerRotationYaw = false; // 禁用控制器旋转影响

接口系统

1
2
3
4
UINTERFACE(MinimalAPI)                      // 声明反射接口类
class UEnemyInterface : public UInterface // 反射接口类
class IEnemyInterface // 功能接口类
virtual void HighlightActor() = 0; // 纯虚函数声明(必须实现)

光标追踪与接口调用

1
2
3
4
GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);  // 光标射线检测
Cast<IEnemyInterface>(CursorHit.GetActor()); // 接口类型转换
LastActor->UnHighlightActor(); // 调用接口方法
ThisActor->HighlightActor(); // 调用接口方法

自定义深度渲染

1
2
3
4
SetRenderCustomDepth(true);                    // 启用自定义深度渲染
SetCustomDepthStencilValue(CUSTOM_DEPTH_RED); // 设置模板值(高亮颜色)
SetCollisionResponseToChannel(ECC_Visibility, ECR_Block); // 可见性碰撞响应
SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore); // 相机碰撞忽略

状态管理逻辑

1
2
if (LastActor != nullptr && LastActor != ThisActor)   // 需要取消高亮的情况
if (ThisActor != nullptr && ThisActor != LastActor) // 需要高亮的情况

2.Intro to the Gameplay Ability System / 游戏技能系统介绍

  • 8个讲座·1小时 23 分钟 / 8 Lectures · 1 Hour 23 Minutes

  • The Gameplay Ability System

    12:14

  • The Main Parts of GAS

    08:46

  • The Player State

    04:28

🎮 AuraPlayerState 玩家状态类

类定义

1
2
UCLASS()
class GAS_AURA_API AAuraPlayerState : public APlayerState
  • 继承自 APlayerState:UE内置玩家状态基类
  • 功能:存储玩家游戏数据(经验值、等级、属性点等)

构造函数配置

1
2
3
4
AAuraPlayerState::AAuraPlayerState()
{
NetUpdateFrequency = 100.f; // 设置网络更新频率为100Hz
}

关键参数解析

参数 类型 默认值 功能说明
NetUpdateFrequency float 2.0f 网络更新频率(单位:Hz)
MinNetUpdateFrequency float 2.0f 最小更新频率
bReplicateRelevancyInfo bool true 是否复制相关性信息

NetUpdateFrequency 详解

1
2
3
4
5
6
7
8
9
// 不同场景的设置建议
NetUpdateFrequency = 100.f; // 高频:MOBA、射击游戏
NetUpdateFrequency = 30.f; // 中频:RPG、动作游戏
NetUpdateFrequency = 2.f; // 低频:棋牌、策略游戏(默认值)

// 实际更新间隔 = 1 / NetUpdateFrequency
// 100Hz → 每0.01秒更新一次
// 30Hz → 每0.033秒更新一次
// 2Hz → 每0.5秒更新一次

网络同步流程

1
2
3
4
5
服务器端 PlayerState
↓ 网络复制(100Hz)
客户端 PlayerState

更新玩家UI、属性显示等

典型应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 存储玩家数据
UPROPERTY(Replicated)
int32 PlayerLevel; // 玩家等级

UPROPERTY(Replicated)
float ExperiencePoints; // 经验值

UPROPERTY(Replicated)
int32 AttributePoints; // 属性点数

// 后续可扩展
UPROPERTY(Replicated)
FString PlayerName; // 玩家名称

UPROPERTY(Replicated)
int32 Gold; // 金币数量

设计考虑

1. **高频更新**:确保属性变化即时同步
2. **带宽控制**:100Hz比默认2Hz消耗更多带宽
3. **游戏类型适配**:根据需求调整频率
4. **GAS集成**:为后续Gameplay Ability System做准备

核心作用:作为玩家数据的网络同步载体,为RPG系统提供基础支持。

-

Ability System Component and Attribute Set

05:08

-

GAS in Multiplayer

10:29

-

Constructing the ASC and AS

12:13

🏗️ GAS 架构设计:分离式组件挂载

核心架构对比

组件 玩家角色 敌人角色 设计原因
ASC 挂载在 PlayerState 挂载在 EnemyBase 玩家数据需要跨关卡保存
AS 挂载在 PlayerState 挂载在 EnemyBase 属性与角色生命期绑定
网络复制 PlayerState 复制 Enemy 自身复制 玩家状态持久化需求

1. 抽象基类设计

接口实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 头文件:AuraCharacterBase.h
class AAuraCharacterBase : public ACharacter, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
UAttributeSet* GetAttributeSet() const { return AttributeSet; };

protected:
UPROPERTY()
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent; // ASC指针

UPROPERTY()
TObjectPtr<UAttributeSet> AttributeSet; // 属性集指针
};
接口方法实现
1
2
3
4
5
// 源文件:AuraCharacterBase.cpp
UAbilitySystemComponent* AAuraCharacterBase::GetAbilitySystemComponent() const
{
return AbilitySystemComponent; // 返回ASC指针
}

2. 玩家实现:PlayerState 挂载

构造函数初始化
1
2
3
4
5
6
7
8
9
10
11
12
// AuraPlayerState.cpp
AAuraPlayerState::AAuraPlayerState()
{
// 创建ASC组件
AbilitySystemComponent = CreateDefaultSubobject<UAuraAbilitySystemComponent>("AbilitySystemComponent");
AbilitySystemComponent->SetIsReplicated(true); // 启用网络复制

// 创建属性集
AttributeSet = CreateDefaultSubobject<UAuraAttributeSet>("Attributeset");

NetUpdateFrequency = 100.f; // 高频网络更新
}
玩家角色获取ASC
1
2
3
4
5
6
7
// 玩家角色需要通过PlayerState获取ASC
AAuraPlayerState* PlayerState = GetPlayerState<AAuraPlayerState>();
if (PlayerState)
{
UAbilitySystemComponent* ASC = PlayerState->GetAbilitySystemComponent();
UAttributeSet* AS = PlayerState->GetAttributeSet();
}

3. 敌人实现:Enemy 自身挂载

构造函数初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// AuraEnemy.cpp
AAuraEnemy::AAuraEnemy()
{
// 碰撞设置(保持不变)
GetMesh()->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
GetMesh()->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);

// 创建ASC组件(直接挂载到敌人)
AbilitySystemComponent = CreateDefaultSubobject<UAuraAbilitySystemComponent>("AbilitySystemComponent");
AbilitySystemComponent->SetIsReplicated(true); // 启用网络复制

// 创建属性集
AttributeSet = CreateDefaultSubobject<UAuraAttributeSet>("Attributeset");
}
敌人角色获取ASC
1
2
3
// 敌人可以直接从自身获取ASC
UAbilitySystemComponent* ASC = GetAbilitySystemComponent(); // 继承自基类
UAttributeSet* AS = GetAttributeSet(); // 基类方法

4. 设计哲学分析

玩家数据持久化
1
2
3
4
// PlayerState的生命周期
进入游戏 → 创建PlayerState → 存储ASC/AS数据
死亡重生 → PlayerState保留 → 数据不丢失
退出关卡 → PlayerState销毁 → 需要保存到存档
敌人临时性
1
2
3
4
// Enemy的生命周期
关卡开始 → 生成敌人 → 创建ASC/AS
玩家击杀 → 敌人销毁 → ASC/AS同时销毁
关卡结束 → 所有敌人销毁 → 无需保存

5. 网络复制策略

玩家复制模式
1
2
3
4
// PlayerState中设置
AbilitySystemComponent->SetIsReplicated(true);
// 通常使用:Mixed(混合)或 Full(完全)复制
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
敌人复制模式
1
2
3
4
// Enemy中设置(相同)
AbilitySystemComponent->SetIsReplicated(true);
// 通常使用:Minimal(最小)复制节省带宽
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);

6. 访问模式对比

操作 玩家代码 敌人代码
获取ASC GetPlayerState()->GetAbilitySystemComponent() GetAbilitySystemComponent()
获取AS GetPlayerState()->GetAttributeSet() GetAttributeSet()
应用效果 通过PlayerState的ASC 直接通过自身ASC
监听属性 监听PlayerState的AS 监听自身AS

7. 优势和考量

优势
1. **数据分离**:玩家进度与角色实体解耦
2. **持久化**:玩家死亡/重生不丢失属性
3. **网络优化**:PlayerState可独立复制频率
4. **存档友好**:PlayerState数据易于序列化
考量
1. **访问路径**:玩家需通过PlayerState访问,增加间接性
2. **初始化时机**:需确保PlayerState在角色之前创建
3. **引用管理**:注意PlayerState与角色的生命周期差异

总结

这种分离式架构是多人RPG游戏的最佳实践

  • 玩家PlayerState作为数据容器,支持进度保存
  • 敌人Enemy自身承载数据,简化生命周期管理

为后续的属性系统技能系统伤害计算奠定了坚实的架构基础。

-

Replication Mode

07:44

🌐 GAS 网络复制模式配置

1. 玩家状态:Mixed 复制模式

1
2
// PlayerState 构造函数新增
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
Mixed 模式特点
  • 本地玩家:接收完整的GameplayEffect数据
  • 其他玩家:只接收最小必要数据
  • 适用场景:玩家自己的角色(需要完整数据),AI控制的角色用Minimal

2. 敌人:Minimal 复制模式

1
2
// Enemy 构造函数新增  
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);
Minimal 模式特点
  • 只复制:GameplayTags和持续时间
  • 不复制:具体的属性修改值
  • 适用场景:AI敌人、小兵(带宽优化)

**3. 复制模式对比表

复制模式 使用场景 描述
Full(完整) 单人游戏 Gameplay Effects 复制到所有客户端
Mixed(混合) 多人游戏,玩家控制 Gameplay Effects 仅复制到所属客户端。Gameplay Cues 和 Gameplay Tags 复制到所有客户端。
Minimal(最小) 多人游戏,AI控制 Gameplay Effects 不进行复制。Gameplay Cues 和 Gameplay Tags 复制到所有客户端。

设计考虑

1. **网络优化**:根据角色重要性选择复制模式
2. **带宽控制**:敌人用Minimal节省服务器资源  
3. **玩家体验**:本地玩家需要完整数据计算伤害等
4. **一致性**:保持接口实现的一致性

-

Init Ability Actor Info

22:01

🎮 GAS 初始化系统

1. 敌人角色初始化

1
2
3
4
5
6
// AuraEnemy.cpp - BeginPlay()
void AAuraEnemy::BeginPlay()
{
Super::BeginPlay();
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
InitAbilityActorInfo 参数
1
2
3
4
InitAbilityActorInfo(
this, // 第1参数:OwnerActor(拥有者)
this // 第2参数:AvatarActor(化身)
);
  • 拥有者:逻辑上的所有者(敌人自身)
  • 化身:实际执行动作的实体(敌人自身)
  • 敌人场景:Owner和Avatar都是敌人自己

2. 玩家角色初始化

服务器端初始化
1
2
3
4
5
6
// AuraCharacter.cpp - PossessedBy()
void AAuraCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
InitAbilityActorInfo(); // 服务器初始化
}
  • 触发时机:服务器获得角色控制权时
  • 典型场景:玩家加入游戏、重生时
客户端初始化
1
2
3
4
5
6
// AuraCharacter.cpp - OnRep_PlayerState()
void AAuraCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
InitAbilityActorInfo(); // 客户端初始化
}
  • 触发时机:客户端PlayerState同步完成时
  • 网络复制:通过OnRep_PlayerState响应复制事件

3. 玩家ASC初始化实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// AuraCharacter.cpp - InitAbilityActorInfo()
void AAuraCharacter::InitAbilityActorInfo()
{
// 获取PlayerState
AAuraPlayerState* AuraPlayerState = GetPlayerState<AAuraPlayerState>();
check(AuraPlayerState); // 断言验证

// 初始化ASC
AuraPlayerState->GetAbilitySystemComponent()->InitAbilityActorInfo(
AuraPlayerState, // Owner:PlayerState
this // Avatar:角色实体
);

// 保存引用
AbilitySystemComponent = AuraPlayerState->GetAbilitySystemComponent();
AttributeSet = AuraPlayerState->GetAttributeSet();
}
玩家参数对比
1
2
3
4
InitAbilityActorInfo(
AuraPlayerState, // Owner:PlayerState(数据持久化)
this // Avatar:角色实体(执行动作)
);

4. 初始化时机对比表

角色类型 服务器初始化 客户端初始化 Owner Avatar
玩家 PossessedBy() OnRep_PlayerState() PlayerState 角色
敌人 BeginPlay() BeginPlay() 敌人自身 敌人自身

5. 关键函数作用

InitAbilityActorInfo()
1
2
// GAS核心初始化函数
ASC->InitAbilityActorInfo(OwnerActor, AvatarActor);
  • 绑定关系:建立Owner、Avatar与ASC的关联
  • 激活系统:使GAS开始工作
  • 必需调用:未调用则技能系统无法使用
check() 宏
1
check(AuraPlayerState);  // 开发时验证,失败则崩溃
  • 调试辅助:确保关键对象存在
  • 发布版本:自动移除,不影响性能

设计模式总结

  • 玩家:分离式初始化(Owner=PlayerState, Avatar=角色)
  • 敌人:一体化初始化(Owner=Avatar=敌人自身)
  • 网络同步:确保两端都正确初始化
  • 生命周期:在合适的时机触发初始化

📚 第二章关键方法总结

GAS核心组件定义

1
2
UCLASS()
class GAS_AURA_API AAuraPlayerState : public APlayerState // 玩家状态类

网络更新频率设置

1
NetUpdateFrequency = 100.f;  // 设置网络更新频率为100Hz(默认2Hz)

GAS接口实现

1
2
class AAuraCharacterBase : public ACharacter, public IAbilitySystemInterface  // 继承GAS接口
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override; // 实现接口方法

ASC(能力系统组件)创建

1
2
AbilitySystemComponent = CreateDefaultSubobject<UAuraAbilitySystemComponent>("AbilitySystemComponent");
AbilitySystemComponent->SetIsReplicated(true); // 启用网络复制

AS(属性集)创建

1
AttributeSet = CreateDefaultSubobject<UAuraAttributeSet>("AttributeSet");

GAS复制模式设置

1
2
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);     // 玩家:混合模式
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal); // 敌人:最小模式

GAS初始化系统调用

1
ASC->InitAbilityActorInfo(OwnerActor, AvatarActor);  // 核心初始化函数

玩家初始化时机函数

1
2
virtual void PossessedBy(AController* NewController) override;  // 服务器端初始化
virtual void OnRep_PlayerState() override; // 客户端初始化

断言验证宏

1
check(AuraPlayerState);  // 开发时验证对象有效性

复制模式枚举值

1
2
3
EGameplayEffectReplicationMode::Full      // 完整复制(单人游戏)
EGameplayEffectReplicationMode::Mixed // 混合复制(玩家控制)
EGameplayEffectReplicationMode::Minimal // 最小复制(AI控制)

玩家状态获取

1
AAuraPlayerState* AuraPlayerState = GetPlayerState<AAuraPlayerState>();  // 获取玩家状态

组件引用保存

1
2
AbilitySystemComponent = AuraPlayerState->GetAbilitySystemComponent();  // 保存ASC引用
AttributeSet = AuraPlayerState->GetAttributeSet(); // 保存AS引用

3.Attributes / 属性

  • 4个讲座·1小时1分钟 / 4 Lectures · 1 Hour 1 Minute

  • Attributes

    06:59

Attributes are numerical quantities associated with a given entity in the game, all attributes are floats, they exist within a structure called FGameplayAttributeData.

属性是与游戏中特定实体相关联的数值量,所有属性均为浮点数,它们存在于名为FGameplayAttributeData的结构中。

-

Health and Mana

17:44

🏥 AuraAttributeSet 属性集实现

1. 类定义和属性声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
UCLASS()
class GAS_AURA_API UAuraAttributeSet : public UAttributeSet
{
GENERATED_BODY()

public:
UAuraAttributeSet();

virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// TArray<FLifetimeProperty>& OutLifetimeProps
// 参数:输出参数,存放所有需要复制的属性信息
// 类型:TArray(动态数组),存储FLifetimeProperty结构

// 核心属性声明
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category = "Vital Attributes")
FGameplayAttributeData Health;

UFUNCTION()
void OnRep_Health(const FGameplayAttributeData& OldHealth) const;
};
代码功能
1. **继承UAttributeSet**:GAS属性系统的基类
2. **网络复制配置函数**GetLifetimeReplicatedProps:告诉UE哪些属性需要网络复制
3. **声明Health属性**:使用`FGameplayAttributeData`类型存储生命值
4. **声明OnRep_Health函数**:属性复制完成时的回调函数

2. 构造函数实现

1
2
3
4
UAuraAttributeSet::UAuraAttributeSet()
{
// 空的构造函数,属性初始化使用默认值
}

3. 网络复制配置

1
2
3
4
5
6
7
8
void UAuraAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
// 1. 首先调用父类方法
Super::GetLifetimeReplicatedProps(OutLifetimeProps);

// 2. 注册Health属性进行网络复制
DOREPLIFETIME_CONDITION_NOTIFY(UAuraAttributeSet, Health, COND_None, REPNOTIFY_Always);
}
DOREPLIFETIME_CONDITION_NOTIFY宏解析
1
2
3
4
5
6
DOREPLIFETIME_CONDITION_NOTIFY(
UAuraAttributeSet, // 参数1:当前类名
Health, // 参数2:要复制的属性名
COND_None, // 参数3:复制条件(无条件,总是复制)
REPNOTIFY_Always // 参数4:通知策略(总是发送通知)
)
作用
  • 告诉UE:Health属性需要通过网络同步
  • 条件COND_None:任何情况下都复制
  • 通知REPNOTIFY_Always:属性变化时总是通知

4. OnRep函数实现

1
2
3
4
void UAuraAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth) const
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAuraAttributeSet, Health, OldHealth);
}
GAMEPLAYATTRIBUTE_REPNOTIFY宏功能
1
2
3
4
// 这个宏内部执行三个操作:
// 1. 比较新旧值,触发属性变化事件
// 2. 更新UI显示(如果绑定了UI)
// 3. 确保属性值正确同步
OnRep_Health参数
1
const FGameplayAttributeData& OldHealth  // 参数:属性复制前的旧值

5. 完整执行流程

服务器端发生属性变化
1
2
3
4
服务器:
1. Health属性值改变(例如:玩家受伤)
2. 自动触发网络复制系统
3. 通过网络发送Health新值给客户端
客户端接收属性变化
1
2
3
4
5
6
7
8
客户端:
1. 收到服务器发来的Health新值
2. UE自动调用OnRep_Health(OldHealth)
3. GAMEPLAYATTRIBUTE_REPNOTIFY宏执行
- 记录旧值
- 更新新值
- 触发属性变化事件
4. UI系统收到事件,更新生命条显示

6. 代码结构总结

1
2
3
4
5
6
7
// 每个属性需要三部分:
1. 声明属性:UPROPERTY(...) FGameplayAttributeData 属性名;
2. 复制注册:DOREPLIFETIME_CONDITION_NOTIFY(类名, 属性名, 条件, 通知策略);
3. OnRep函数:void OnRep_属性名(const FGameplayAttributeData& Old值) const;

// 对应关系:
Health属性 ←→ OnRep_Health函数 ←→ DOREPLIFETIME_CONDITION_NOTIFY注册

7. 核心机制

  • 网络复制:服务器向客户端同步属性值

  • 回调通知:属性复制完成后调用指定函数

  • 自动同步:GAS系统自动处理属性变化和UI更新

  • Attribute Accessors

    12:05

🚀 AuraAttributeSet 属性集升级

1. 新增:属性访问器宏定义

1
2
3
4
5
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
宏展开后的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
// 以Health为例,ATTRIBUTE_ACCESSORS(UAuraAttributeSet, Health) 展开为:

// 1. 属性元数据获取器
static FGameplayAttribute GetHealthAttribute();

// 2. 当前值获取器
float GetHealth() const;

// 3. 当前值设置器
void SetHealth(float NewVal);

// 4. 初始值设置器
void InitHealth(float NewVal);

2. 属性声明新增宏调用

1
2
3
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category = "Vital Attributes")
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UAuraAttributeSet, Health); // 新增:为Health生成访问函数

3. 构造函数初始化属性值

1
2
3
4
5
6
7
8
UAuraAttributeSet::UAuraAttributeSet()
{
// 使用宏生成的Init函数设置初始值
InitHealth(100.f); // Health初始值 = 100
InitMaxHealth(100.f); // MaxHealth初始值 = 100
InitMana(50.f); // Mana初始值 = 50
InitMaxMana(50.f); // MaxMana初始值 = 50
}
Init函数来源
1
2
// 来自宏:GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
// 生成函数:InitHealth(float), InitMaxHealth(float)等

4. 生成的访问函数使用示例

获取属性值
1
2
3
4
5
6
// 通过宏生成的Get函数
float CurrentHealth = GetHealth(); // 获取当前生命值
float CurrentMaxHealth = GetMaxHealth(); // 获取最大生命值

// 之前的方法(不方便)
float OldWay = Health.GetCurrentValue(); // 需要调用GetCurrentValue()
设置属性值
1
2
3
4
5
6
7
8
// 通过宏生成的Set函数
SetHealth(75.f); // 设置生命值为75
SetMana(30.f); // 设置魔法值为30

// 之前的方法(复杂)
FGameplayAttributeData NewHealth;
NewHealth.SetCurrentValue(75.f);
Health = NewHealth;

5. 宏的功能详解

宏组件 生成的函数 作用
GAMEPLAYATTRIBUTE_PROPERTY_GETTER GetHealthAttribute() 获取属性的元数据(类型、名称等)
GAMEPLAYATTRIBUTE_VALUE_GETTER GetHealth() 获取属性的当前值(float)
GAMEPLAYATTRIBUTE_VALUE_SETTER SetHealth(float) 设置属性的当前值
GAMEPLAYATTRIBUTE_VALUE_INITTER InitHealth(float) 初始化属性的基础值

6. 代码结构对比

升级前(手动管理)
1
2
3
4
5
6
7
8
9
// 获取值
float health = Health.GetCurrentValue();

// 设置值(复杂)
FGameplayAttributeData newHealth;
newHealth.SetCurrentValue(100.f);
Health = newHealth;

// 没有统一的初始化方法
升级后(宏辅助)
1
2
3
4
5
6
7
8
// 获取值(简洁)
float health = GetHealth();

// 设置值(简单)
SetHealth(100.f);

// 初始化(统一)
InitHealth(100.f); // 构造函数中调用

7. 关键改进总结

1. **代码简化**:宏自动生成Get/Set/Init函数
2. **类型安全**:统一的访问接口
3. **初始化标准化**:构造函数中统一初始化所有属性
4. **可维护性**:属性声明和访问函数绑定在一起
使用新宏的优势
1
2
3
// 之前:手动编写每个属性的访问函数
// 之后:一行宏搞定所有功能
// 结果:减少代码量,提高一致性,减少错误

-

Effect Actor

24:20

⚠️ AuraEffectActor 临时效果实现与const_cast问题

1. Actor基础结构

1
2
3
4
5
6
7
8
9
10
11
UCLASS()
class GAS_AURA_API AAuraEffectActor : public AActor
{
// 组件
TObjectPtr<UStaticMeshComponent> Mesh; // 可视网格
TObjectPtr<USphereComponent> Sphere; // 碰撞检测球体

// 碰撞回调函数
virtual void OnOverlap(...); // 进入碰撞区域
virtual void EndOverlap(...); // 离开碰撞区域
};

2. 当前的临时解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void AAuraEffectActor::OnOverlap(...)
{
// 1. 检查OtherActor是否实现了IAbilitySystemInterface
if(IAbilitySystemInterface* ASCInterface = Cast<IAbilitySystemInterface>(OtherActor))
{
// 2. 获取OtherActor的属性集
const UAuraAttributeSet* AuraAttributeSet =
Cast<UAuraAttributeSet>(ASCInterface->GetAbilitySystemComponent()
->GetAttributeSet(UAuraAttributeSet::StaticClass()));

// 3. ⚠️ 使用const_cast移除const修饰符(危险操作)
UAuraAttributeSet* MutableAuraAttributeSet =
const_cast<UAuraAttributeSet*>(AuraAttributeSet);

// 4. 直接修改生命值
MutableAuraAttributeSet->SetHealth(AuraAttributeSet->GetHealth() + 25.f);

// 5. 销毁自身
Destroy();
}
}

3. const_cast的问题分析

什么是const_cast?
1
2
3
4
5
6
7
8
// const_cast语法:移除或添加const修饰符
const_cast<Type*>(const_pointer); // 移除const
const_cast<const Type*>(pointer); // 添加const

// 当前代码:
const UAuraAttributeSet* AuraAttributeSet = ...; // const指针
UAuraAttributeSet* MutableAuraAttributeSet = // 移除const
const_cast<UAuraAttributeSet*>(AuraAttributeSet);
const_cast的严重问题

问题1:违反const承诺

1
2
3
4
5
6
7
8
// GetAttributeSet返回const指针的承诺:
// "这个对象是只读的,我不会修改它"
const UAttributeSet* GetAttributeSet(...) const;

// 使用const_cast打破了这个承诺
// 可能导致:
// 1. 其他代码依赖const保证,现在被破坏
// 2. 多线程环境下的数据竞争

问题2:绕过GAS系统

1
2
3
4
5
6
7
8
9
// GAS正确的属性修改方式:
ASC->ApplyModToAttribute(Attribute, Modifier); // 通过ASC系统

// 当前错误方式:
直接调用 SetHealth() // 绕过GAS,不会触发:
// - 属性变化事件
// - UI更新
// - 网络复制
// - GameplayEffect的后续处理

问题3:网络同步问题

1
2
3
4
5
// GAS修改属性会:
1. 服务器修改 → 2. 触发复制 → 3. 客户端同步

// const_cast直接修改:
1. 本地修改 → 2. 网络不同步 → 3. 其他客户端看不到变化

4. 总结:为什么const_cast是坏的

问题 后果 正确做法
违反const约定 破坏代码安全性,可能导致崩溃 使用const正确的方法
绕过GAS系统 不触发事件、UI不更新 通过ASC应用GameplayEffect
网络不同步 多人游戏不同步 GAS自动处理网络复制
代码维护困难 难以调试和追踪 使用标准GAS流程

注释中的TODO:

1
2
//TODO: 将此更改为应用游戏效果,目前使用 const_cast 作为临时解决方案!
// 翻译:这只是临时方案,后面要用GameplayEffect重写!

-

Section 4 Quiz

3 问题

📚 第三章关键方法总结

属性集类定义

1
2
UCLASS()
class UAuraAttributeSet : public UAttributeSet // 继承GAS属性集基类

属性声明

1
2
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category = "Vital Attributes")
FGameplayAttributeData Health; // 核心属性声明

属性访问器宏

1
2
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName)                 // 属性访问器宏定义
ATTRIBUTE_ACCESSORS(UAuraAttributeSet, Health) // 应用宏到属性

属性初始化

1
2
InitHealth(100.f)                                                    // 初始化属性值
InitMana(50.f) // 初始化魔法值

属性访问函数

1
2
3
GetHealth()                                                          // 获取当前生命值
SetHealth(75.f) // 设置生命值
GetHealthAttribute() // 获取属性元数据

网络复制配置

1
2
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override
DOREPLIFETIME_CONDITION_NOTIFY(UAuraAttributeSet, Health, COND_None, REPNOTIFY_Always)

复制通知函数

1
2
3
UFUNCTION()
void OnRep_Health(const FGameplayAttributeData& OldHealth) const // 属性复制回调
GAMEPLAYATTRIBUTE_REPNOTIFY(UAuraAttributeSet, Health, OldHealth) // 属性变化通知宏

属性数据类型

1
FGameplayAttributeData                                               // GAS属性数据类型

复制条件枚举

1
2
3
COND_None                                                            // 无条件复制
COND_OwnerOnly // 仅所有者复制
COND_InitialOnly // 仅初始复制

通知策略枚举

1
2
REPNOTIFY_Always                                                     // 总是通知
REPNOTIFY_OnChanged // 变化时通知

属性分类

1
Category = "Vital Attributes"                                        // 重要属性分类

危险操作(临时方案)

1
const_cast<UAuraAttributeSet*>(AuraAttributeSet)                     // 移除const修饰符(危险)

属性集获取

1
GetAbilitySystemComponent()->GetAttributeSet(UAuraAttributeSet::StaticClass())

宏生成的函数类型

1
2
3
4
GAMEPLAYATTRIBUTE_PROPERTY_GETTER                                    // 属性元数据获取器
GAMEPLAYATTRIBUTE_VALUE_GETTER // 属性值获取器
GAMEPLAYATTRIBUTE_VALUE_SETTER // 属性值设置器
GAMEPLAYATTRIBUTE_VALUE_INITTER // 属性值初始化器

属性模式

1
2
Health / MaxHealth                                                   // 当前值/最大值配对模式
Mana / MaxMana // 魔法值配对模式

属性变化响应流程

1
服务器修改 → 网络复制 → 客户端OnRep回调 → GAMEPLAYATTRIBUTE_REPNOTIFY → UI更新

4.RPG Game UI / RPG游戏用户界面

  • 9 个讲座·2 小时 27 分钟 / 9 Lectures · 2 Hours 27 Minutes

  • Game UI Architecture

    07:36

View(表现层):

数据的视觉表现(eg:血条 法力值等等)

–>AuraUserWidget

(Widget)Controller(控制层):

作为 View 和 Model 的中介 View想改变视觉表现得通过 Controller 而 Model向 View 传递数据得通过 Controller

–>AuraWidgetController

Model(数据层):

相当于数据库 存放Attribute 的值(FGameplayAttribute)

–>UAuraAttributeSet

-

Aura User Widget and Widget Controller

10:39

🎮 Aura UI 系统的 MVC 架构实现

1. 架构总览

graph TD
    Model[Model层
UAttributeSet] -->|数据变化| Controller[Controller层
UAuraWidgetController] Controller -->|更新通知| View[View层
UAuraUserWidget] View -->|用户交互| Controller

2. Controller层:UAuraWidgetController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
UCLASS()
class GAS_AURA_API UAuraWidgetController : public UObject
{
GENERATED_BODY()

protected:
// 四个核心数据源
UPROPERTY(BlueprintReadOnly, Category="WidgetController")
TObjectPtr<APlayerController> PlayerController; // 玩家控制器

UPROPERTY(BlueprintReadOnly, Category="WidgetController")
TObjectPtr<APlayerState> PlayerState; // 玩家状态

UPROPERTY(BlueprintReadOnly, Category="WidgetController")
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent; // GAS组件

UPROPERTY(BlueprintReadOnly, Category="WidgetController")
TObjectPtr<UAttributeSet> AttributeSet; // 属性集
};
四个数据源的作用
1
2
3
4
1. PlayerController: 处理玩家输入、相机控制
2. PlayerState: 存储玩家数据(等级、经验等)
3. AbilitySystemComponent: 管理技能和属性修改
4. AttributeSet: 具体的属性值(生命、魔法等)

3. View层:UAuraUserWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
UCLASS()
class GAS_AURA_API UAuraUserWidget : public UUserWidget
{
GENERATED_BODY()

public:
// 设置Controller
UFUNCTION(BlueprintCallable)
void SetWidgetController(UObject* InWidgetController);

// Controller引用
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UObject> WidgetController;

protected:
// 蓝图事件:Controller设置完成后触发
UFUNCTION(BlueprintImplementableEvent)
void WidgetControllerSet();
};

4. SetWidgetController函数实现

1
2
3
4
5
6
7
8
void UAuraUserWidget::SetWidgetController(UObject* InWidgetController)
{
// 1. 保存Controller引用
WidgetController = InWidgetController;

// 2. 触发蓝图事件
WidgetControllerSet();
}
函数调用流程
1
2
3
4
5
6
7
// 在蓝图或C++中调用:
AuraUserWidget->SetWidgetController(WidgetController);

// 执行顺序:
1. 设置WidgetController = 传入的Controller
2. 自动调用WidgetControllerSet()事件
3. 蓝图中处理数据绑定和UI初始化

5. 蓝图事件:WidgetControllerSet

1
2
3
4
5
6
7
8
9
// C++声明(接口)
UFUNCTION(BlueprintImplementableEvent)
void WidgetControllerSet();

// 蓝图实现(举例):
// 1. 获取Controller
// 2. 绑定属性变化事件
// 3. 初始化UI显示
// 4. 设置按钮点击事件
BlueprintImplementableEvent特性
1
2
3
4
5
// 这个宏创建的函数:
1. 只有声明,没有C++实现
2. 必须在蓝图中实现
3. 自动生成调用节点
4. 无法在C++中直接调用

6. 数据流向示例

生命值更新流程
1
2
3
4
5
6
7
8
9
10
11
// 1. 模型层变化
AttributeSet::Health 值改变(玩家受伤/治疗)

// 2. Controller层监听
WidgetController监听到Health属性变化

// 3. 通知View层
WidgetController触发UI更新事件

// 4. View层更新
AuraUserWidget中的生命条更新显示

7. 使用UObject作为基类的设计考虑

1
2
3
4
5
6
7
// WidgetController类型:
TObjectPtr<UObject> WidgetController; // 基类指针

// 为什么用UObject而不是具体类?
1. 灵活性:可以传递不同类型的Controller
2. 蓝图友好:蓝图中可以Cast为具体类型
3. 扩展性:方便添加新的Controller类型
类型安全转换
1
2
3
4
5
6
7
8
9
// 在蓝图中使用:
UAuraWidgetController* AuraController =
Cast<UAuraWidgetController>(WidgetController);

if (AuraController)
{
// 安全地使用AuraController
float Health = AuraController->GetHealth();
}

8. MVC架构优势

职责 优势
Model 数据(AttributeSet) 数据与显示分离
View UI显示(UserWidget) 纯显示逻辑,易于替换
Controller 业务逻辑(WidgetController) 集中处理,易于维护

总结:这个MVC架构为UI系统提供了清晰的分层结构,为后续的复杂UI功能(属性面板、技能栏、背包等)奠定了坚实的基础。

-

Globe Progress Bar

28:38

-

Health Globe

10:38

-

Aura HUD

08:14

🖥️ AuraHUD:UI管理系统

1. HUD基类继承

1
2
UCLASS()
class GAS_AURA_API AAuraHUD : public AHUD
  • 继承自 AHUD:虚幻引擎的HUD基类
  • 作用:在游戏屏幕上显示UI

2. 成员变量声明

1
2
3
4
5
6
7
8
9
public:
// 当前显示的覆盖层Widget实例
UPROPERTY()
TObjectPtr<UAuraUserWidget> OverlayWidget;

private:
// 要创建的Widget的蓝图类(在编辑器中设置)
UPROPERTY(EditAnywhere)
TSubclassOf<UAuraUserWidget> OverlayWidgetClass;
变量详细说明
1
2
3
4
5
6
7
8
9
// 1. OverlayWidget(实例)
TObjectPtr<UAuraUserWidget> OverlayWidget;
// 作用:存储已经创建出来的UI对象
// 类型:UAuraUserWidget指针

// 2. OverlayWidgetClass(类引用)
TSubclassOf<UAuraUserWidget> OverlayWidgetClass;
// 作用:告诉CreateWidget函数要创建哪个类的UI
// 编辑器设置:在AuraHUD的蓝图实例中选择一个Widget蓝图

3. BeginPlay函数实现

1
2
3
4
5
6
7
8
9
10
void AAuraHUD::BeginPlay()
{
Super::BeginPlay();

// 1. 创建Widget实例
UUserWidget* Widget = CreateWidget<UUserWidget>(GetWorld(), OverlayWidgetClass);

// 2. 将Widget添加到屏幕
Widget->AddToViewport();
}
代码执行步骤
1
2
3
4
1. 游戏开始 → 调用BeginPlay()
2. 调用父类AHUD的BeginPlay
3. CreateWidget创建UI对象
4. AddToViewport把UI显示到屏幕上

4. CreateWidget函数详解

1
CreateWidget<UUserWidget>(GetWorld(), OverlayWidgetClass);
函数参数
参数 作用
模板参数 <UUserWidget> 返回的指针类型
参数1 GetWorld() 当前的游戏世界
参数2 OverlayWidgetClass 要创建的Widget类

5. AddToViewport函数

1
Widget->AddToViewport();
  • 功能:把Widget添加到游戏屏幕
  • 效果:玩家可以看到这个UI

6. 当前代码的逻辑流程

graph LR
    A[游戏开始] --> B[AAuraHUD::BeginPlay]
    B --> C[CreateWidget创建UI]
    C --> D[AddToViewport显示UI]
    D --> E[玩家看到覆盖层界面]

7. 总结当前代码功能

1. **声明了一个UI类引用**:在编辑器中设置要显示哪个Widget
2. **游戏开始时创建UI**:在BeginPlay中实例化Widget
3. **显示到屏幕**:通过AddToViewport显示给玩家看

-

Overlay Widget Controller

32:15

🎮 Aura UI 系统的初始化流程

1. 新增:FWidgetControllerParams 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
USTRUCT(BlueprintType)
struct FWidgetControllerParams
{
GENERATED_BODY()

// 构造函数:接受4个参数
FWidgetControllerParams(APlayerController* PC, APlayerState* PS,
UAbilitySystemComponent* ASC, UAttributeSet* AS)
: PlayerController(PC), PlayerState(PS),
AbilitySystemComponent(ASC), AttributeSet(AS) {}

// 四个数据源指针
TObjectPtr<APlayerController> PlayerController; // 玩家控制器
TObjectPtr<APlayerState> PlayerState; // 玩家状态
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent; // GAS组件
TObjectPtr<UAttributeSet> AttributeSet; // 属性集
};
  • 作用:打包UI需要的4个核心数据源
  • 好处:一个结构体传递所有参数,代码更简洁

2. WidgetController参数设置函数

1
2
3
4
5
6
7
8
void UAuraWidgetController::SetWidgetControllerParams(const FWidgetControllerParams& WCParams)
{
// 将结构体中的参数赋给成员变量
PlayerController = WCParams.PlayerController;
PlayerState = WCParams.PlayerState;
AbilitySystemComponent = WCParams.AbilitySystemComponent;
AttributeSet = WCParams.AttributeSet;
}
  • 功能:一次性设置Controller的所有数据源
  • 调用时机:Controller创建后立即调用

3. HUD中的Controller管理

1
2
3
4
5
6
7
// AuraHUD.h 新增成员
private:
UPROPERTY()
TObjectPtr<UOverlayWidgetController> OverlayWidgetController; // Controller实例

UPROPERTY(EditAnywhere)
TSubclassOf<UOverlayWidgetController> OverlayWidgetControllerClass; // Controller类

4. GetOverlayWidgetController函数(单例模式)

1
2
3
4
5
6
7
8
9
10
11
12
UOverlayWidgetController* AAuraHUD::GetOverlayWidgetController(const FWidgetControllerParams& WCParams)
{
if (OverlayWidgetController == nullptr) // 如果还没创建
{
// 1. 创建Controller实例
OverlayWidgetController = NewObject<UOverlayWidgetController>(this, OverlayWidgetControllerClass);

// 2. 设置数据源参数
OverlayWidgetController->SetWidgetControllerParams(WCParams);
}
return OverlayWidgetController;
}
  • 单例模式:确保整个游戏只有一个OverlayController
  • 懒加载:第一次需要时才创建

5. InitOverlay初始化函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void AAuraHUD::InitOverlay(APlayerController* PC, APlayerState* PS, 
UAbilitySystemComponent* ASC, UAttributeSet* AS)
{
// 1. 检查类引用是否设置(开发时断言)
checkf(OverlayWidgetClass, TEXT("覆层类未初始化,请填写BP_AuraHUD"));
checkf(OverlayWidgetControllerClass, TEXT("覆层控制器类未初始化,请填写BP_AuraHUD"));

// 2. 创建UI Widget
UUserWidget* Widget = CreateWidget<UUserWidget>(GetWorld(), OverlayWidgetClass);
OverlayWidget = Cast<UAuraUserWidget>(Widget);

// 3. 准备Controller参数
const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS);

// 4. 获取或创建Controller
UOverlayWidgetController* WidgetController = GetOverlayWidgetController(WidgetControllerParams);

// 5. 将Controller绑定到Widget
OverlayWidget->SetWidgetController(WidgetController);

// 6. 显示UI
Widget->AddToViewport();
}

6. 在玩家角色中初始化UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void AAuraCharacter::InitAbilityActorInfo()
{
// 1. 获取PlayerState和GAS组件(之前的代码)
AAuraPlayerState* AuraPlayerState = GetPlayerState<AAuraPlayerState>();
check(AuraPlayerState);
AuraPlayerState->GetAbilitySystemComponent()->InitAbilityActorInfo(AuraPlayerState, this);
AbilitySystemComponent = AuraPlayerState->GetAbilitySystemComponent();
AttributeSet = AuraPlayerState->GetAttributeSet();

// 2. 新增:初始化UI系统
if (AAuraPlayerController* AuraPlayerController = Cast<AAuraPlayerController>(GetController()))
{
if (AAuraHUD* AuraHUD = Cast<AAuraHUD>(AuraPlayerController->GetHUD()))
{
// 调用HUD初始化UI
AuraHUD->InitOverlay(AuraPlayerController, AuraPlayerState,
AbilitySystemComponent, AttributeSet);
}
}
}

7. 代码执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
玩家加入游戏 → 角色被控制器拥有(PossessedBy)

调用InitAbilityActorInfo()

获取PlayerState、ASC、AS

通过Controller找到HUD

调用HUD.InitOverlay(4个参数)

HUD创建Widget和Controller

Controller绑定数据源,Widget绑定Controller

UI显示在屏幕上

8. Cast操作的作用

1
2
3
4
5
6
7
8
9
// 两次Cast确保类型正确:
1. Cast<AAuraPlayerController>(GetController())
// 确保Controller是Aura自定义的

2. Cast<AAuraHUD>(AuraPlayerController->GetHUD())
// 确保HUD是Aura自定义的

3. Cast<UAuraUserWidget>(Widget)
// 确保创建的Widget是Aura自定义的

9. checkf断言函数

1
2
3
checkf(OverlayWidgetClass, TEXT("错误信息"));
// 作用:开发时检查,如果条件为false则崩溃并显示错误信息
// 发布版本中自动移除,不影响性能

-

Broadcasting Initial Values

25:37

📢 广播初始值系统

1. 新增虚函数 BroadcastInitialValues

1
2
// 基类 UAuraWidgetController
virtual void BroadcastInitialValues();
  • 作用:通知UI显示属性的初始值
  • 虚函数:子类可以重写实现特定逻辑

2. 基类实现(空函数)

1
2
3
4
5
void UAuraWidgetController::BroadcastInitialValues()
{
// 基类不实现具体逻辑
// 子类需要重写这个函数
}

3. 派生类 UOverlayWidgetController 重写

1
2
3
4
5
6
7
// OverlayWidgetController.h
class UOverlayWidgetController : public UAuraWidgetController
{
GENERATED_BODY()
public:
virtual void BroadcastInitialValues() override;
};

4. BroadcastInitialValues 实现

1
2
3
4
5
6
7
8
9
10
11
void UOverlayWidgetController::BroadcastInitialValues()
{
// 1. 安全地转换为AuraAttributeSet
const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);

// 2. 广播生命值初始值
OnHealtChanged.Broadcast(AuraAttributeSet->GetHealth());

// 3. 广播最大生命值初始值
OnHMaxHealtChanged.Broadcast(AuraAttributeSet->GetMaxHealth());
}
关键函数说明
1
2
3
4
5
6
7
8
9
// 1. CastChecked
CastChecked<UAuraAttributeSet>(AttributeSet)
// 作用:安全类型转换,如果转换失败则断言崩溃
// 前提:确保AttributeSet确实是UAuraAttributeSet类型

// 2. Broadcast函数
OnHealtChanged.Broadcast(值);
// 作用:通知所有监听这个事件的UI更新显示
// 参数:要广播的属性值

5. 委托声明

1
2
3
4
5
6
7
8
9
10
// OverlayWidgetController.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealtChangedSignature, float, NewHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxHealtChangedSignature, float, NewMaxHealth);

// 委托变量
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnHealtChangedSignature OnHealtChanged; // 生命值变化委托

UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnMaxHealtChangedSignature OnHMaxHealtChanged; // 最大生命值变化委托
委托宏解析
1
2
3
4
5
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
FOnHealtChangedSignature, // 委托类型名
float, // 参数类型
NewHealth // 参数名
)

6. HUD中的调用时机

1
2
3
4
5
6
7
8
9
10
11
12
13
void AAuraHUD::InitOverlay(...)
{
// ... 前面创建Widget和Controller的代码

// 1. 绑定Controller到Widget
OverlayWidget->SetWidgetController(WidgetController);

// 2. 新增:广播初始值
WidgetController->BroadcastInitialValues();

// 3. 显示UI
Widget->AddToViewport();
}

7. 完整的UI初始化流程

1
2
3
4
5
6
1. 创建Widget实例
2. 创建Controller实例
3. Controller设置数据源参数
4. Widget绑定Controller
5. Controller广播初始值 ← 新增步骤
6. Widget显示到屏幕

8. 为什么需要广播初始值?

问题场景
1
2
3
4
5
6
7
8
// 如果没有广播初始值:
// 1. UI创建时显示默认值(可能是0)
// 2. 玩家看到生命条为空
// 3. 需要等待属性变化事件才能看到正确值

// 有了广播初始值:
// 1. UI一创建就显示正确的当前值
// 2. 玩家立即看到正确的生命值和魔法值

9. 广播的具体作用

1
2
3
4
5
6
7
8
9
// OnHealtChanged.Broadcast(AuraAttributeSet->GetHealth());
// 执行过程:
1. 获取当前生命值(如:100
2. 触发OnHealtChanged委托
3. 所有绑定到这个委托的UI函数被调用
4. UI更新显示为100

// 蓝图中:
// 可以绑定OnHealtChanged事件来更新生命条

-

Listening for Attribute Changes

11:58

📡 属性变化监听系统

1. 新增虚函数 BindCallbacksToDependencies

1
2
// 基类 UAuraWidgetController
virtual void BindCallbacksToDependencies();
  • 作用:绑定属性变化监听器
  • 虚函数:子类重写实现特定监听

2. 基类实现(空函数)

1
2
3
4
5
6
7
8
void UAuraWidgetController::BroadcastInitialValues()
{
// 基类实现为空
}
void UAuraWidgetController::BindCallbacksToDependencies()
{
// 基类实现为空
}

3. OverlayWidgetController 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
void UOverlayWidgetController::BindCallbacksToDependencies()
{
// 1. 安全转换为AuraAttributeSet
const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);

// 2. 绑定生命值变化监听
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AuraAttributeSet->GetHealthAttribute()).AddUObject(this, &UOverlayWidgetController::HealthChanged);

// 3. 绑定最大生命值变化监听
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AuraAttributeSet->GetMaxHealthAttribute()).AddUObject(this, &UOverlayWidgetController::MaxHealthChanged);
}

4. 关键函数详解

GetGameplayAttributeValueChangeDelegate
1
2
// GAS提供的属性变化委托获取函数
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(属性)
  • 参数:要监听的属性(通过GetHealthAttribute()获取)
  • 返回FOnGameplayAttributeValueChange委托
AddUObject 绑定函数
1
.AddUObject(this, &UOverlayWidgetController::HealthChanged)
参数 作用
this 拥有回调函数的对象
&UOverlayWidgetController::HealthChanged 成员函数指针

5. 回调函数实现

1
2
3
4
5
6
7
8
9
void UOverlayWidgetController::HealthChanged(const FOnAttributeChangeData& Data) const
{
OnHealthChanged.Broadcast(Data.NewValue);
}

void UOverlayWidgetController::MaxHealthChanged(const FOnAttributeChangeData& Data) const
{
OnMaxHealthChanged.Broadcast(Data.NewValue);
}
FOnAttributeChangeData 结构体
1
2
3
4
5
6
7
// GAS提供的属性变化数据
struct FOnAttributeChangeData
{
float NewValue; // 新值
float OldValue; // 旧值
// 其他相关信息
};

6. HUD中的调用时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// GetOverlayWidgetController 函数中
UOverlayWidgetController* AAuraHUD::GetOverlayWidgetController(const FWidgetControllerParams& WCParams)
{
if (OverlayWidgetController == nullptr)
{
// 1. 创建Controller
OverlayWidgetController = NewObject<UOverlayWidgetController>(this, OverlayWidgetControllerClass);

// 2. 设置数据源
OverlayWidgetController->SetWidgetControllerParams(WCParams);

// 3. 新增:绑定属性监听
OverlayWidgetController->BindCallbacksToDependencies();
}
return OverlayWidgetController;
}

7. 完整的UI初始化流程

1
2
3
4
5
6
7
1. 创建Widget实例
2. 创建Controller实例
3. Controller设置数据源参数
4. Controller绑定属性监听 ← 新增步骤
5. Widget绑定Controller
6. Controller广播初始值
7. Widget显示到屏幕

8. 工作流程示意图

sequenceDiagram
    participant ASC as AbilitySystemComponent
    participant Controller as OverlayWidgetController
    participant UI as UserWidget

    Note over ASC,UI: 1. 初始设置
    Controller->>ASC: GetGameplayAttributeValueChangeDelegate(Health)
    ASC-->>Controller: 返回委托
    
    Note over ASC,UI: 2. 属性变化时
    ASC->>ASC: Health属性值改变
    ASC->>Controller: 触发HealthChanged回调
    Controller->>UI: OnHealthChanged.Broadcast(新值)
    UI->>UI: 更新生命条显示

9. 属性监听机制详解

委托绑定链
1
2
3
4
5
// 完整的委托绑定链:
1. GAS内部存储属性变化委托
2. 通过GetGameplayAttributeValueChangeDelegate获取委托
3. 使用AddUObject将成员函数绑定到委托
4. 属性变化时,GAS自动调用所有绑定的函数
实时响应优势
1
2
3
4
5
6
// 相比每帧检查的优势:
// 旧方式(效率低):
每帧检查:if (CurrentHealth != LastHealth) { 更新UI }

// 新方式(事件驱动):
GAS自动通知:属性变化 → 立即更新UI

核心改进:从被动轮询变为事件驱动,UI实时响应属性变化,效率更高,响应更快。

-

Callbacks for Mana Changes

11:02

🔄 扩展Mana属性监听

1. 新增Mana相关委托

1
2
3
4
5
6
7
8
9
10
// 声明Mana属性变化委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnManaChangedSignature, float, NewMana);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxManaChangedSignature, float, NewMaxMana);

// 委托变量
UPROPERTY(BlueprintAssignable)
FOnManaChangedSignature OnManaChanged; // 魔法值变化委托

UPROPERTY(BlueprintAssignable)
FOnMaxManaChangedSignature OnMaxManaChanged; // 最大魔法值变化委托

2. 新增回调函数

1
2
3
// 魔法值变化回调
void ManaChanged(const FOnAttributeChangeData& Data) const;
void MaxManaChanged(const FOnAttributeChangeData& Data) const;

3. BroadcastInitialValues 扩展

1
2
3
4
5
6
7
8
9
10
11
12
void UOverlayWidgetController::BroadcastInitialValues()
{
const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);

// 原有:生命值
OnHealthChanged.Broadcast(AuraAttributeSet->GetHealth());
OnMaxHealthChanged.Broadcast(AuraAttributeSet->GetMaxHealth());

// 新增:魔法值
OnManaChanged.Broadcast(AuraAttributeSet->GetMana());
OnMaxManaChanged.Broadcast(AuraAttributeSet->GetMaxMana());
}

4. BindCallbacksToDependencies 扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void UOverlayWidgetController::BindCallbacksToDependencies()
{
const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);

// 原有:生命值监听
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AuraAttributeSet->GetHealthAttribute()).AddUObject(this, &UOverlayWidgetController::HealthChanged);

// 新增:魔法值监听
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AuraAttributeSet->GetManaAttribute()).AddUObject(this, &UOverlayWidgetController::ManaChanged);

// 最大值的监听(同理)
}

5. 回调函数实现

1
2
3
4
5
6
7
8
9
10
// 魔法值变化回调
void UOverlayWidgetController::ManaChanged(const FOnAttributeChangeData& Data) const
{
OnManaChanged.Broadcast(Data.NewValue);
}

void UOverlayWidgetController::MaxManaChanged(const FOnAttributeChangeData& Data) const
{
OnMaxManaChanged.Broadcast(Data.NewValue);
}

总结:完全复制了Health属性的监听模式,为Mana属性建立了相同的监听机制,UI现在可以实时响应生命值和魔法值的变化。

📚 第四章关键方法总结

MVC架构定义

1
2
3
4
5
6
UCLASS()
class UAuraWidgetController : public UObject // Controller层
UCLASS()
class UAuraUserWidget : public UUserWidget // View层
UCLASS()
class UAuraAttributeSet : public UAttributeSet // Model层

数据传递结构体

1
2
3
USTRUCT(BlueprintType)
struct FWidgetControllerParams // UI参数打包
FWidgetControllerParams(APlayerController*, APlayerState*, UAbilitySystemComponent*, UAttributeSet*)

WidgetController参数设置

1
void SetWidgetControllerParams(const FWidgetControllerParams& WCParams)  // 设置4个数据源

UI创建与管理

1
2
3
CreateWidget<T>(GetWorld(), WidgetClass)                        // 创建Widget实例
Widget->AddToViewport() // 显示到屏幕
Cast<T>(指针) // 安全类型转换

HUD单例Controller模式

1
2
UOverlayWidgetController* GetOverlayWidgetController(const FWidgetControllerParams&)  // 单例获取
if (Controller == nullptr) Controller = NewObject<T>(this, ControllerClass) // 懒加载创建

属性变化委托声明

1
2
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature, float, NewHealth)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnManaChangedSignature, float, NewMana)

委托变量声明

1
2
UPROPERTY(BlueprintAssignable)
FOnHealthChangedSignature OnHealthChanged // 蓝图可绑定委托

虚函数框架

1
2
virtual void BroadcastInitialValues()                           // 广播初始值
virtual void BindCallbacksToDependencies() // 绑定属性监听

属性值获取

1
2
AuraAttributeSet->GetHealth()                                   // 获取生命值
AuraAttributeSet->GetMana() // 获取魔法值

属性变化监听绑定

1
2
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(属性)
.AddUObject(this, &UOverlayWidgetController::回调函数) // 绑定属性变化回调

属性变化回调函数

1
2
void HealthChanged(const FOnAttributeChangeData& Data) const    // 属性变化响应
OnHealthChanged.Broadcast(Data.NewValue) // 广播新值

开发调试函数

1
2
checkf(条件, TEXT("错误信息"))                                 // 开发时断言检查
CastChecked<T>(指针) // 安全转换(失败则断言)

蓝图事件声明

1
2
UFUNCTION(BlueprintImplementableEvent)
void WidgetControllerSet() // 蓝图实现的事件

WidgetController设置

1
2
void SetWidgetController(UObject* InWidgetController)           // 设置Controller引用
WidgetControllerSet() // 触发蓝图事件

UI初始化流程函数

1
2
void InitOverlay(APlayerController*, APlayerState*, UAbilitySystemComponent*, UAttributeSet*)
void InitAbilityActorInfo() // 角色GAS初始化

数据类型与指针

1
2
TObjectPtr<T>                                                   // UE5安全对象指针
TSubclassOf<T> // 类型安全的类引用

5.Gameplay Effects / 游戏效果

  • 12 个讲座·3 小时 31分钟 / 12 Lectures · 3 Hours 31 Minutes

  • Gameplay Effects

    07:41

  • Effect Actor Improved

    29:48

🎯 AuraEffectActor 改进:使用GameplayEffect系统

1. 类定义改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
UCLASS()
class GAS_AURA_API AAuraEffectActor : public AActor
{
GENERATED_BODY()

public:
AAuraEffectActor();

protected:
virtual void BeginPlay() override;

// 新的应用效果函数
UFUNCTION(BlueprintCallable)
void ApplyEffectToTarget(AActor* Target, TSubclassOf<UGameplayEffect> GameplayEffectClass);

// GameplayEffect类引用
UPROPERTY(EditAnywhere, Category="Applied Effects")
TSubclassOf<UGameplayEffect> InstantGameplayEffectClass;
};
关键改进
1
2
3
4
5
6
7
8
9
// 移除了之前的:
// - OnOverlap/EndOverlap碰撞函数
// - Mesh和Sphere组件
// - const_cast危险操作

// 新增了:
// 1. ApplyEffectToTarget函数:正确的GAS应用方式
// 2. InstantGameplayEffectClass:要应用的GameplayEffect类
// 3. 通过蓝图调用,更加灵活

2. ApplyEffectToTarget函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void AAuraEffectActor::ApplyEffectToTarget(AActor* Target, TSubclassOf<UGameplayEffect> GameplayEffectClass)
{
// 1. 获取目标的AbilitySystemComponent
UAbilitySystemComponent* TargetASC =
UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Target);

if (TargetASC == nullptr) return; // 如果目标没有ASC,直接返回

// 2. 验证GameplayEffectClass是否有效
check(GameplayEffectClass); // 开发时断言检查

// 3. 创建效果上下文
FGameplayEffectContextHandle EffectContextHandle = TargetASC->MakeEffectContext();
EffectContextHandle.AddSourceObject(this); // 设置效果来源为本Actor

// 4. 创建效果规格
const FGameplayEffectSpecHandle EffectSpecHandle =
TargetASC->MakeOutgoingSpec(GameplayEffectClass, 1.f, EffectContextHandle);

// 5. 应用效果到目标自身
TargetASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
}

3. 关键函数详解

GetAbilitySystemComponent
1
UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Target)
  • 作用:从Actor获取其AbilitySystemComponent
  • 参数Target - 要获取ASC的目标Actor
  • 返回:目标的ASC指针(可能为nullptr)
MakeEffectContext
1
TargetASC->MakeEffectContext()
  • 作用:创建GameplayEffect的上下文
  • 上下文包含:施法者、目标、来源、时间戳等信息
AddSourceObject
1
EffectContextHandle.AddSourceObject(this)
  • 作用:设置效果的来源对象
  • 用途:用于追踪谁施加了这个效果
MakeOutgoingSpec
1
TargetASC->MakeOutgoingSpec(GameplayEffectClass, 1.f, EffectContextHandle)
参数 作用
GameplayEffectClass 要创建的GameplayEffect类型
1.f 效果的等级(Level)
EffectContextHandle 效果上下文
ApplyGameplayEffectSpecToSelf
1
TargetASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get())
  • 作用:将GameplayEffect应用到自身
  • 正确的方法:通过ASC系统应用,而不是直接修改属性

4. GameplayEffect应用流程

1
2
3
4
5
6
7
8
9
10
11
1. 获取目标的ASC

2. 验证效果类有效

3. 创建效果上下文(设置来源)

4. 创建效果规格(包含等级、上下文)

5. 应用效果到目标

6. GAS系统自动处理:属性修改、网络复制、UI更新等

5. 构造函数的简化

1
2
3
4
5
6
AAuraEffectActor::AAuraEffectActor()
{
PrimaryActorTick.bCanEverTick = false; // 禁用Tick

SetRootComponent(CreateDefaultSubobject<USceneComponent>("SceneRoot"));
}
  • 移除了:Mesh和Sphere组件
  • 简化了:只有一个SceneRoot作为根组件
  • 更灵活:可以在蓝图中添加需要的组件

6. 与之前版本的对比

旧版本(错误)
1
2
// 直接修改属性,绕过GAS
const_cast<UAuraAttributeSet*>(AuraAttributeSet)->SetHealth(...);
新版本(正确)
1
2
// 通过GAS系统应用效果
TargetASC->ApplyGameplayEffectSpecToSelf(...);
优势对比
方面 旧版本 新版本
GAS集成 绕过系统 完全集成
网络同步 不同步 自动同步
事件触发 不触发 触发所有相关事件
安全性 危险(const_cast) 安全
可扩展性 固定功能 可通过不同GameplayEffect实现不同效果

7. InstantGameplayEffectClass的使用

1
2
UPROPERTY(EditAnywhere, Category="Applied Effects")
TSubclassOf<UGameplayEffect> InstantGameplayEffectClass;
  • 在编辑器中设置:选择具体的GameplayEffect蓝图
  • 即时效果:Instant类型的GameplayEffect(立即生效)
  • 可配置:不同的Actor实例可以使用不同的效果

8. 如何使用这个Actor

1
2
3
4
5
6
7
// 蓝图中:
// 1. 创建AuraEffectActor实例
// 2. 设置InstantGameplayEffectClass属性
// 3. 调用ApplyEffectToTarget函数,传入目标和效果类

// 或者直接在蓝图中调用:
AuraEffectActor->ApplyEffectToTarget(Player, HealingEffect);

9. 修复的核心问题

1
2
3
4
5
6
7
8
9
// 之前注释中的TODO已经实现:
// TODO: 将此更改为应用游戏效果,目前使用 const_cast 作为临时解决方案!

// 现在:
// √ 移除了const_cast危险操作
// √ 使用正确的GAS API
// √ 支持网络同步
// √ 触发所有相关事件
// √ 可以通过配置实现不同效果

总结:这个改进将效果Actor从危险的临时方案变成了标准的GAS实现,通过GameplayEffect系统实现了安全、可扩展、网络同步的属性修改功能。

-

Instant Gameplay Effects

20:26

img

蓝图中应用GE

img

Instant效果

-

Duration Gameplay Effects

18:21

img

Duration效果

-

Periodic Gameplay Effects

16:17

5-4

-

Effect Stacking

14:36

-

Infinite Gameplay Effects

12:55

🎛️ AuraEffectActor 增强:多类型效果系统

1. 新增枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 效果应用策略枚举
UENUM(BlueprintType)
enum class EEffectApplicationPolicy : uint8
{
ApplyOnOverlap, // 重叠时应用
ApplyOnEndOverlap, // 结束重叠时应用
DoNotApply // 不应用
};

// 效果移除策略枚举
UENUM(BlueprintType)
enum class EEffectRemovalPolicy : uint8
{
RemoveOnEndOverlap, // 结束重叠时移除
DoNotRemove // 不移除
};
枚举作用
1
2
// EEffectApplicationPolicy:控制何时应用效果
// EEffectRemovalPolicy:控制何时移除无限持续效果

2. 扩展的效果类型系统

三种效果类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 即时效果(Instant)- 立即生效,无持续时间
UPROPERTY(EditAnywhere)
TSubclassOf<UGameplayEffect> InstantGameplayEffectClass; // 效果类
EEffectApplicationPolicy InstantEffectApplicationPolicy; // 应用策略

// 2. 持续效果(Duration)- 固定时间段后自动移除
UPROPERTY(EditAnywhere)
TSubclassOf<UGameplayEffect> DurationGameplayEffectClass; // 效果类
EEffectApplicationPolicy DurationEffectApplicationPolicy; // 应用策略

// 3. 无限效果(Infinite)- 永久持续,需要手动移除
UPROPERTY(EditAnywhere)
TSubclassOf<UGameplayEffect> InfiniteGameplayEffectClass; // 效果类
EEffectApplicationPolicy InfiniteEffectApplicationPolicy; // 应用策略
EEffectRemovalPolicy InfiniteEffectRemovalPolicy; // 移除策略

3. 新增碰撞处理函数

1
2
3
4
5
6
7
// 重叠开始回调
UFUNCTION(BlueprintCallable)
void OnOverlap(AActor* TargetActor);

// 重叠结束回调
UFUNCTION(BlueprintCallable)
void OnEndOverlap(AActor* TargetActor);
函数设计
1
2
3
// 蓝图可调用:可以在蓝图中手动触发
// 参数TargetActor:重叠的目标Actor
// 可以在蓝图中连接到碰撞事件

4. 效果移除相关

1
2
3
// 效果移除时是否销毁Actor
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Applied Effects")
bool bDestroyOnEffectRemoval = false;
bDestroyOnEffectRemoval作用
1
2
3
// true:当无限效果被移除时,销毁这个Actor
// false:保留Actor,可以重复使用
// 默认值:false(不销毁)

5. GameplayEffect类型说明

三种效果类型对比
类型 持续时间 应用时机 移除时机
Instant 立即生效,无持续 一次性的 无法移除
Duration 固定时间段 定时移除 时间到自动移除
Infinite 永久持续 需要手动移除 根据策略移除

6. 策略配置示例

在编辑器中配置
1
2
3
4
5
6
7
8
9
10
11
12
13
// 示例1:治疗药水(即时效果)
InstantGameplayEffectClass = GE_HealingPotion
InstantEffectApplicationPolicy = ApplyOnOverlap

// 示例2:中毒效果(持续效果)
DurationGameplayEffectClass = GE_Poison
DurationEffectApplicationPolicy = ApplyOnOverlap

// 示例3:增益光环(无限效果)
InfiniteGameplayEffectClass = GE_BuffAura
InfiniteEffectApplicationPolicy = ApplyOnOverlap
InfiniteEffectRemovalPolicy = RemoveOnEndOverlap
bDestroyOnEffectRemoval = false

7. 当前代码状态

已实现的函数
1
2
3
4
5
6
// 1. 核心效果应用函数(完整实现)
void ApplyEffectToTarget(AActor* TargetActor, TSubclassOf<UGameplayEffect> GameplayEffectClass)

// 2. 碰撞处理函数(待实现)
void OnOverlap(AActor* TargetActor) // 需要实现具体逻辑
void OnEndOverlap(AActor* TargetActor) // 需要实现具体逻辑
待实现逻辑
1
2
3
4
5
6
7
8
9
// OnOverlap函数需要实现:
1. 检查目标是否有ASC
2. 根据三种效果类型的策略决定是否应用
3. 调用ApplyEffectToTarget应用相应效果

// OnEndOverlap函数需要实现:
1. 检查无限效果的移除策略
2. 如果需要移除,找到并移除对应的无限效果
3. 如果bDestroyOnEffectRemoval为true,销毁自身

8. 数据结构设计思路

配置驱动设计
1
2
3
4
5
// 通过UPROPERTY配置,而不是硬编码
// 优势:
1. 可在编辑器可视化配置
2. 不同实例可以不同配置
3. 非程序员也可以调整
策略模式应用
1
2
// 使用枚举控制行为,而不是if-else逻辑
// 更清晰,更容易扩展

9. 与碰撞组件的配合

建议的蓝图设置
1
2
3
4
5
在AuraEffectActor蓝图中:
1. 添加Sphere/Capsule碰撞组件
2. 绑定碰撞事件到OnOverlap/OnEndOverlap
3. 配置各种效果类型和策略
4. 放置到关卡中

10. 当前架构优势

1
2
3
4
5
// 1. 支持多种效果类型(Instant/Duration/Infinite)
// 2. 灵活的策略配置(应用时机、移除时机)
// 3. 蓝图友好(所有配置和函数都暴露给蓝图)
// 4. 可重用性(一个Actor支持多种效果配置)
// 5. 易于扩展(可以添加更多策略或效果类型)

总结:这个扩展将AuraEffectActor从一个简单的即时效果应用器升级为完整的、可配置的多类型效果系统,支持三种GameplayEffect类型和灵活的应用/移除策略。

-

Instant and Duration Application Policy

04:16

AuraEffectActor:即时与持续效果应用策略

1. OnOverlap函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void AAuraEffectActor::OnOverlap(AActor* TargetActor)
{
// 1. 检查即时效果:是否在重叠时应用
if (InstantEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
{
ApplyEffectToTarget(TargetActor, InstantGameplayEffectClass);
}

// 2. 检查持续效果:是否在重叠时应用
if (DurationEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
{
ApplyEffectToTarget(TargetActor, DurationGameplayEffectClass);
}
}
执行逻辑
1
2
3
4
5
6
7
8
9
玩家进入碰撞区域(OnOverlap):

检查InstantEffectApplicationPolicy策略
↓ 如果策略是ApplyOnOverlap
调用ApplyEffectToTarget应用即时效果

检查DurationEffectApplicationPolicy策略
↓ 如果策略是ApplyOnOverlap
调用ApplyEffectToTarget应用持续效果

2. OnEndOverlap函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void AAuraEffectActor::OnEndOverlap(AActor* TargetActor)
{
// 1. 检查即时效果:是否在结束重叠时应用
if (InstantEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnEndOverlap)
{
ApplyEffectToTarget(TargetActor, InstantGameplayEffectClass);
}

// 2. 检查持续效果:是否在结束重叠时应用
if (DurationEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnEndOverlap)
{
ApplyEffectToTarget(TargetActor, DurationGameplayEffectClass);
}
}
执行逻辑
1
2
3
4
5
6
7
8
9
玩家离开碰撞区域(OnEndOverlap):

检查InstantEffectApplicationPolicy策略
↓ 如果策略是ApplyOnEndOverlap
调用ApplyEffectToTarget应用即时效果

检查DurationEffectApplicationPolicy策略
↓ 如果策略是ApplyOnEndOverlap
调用ApplyEffectToTarget应用持续效果

3. 策略组合示例

药水效果(进入时治疗)
1
2
InstantGameplayEffectClass = GE_HealthPotion
InstantEffectApplicationPolicy = EEffectApplicationPolicy::ApplyOnOverlap // 进入时应用
陷阱效果(离开时伤害)
1
2
DurationGameplayEffectClass = GE_PoisonTrap
DurationEffectApplicationPolicy = EEffectApplicationPolicy::ApplyOnEndOverlap // 离开时应用
双向效果(进入和离开都有不同效果)
1
2
3
4
5
6
7
// 进入时:获得加速效果
DurationGameplayEffectClass = GE_SpeedBoost
DurationEffectApplicationPolicy = EEffectApplicationPolicy::ApplyOnOverlap

// 离开时:获得减速效果
InstantGameplayEffectClass = GE_SlowDebuff
InstantEffectApplicationPolicy = EEffectApplicationPolicy::ApplyOnEndOverlap

4. 策略枚举使用说明

EEffectApplicationPolicy 的三个值
1
2
3
ApplyOnOverlap:     // 进入碰撞区域时应用
ApplyOnEndOverlap: // 离开碰撞区域时应用
DoNotApply: // 不应用(默认值)
使用模式
1
2
3
4
5
6
7
8
9
10
11
// 模式1:只进不出
InstantEffectApplicationPolicy = ApplyOnOverlap
DurationEffectApplicationPolicy = DoNotApply

// 模式2:只出不进
InstantEffectApplicationPolicy = DoNotApply
DurationEffectApplicationPolicy = ApplyOnEndOverlap

// 模式3:进出都有
InstantEffectApplicationPolicy = ApplyOnOverlap
DurationEffectApplicationPolicy = ApplyOnEndOverlap

5. 策略执行流程图

  graph TD
      A[玩家进入碰撞区域] --> B{Instant策略检查}
      B -->|ApplyOnOverlap| C[应用即时效果]
      B -->|其他| D
      
      D --> E{Duration策略检查}
      E -->|ApplyOnOverlap| F[应用持续效果]
      E -->|其他| G[跳过]
      
      H[玩家离开碰撞区域] --> I{Instant策略检查}
      I -->|ApplyOnEndOverlap| J[应用即时效果]
      I -->|其他| K
      
      K --> L{Duration策略检查}
      L -->|ApplyOnEndOverlap| M[应用持续效果]
      L -->|其他| N[跳过]

6. 与之前架构的对比

之前:固定行为
1
2
3
4
5
6
// 旧版本的OnOverlap:
1. 总是应用效果
2. 总是立即销毁Actor
3. 没有策略配置

// 行为固定,不可配置
现在:策略驱动
1
2
3
4
5
6
// 新版本的OnOverlap:
1. 根据策略决定是否应用
2. 可以配置不同时机(进入/离开)
3. 可以配置不同效果类型(Instant/Duration)

// 行为可配置,更灵活

总结:这个实现为即时和持续效果添加了灵活的应用策略,可以根据配置在进入或离开碰撞区域时应用不同的效果。但还需要完善无限效果的处理逻辑。

-

Infinite Effect Application and Removal

28:37

♾️ AuraEffectActor:无限效果应用与移除系统

1. 新增关键数据结构

1
2
// 存储活动效果句柄和对应的ASC
TMap<FActiveGameplayEffectHandle, UAbilitySystemComponent*> ActiveEffectHandles;
数据结构说明
1
2
3
// TMap<键类型, 值类型>
// 键:FActiveGameplayEffectHandle - 活动GameplayEffect的唯一标识句柄
// 值:UAbilitySystemComponent* - 应用该效果的ASC
为什么需要这个映射
1
2
3
// 即时效果:应用后立即完成,不需要追踪
// 持续效果:自动过期,不需要追踪
// 无限效果:永久持续,需要存储句柄以便后续移除

2. 头文件新增包含

1
#include "GameplayEffect.h"  // 新增包含,用于FActiveGameplayEffectHandle

3. 变量重命名

1
2
3
4
5
// 之前:
EEffectRemovalPolicy InfiniteGameplayRemovalPolicy

// 现在:
EEffectRemovalPolicy InfiniteEffectRemovalPolicy // 更一致的命名

4. 无限效果的特殊性

与其他效果类型的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 即时效果(Instant):
// - 立即生效并结束
// - 不需要移除
// - 不需要存储句柄

// 持续效果(Duration):
// - 生效一段时间后自动结束
// - 自动移除
// - 不需要存储句柄

// 无限效果(Infinite):
// - 永久持续
// - 需要手动移除
// - 需要存储句柄以便后续移除

5. FActiveGameplayEffectHandle 的作用

什么是效果句柄
1
2
3
4
5
6
7
// 当应用GameplayEffect时,GAS返回一个句柄:
FActiveGameplayEffectHandle Handle = ASC->ApplyGameplayEffectSpecToSelf(...);

// 这个句柄用于:
1. 唯一标识一个活动中的效果
2. 后续移除这个效果
3. 查询效果状态
句柄的特性
1
2
3
// 每个活动效果都有唯一的句柄
// 即使相同的效果类,不同实例也有不同句柄
// 句柄在效果被移除后失效

6. ActiveEffectHandles 映射的使用

存储应用的效果
1
2
3
// 应用无限效果时:
FActiveGameplayEffectHandle Handle = ASC->ApplyGameplayEffectSpecToSelf(...);
ActiveEffectHandles.Add(Handle, ASC); // 存储句柄和ASC
移除效果时查找
1
2
3
4
// 需要移除效果时:
// 遍历ActiveEffectHandles查找对应的ASC和句柄
// 使用句柄移除效果
ASC->RemoveActiveGameplayEffect(Handle, 1); // 1表示移除所有堆叠层数

7. 预期的实现逻辑

OnOverlap中的无限效果处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void AAuraEffectActor::OnOverlap(AActor* TargetActor)
{
// ... 已有的即时和持续效果逻辑

// 新增无限效果处理:
if (InfiniteEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
{
// 应用无限效果
FActiveGameplayEffectHandle Handle = ApplyEffectToTarget(TargetActor, InfiniteGameplayEffectClass);

// 如果应用成功,存储句柄
if (Handle.IsValid())
{
UAbilitySystemComponent* TargetASC = GetASCFromActor(TargetActor);
ActiveEffectHandles.Add(Handle, TargetASC);
}
}
}
OnEndOverlap中的无限效果移除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void AAuraEffectActor::OnEndOverlap(AActor* TargetActor)
{
// ... 已有的即时和持续效果逻辑

// 新增无限效果移除:
if (InfiniteEffectRemovalPolicy == EEffectRemovalPolicy::RemoveOnEndOverlap)
{
// 查找并移除该目标的所有无限效果
RemoveInfiniteEffectsFromTarget(TargetActor);

// 如果需要,销毁Actor
if (bDestroyOnEffectRemoval)
{
Destroy();
}
}
}

8. 无限效果应用场景示例

光环效果(进入区域获得,离开区域失去)
1
2
3
4
InfiniteGameplayEffectClass = GE_RegenerationAura      // 恢复光环
InfiniteEffectApplicationPolicy = ApplyOnOverlap // 进入时应用
InfiniteEffectRemovalPolicy = RemoveOnEndOverlap // 离开时移除
bDestroyOnEffectRemoval = false // Actor不销毁,可重复使用
永久增益(一次性应用,不自动移除)
1
2
3
4
InfiniteGameplayEffectClass = GE_PermanentStrengthBuff  // 永久力量增益
InfiniteEffectApplicationPolicy = ApplyOnOverlap // 进入时应用
InfiniteEffectRemovalPolicy = DoNotRemove // 不移除,永久生效
bDestroyOnEffectRemoval = true // 应用后销毁Actor

9. 设计优势

灵活的效果管理
1
2
3
// 可以同时管理多个目标的多个无限效果
// 可以精准控制每个效果的移除时机
// 支持复杂的叠加和移除逻辑
资源管理
1
2
3
// 通过bDestroyOnEffectRemoval控制Actor生命周期
// 避免内存泄漏:及时移除不需要的效果句柄
// 支持效果的重用和共享

总结:这个扩展为AuraEffectActor添加了完整的无限效果管理系统,通过TMap存储效果句柄,实现了精确的效果应用、追踪和移除功能。现在支持三种完整的效果类型,每种都有独立的策略配置。

-

PreAttributeChange

12:16

🔧 AuraAttributeSet:属性前变更回调

1. 新增虚函数重写

1
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
函数作用
1
2
// 在属性值实际改变之前被调用
// 可以对即将设置的新值进行验证和调整

2. PreAttributeChange 函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void UAuraAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
// 1. 首先调用父类的实现
Super::PreAttributeChange(Attribute, NewValue);

// 2. 检查是否是生命值属性在改变
if (Attribute == GetHealthAttribute())
{
// 限制生命值在 0 到 最大生命值 之间
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
}

// 3. 检查是否是魔法值属性在改变
if (Attribute == GetManaAttribute())
{
// 限制魔法值在 0 到 最大魔法值 之间
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxMana());
}
}

3. 函数参数详解

1
2
3
4
PreAttributeChange(
const FGameplayAttribute& Attribute, // 参数1:正在改变的属性
float& NewValue // 参数2:将要设置的新值(引用传递)
)
参数说明
参数 类型 作用
Attribute const FGameplayAttribute& 正在修改的属性引用
NewValue float& 将要设置的新值(可修改)

4. 属性比较方法

1
2
3
// 使用宏生成的属性获取函数进行比较
Attribute == GetHealthAttribute() // 检查是否是生命值属性
Attribute == GetManaAttribute() // 检查是否是魔法值属性
GetHealthAttribute() 的来源
1
2
// 来自宏:ATTRIBUTE_ACCESSORS(UAuraAttributeSet, Health)
// 生成函数:static FGameplayAttribute GetHealthAttribute()

5. FMath::Clamp 函数

1
FMath::Clamp(NewValue, 0.f, GetMaxHealth())
参数 作用
NewValue 要限制的数值
0.f 最小值(下限)
GetMaxHealth() 最大值(上限)
Clamp 的效果
1
2
3
// 如果 NewValue < 0 → 强制设为 0
// 如果 NewValue > MaxHealth → 强制设为 MaxHealth
// 如果 0 ≤ NewValue ≤ MaxHealth → 保持原值

6. 执行时机

GAS 属性修改流程
1
2
3
4
5
6
1. 开始修改属性(如:玩家受到伤害)
2. 调用 PreAttributeChange(Health, -10) ← 新增步骤
3. 验证并调整新值(确保在有效范围内)
4. 实际修改属性值
5. 触发属性变化事件
6. 更新UI显示
实际应用示例
1
2
3
// 假设:当前生命值=50,最大生命值=100
// 情况1:治疗+80 → NewValue=130 → Clamp后=100(不超过最大生命值)
// 情况2:伤害-60 → NewValue=-10 → Clamp后=0(不低于0)

7. 构造函数中的初始值调整

1
2
3
4
5
6
7
UAuraAttributeSet::UAuraAttributeSet()
{
InitHealth(50.f); // 初始生命值:50(最大生命值的50%)
InitMaxHealth(100.f); // 最大生命值:100
InitMana(25.f); // 初始魔法值:25(最大魔法值的50%)
InitMaxMana(50.f); // 最大魔法值:50
}
初始值设计
1
2
3
// 生命值:50/100(50%)
// 魔法值:25/50(50%)
// 为玩家提供一定的缓冲空间

8. 为什么需要 PreAttributeChange

数据完整性保护
1
2
3
4
5
6
// 防止生命值溢出:
// 没有保护:生命值可能变成负数或超过最大值
// 有保护:生命值始终保持在有效范围内

// 防止魔法值溢出:
// 同理,魔法值也保持在有效范围内
游戏逻辑一致性
1
2
3
// 确保所有修改都遵守相同的规则
// 无论修改来自何处(技能、药水、装备等)
// 都会经过相同的验证逻辑

9. 与 Clamp 相关的其他属性

当前只限制了 Health 和 Mana
1
2
3
4
5
// 为什么只限制 Health 和 Mana?
// 因为这些是"当前值"属性,需要限制在"最大值"范围内

// MaxHealth 和 MaxMana 是"最大值"属性
// 通常不需要限制,或者有特殊的限制逻辑
如果需要限制最大值属性
1
2
3
4
5
6
// 可以添加对 MaxHealth 和 MaxMana 的限制
if (Attribute == GetMaxHealthAttribute())
{
// 限制最大生命值在合理范围内(如:50-500)
NewValue = FMath::Clamp(NewValue, 50.f, 500.f);
}

10. 设计考虑

性能影响
1
2
3
// PreAttributeChange 在每次属性修改时都会调用
// 但只做简单的比较和 Clamp 操作
// 性能开销很小,可以放心使用
可扩展性
1
2
3
// 可以轻松添加对其他属性的限制
// 支持复杂的验证逻辑(如:等级限制、装备要求等)
// 可以记录属性修改日志,便于调试

总结PreAttributeChange 函数为属性修改添加了一层验证保护,确保生命值和魔法值始终保持在有效范围内(0到最大值之间),提高了游戏的稳定性和数据一致性。

  • 5-5

    PostGameplayEffectExecute

    30:57

🔄 AuraAttributeSet:效果后处理系统

1. 新增虚函数重写

1
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
函数作用
1
2
// 在GameplayEffect执行完成后被调用
// 可以执行效果后的处理逻辑(如:经验值计算、状态更新等)

2. FEffectProperties 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
USTRUCT()
struct FEffectProperties
{
GENERATED_BODY()

FGameplayEffectContextHandle EffectContextHandle; // 效果上下文句柄

// 来源端(谁施加效果)
UAbilitySystemComponent* SourceASC = nullptr; // 来源ASC
AActor* SourceAvatarActor = nullptr; // 来源化身Actor
AController* SourceController = nullptr; // 来源控制器
ACharacter* SourceCharacter = nullptr; // 来源角色

// 目标端(谁受到效果)
UAbilitySystemComponent* TargetASC = nullptr; // 目标ASC
AActor* TargetAvatarActor = nullptr; // 目标化身Actor
AController* TargetController = nullptr; // 目标控制器
ACharacter* TargetCharacter = nullptr; // 目标角色
};
结构体作用
1
2
3
// 集中存储效果相关的所有信息
// 便于在PostGameplayEffectExecute中访问
// 避免重复解析GameplayEffect数据

3. SetEffectProperties 辅助函数

1
2
3
4
5
6
7
8
void UAuraAttributeSet::SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const
{
// 1. 获取效果上下文
Props.EffectContextHandle = Data.EffectSpec.GetContext();

// 2. 获取来源ASC
Props.SourceASC = Props.EffectContextHandle.GetOriginalInstigatorAbilitySystemComponent();
}
来源端信息提取
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
// 检查来源ASC是否有效
if (IsValid(Props.SourceASC) &&
Props.SourceASC->AbilityActorInfo.IsValid() &&
Props.SourceASC->AbilityActorInfo->AvatarActor.IsValid())
{
// 获取化身Actor
Props.SourceAvatarActor = Props.SourceASC->AbilityActorInfo->AvatarActor.Get();

// 获取控制器
Props.SourceController = Props.SourceASC->AbilityActorInfo->PlayerController.Get();

// 如果控制器为空,尝试从化身Actor获取
if (Props.SourceController == nullptr && Props.SourceAvatarActor != nullptr)
{
if (const APawn* Pawn = Cast<APawn>(Props.SourceAvatarActor))
{
Props.SourceController = Pawn->GetController();
}
}

// 从控制器获取角色
if (Props.SourceController)
{
Props.SourceCharacter = Cast<ACharacter>(Props.SourceController->GetPawn());
}
}
目标端信息提取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 检查目标AbilityActorInfo是否有效
if (Data.Target.AbilityActorInfo.IsValid() &&
Data.Target.AbilityActorInfo->AvatarActor.IsValid())
{
// 获取目标化身Actor
Props.TargetAvatarActor = Data.Target.AbilityActorInfo->AvatarActor.Get();

// 获取目标控制器
Props.TargetController = Data.Target.AbilityActorInfo->PlayerController.Get();

// 获取目标角色
Props.TargetCharacter = Cast<ACharacter>(Props.TargetAvatarActor);

// 获取目标ASC
Props.TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Props.TargetAvatarActor);
}

4. PostGameplayEffectExecute 函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void UAuraAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
// 1. 调用父类实现
Super::PostGameplayEffectExecute(Data);

// 2. 创建效果属性结构体
FEffectProperties Props;

// 3. 填充效果属性信息
SetEffectProperties(Data, Props);

// 4. 后续处理逻辑(待实现)
// 可以在这里添加经验值计算、状态更新等逻辑
}

5. FGameplayEffectModCallbackData 参数详解

1
2
3
4
// Data参数包含的信息:
1. Data.EffectSpec // 效果规格(包含效果类型、等级、持续时间等)
2. Data.EvaluatedData // 评估数据(实际修改的值)
3. Data.Target // 目标信息(AbilityActorInfo)
Data.EvaluatedData 结构
1
2
3
4
5
// EvaluatedData包含:
1. Attribute // 被修改的属性
2. AttributeType // 属性类型
3. Magnitude // 修改的幅度
4. ModifierOp // 修改操作(加、乘、覆盖等)

6. 执行时机对比

PreAttributeChange vs PostGameplayEffectExecute
1
2
3
4
5
6
7
8
9
// PreAttributeChange(属性前变更):
时机:属性值实际改变之前
作用:验证和调整将要设置的新值
例子:限制生命值在0-100之间

// PostGameplayEffectExecute(效果后执行):
时机:GameplayEffect执行完成后
作用:处理效果后的逻辑
例子:计算经验值、更新状态、播放音效等
完整的属性修改流程
1
2
3
4
5
6
1. GameplayEffect开始执行
2. 调用PreAttributeChange(验证新值)
3. 实际修改属性值
4. 调用PostGameplayEffectExecute(后处理)
5. 触发属性变化事件
6. 更新UI显示

7. 效果来源和目标解析

为什么需要这个复杂的信息提取
1
2
3
4
5
6
// 游戏中可能需要:
1. 知道谁造成了伤害(来源)
2. 知道谁受到了伤害(目标)
3. 计算经验值(基于来源和目标的关系)
4. 播放不同的音效(基于效果类型)
5. 显示不同的特效(基于来源阵营)
典型应用场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 场景1:玩家攻击敌人
Source: 玩家角色
Target: 敌人角色
需要:计算玩家获得的经验值

// 场景2:药水治疗玩家
Source: 药水Actor
Target: 玩家角色
需要:播放治疗特效

// 场景3:环境伤害
Source: 陷阱Actor
Target: 玩家角色
需要:显示陷阱触发效果

8. 安全检查和空指针处理

多重安全检查
1
2
3
4
5
6
7
8
// 来源端检查:
1. IsValid(Props.SourceASC) // ASC是否有效
2. Props.SourceASC->AbilityActorInfo.IsValid() // AbilityActorInfo是否有效
3. Props.SourceASC->AbilityActorInfo->AvatarActor.IsValid() // 化身Actor是否有效

// 目标端检查:
1. Data.Target.AbilityActorInfo.IsValid() // AbilityActorInfo是否有效
2. Data.Target.AbilityActorInfo->AvatarActor.IsValid() // 化身Actor是否有效
控制器获取的回退逻辑
1
2
3
4
5
6
7
8
// 如果ASC中的控制器为空,尝试从化身Actor获取
if (Props.SourceController == nullptr && Props.SourceAvatarActor != nullptr)
{
if (const APawn* Pawn = Cast<APawn>(Props.SourceAvatarActor))
{
Props.SourceController = Pawn->GetController(); // 回退获取控制器
}
}

9. 当前代码状态

已实现的功能
1
2
3
4
5
6
// 1. 结构体定义完成(FEffectProperties)
// 2. 信息提取函数完成(SetEffectProperties)
// 3. 后处理框架搭建完成(PostGameplayEffectExecute)

// 待实现:
// 具体的后处理逻辑(经验值计算、状态更新等)
后续扩展方向
1
2
3
4
5
// 可以在PostGameplayEffectExecute中添加:
1. 经验值系统:根据伤害计算经验
2. 状态更新:更新角色的战斗状态
3. 事件触发:触发全局事件通知
4. 成就系统:追踪特定效果的应用

总结:这个扩展为AuraAttributeSet添加了完整的GameplayEffect后处理系统,能够提取详细的来源和目标信息,为后续的游戏逻辑(如经验值系统、状态系统等)提供了基础数据支持。

-

Curve Tables for Scalable Floats

15:19

📚 第五章关键方法总结

AuraEffectActor 基础改进

1
2
3
void ApplyEffectToTarget(AActor* Target, TSubclassOf<UGameplayEffect> GameplayEffectClass);  // 应用效果到目标
UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Target) // 获取目标ASC
TargetASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get()) // 应用GameplayEffect

效果策略枚举

1
2
3
4
UENUM(BlueprintType)
enum class EEffectApplicationPolicy : uint8 // 效果应用策略
UENUM(BlueprintType)
enum class EEffectRemovalPolicy : uint8 // 效果移除策略

碰撞处理函数

1
2
void OnOverlap(AActor* TargetActor);      // 重叠开始
void OnEndOverlap(AActor* TargetActor); // 重叠结束

三种效果类型系统

1
2
3
TSubclassOf<UGameplayEffect> InstantGameplayEffectClass;   // 即时效果
TSubclassOf<UGameplayEffect> DurationGameplayEffectClass; // 持续效果
TSubclassOf<UGameplayEffect> InfiniteGameplayEffectClass; // 无限效果

效果应用策略变量

1
2
3
4
5
EEffectApplicationPolicy InstantEffectApplicationPolicy;    // 即时效果策略
EEffectApplicationPolicy DurationEffectApplicationPolicy; // 持续效果策略
EEffectApplicationPolicy InfiniteEffectApplicationPolicy; // 无限效果策略
EEffectRemovalPolicy InfiniteEffectRemovalPolicy; // 无限效果移除策略
bool bDestroyOnEffectRemoval = false; // 效果移除时是否销毁

效果创建函数

1
2
3
FGameplayEffectContextHandle EffectContextHandle = TargetASC->MakeEffectContext();  // 创建效果上下文
EffectContextHandle.AddSourceObject(this); // 添加来源对象
FGameplayEffectSpecHandle EffectSpecHandle = TargetASC->MakeOutgoingSpec(...); // 创建效果规格

AuraAttributeSet 属性前变更

1
2
3
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;  // 属性前变更回调
Attribute == GetHealthAttribute() // 属性比较
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth()); // 限制生命值范围

FEffectProperties 结构体

1
2
3
4
5
USTRUCT()
struct FEffectProperties // 效果属性结构体
FGameplayEffectContextHandle EffectContextHandle; // 效果上下文句柄
UAbilitySystemComponent* SourceASC = nullptr; // 来源ASC
UAbilitySystemComponent* TargetASC = nullptr; // 目标ASC

效果后处理系统

1
2
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;  // 效果后执行回调
void SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const; // 设置效果属性

效果来源和目标信息提取

1
2
Props.SourceASC = Props.EffectContextHandle.GetOriginalInstigatorAbilitySystemComponent();  // 获取来源ASC
Props.TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Props.TargetAvatarActor); // 获取目标ASC

控制器获取逻辑

1
2
3
4
if (const APawn* Pawn = Cast<APawn>(Props.SourceAvatarActor))
{
Props.SourceController = Pawn->GetController(); // 从Pawn获取控制器
}

GameplayEffectModCallbackData 使用

1
2
3
Data.EffectSpec.GetContext()      // 获取效果上下文
Data.Target.AbilityActorInfo // 目标AbilityActorInfo
Data.EvaluatedData // 评估数据(包含属性修改信息)

效果应用流程函数

1
2
3
TargetASC->MakeEffectContext()    // 创建效果上下文
TargetASC->MakeOutgoingSpec() // 创建出站效果规格
TargetASC->ApplyGameplayEffectSpecToSelf() // 应用效果到自身

FGameplayAttributeData 类型

1
2
3
FGameplayAttributeData            // GAS属性数据类型
GetCurrentValue() // 获取当前值
SetCurrentValue() // 设置当前值

属性值限制逻辑

1
2
FMath::Clamp(NewValue, 0.f, GetMaxHealth())  // 限制生命值
FMath::Clamp(NewValue, 0.f, GetMaxMana()) // 限制魔法值

6.Gameplay Tags / 游戏标签

  • 16 个讲座·3 小时 23 分钟 / 16 Lectures · 3 Hours 23 Minutes

  • Gameplay Tags

    05:55

  • Creating Gameplay Tags in the Editor

    07:42

  • Creating Gameplay Tags from Data Tables

    06:22

  • Adding Gameplay Tags to Gameplay Effects

    00:15

  • Apply Gameplay Tags with Effects

    18:18

  • Gameplay Effect Delegates

    12:46

📡 GameplayEffect委托系统

1. 新增AbilityActorInfoSet函数

1
2
// UAuraAbilitySystemComponent类
void AbilityActorInfoSet();
函数实现
1
2
3
4
5
6
7
8
void UAuraAbilitySystemComponent::AbilityActorInfoSet()
{
// 绑定GameplayEffect应用委托
OnGameplayEffectAppliedDelegateToSelf.AddUObject(
this, // 目标对象
&UAuraAbilitySystemComponent::EffectApplied // 回调函数
);
}

2. OnGameplayEffectAppliedDelegateToSelf委托

1
2
// GAS提供的委托:当GameplayEffect应用到自身时触发
OnGameplayEffectAppliedDelegateToSelf
委托特性
1
2
3
4
5
6
// 参数类型:
UAbilitySystemComponent* AbilitySystemComponent, // 应用效果的ASC
const FGameplayEffectSpec& EffectSpec, // 效果规格
FActiveGameplayEffectHandle ActiveEffectHandle // 活动效果句柄

// 触发时机:效果成功应用到ASC自身时

3. EffectApplied回调函数

1
2
3
4
5
6
7
8
9
10
11
void UAuraAbilitySystemComponent::EffectApplied(
UAbilitySystemComponent* AbilitySystemComponent,
const FGameplayEffectSpec& EffectSpec,
FActiveGameplayEffectHandle ActiveEffectHandle)
{
// 预留的回调函数,可用于:
// 1. 播放音效/特效
// 2. 显示UI通知
// 3. 记录战斗日志
// 4. 触发其他系统反应
}

4. 在角色初始化中调用

敌人角色
1
2
3
4
5
6
7
8
void AAuraEnemy::InitAbilityActorInfo()
{
// 1. 初始化ASC
AbilitySystemComponent->InitAbilityActorInfo(this, this);

// 2. 设置能力Actor信息(新增)
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->AbilityActorInfoSet();
}
玩家角色
1
2
3
4
5
6
7
8
9
void AAuraCharacter::InitAbilityActorInfo()
{
// ... 原有代码

// 新增:设置能力Actor信息
Cast<UAuraAbilitySystemComponent>(AuraPlayerState->GetAbilitySystemComponent())->AbilityActorInfoSet();

// ... 后续代码
}

5. 类型转换的重要性

1
2
3
4
5
6
7
8
9
10
// 为什么需要Cast?
// ASC基类没有AbilityActorInfoSet函数

// 玩家角色:
Cast<UAuraAbilitySystemComponent>(AuraPlayerState->GetAbilitySystemComponent())
->AbilityActorInfoSet();

// 敌人角色:
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)
->AbilityActorInfoSet();

6. 执行时机流程

完整的初始化流程
1
2
3
4
5
6
1. 角色创建
2. 调用InitAbilityActorInfo()
3. 调用ASC->InitAbilityActorInfo(Owner, Avatar)
4. 调用ASC->AbilityActorInfoSet()
5. 绑定EffectApplied委托
6. 后续GameplayEffect应用时触发委托
委托触发流程
1
2
3
4
1. 任何GameplayEffect应用到角色
2. GAS系统自动触发OnGameplayEffectAppliedDelegateToSelf
3. 调用绑定的EffectApplied回调函数
4. 执行自定义逻辑

7. 设计模式:观察者模式

委托系统的作用
1
2
3
4
5
6
7
8
// 观察者模式实现:
观察者:UAuraAbilitySystemComponent(订阅效果应用事件)
被观察者:GAS系统(发布效果应用事件)

// 好处:
1. 解耦:效果应用逻辑与响应逻辑分离
2. 扩展性:可以添加多个观察者
3. 灵活性:不同角色可以有不同响应

8. 与AttributeSet的PostGameplayEffectExecute对比

两种回调的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
// EffectApplied(在ASC中):
时机:效果刚应用到ASC时
作用:通用响应(音效、特效、日志)
访问:效果规格、效果句柄

// PostGameplayEffectExecute(在AttributeSet中):
时机:属性修改完成后
作用:具体属性相关的逻辑(如经验计算)
访问:来源、目标详细信息

// 两者可以配合使用:
EffectApplied:处理通用响应
PostGameplayEffectExecute:处理属性相关逻辑

9. 当前的实现状态

已完成的绑定
1
2
3
4
5
6
// ✅ 委托绑定完成
OnGameplayEffectAppliedDelegateToSelf.AddUObject(this, &UAuraAbilitySystemComponent::EffectApplied);

// ✅ 角色初始化调用完成
敌人:在BeginPlay中调用InitAbilityActorInfo()
玩家:在PossessedBy/OnRep_PlayerState中调用InitAbilityActorInfo()
待实现的内容
1
2
3
// 🔄 EffectApplied函数目前为空
// 需要根据游戏需求添加具体逻辑
// 如:伤害数字、状态提示、音效播放等

总结:这个系统为GameplayEffect应用添加了事件驱动的响应机制,通过委托系统实现了观察者模式,为后续的战斗反馈、UI显示、音效播放等功能提供了基础框架。

-

Get All Asset Tags

10:34

🏷️ 获取GameplayEffect的所有Asset Tags

1. 新增标签容器获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void UAuraAbilitySystemComponent::EffectApplied(...)
{
// 1. 创建空的标签容器
FGameplayTagContainer TagContainer;

// 2. 从EffectSpec获取所有Asset Tags
EffectSpec.GetAllAssetTags(TagContainer);

// 3. 遍历所有标签
for (const FGameplayTag& Tag : TagContainer)
{
// TODO: 将标签广播到Widget Controller
// 可以在这里处理特定的效果标签
}
}

2. FGameplayTagContainer 容器

1
2
3
4
5
6
7
// 用于存储GameplayTag的容器
FGameplayTagContainer TagContainer;

// 特性:
// 1. 自动管理内存
// 2. 支持快速标签查询
// 3. 支持集合操作(并集、交集、差集)

3. EffectSpec.GetAllAssetTags() 函数

1
2
3
4
5
6
// 函数签名:
void UGameplayEffect::GetAllAssetTags(FGameplayTagContainer& OutTagContainer) const

// 作用:
// 获取GameplayEffect定义的所有Asset Tags
// 填充到传入的TagContainer中
Asset Tags vs Dynamic Tags
1
2
3
4
5
6
7
8
// Asset Tags(资产标签):
// 在GameplayEffect蓝图中静态定义
// 用于标识效果的类型(如:Damage, Healing, Buff等)
// 不会在运行时改变

// Dynamic Tags(动态标签):
// 在运行时动态添加/移除
// 用于临时状态(如:Stunned, Burning等)

4. 标签遍历循环

1
2
3
4
5
6
7
8
// C++范围for循环遍历标签
for (const FGameplayTag& Tag : TagContainer)
{
// Tag变量包含:
// 1. 标签名称(如:"Effect.Damage.Fire")
// 2. 标签深度信息
// 3. 比较和匹配功能
}

5. 标签系统工作流程

1
2
3
4
5
1. 在GameplayEffect蓝图中定义Asset Tags
2. 效果应用到角色时触发EffectApplied
3. 获取所有Asset Tags到容器中
4. 遍历每个标签进行处理
5. 将标签信息传递到UI系统

总结:这个功能为GameplayEffect系统添加了标签处理能力,能够识别和响应不同类型的游戏效果,为后续的UI反馈、音效播放、特效显示等提供了数据基础。标签系统的灵活性使得可以轻松扩展新的效果类型和处理逻辑。

-

Broadcasting Effect Asset Tags

10:03

📢 广播效果资产标签系统

1. 新增委托声明

1
2
// 声明效果标签委托
DECLARE_MULTICAST_DELEGATE_OneParam(FEffectAssetTags, const FGameplayTagContainer& /*AssetTags*/)
委托宏说明
1
2
3
4
5
6
7
8
9
// DECLARE_MULTICAST_DELEGATE_OneParam
// 创建一个可以绑定多个回调的委托
// OneParam: 带一个参数的委托
// 参数类型:const FGameplayTagContainer&
// 参数名:AssetTags

// 与蓝图委托的区别:
// DECLARE_MULTICAST_DELEGATE: 纯C++委托(无蓝图支持)
// DECLARE_DYNAMIC_MULTICAST_DELEGATE: 支持蓝图的委托

2. 委托变量

1
2
// 在UAuraAbilitySystemComponent类中
FEffectAssetTags EffectAssetTags;
委托作用
1
2
3
// 当GameplayEffect应用时
// 广播该效果的Asset Tags
// 其他系统可以订阅这个委托来响应效果标签

3. EffectApplied函数更新

1
2
3
4
5
6
7
8
9
void UAuraAbilitySystemComponent::EffectApplied(...)
{
// 1. 获取效果的所有Asset Tags
FGameplayTagContainer TagContainer;
EffectSpec.GetAllAssetTags(TagContainer);

// 2. 广播标签容器到所有订阅者
EffectAssetTags.Broadcast(TagContainer);
}
Broadcast函数
1
2
3
4
5
// 触发委托,调用所有绑定的回调函数
EffectAssetTags.Broadcast(TagContainer);

// 每个订阅了这个委托的类都会收到通知
// 参数TagContainer会被传递给每个回调函数

4. 在WidgetController中绑定委托

BindCallbacksToDependencies函数扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void UOverlayWidgetController::BindCallbacksToDependencies()
{
// ... 已有的属性变化绑定

// 新增:绑定效果标签委托
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->EffectAssetTags.AddLambda(
[](const FGameplayTagContainer& AssetTags)
{
// 处理接收到的标签
for (const FGameplayTag& Tag : AssetTags)
{
// TODO: 处理每个标签
// 可以在这里显示UI提示、播放音效等
}
}
);
}

5. Lambda表达式绑定

1
2
3
4
5
6
7
8
9
10
11
// 使用Lambda表达式绑定委托
EffectAssetTags.AddLambda(
[](const FGameplayTagContainer& AssetTags) // Lambda参数
{
// Lambda函数体
for (const FGameplayTag& Tag : AssetTags)
{
// 处理每个标签
}
}
);
Lambda表达式语法
1
2
3
4
5
6
7
8
9
10
// 基本语法:
[](参数列表) { 函数体 }

// 捕获列表[]:
// 空[]: 不捕获任何外部变量
// [=]: 以值方式捕获所有外部变量
// [&]: 以引用方式捕获所有外部变量
// [this]: 捕获当前类的this指针

// 在这个例子中是空捕获列表,因为不需要访问外部变量

6. 标签处理流程

1
2
3
4
5
6
1. GameplayEffect应用到角色
2. ASC触发EffectApplied回调
3. 获取效果的Asset Tags
4. 通过EffectAssetTags委托广播标签
5. WidgetController的Lambda接收到标签
6. 遍历处理每个标签

7. 实际应用示例

创建特定效果的GameplayEffect蓝图
1
2
3
4
5
6
// 效果名称:GE_Fireball
// Asset Tags添加:
// - Effect.Damage.Fire
// - School.Evocation
// - Element.Fire
// - DamageType.Magical
在WidgetController中处理标签
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
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->EffectAssetTags.AddLambda(
[](const FGameplayTagContainer& AssetTags)
{
// 检查是否包含火焰伤害标签
if (AssetTags.HasTag(FGameplayTag::RequestGameplayTag("Effect.Damage.Fire")))
{
// 显示火焰伤害UI提示
// 播放火焰音效
// 更新战斗日志
}

// 检查是否包含治疗标签
if (AssetTags.HasTag(FGameplayTag::RequestGameplayTag("Effect.Healing")))
{
// 显示治疗绿色数字
// 播放治疗音效
}

// 检查控制效果
if (AssetTags.HasTag(FGameplayTag::RequestGameplayTag("Effect.CrowdControl")))
{
// 显示控制效果图标
// 更新状态栏
}
}
);

8. 委托系统的优势

解耦设计
1
2
3
// 发布者(ASC):只负责广播标签
// 订阅者(WidgetController):只负责处理标签
// 两者互不依赖,可以独立修改
多订阅者支持
1
2
3
4
5
6
7
// 多个系统可以同时订阅:
// 1. WidgetController: 显示UI提示
// 2. AudioManager: 播放音效
// 3. VFXManager: 播放特效
// 4. AchievementSystem: 追踪成就

// 每个系统只关心自己需要的标签

9. 完整的标签处理系统架构

  graph TD
      A[GameplayEffect应用] --> B[ASC:EffectApplied]
      B --> C[获取Asset Tags]
      C --> D[广播EffectAssetTags委托]
      D --> E[WidgetController:Lambda接收]
      E --> F[遍历处理每个标签]
      F --> G[显示UI提示/播放音效等]

总结:这个系统建立了一个完整的效果标签广播机制,使得UI系统能够实时响应不同类型的游戏效果,为战斗反馈、状态显示、音效播放等提供了灵活的事件驱动架构。

-

UI Widget Data Table

12:40

📊 UI数据表系统:FUIWidgetRow 结构体

1. 新增数据结构:FUIWidgetRow

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

UPROPERTY(EditAnywhere, BlueprintReadOnly)
FGameplayTag MessageTag = FGameplayTag(); // 消息标签

UPROPERTY(EditAnywhere, BlueprintReadOnly)
FText Message = FText(); // 消息文本

UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<UAuraUserWidget> MessageWidget; // 消息Widget类

UPROPERTY(EditAnywhere, BlueprintReadOnly)
UTexture2D* Image = nullptr; // 图像资源
};
继承关系
1
2
// 继承自FTableRowBase - UE数据表系统的基础结构
// 可以用在DataTable中作为行数据结构

2. 结构体成员详解

MessageTag(消息标签)
1
FGameplayTag MessageTag = FGameplayTag();
  • 作用:标识UI消息类型的GameplayTag
  • 用途:与GameplayEffect的Asset Tags对应
Message(消息文本)
1
FText Message = FText();
  • 作用:要显示的文本内容
  • 类型:使用FText支持本地化
MessageWidget(消息Widget类)
1
TSubclassOf<UAuraUserWidget> MessageWidget;
  • 作用:指定显示消息的自定义Widget类
  • 限制:必须是UAuraUserWidget或其子类
Image(图像资源)
1
UTexture2D* Image = nullptr;
  • 作用:关联的图标或图片资源
  • 示例:技能图标、状态图标

3. MessageWidgetDataTable 数据表引用

1
2
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Widget Data")
TObjectPtr<UDataTable> MessageWidgetDataTable;
属性说明
1
2
3
4
EditDefaultsOnly:    // 只能在类默认值中编辑
BlueprintReadOnly: // 蓝图只读访问
Category: // 编辑器分类为"Widget Data"
TObjectPtr<UDataTable>: // 指向数据表的智能指针

4. 当前代码状态

已有的绑定
1
2
3
4
5
6
7
8
9
10
11
// 在BindCallbacksToDependencies中:
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->EffectAssetTags.AddLambda(
[](const FGameplayTagContainer& AssetTags)
{
for(const FGameplayTag& Tag : AssetTags)
{
// 这里可以添加数据表查找逻辑
// 使用MessageWidgetDataTable查找对应的FUIWidgetRow
}
}
);

5. 数据结构设计思路

数据表与标签的映射
1
2
3
4
5
6
7
8
// GameplayEffect中的Asset Tags示例:
"Effect.Damage.Fire" // 火焰伤害效果

// 数据表中的对应行:
MessageTag: "Message.Damage.Fire"
Message: "受到火焰伤害!"
MessageWidget: WBP_DamageMessage
Image: FireIcon_Texture
数据驱动UI系统
1
2
3
4
// 分离数据与逻辑:
// 1. 数据:在数据表中配置所有消息类型
// 2. 逻辑:代码根据标签查找并显示对应消息
// 3. 好处:非程序员可以轻松修改UI内容

6. 与其他系统的关系

与GameplayEffect系统的集成
1
2
3
4
5
6
// 工作流程:
1. GameplayEffect应用到角色
2. ASC获取Effect的Asset Tags
3. 广播标签到WidgetController
4. WidgetController查找数据表
5. 显示对应的UI消息
与现有属性系统的对比
1
2
3
// 属性变化系统:处理数值更新(OnHealthChanged等)
// 效果消息系统:处理类型消息(基于GameplayTag)
// 两者独立工作,互不干扰

7. 结构体使用方式

在编辑器中创建数据表
1
2
3
4
5
// 步骤:
1. 右键创建DataTable
2. 选择Row Structure为FUIWidgetRow
3. 添加行,每行对应一种消息类型
4. 在WidgetController蓝图中设置MessageWidgetDataTable引用

总结:这个数据表系统为游戏效果提供了可配置的UI反馈机制,通过GameplayTag将GameplayEffect与UI消息关联起来,实现了数据驱动的UI显示系统。

-

Retrieving Rows from Data Tables

16:17

🔧 模板函数:GetDataTableRowByTag

1. 新增模板函数声明

1
2
template<typename T>
T* GetDataTableRowByTag(UDataTable* DataTable, const FGameplayTag& Tag);
模板参数说明
1
2
3
// <typename T>:类型参数
// T*:返回对应类型的指针
// 调用时指定具体类型:GetDataTableRowByTag<FUIWidgetRow>(...)

2. 模板函数实现

1
2
3
4
5
template <typename T>
T* UOverlayWidgetController::GetDataTableRowByTag(UDataTable* DataTable, const FGameplayTag& Tag)
{
return DataTable->FindRow<T>(Tag.GetTagName(), TEXT(""));
}

3. 函数参数详解

UDataTable DataTable*
1
2
// 要查询的数据表指针
// 来自成员变量:MessageWidgetDataTable
const FGameplayTag& Tag
1
2
// 用于查找行的标签
// 从EffectAssetTags委托接收到的GameplayTag

4. DataTable->FindRow函数

1
2
3
4
DataTable->FindRow<T>(
Tag.GetTagName(), // 参数1:行名(使用标签的字符串名称)
TEXT("") // 参数2:上下文信息(用于错误报告)
)
Tag.GetTagName()
1
2
// 将FGameplayTag转换为FName
// 示例:Tag="Message.Damage.Fire" → TagName="Message.Damage.Fire"
返回值
1
2
// 找到:返回对应行数据的指针(T*)
// 未找到:返回nullptr

5. Lambda中的使用

1
2
3
4
5
6
7
8
9
10
11
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->EffectAssetTags.AddLambda(
[this](const FGameplayTagContainer& AssetTags)
{
// 遍历所有收到的标签
for(const FGameplayTag& Tag : AssetTags)
{
// 调用模板函数查找数据表行
GetDataTableRowByTag<FUIWidgetRow>(MessageWidgetDataTable, Tag);
}
}
);

6. 模板函数的作用

通用数据表查询
1
2
3
4
5
6
7
8
9
// 可以查询任何类型的数据表行
// 只需要行类型继承自FTableRowBase

// 当前使用:
GetDataTableRowByTag<FUIWidgetRow>(...) // 查找FUIWidgetRow类型的数据

// 未来可以扩展:
GetDataTableRowByTag<FItemDataRow>(...) // 查找物品数据
GetDataTableRowByTag<FSkillDataRow>(...) // 查找技能数据
类型安全
1
2
3
// 编译时检查类型
// 确保数据表的行结构与模板参数匹配
// 避免运行时类型错误

7. 当前代码状态

已实现的功能
1
2
3
4
5
// 1. 模板函数声明和实现完成
// 2. 在Lambda中调用模板函数
// 3. 传递正确的参数:MessageWidgetDataTable和Tag

// 当前只是查找,没有处理查找结果

8. 模板函数的优势

代码复用性
1
2
3
4
5
6
7
8
9
10
// 一个函数处理多种数据表类型
// 避免为每种数据类型写重复的查找函数

// 不用模板的写法(重复代码):
FUIWidgetRow* FindUIWidgetRow(UDataTable* Table, FGameplayTag Tag) {...}
FItemDataRow* FindItemDataRow(UDataTable* Table, FGameplayTag Tag) {...}
FSkillDataRow* FindSkillDataRow(UDataTable* Table, FGameplayTag Tag) {...}

// 使用模板(一行代码):
template<typename T> T* FindRow(UDataTable* Table, FGameplayTag Tag) {...}
编译时类型检查
1
2
3
4
// 如果调用时类型错误,编译时会报错
// 例如:
GetDataTableRowByTag<FItemDataRow>(MessageWidgetDataTable, Tag);
// 编译错误:FItemDataRow与数据表行结构不匹配

9. Lambda捕获列表 [this]

1
2
3
4
5
6
7
8
// Lambda表达式:[this]
// 作用:捕获当前类的this指针
// 允许在Lambda中访问类的成员变量和函数

// 可以访问:
// MessageWidgetDataTable
// GetDataTableRowByTag函数
// 其他成员变量和函数

10. 完整的查询流程

1
2
3
4
5
6
7
// 简化后的流程:
1. GameplayEffect应用 → 获取Asset Tags
2. EffectAssetTags委托广播标签
3. Lambda接收到标签容器
4. 遍历每个标签
5. 调用GetDataTableRowByTag查找数据表
6. 返回对应的FUIWidgetRow指针(或nullptr

总结:这个模板函数提供了一个通用的数据表查询工具,可以根据GameplayTag查找任何类型的数据表行。它简化了数据访问逻辑,提高了代码的复用性和类型安全性。

-

Broadcasting Data Table Rows

15:49

📤 广播数据表行:MessageWidgetRowDelegate

1. 新增委托声明

1
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMessageWidgetRowSignature, FUIWidgetRow, Row);
委托参数
1
2
3
// FMessageWidgetRowSignature:委托类型名
// FUIWidgetRow:参数类型(值传递)
// Row:参数名

2. 委托变量

1
2
UPROPERTY(BlueprintAssignable, Category="GAS|Messages")
FMessageWidgetRowSignature MessageWidgetRowDelegate;
属性说明
1
2
BlueprintAssignable: // 蓝图可以绑定到这个委托
Category="GAS|Messages": // 在编辑器中分类显示

3. 标签匹配逻辑

1
2
3
4
5
6
7
8
// 创建基础消息标签
FGameplayTag MessageTag = FGameplayTag::RequestGameplayTag(FName("Message"));

// 检查接收到的标签是否匹配"Message"前缀
if (Tag.MatchesTag(MessageTag))
{
// 标签以"Message"开头(如:"Message.HealthPotion")
}
MatchesTag函数
1
2
3
4
5
6
7
8
9
10
11
// 检查标签是否匹配另一个标签或其子标签
Tag.MatchesTag(MessageTag)

// 示例:
Tag = "Message.HealthPotion"
MessageTag = "Message"
结果:true(匹配,因为是Message的子标签)

Tag = "Effect.Damage"
MessageTag = "Message"
结果:false(不匹配,不是Message的子标签)

4. 数据表行查找与广播

1
2
3
4
5
6
7
8
9
// 1. 查找数据表行
const FUIWidgetRow* Row = GetDataTableRowByTag<FUIWidgetRow>(MessageWidgetDataTable, Tag);

// 2. 检查是否找到
if (Row)
{
// 3. 广播行数据到所有订阅者
MessageWidgetRowDelegate.Broadcast(*Row);
}
GetDataTableRowByTag调用
1
2
3
4
5
// 使用模板函数查找
GetDataTableRowByTag<FUIWidgetRow>(
MessageWidgetDataTable, // 数据表指针
Tag // 标签(如:"Message.HealthPotion")
)

5. 前向声明修正

1
2
3
4
5
// 修正前(错误):
class UAuraUserWidget; // 声明在FUIWidgetRow之后

// 修正后(正确):
TSubclassOf<class UAuraUserWidget> MessageWidget; // 使用前向声明

6. 完整的标签处理流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Lambda中的完整逻辑:
[this](const FGameplayTagContainer& AssetTags)
{
// 1. 遍历所有效果标签
for(const FGameplayTag& Tag : AssetTags)
{
// 2. 检查是否是消息标签
FGameplayTag MessageTag = FGameplayTag::RequestGameplayTag(FName("Message"));
if(Tag.MatchesTag(MessageTag))
{
// 3. 在数据表中查找对应行
const FUIWidgetRow* Row = GetDataTableRowByTag<FUIWidgetRow>(MessageWidgetDataTable, Tag);

// 4. 如果找到,广播行数据
if (Row)
{
MessageWidgetRowDelegate.Broadcast(*Row);
}
}
}
}

7. 标签命名规范

效果标签 vs 消息标签
1
2
3
4
5
6
7
// 效果标签(GameplayEffect中使用):
"Effect.Damage.Fire" // 火焰伤害效果
"Effect.Healing.Potion" // 药水治疗效果

// 消息标签(数据表中使用):
"Message.Damage.Fire" // 火焰伤害消息
"Message.Healing.Potion" // 药水治疗消息
标签前缀系统
1
2
3
4
5
// 标签前缀:
"Effect" // 游戏效果
"Message" // UI消息
"Status" // 角色状态
"Ability" // 技能能力

8. MessageWidgetRowDelegate的使用

在蓝图中绑定
1
2
3
4
// 蓝图可以:
// 1. 绑定到MessageWidgetRowDelegate
// 2. 当收到Row数据时,显示对应的UI
// 3. 使用Row中的Message、MessageWidget、Image
广播参数传递
1
2
3
4
5
// 广播FUIWidgetRow结构体(值传递)
MessageWidgetRowDelegate.Broadcast(*Row);

// 接收方获得完整的行数据副本
// 包含:MessageTag、Message、MessageWidget、Image

9. 安全性检查

空指针检查
1
2
3
4
5
6
7
8
9
10
11
12
// 应该添加的检查:
if (!MessageWidgetDataTable)
{
UE_LOG(LogTemp, Warning, TEXT("MessageWidgetDataTable为空"));
return;
}

// 当前只有Row的检查
if (Row) // 检查是否找到数据
{
MessageWidgetRowDelegate.Broadcast(*Row);
}

10. 标签匹配的工作原理

标签层级系统
1
2
3
4
5
6
7
8
9
10
11
12
13
// 标签是分层的:
"Message"
├── "Message.Damage"
│ ├── "Message.Damage.Fire"
│ └── "Message.Damage.Ice"
└── "Message.Healing"
├── "Message.Healing.Potion"
└── "Message.Healing.Spell"

// MatchesTag检查:
"Message.Damage.Fire".MatchesTag("Message") // true
"Message.Damage.Fire".MatchesTag("Message.Damage") // true
"Message.Damage.Fire".MatchesTag("Message.Healing") // false

11. 当前系统的工作流程

1
2
3
4
5
6
1. GameplayEffect应用 → 获取效果标签(如:"Effect.Damage.Fire")
2. ASC广播标签到WidgetController
3. WidgetController检查标签是否匹配"Message"前缀
4. 如果是消息标签,查找数据表
5. 找到对应行,广播FUIWidgetRow数据
6. UI系统接收行数据,显示对应消息

12. 委托分类整理

1
2
3
4
5
6
7
8
// 属性变化委托(Category="GAS|Attributes")
OnHealthChanged // 生命值变化
OnMaxHealthChanged // 最大生命值变化
OnManaChanged // 魔法值变化
OnMaxManaChanged // 最大魔法值变化

// 消息委托(Category="GAS|Messages")
MessageWidgetRowDelegate // 数据表行消息

总结:这个系统实现了效果标签到UI消息的完整转换流程。通过标签匹配机制筛选消息标签,在数据表中查找对应的UI配置,然后通过委托广播给UI系统显示。实现了GameplayEffect系统与UI系统的解耦和灵活配置。

-

Message Widget

15:24

-

Animating the Message Widget

17:38

-

Replacing Callbacks with Lambdas

09:44

🔄 使用Lambda替换回调函数

1. 委托统一化

1
2
3
4
5
6
7
8
// 之前:四个不同的委托类型
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature, float, NewHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxHealthChangedSignature, float, NewMaxHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnManaChangedSignature, float, NewMana);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxManaChangedSignature, float, NewMaxMana);

// 现在:一个通用委托类型
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAttributeChangedSignature, float, NewValue);

2. 删除成员函数

1
2
3
4
5
// 删除的成员函数:
void HealthChanged(const FOnAttributeChangeData& Data) const;
void MaxHealthChanged(const FOnAttributeChangeData& Data) const;
void ManaChanged(const FOnAttributeChangeData& Data) const;
void MaxManaChanged(const FOnAttributeChangeData& Data) const;

3. Lambda绑定属性变化委托

生命值变化绑定
1
2
3
4
5
6
7
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AuraAttributeSet->GetHealthAttribute()).AddLambda(
[this](const FOnAttributeChangeData& Data)
{
OnHealthChanged.Broadcast(Data.NewValue);
}
);
魔法值变化绑定
1
2
3
4
5
6
7
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AuraAttributeSet->GetManaAttribute()).AddLambda(
[this](const FOnAttributeChangeData& Data)
{
OnManaChanged.Broadcast(Data.NewValue);
}
);

4. Lambda语法分析

捕获列表 [this]
1
2
3
// 捕获当前类的this指针
// 允许访问成员变量(OnHealthChanged等)
// 允许调用成员函数
参数列表
1
2
3
(const FOnAttributeChangeData& Data)
// const引用:避免拷贝
// 包含:NewValue, OldValue等属性变化数据
函数体
1
2
3
4
{
OnHealthChanged.Broadcast(Data.NewValue);
}
// 直接广播新值到UI

5. 与之前AddUObject的对比

之前的方式(AddUObject)
1
2
3
4
5
6
7
8
9
10
11
// 需要定义成员函数
void HealthChanged(const FOnAttributeChangeData& Data) const;

// 绑定到成员函数指针
AddUObject(this, &UOverlayWidgetController::HealthChanged);

// 成员函数实现
void UOverlayWidgetController::HealthChanged(const FOnAttributeChangeData& Data) const
{
OnHealthChanged.Broadcast(Data.NewValue);
}
现在的方式(AddLambda)
1
2
3
4
5
6
7
// 直接在绑定处定义逻辑
AddLambda(
[this](const FOnAttributeChangeData& Data)
{
OnHealthChanged.Broadcast(Data.NewValue);
}
);

6. 代码简化效果

行数减少
1
2
3
4
5
6
7
8
9
// 之前:
// 4个函数声明(头文件)
// 4个函数实现(cpp文件)
// 4个AddUObject绑定调用

// 现在:
// 0个函数声明
// 0个函数实现
// 4个AddLambda绑定调用(更紧凑)
逻辑集中
1
2
3
// 之前:逻辑分散在多个函数中
// 现在:逻辑集中在BindCallbacksToDependencies函数中
// 更容易查看和理解整个绑定关系

7. Lambda的优势

减少代码量
1
2
// 无需为简单的转发逻辑创建单独函数
// 逻辑内联,减少文件跳转
提高可读性
1
2
// 绑定逻辑和回调逻辑在一起
// 更容易理解完整的执行流程
减少命名负担
1
2
// 无需为简单回调函数想名字
// 减少全局命名空间污染

8. 当前的绑定结构

属性变化绑定(4个)
1
2
3
4
1. Health属性变化 → OnHealthChanged委托
2. MaxHealth属性变化 → OnMaxHealthChanged委托
3. Mana属性变化 → OnManaChanged委托
4. MaxMana属性变化 → OnMaxManaChanged委托
效果标签绑定(1个)
1
EffectAssetTags委托 → 处理消息标签 → 广播数据表行

9. Lambda的性能考虑

内存开销
1
2
3
// Lambda创建小型的匿名函数对象
// 对于简单的转发逻辑,开销很小
// 比虚函数调用更高效
内联优化
1
2
// 编译器更容易内联Lambda逻辑
// 可能产生更优化的机器码

10. 委托变量的使用

仍然使用单独的委托变量
1
2
3
4
5
6
7
8
9
10
// 虽然委托类型统一了
// 但每个属性仍有自己的委托变量

UPROPERTY(BlueprintAssignable)
FOnAttributeChangedSignature OnHealthChanged; // 生命值变化

UPROPERTY(BlueprintAssignable)
FOnAttributeChangedSignature OnMaxHealthChanged; // 最大生命值变化

// 这样UI可以分别绑定不同属性的变化
蓝图绑定不受影响
1
2
3
4
// 蓝图中仍然可以:
// 绑定到OnHealthChanged事件
// 绑定到OnManaChanged事件
// 逻辑与之前完全相同

11. 代码组织结构

BindCallbacksToDependencies现在包含
1
2
3
1. 4个属性变化的Lambda绑定
2. 1个效果标签的Lambda绑定
3. 所有逻辑集中在一个函数中
函数变长但更完整
1
2
3
// 好处:一次性看到所有绑定关系
// 缺点:函数可能变得较长
// 权衡:简单逻辑适合Lambda,复杂逻辑仍可用单独函数

12. Lambda的局限性

不适合复杂逻辑
1
2
3
4
// 如果回调逻辑很复杂
// 还是应该使用单独的函数

// 示例:需要多行处理、条件判断、调用其他函数等
调试难度
1
2
// Lambda在调试器中显示为匿名函数
// 可能比命名函数更难调试

总结:使用Lambda表达式简化了属性变化回调的代码结构,减少了函数定义和文件跳转,使绑定逻辑更加集中和清晰。对于简单的转发逻辑,Lambda是更简洁的选择。

-

Ghost Globe

33:43

-

Properly Clamping Attributes

09:41

🔒 属性双重钳制系统

1. 新增PostGameplayEffectExecute处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void UAuraAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);

// 1. 提取效果属性信息
FEffectProperties Props;
SetEffectProperties(Data, Props);

// 2. 检查是否是生命值属性被修改
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
// 3. 双重钳制生命值
SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
}

// 4. 检查是否是魔法值属性被修改
if (Data.EvaluatedData.Attribute == GetManaAttribute())
{
// 5. 双重钳制魔法值
SetMana(FMath::Clamp(GetMana(), 0.f, GetMaxMana()));
}
}

2. Data.EvaluatedData结构

1
2
3
4
// EvaluatedData包含:
Data.EvaluatedData.Attribute // 被修改的属性
Data.EvaluatedData.Magnitude // 修改的幅度
Data.EvaluatedData.ModifierOp // 修改操作类型
属性比较
1
2
3
// 检查被修改的属性
Data.EvaluatedData.Attribute == GetHealthAttribute()
Data.EvaluatedData.Attribute == GetManaAttribute()

3. 双重钳制的作用

第一层:PreAttributeChange
1
2
3
4
// 时机:属性值改变之前
// 作用:验证将要设置的新值
// 位置:PreAttributeChange函数中
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
第二层:PostGameplayEffectExecute
1
2
3
4
// 时机:GameplayEffect执行完成后
// 作用:确保最终值在有效范围内
// 位置:PostGameplayEffectExecute函数中
SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));

4. 为什么需要双重钳制

场景分析
1
2
3
4
5
6
7
8
9
10
11
12
// 假设:当前生命值=80,最大生命值=100
// GameplayEffect要增加生命值+30

// 流程:
1. PreAttributeChange: NewValue=110 → Clamp为100
2. 实际设置生命值为100
3. PostGameplayEffectExecute: GetHealth()=100 → Clamp后还是100
// 结果:生命值正确限制在100

// 如果没有双重钳制:
// 某些特殊情况可能导致值超出范围
// 双重钳制提供额外的安全保障
网络同步考虑
1
2
3
// 服务器和客户端可能不同步
// 双重钳制确保两端都有相同的验证逻辑
// 避免客户端显示错误的值

5. FMath::Clamp函数使用

1
2
3
4
5
6
7
// 语法:FMath::Clamp(Value, Min, Max)
// 作用:限制Value在Min和Max之间

SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
// GetHealth(): 当前生命值
// 0.f: 最小值(不能低于0)
// GetMaxHealth(): 最大值(不能超过最大生命值)

6. SetHealth vs GetHealth

属性访问器函数
1
2
3
// 来自宏:ATTRIBUTE_ACCESSORS
GetHealth() // 获取当前生命值(float)
SetHealth() // 设置生命值(更新属性并触发事件)
SetHealth的内部工作
1
2
3
4
5
// SetHealth不仅设置值,还会:
1. 更新内部存储的值
2. 触发属性变化事件
3. 通知UI系统更新
4. 处理网络复制

7. 执行时机对比

完整的属性修改流程
1
2
3
4
5
6
1. GameplayEffect开始执行
2. PreAttributeChange(第一层钳制,验证新值)
3. 实际修改属性值
4. PostGameplayEffectExecute(第二层钳制,确保最终值)
5. 触发属性变化事件
6. UI系统接收更新
Pre vs Post的区别
1
2
3
4
5
6
7
8
9
// PreAttributeChange:
// - 修改前的验证
// - 影响的是"将要设置"的值(NewValue参数)
// - 可以阻止无效值被设置

// PostGameplayEffectExecute:
// - 修改后的验证
// - 影响的是"已经设置"的值(GetHealth()返回值)
// - 纠正任何可能超出范围的值

8. 当前只处理Health和Mana

处理的属性
1
2
3
4
5
6
7
// 当前双重钳制:
1. Health(生命值):限制在0 - MaxHealth之间
2. Mana(魔法值):限制在0 - MaxMana之间

// 未处理:
MaxHealth(最大生命值):可能也需要限制
MaxMana(最大魔法值):可能也需要限制
如果需要限制最大值属性
1
2
3
4
5
6
// 可以添加:
if (Data.EvaluatedData.Attribute == GetMaxHealthAttribute())
{
// 限制最大生命值在合理范围
SetMaxHealth(FMath::Clamp(GetMaxHealth(), 50.f, 500.f));
}

9. Props变量的使用

当前Props未使用
1
2
3
4
5
6
7
8
9
10
// 虽然提取了效果属性信息
FEffectProperties Props;
SetEffectProperties(Data, Props);

// 但Props变量在后续逻辑中没有使用
// 可以用于:
// 1. 记录伤害来源
// 2. 计算经验值
// 3. 播放特定特效
// 4. 触发其他系统

总结:双重钳制系统为生命值和魔法值提供了双重安全保障,确保这些关键属性始终在有效范围内。第一层在修改前验证,第二层在修改后确认,构成了完整的数据完整性保护机制。

📚 第六章关键方法总结

Gameplay Effect Delegates(效果委托)

1
2
3
void AbilityActorInfoSet();  // 设置能力Actor信息
void EffectApplied(...); // 效果应用回调
OnGameplayEffectAppliedDelegateToSelf.AddUObject(...) // 绑定效果应用委托

Asset Tags获取与广播

1
2
3
EffectSpec.GetAllAssetTags(TagContainer);  // 获取效果所有Asset Tags
EffectAssetTags.Broadcast(TagContainer); // 广播标签容器
DECLARE_MULTICAST_DELEGATE_OneParam(FEffectAssetTags, const FGameplayTagContainer&) // 效果标签委托

UI数据表系统

1
2
3
4
USTRUCT(BlueprintType)
struct FUIWidgetRow : public FTableRowBase // UI数据表行结构体
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMessageWidgetRowSignature, FUIWidgetRow, Row) // 数据表行委托
TObjectPtr<UDataTable> MessageWidgetDataTable; // 消息Widget数据表

数据表查询模板函数

1
2
3
template<typename T>
T* GetDataTableRowByTag(UDataTable* DataTable, const FGameplayTag& Tag); // 模板数据表查询
DataTable->FindRow<T>(Tag.GetTagName(), TEXT("")) // 根据标签名查找数据表行

Lambda表达式替换

1
2
AddLambda([this](const FOnAttributeChangeData& Data) { ... })  // Lambda绑定委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAttributeChangedSignature, float, NewValue) // 统一属性变化委托

属性双重钳制

1
2
SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));  // PostGameplayEffectExecute中再次钳制
SetMana(FMath::Clamp(GetMana(), 0.f, GetMaxMana()));

标签匹配检查

1
2
Tag.MatchesTag(MessageTag)  // 检查标签是否匹配
FGameplayTag::RequestGameplayTag(FName("Message")) // 创建基础消息标签

委托变量分类

1
2
Category="GAS|Attributes"  // 属性变化委托分类
Category="GAS|Messages" // 消息委托分类

成员函数删除

1
2
3
4
5
// 删除的四个属性变化回调函数:
void HealthChanged(...) const;
void MaxHealthChanged(...) const;
void ManaChanged(...) const;
void MaxManaChanged(...) const;

Lambda捕获列表

1
[this]  // 捕获当前对象指针,可访问成员变量

效果属性提取

1
2
SetEffectProperties(Data, Props)  // 提取GameplayEffect的来源和目标信息
FEffectProperties结构体包含SourceASC、TargetASC等

数据完整性保护

1
2
Data.EvaluatedData.Attribute == GetHealthAttribute()  // 检查被修改的属性
PostGameplayEffectExecute中的双重验证确保属性值有效

7.RPG Attributes / RPG属性

  • 11个讲座·2 小时 48 分钟 / 11 Lectures · 2 Hours 48 Minutes

  • Initialize Attributes from a Data Table

    14:44

  • Initialize Attributes with Gameplay Effects

    15:52

  • Attribute Based Modifiers

    09:09

  • Modifier Order of Operations

    11:08

  • Modifier Coefficients

    06:54

  • Secondary Attributes

    13:47

  • Derived Attributes

    31:39

  • Custom Calculations

    06:14

  • Player Level and Combat Interface

    13:07

  • Modifier Magnitude Calculations

    37:27

  • Initializing Vital Attributes

    07:47

8.Attribute Menu / 属性菜单

  • 21个讲座·4 小时 42 分钟 / 21 Lectures · 4 Hours 42 Minutes

  • Attribute Menu - Game Plan

    05:20

  • Attribute Menu - Framed Value

    11:08

  • Attribute Menu - Text Value Row

    08:28

  • Attribute Menu - Text Value Button Row

    06:43

  • Attribute Menu - Construction

    18:43

  • Button Widget

    21:34

  • Wide Button Widget

    09:19

  • Opening the Attribute Menu

    07:06

  • Closing the Attribute Menu

    05:40

  • Plan for Displaying Attribute Data

    10:07

  • Gameplay Tags Singleton

    09:52

  • Aura Asset Manager

    14:48

  • Native Gameplay Tags

    09:10

  • Attribute Info Data Asset

    15:46

  • Attribute Menu Widget Controller

    05:17

  • Aura Ability System Blueprint Library

    15:05

  • Constructing the Attribute Menu Widget Controller

    17:07

  • Attribute Info Delegate

    20:30

  • Widget Attribute Tags

    16:01

  • Mapping Tags to Attributes

    37:58

  • Responding to Attribute Changes

    16:07

9.Gameplay Abilities / 游戏技能

  • 15 个讲座·3 小时 56 分钟 / 15 Lectures · 3 Hours 56 Minutes

  • Gameplay Abilities

    05:09

  • Granting Abilities

    15:07

  • Settings on Gameplay Abilities

    18:31

  • Input Config Data Asset

    22:19

  • Aura Input Component

    14:52

  • Callbacks for Ability Input

    12:46

  • Activating Abilities

    27:22

  • Click To Move

    16:23

  • Setting Up Click to Move

    18:17

  • Setting Up Auto Running

    11:48

  • Implementing Auto Running

    11:42

  • Code Clean Up

    11:29

  • Aura Projectile

    14:25

  • Aura Projectile Spell

    13:37

  • Spawning Projectiles

    22:33

10.Ability Tasks / 技能任务

  • 15 个讲座·3 小时 53 分钟 / 15 Lectures · 3 Hours 53 Minutes

  • Ability Tasks

    16:54

  • Sending Gameplay Events

    10:38

  • Spawn FireBolt from Event

    05:50

  • Custom Ability Tasks

    20:18

  • Target Data

    06:10

  • Send Mouse Cursor Data

    13:42

  • Receiving Target Data

    16:11

  • Prediction in GAS

    10:53

  • Orienting the Projectile

    16:55

  • Motion Warping

    15:16

  • Projectile Impact

    16:05

  • Projectile Collision Channel

    09:31

  • Projectile Gameplay Effect

    23:42

  • Enemy Health Bar

    30:23

  • Ghost Bar

    20:56

11.RPG Character Classes / RPG角色职业

  • 5 个讲座·1小时 11分钟 / 5 Lectures · 1 Hour 11 Minutes

  • RPG Character Classes

    06:24

  • Character Class Info

    07:27

  • Default Attribute Effects

    04:28

  • Curve Tables - CSV and JSON

    29:52

  • Initializing Enemy Attributes

    22:40

12.Damage / 伤害

  • 17个讲座·4 小时 32 分钟 / 17 Lectures · 4 Hours 32 Minutes

  • Meta Attributes

    04:29

  • Damage Meta Attribute

    08:44

  • Set By Caller Magnitude

    07:37

  • Ability Damage

    14:46

  • Enemy Hit React

    26:52

  • Activating the Enemy Hit React Ability

    17:32

  • Enemy Death

    12:49

  • Dissolve Effect

    22:19

  • Floating Text Widget

    15:03

  • Showing Damage Text

    25:24

  • Execution Calculations

    06:29

  • Damage Execution Calculation

    08:39

  • ExecCalcs - Capturing Attributes

    28:27

  • Implementing Block Chance

    18:30

  • Implementing Armor and Armor Penetration

    12:25

  • Damage Calculation Coefficients

    24:57

  • Implementing Critical Hits

    17:23

13.Advanced Damage Techniques / 高级伤害技术

  • 14 个讲座·4 小时16 分钟 / 14 Lectures · 4 Hours 16 Minutes

  • The Gameplay Effect Context

    34:48

  • Custom Gameplay Effect Context

    14:24

  • NetSerialize

    27:41

  • Implementing Net Serialize

    07:23

  • Struct Ops Type Traits

    07:46

  • Aura Ability System Globals

    11:17

  • Using a Custom Effect Context

    23:00

  • Floating Text Color

    19:23

  • Hit Message

    16:17

  • Damage Types

    20:25

  • Mapping Damage Types to Resistances

    10:23

  • Resistance Attributes

    21:30

  • Resistance Damage Reduction

    17:42

  • Multiplayer Test

    23:59

14.Enemy AI / 敌人AI

  • 14 个讲座·2 小时 33 分钟 / 14 Lectures · 2 Hours 33 Minutes

  • Enemy AI Setup

    04:43

  • AI Controller Blackboard and Behavior Tree

    15:41

  • Behavior Tree Service

    10:25

  • Blackboard Keys

    13:40

  • Finding the Nearest Player

    09:10

  • AI and Effect Actors

    15:12

  • Behavior Tree Decorators

    18:39

  • Attack Behavior Tree Task

    10:33

  • Find New Location Around Target

    14:43

  • Environment Query System

    03:24

  • Environment Queries

    09:29

  • EQS Tests

    09:59

  • Distance Test

    05:21

  • Using EQS Queries in Behavior Trees

    11:44

15.Enemy Melee Attacks / 敌人近战攻击

  • 13 个讲座·3 小时8分钟 / 13 Lectures · 3 Hours 8 Minutes

  • Melee Attack Ability

    24:41

  • Attack Montage

    08:05

  • Combat Target

    15:16

  • Melee Attack Gameplay Event

    16:12

  • Get Live Players Within Radius

    32:07

  • Causing Melee Damage

    15:31

  • Multiplayer Melee Test

    03:13

  • Montage Gameplay Tags

    07:26

  • Tagged Montage

    09:36

  • Multiple Attack Sockets

    09:10

  • Ghoul Enemy

    11:58

  • Ghoul Attack Montages

    12:49

  • Melee Polish

    21:56

16.Enemy Ranged Attacks / 敌人远程攻击

  • 9 个讲座·1小时 15 分钟 / 9 Lectures · 1 Hour 15 Minutes

  • Ranged Attack

    06:50

  • Rock Projectile

    03:21

  • Ranged Damage Curve

    03:47

  • Granting Ranged Attacks

    02:51

  • Slingshot Attack Montage

    05:20

  • Playing the Ranged Attack Montage

    13:53

  • Spawning the Rock Projectile

    14:03

  • Slingshot Animation Blueprint

    11:40

  • Slingshot Attack Montage

    13:11

17.Enemy Spell Attacks / 敌人法术攻击

  • 5个讲座·38 分钟 / 5 Lectures · 38 Minutes

  • Goblin Shaman

    07:31

  • Shaman Attack Montage

    04:06

  • Shaman Attack Ability

    10:33

  • Dead Blackboard Key

    07:18

  • Enemies Multiplayer Testing

    08:48

18.Enemy Finishing Touches / 敌人最终完善

  • 26 个讲座·4 小时 33 分钟 / 26 Lectures · 4 Hours 33 Minutes

  • Goblin Spear - Sound Notifies

    06:19

  • Impact Effects

    16:35

  • Melee Impact Gameplay Cue

    18:58

  • Montage and Socket Tags

    22:13

  • Goblin Spear - Hurt and Death Sounds

    07:19

  • Goblin Slingshot - Sound Notifies

    04:31

  • Rock Impact Effects

    07:01

  • Goblin Shaman - Sound Notifies

    04:11

  • Ghoul - Sound Notifies

    10:21

  • Ghoul - Swipe Trail

    04:48

  • Demon Blueprint

    05:55

  • Demon Melee Attack

    14:50

  • Demon Ranged Attack

    11:04

  • Demon - Sound Notifies

    09:43

  • Demon - Dissolve Effect

    01:56

  • Shaman Summon Locations

    28:23

  • Async Spawn Times

    10:34

  • Summoning Particle Effect

    04:54

  • Select Minion Class at Random

    06:01

  • Minion Summon Montage

    05:48

  • Minion Count

    07:34

  • Elementalist Behavior Tree

    09:35

  • Elementalist Attack Task

    14:11

  • Decrementing Minion Count

    12:18

  • Adding Juice with Tweening

    08:25

  • Enemies Final Polish

    19:42

19.Level Tweaks / 关卡调整

  • 5 个讲座·1小时 54 分钟 / 5 Lectures · 1 Hour 54 Minutes

  • Level Lighting and Post Process

    28:39

  • Texture Streaming Pool Over Budget

    20:41

  • Flame Pillar Actor

    13:23

  • Fade Actor

    24:49

  • Fading Out Obstructing Geometry

    26:00

20.Cost and Cooldown / 消耗与冷却

  • 14 个讲座·3 小时 48 分钟 / 14 Lectures · 3 Hours 48 Minutes

  • Health Mana Spells Widget

    16:19

  • Spell Globe

    20:04

  • Adding Spell Globes

    15:21

  • XP Bar

    10:25

  • Ability Info Data Asset

    20:01

  • Initialize Overlay Startup Abilities

    13:26

  • For Each Ability Delegate

    19:46

  • Binding Widget Events to the Ability Info Delegate

    18:55

  • Gameplay Ability Cost

    12:29

  • Gameplay Ability Cooldown

    09:22

  • Cooldown Async Task

    38:18

  • Cooldown Tags in Ability Info

    06:58

  • Showing Cooldown Time in the HUD

    13:43

  • Modeling Mode

    13:02

21.Experience and Leveling Up / 经验与升级

  • 15 个讲座·3 小时 54 分钟 / 15 Lectures · 3 Hours 54 Minutes

  • Experience and Leveling Up

    14:03

  • Level Up Info Data Asset

    16:10

  • Adding XP to the Player State

    11:05

  • Listening for XP Changes

    17:55

  • Awarding XP Game Plan

    06:51

  • XP Reward for Enemies

    16:36

  • Incoming XP Meta Attribute

    04:13

  • Passively Listening for Events

    18:38

  • Sending XP Events

    14:40

  • Showing XP in the HUD

    14:51

  • Level Up Interface Function

    16:59

  • Leveling Up

    21:56

  • Showing Level in the HUD

    24:37

  • Level Up Niagara System

    17:02

  • Level Up HUD Message

    18:17

22.Attribute Points / 属性点

  • 6 个讲座·1小时 15 分钟 / 6 Lectures · 1 Hour 15 Minutes

  • Attribute Points Member Variable

    09:11

  • Showing Attribute Points in the HUD

    08:25

  • Attribute Upgrade Buttons

    08:34

  • Upgrading Attributes

    18:43

  • Top Off Our Fluids

    14:12

  • Attribute Menu Polish

    16:08

23.Spell Menu / 法术菜单

  • 23 个讲座·5 小时 45 分钟 / 23 Lectures · 5 Hours 45 Minutes

  • Spell Menu Design

    06:00

  • Spell Globe Button

    13:10

  • Offensive Spell Tree

    11:45

  • Passive Spell Tree

    09:19

  • Equipped Spell Row

    16:57

  • Spell Menu Widget

    19:52

  • Spell Description Box

    12:05

  • Spell Menu Button

    24:29

  • Spell Menu Widget Controller

    28:45

  • Constructing the Spell Menu Widget Controller

    21:46

  • Equipped Row Button

    26:05

  • Ability Status and Type

    13:33

  • Showing Abilities in the Spell Tree

    24:07

  • Ability Level Requirement

    12:09

  • Update Ability Statuses

    12:34

  • Updating Status in the Spell Menu

    14:07

  • Show Spell Points

    10:10

  • Selecting Icons

    12:12

  • Deselecting Icons

    07:01

  • Spell Menu Buttons

    39:22

  • Selected Ability

    12:14

  • Spending Spell Points

    29:55

  • Rich Text Blocks

    12:59

  • Spell Descriptions

    19:26

  • FireBolt Description

    12:37

  • Cost and Cooldown in Spell Description

    33:40

  • Self Deselect

    11:17

  • Equipped Spell Row Animations

    17:33

  • Ability Types

    22:47

  • Equipping Abilities

    42:03

  • Updating the Overlay When Equipping Abilities

    09:52

  • Globe Reassigned

    07:11

  • Unbinding Delegates

    05:38

24.Combat Tricks / 战斗技巧

  • 15 个讲座·3 小时 42 分钟 / 15 Lectures · 3 Hours 42 Minutes

  • Debuff Tags

    17:45

  • Debuff Parameters

    06:16

  • Damage Effect Params Struct

    24:36

  • Using Damage Effect Params

    14:27

  • Determining Debuff

    23:40

  • Debuff Info in the Effect Context

    18:51

  • Debuff in the Attribute Set

    15:16

  • Dynamic Gameplay Effects

    24:36

  • Debuff Niagara Component

    26:40

  • Death Impulse Magnitude

    05:25

  • Death Impulse in the Effect Context

    12:30

  • Handling Death Impulse

    18:56

  • Knockback

    35:08

25.What a Shock / 震惊法术(示例)

  • 17 个讲座·4 小时 53 分钟 / 17 Lectures · 4 Hours 53 Minutes

  • FireBolt Projectile Spread

    28:39

  • Spawning Multiple Projectiles

    15:28

  • Homing Projectiles

    21:10

  • Click Niagara System

    03:41

  • Invoke Replicated Event

    24:30

  • Aura Beam Spell

    16:37

  • Electrocute Montage

    21:24

  • Player Block Tags

    13:58

  • GameplayCue Notify Paths

    13:38

  • Gameplay Cue Notify Actor

    19:13

  • Electrocute Looping Sound

    06:18

  • Target Trace Channel

    09:37

  • First Trace Target

    28:39

  • Additional Targets

    23:37

  • Shock Loop Cues on Additional Targets

    16:16

  • Electrocute Cost Cooldown and Damage

    08:54

  • Applying Electrocute Cost and Damage

    14:20

  • Electrocute Polish

    24:50

  • Explode Dem FireBoltz

    07:36

  • Stun

    47:55

  • Stun Niagara System

    16:49

  • Shock Loop Animations

    20:19

26.Passive Spells / 被动法术

  • 2 个讲座·8 分钟 / 2 Lectures · 8 Minutes

  • Passive Spell tags

    02:31

  • Aura Passive Ability

    08:08

  • Passive Ability Info

    07:55

  • Passive Tags in Spell Tree

    07:23

  • Multiple Level Up Rewards

    10:18

  • Passive Ability Activation

    37:30

  • Passive Niagara Component

    30:31

27.Arcane Shards / 奥术碎片

  • 18 个讲座·4 小时 39 分钟 / 18 Lectures · 4 Hours 39 Minutes

  • Magic Circle

    12:25

  • Spawning Magic Circles

    11:35

  • Magic Circle Interface Functions

    09:56

  • Arcane Shards Spell

    13:25

  • Wait Input Press

    08:58

  • Anti Aliasing and Moving Decals

    04:10

  • Point Collection

    46:45

  • Async Point Locations

    10:21

  • Gameplay Cue Notify Burst

    17:26

  • Arcane Shards Montage

    14:36

  • Radial Damage Parameters

    19:29

  • Setting Radial Damage Parameters

    10:18

  • Radial Damage with Falloff

    21:18

  • Tying Radial Damage All Together

    27:10

  • Ignore Enemies while Magic Circle Active

    06:52

  • Knockback Force and Death Impulse Overrides

    15:50

  • Spell Descriptions

    13:45

  • Arcane Shards Cost and Cooldown

    14:23

28.Fire Blast / 火焰冲击

  • 9 个讲座·1小时 59 分钟 / 9 Lectures · 1 Hour 59 Minutes

  • FireBlast Ability

    12:36

  • FireBlast Cost and Cooldown

    05:44

  • Aura Fire Ball

    12:21

  • Spawning FireBalls

    10:02

  • FireBall Timelines

    17:39

  • Causing FireBall Damage

    10:51

  • FireBall Explosive Damage

    31:26

  • Empty Cooldown Texture

    05:26

  • Execute Local Gameplay Cues

    12:43

29.Saving Progress / 保存进度

  • 23 个讲座·5 小时 45 分钟 / 23 Lectures · 5 Hours 45 Minutes

  • Saving Progress

    08:54

  • Main Menu

    33:09

  • Play and Quit Buttons

    12:20

  • Vacant Load Slot

    13:41

  • Enter Name Load Slot

    03:42

  • Taken Load Slot

    05:07

  • Load Menu

    11:40

  • MVVM

    07:45

  • Changes Needed for 5.3+

    34:30

  • View Model Class

    13:21

  • Constructing a View Model

    19:31

  • Load Slot View Model

    24:19

  • Switching the Widget Switcher

    19:38

  • Save Game Object

    15:10

  • Binding Variables to ViewModels

    11:15

  • Load Slot Status

    17:19

  • Enabling the Select Slot Button

    09:12

  • Enabling Play and Delete Buttons

    08:34

  • Are You Sure Widget

    24:17

  • Deleting a Slot

    16:48

  • Map Name Field Notify

    16:21

  • Saving the Map Name

    04:17

  • Traveling to the Saved Map

    13:42

30.Checkpoints / 检查点

  • 15 个讲座·3 小时 42 分钟 / 15 Lectures · 3 Hours 42 Minutes

  • Choosing the Player Start

    09:34

  • Setting the Default Player Start

    12:50

  • Save the Player Start Tag

    07:49

  • Checkpoints

    21:18

  • Interface Function for Saving Progress

    19:45

  • Saving Player Data

    08:33

  • Loading Player Data

    15:34

  • Initializing Attributes From Disk

    24:30

  • Showing Player Level in Load Screen

    06:19

  • Saving Abilities

    14:46

  • Notes on this lecture

    00:05

  • Loading Abilities

    29:41

  • Data Structures for Saving Data

    13:15

  • Saving World State

    21:47

  • Loading World State

    15:46

31.Map Entrance / 地图入口

  • 17 个讲座·4 小时 53 分钟 / 17 Lectures · 4 Hours 53 Minutes

  • Different Highlight Colors

    10:10

  • Highlight Interface

    06:47

  • Targeting Status

    23:49

  • Highlighting Non-Enemies

    06:15

  • Set Move-To Location

    15:50

  • Beacons

    15:49

  • Map Entrance

    31:09

  • Dungeon Stair Entrance

    14:04

  • Dungeon Entrance Blueprints

    14:50

  • Polish Menu

    04:58

  • Spawn Volumes

    24:54

  • Player Death

    18:10

  • Loot Tiers

    38:41

  • Loot Effects

    21:06

  • Loot Drop Curve

    12:41

  • Pickup Sounds

    12:46

  • Quit Button

    20:51

32.Course Conclusion / 课程总结

  • 2 个讲座·8 分钟 / 2 Lectures · 8 Minutes

  • Quest - Levels

    01:20

  • Conclusion - Bonus Video

    06:24

  • Title: Unreal Engine——《Aura》Gameplay Ability System学习报告
  • Author: ELecmark
  • Created at : 2025-12-09 14:48:06
  • Updated at : 2025-12-18 01:53:05
  • Link: https://elecmark.github.io/2025/12/09/《Aura》UE5-Gameplay-Ability-System学习报告/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
Unreal Engine——《Aura》Gameplay Ability System学习报告