ASC学习笔记0004:通知相关方能力规格已被修改

中文注释:UrealEngine-5.2.1源码-AbilitySystemComponent.h

学习内容:

cpp 复制代码
/** 通知相关方能力规格已被修改 */
DECLARE_MULTICAST_DELEGATE_OneParam(FAbilitySpecDirtied, const FGameplayAbilitySpec&);

一. 这是一个在 Unreal Engine 中常见的委托声明,用于实现观察者模式。让我为您详细解释这个代码:

代码解析

各部分含义:

  • DECLARE_MULTICAST_DELEGATE_OneParam:声明一个多播委托宏,可以绑定多个回调函数

  • FAbilitySpecDirtied:委托类型名称

  • const FGameplayAbilitySpec&:委托参数类型(常量引用)

实际使用示例

1. 声明委托变量

cpp 复制代码
// 在适当的类中声明委托实例
FAbilitySpecDirtied OnAbilitySpecDirtied;

2. 绑定回调函数

cpp 复制代码
// 绑定成员函数
OnAbilitySpecDirtied.AddUObject(this, &MyClass::HandleAbilitySpecDirtied);

// 绑定 Lambda 表达式
OnAbilitySpecDirtied.AddLambda([](const FGameplayAbilitySpec& AbilitySpec) {
    UE_LOG(LogTemp, Warning, TEXT("Ability spec dirtied: %s"), *AbilitySpec.GetName());
});

3. 触发委托广播

cpp 复制代码
// 当能力规格被修改时调用
void UMyAbilitySystem::MarkAbilitySpecDirty(const FGameplayAbilitySpec& AbilitySpec)
{
    // ... 执行修改逻辑 ...
    
    // 广播通知所有监听者
    OnAbilitySpecDirtied.Broadcast(AbilitySpec);
}

4. 处理函数示例

cpp 复制代码
void UMyClass::HandleAbilitySpecDirtied(const FGameplayAbilitySpec& AbilitySpec)
{
    // 更新UI、重新计算属性等
    UpdateAbilityUI(AbilitySpec);
    RecalculateCombatStats();
}

应用场景

这种模式常用于:

  • 游戏能力系统:当技能属性变化时通知UI更新

  • 属性系统:属性值变化时触发相关计算

  • 状态系统:状态改变时通知多个系统

优势

  • 解耦:发送者和接收者不需要直接引用彼此

  • 灵活性:可以动态添加/移除监听者

  • 类型安全:编译时检查参数类型匹配

  • 性能:比轮询方式更高效

这是 Unreal Engine 中实现事件驱动架构的核心机制之一。

二. 在实际项目中,FAbilitySpecDirtied 委托在Gameplay Ability System (GAS) 中有多种重要的应用场景。以下是一些具体的实际应用案例:

1. UI 系统更新

技能栏UI实时更新

cpp 复制代码
// 技能槽位UI组件
UCLASS()
class UAbilitySlotWidget : public UUserWidget
{
    GENERATED_BODY()
    
public:
    void BindToAbilitySystem(UAbilitySystemComponent* ASC, FGameplayAbilitySpecHandle Handle)
    {
        AbilitySystem = ASC;
        AbilityHandle = Handle;
        
        // 绑定到脏数据委托
        if (UMyAbilitySystemComponent* MyASC = Cast<UMyAbilitySystemComponent>(ASC))
        {
            MyASC->OnAbilitySpecDirtied.AddUObject(this, &UAbilitySlotWidget::OnAbilitySpecDirtied);
        }
    }
    
private:
    UFUNCTION()
    void OnAbilitySpecDirtied(const FGameplayAbilitySpec& Spec)
    {
        if (Spec.Handle == AbilityHandle)
        {
            UpdateSlotDisplay(Spec);
        }
    }
    
    void UpdateSlotDisplay(const FGameplayAbilitySpec& Spec)
    {
        // 更新冷却时间显示
        UpdateCooldownDisplay(Spec);
        
        // 更新技能等级显示
        UpdateLevelDisplay(Spec);
        
        // 更新可用状态(法力值不足、被沉默等)
        UpdateAvailability(Spec);
        
        // 更新快捷键显示
        UpdateInputBinding(Spec);
    }
};

