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 委托在复杂游戏系统中的重要作用,特别是在需要实时响应技能状态变化的场景中。

相关推荐
zhangzhangkeji1 小时前
UE5 蓝图-游老师-13-事件、函数、宏、事件分发器:在自定义蓝图(包括 UI 控件蓝图)中就可以创建事件分发器
ue5
盐焗西兰花1 小时前
鸿蒙学习实战之路:状态管理最佳实践
学习·华为·harmonyos
NiNi_suanfa1 小时前
【Qt】Qt 批量修改同类对象
开发语言·c++·qt
Zhichao_971 小时前
【UE5.3】小白人动画重定向
ue5
信奥胡老师2 小时前
苹果电脑(mac系统)安装vscode与配置c++环境,并可以使用万能头文件全流程
c++·ide·vscode·macos·编辑器
妖灵翎幺2 小时前
C++ 中的 :: 操作符详解(一切情况)
开发语言·c++·ide
小毅&Nora2 小时前
【人工智能】【深度学习】 ⑦ 从零开始AI学习路径:从Python到大模型的实战指南
人工智能·深度学习·学习
Maxwell_li12 小时前
Pandas 描述分析和分组分析学习文档
学习·数据分析·numpy·pandas·matplotlib
star _chen2 小时前
C++实现完美洗牌算法
开发语言·c++·算法
雷工笔记2 小时前
MES学习笔记之SCADA采集的数据如何与MES中的任务关联起来?
笔记·学习