2. 技能系统内部管理

技能堆栈和状态同步

cpp 复制代码
// 技能管理器
UCLASS()
class UAbilityManagerComponent : public UActorComponent
{
    GENERATED_BODY()
    
public:
    void InitializeWithASC(UAbilitySystemComponent* ASC)
    {
        if (UMyAbilitySystemComponent* MyASC = Cast<UMyAbilitySystemComponent>(ASC))
        {
            MyASC->OnAbilitySpecDirtied.AddUObject(this, &UAbilityManagerComponent::OnAbilitySpecDirtied);
        }
    }
    
private:
    UFUNCTION()
    void OnAbilitySpecDirtied(const FGameplayAbilitySpec& Spec)
    {
        // 处理技能堆栈变化
        if (Spec.GetStackCount() != CachedStackCounts.FindRef(Spec.Handle))
        {
            HandleStackCountChanged(Spec);
        }
        
        // 处理技能激活状态变化
        if (Spec.IsActive() != CachedActiveStates.FindRef(Spec.Handle))
        {
            HandleActivationChanged(Spec);
        }
        
        // 更新缓存
        UpdateCachedData(Spec);
    }
    
    void HandleStackCountChanged(const FGameplayAbilitySpec& Spec)
    {
        // 多层技能效果处理
        if (Spec.Ability->AbilityTags.HasTag(FGameplayTag::RequestGameplayTag("Ability.Stackable")))
        {
            UpdateStackableEffects(Spec);
        }
        
        // 通知技能效果系统
        OnAbilityStackChanged.Broadcast(Spec.Handle, Spec.GetStackCount());
    }
};

3. 存档和数据持久化

实时保存技能状态

cpp 复制代码
// 存档系统
UCLASS()
class USaveSystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()
    
public:
    void TrackPlayerAbilitySystem(APlayerController* Player)
    {
        if (UMyAbilitySystemComponent* ASC = GetASCFromPlayer(Player))
        {
            ASC->OnAbilitySpecDirtied.AddUObject(this, &USaveSystem::OnAbilitySpecDirtied);
            TrackedPlayers.Add(Player);
        }
    }
    
private:
    UFUNCTION()
    void OnAbilitySpecDirtied(const FGameplayAbilitySpec& Spec)
    {
        // 延迟保存,避免频繁IO操作
        if (!GetWorld()->GetTimerManager().IsTimerActive(SaveTimerHandle))
        {
            GetWorld()->GetTimerManager().SetTimer(SaveTimerHandle, this, &USaveSystem::SaveDirtyAbilityData, 2.0f, false);
        }
        
        // 标记需要保存的数据
        DirtyAbilityHandles.Add(Spec.Handle);
    }
    
    void SaveDirtyAbilityData()
    {
        // 批量保存脏数据
        for (auto& Handle : DirtyAbilityHandles)
        {
            SaveAbilityStateToProfile(Handle);
        }
        DirtyAbilityHandles.Empty();
    }
};

4. AI 行为树更新

AI技能决策更新

cpp 复制代码
// AI感知组件
UCLASS()
class UAIAbilityPerceptionComponent : public UActorComponent
{
    GENERATED_BODY()
    
public:
    void SetupAbilityTracking(UAbilitySystemComponent* ASC)
    {
        if (UMyAbilitySystemComponent* MyASC = Cast<UMyAbilitySystemComponent>(ASC))
        {
            MyASC->OnAbilitySpecDirtied.AddUObject(this, &UAIAbilityPerceptionComponent::OnAbilitySpecDirtied);
        }
    }
    
private:
    UFUNCTION()
    void OnAbilitySpecDirtied(const FGameplayAbilitySpec& Spec)
    {
        // 更新AI的黑板数据
        UpdateBlackboardWithAbilityState(Spec);
        
        // 重新评估当前行为
        ReevaluateCurrentBehavior(Spec);
        
        // 检查技能条件变化
        CheckAbilityConditions(Spec);
    }
    
    void UpdateBlackboardWithAbilityState(const FGameplayAbilitySpec& Spec)
    {
        if (UBlackboardComponent* Blackboard = GetBlackboardComponent())
        {
            // 更新技能冷却状态
            Blackboard->SetValueAsBool(FName(*FString::Printf(TEXT("bAbility_%s_Ready"), *Spec.Ability->GetName())), 
                                      IsAbilityReady(Spec));
            
            // 更新技能资源消耗
            Blackboard->SetValueAsBool(FName(*FString::Printf(TEXT("bAbility_%s_Affordable"), *Spec.Ability->GetName())), 
                                      CanAffordAbilityCost(Spec));
        }
    }
};

5. 网络同步和预测

客户端预测修正

cpp 复制代码
// 客户端预测系统
UCLASS()
class UClientPredictionComponent : public UActorComponent
{
    GENERATED_BODY()
    
public:
    void InitializePrediction(UAbilitySystemComponent* ASC)
    {
        if (UMyAbilitySystemComponent* MyASC = Cast<UMyAbilitySystemComponent>(ASC))
        {
            MyASC->OnAbilitySpecDirtied.AddUObject(this, &UClientPredictionComponent::OnAbilitySpecDirtied);
        }
    }
    
private:
    UFUNCTION()
    void OnAbilitySpecDirtied(const FGameplayAbilitySpec& Spec)
    {
        // 服务器修正时同步状态
        if (GetOwnerRole() == ROLE_AutonomousProxy)
        {
            HandleServerCorrection(Spec);
        }
        
        // 预测失败时的回滚处理
        if (Spec.PredictionKey.IsValidKey() && Spec.PredictionKey.IsCaughtUp())
        {
            HandlePredictionCorrection(Spec);
        }
    }
    
    void HandleServerCorrection(const FGameplayAbilitySpec& Spec)
    {
        // 修正客户端预测的状态
        CorrectPredictedState(Spec);
        
        // 更新UI显示
        UpdatePredictedUI(Spec);
    }
};

6. 调试和数据分析

实时调试信息

cpp 复制代码
// 调试HUD
void AAbilityDebugHUD::DrawHUD()
{
    Super::DrawHUD();
    
    // 显示最近更新的技能信息
    for (const auto& DirtyAbility : RecentlyDirtiedAbilities)
    {
        DrawAbilityDebugInfo(DirtyAbility);
    }
}

void AAbilityDebugHUD::OnAbilitySpecDirtied(const FGameplayAbilitySpec& Spec)
{
    // 记录脏数据历史用于调试
    FAbilityDirtyEvent Event;
    Event.AbilityName = Spec.Ability->GetName();
    Event.Timestamp = GetWorld()->GetTimeSeconds();
    Event.StackCount = Spec.GetStackCount();
    Event.bIsActive = Spec.IsActive();
    
    RecentlyDirtiedAbilities.Add(Event);
    
    // 保持最近10个事件
    if (RecentlyDirtiedAbilities.Num() > 10)
    {
        RecentlyDirtiedAbilities.RemoveAt(0);
    }
}

这些实际应用案例展示了 FAbilitySpecDirtied 委托在复杂游戏系统中的重要作用,特别是在需要实时响应技能状态变化的场景中。

相关推荐
hadage2332 小时前
--- git 笔记 ---
笔记·git·elasticsearch
阿巴~阿巴~2 小时前
IPv4地址转换函数详解及C++容器安全删除操作指南
linux·服务器·c++·网络协议·算法·c++容器安全删除操作·ipv4地址转换函数
oioihoii2 小时前
C/C++混合项目中的头文件管理:.h与.hpp的分工与协作
java·c语言·c++
im_AMBER2 小时前
数据结构 11 图
数据结构·笔记·学习·图论
彷徨而立2 小时前
【C/C++】不能在派生类的构造函数初始化列表中直接初始化属于基类的成员变量
c语言·c++
一条破秋裤2 小时前
前端性能问题分析
学习
老虎06272 小时前
黑马点评学习笔记11(Redission)
笔记·学习
远程软件小帮手2 小时前
好用的远程软件!ToDesk、向日葵、UU远程横测
运维·服务器·游戏·电脑
小呀小萝卜儿3 小时前
2025-11-14 学习记录--Python-特征归一化方法(Min-Max或StandardScaler)
开发语言·python·学习