第6章---GameplayTag初识以及拾起物品UI制作

文件结构

Source

  • Private
    • AbilitySystemComponen
      • RPGAbilitySystemComponent.cpp
      • RPGAttributeSet.cpp
    • Character
      • PGGameCharacterBase.cpp
      • RPGGameEnemy.cpp
      • RPGGamePlayerCharacter.cpp
    • Game
      • RPGGameModeBase.cpp
    • Interaction
      • EnemyInterface.cpp
    • Player
      • RPGPlayerController.cpp
      • RPGPlayerState.cpp
    • Actor
      • RPGEffectActor.cpp
    • UI
      • HUD
        • RPGHUD.cpp
      • WidgetController
        • OverlayWidgetController.cpp
        • RPGWidgetController.cpp
      • Widgets
        • RPGUserWidget.cpp
  • Public
    • AbilitySystemComponent
      • RPGAbilitySystemComponent.h
      • RPGAttributeSet.h
    • Character
      • RPGGameCharacterBase.h
      • RPGGameEnemy.h
      • RPGGamePlayerCharacter.h
    • Game
      • RPGGameModeBase.h
    • Interaction
      • EnemyInterface.h
    • Player
      • RPGPlayerController.h
      • RPGPlayerState.h
    • Actor
      • RPGEffectActor.h
    • UI
      • HUD
        • RPGHUD.h
      • WidgetController
        • OverlayWidgetController.h
        • RPGWidgetController.h
      • Widgets
        • RPGUserWidget.h

文件概述

RPGAbilitySystemComponent

.h文件
// Copyright KimiLiu

#pragma once

#include "CoreMinimal.h"
#include "AbilitySystemComponent.h"
#include "RPGAbilitySystemComponent.generated.h"

DECLARE_MULTICAST_DELEGATE_OneParam(FEffectAssetTags, const FGameplayTagContainer&/*AssetTags*/);

/**
 * Ability System Component 拥有两个Owner: Owner Actor 以及Avatar Actor,这两个Actor有可能相同,有可能不相同
 *  Owner Actor 指的是实例化组件的Actor
 *  Avatar Actor 指的是ASC实际上服务的Actor,例如
 *     - RPGGameEnemy 内的ASC的 Owner Actor 和 Avatar Actor 都是 RPGGameEnemy 这个 Actor 类自身。因为ASC由 RPGGameEnemy 实例
 *        化,同时服务于 RPGGameEnemy
 *     - RPGGamePlayerCharacter 内的ASC的 Owner Actor 是 RPGPlayerState,而 Avatar Actor 是RPGGamePlayerCharacter。因为
 *        ASC由RPGPlayerState实例化,但是ASC服务于PRGGamePlayerCharacter
 */
UCLASS()
class AURA_API URPGAbilitySystemComponent : public UAbilitySystemComponent
{
    GENERATED_BODY()

public:
    // ASC初始化完成(设置了实际所有者以及逻辑所有者)
    void AbilityActorInfoSet();

    // 
    FEffectAssetTags EffectAssetTags;

protected:
    // 当有任何GE在自身上被应用时会触发委托调用该函数
    void EffectApplied(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayEffectSpec& GameplayEffectSpec, FActiveGameplayEffectHandle ActiveGameplayEffectHandle);
    
};
.cpp文件
// Copyright KimiLiu


#include "AbilitySytstem/RPGAbilitySystemComponent.h"

void URPGAbilitySystemComponent::AbilityActorInfoSet()
{
    // 绑定委托
    OnGameplayEffectAppliedDelegateToSelf.AddUObject(this, &URPGAbilitySystemComponent::EffectApplied);
}

void URPGAbilitySystemComponent::EffectApplied(UAbilitySystemComponent* AbilitySystemComponent,
    const FGameplayEffectSpec& GameplayEffectSpec, FActiveGameplayEffectHandle ActiveGameplayEffectHandle)
{
    //GEngine->AddOnScreenDebugMessage(1, 5.f, FColor::Red, FString("Effect Applied"));
    
    FGameplayTagContainer TagContainer;
    GameplayEffectSpec.GetAllAssetTags(TagContainer);

    // 广播
    EffectAssetTags.Broadcast(TagContainer);
    

    
}

RPGAttributeSet

.h文件
// Copyright KimiLiu

#pragma once

#include "CoreMinimal.h"
#include "AbilitySystemComponent.h"
#include "AttributeSet.h"
#include "RPGAttributeSet.generated.h"

#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
    GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

// 逻辑处理数据结构,在这个结构中能够获取目标ASC以及源头ASC,以及目标对象,源头对象,目标控制器,源头控制器
USTRUCT()
struct FEffectProperties
{
    GENERATED_BODY()

    FEffectProperties(){};

    FGameplayEffectContextHandle EffectContextHandle;

    UPROPERTY()
    UAbilitySystemComponent* SourceASC = nullptr;

    UPROPERTY()
    AActor* SourceAvatarActor = nullptr;

    UPROPERTY()
    AController* SourceController = nullptr;

    UPROPERTY()
    ACharacter* SourceCharacter = nullptr;

    UPROPERTY()
    UAbilitySystemComponent* TargetASC = nullptr;

    UPROPERTY()
    AActor* TargetAvatarActor = nullptr;

    UPROPERTY()
    AController* TargetController = nullptr;

    UPROPERTY()
    ACharacter* TargetCharacter = nullptr;
};

/**
 * AS拥有预测功能(Prediction)能够让多人游戏的客户端在拥有更少的延迟。客户端能够立刻改变自己维护的AS,然后通知服务端,由服务端判定这个更
 *  改是否合法,如果不合法,则服务端拒绝更改AS并通知客户端回滚AS
 */
UCLASS()
class AURA_API URPGAttributeSet : public UAttributeSet
{
    GENERATED_BODY()

public:
    URPGAttributeSet();

    //复制变量时必须重写的函数,用于注册需要复制的变量
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
    
    //在属性被更改前调用,不用来处理逻辑,只用来限制值大小
    virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;

    //在属性被更改后调用,用来处理逻辑
    virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;

    /**
     * 创建AS属性步骤:
     * 1. 声明FGameplayAttributeData类型变量
     * 2. 用UPROPERTY()宏修饰
     * 3. 如果是多人游戏,需要在宏内声明: ReplicatedUsing = OnRep_属性名,同时声明一个UFUNCTION()方法OnRep_属性名()。当服务端的该属性
     *    值变化时,OnRep_属性名()将会被调用,我们在这个函数内处理变化事件
     * 4. 实现OnRep_属性名()函数,在函数内调用GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, 属性名, 旧属性值)宏,用来保存旧值用于
     *    服务端通知客户端进行回滚
     * 5. 重写GetLifetimeReplicatedProps()函数,在该函数内注册属性
     * 6. 使用ATTRIBUTE_ACCESSORS()宏来初始化value_getter, property_getter, value_setter, initter
     */
    
    UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category="Vital Attribute")
    FGameplayAttributeData Health;
    ATTRIBUTE_ACCESSORS(URPGAttributeSet, Health);

    UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxHealth, Category="Vital Attribute")
    FGameplayAttributeData MaxHealth;
    ATTRIBUTE_ACCESSORS(URPGAttributeSet, MaxHealth);
    
    UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Mana, Category="Vital Attribute")
    FGameplayAttributeData Mana;
    ATTRIBUTE_ACCESSORS(URPGAttributeSet, Mana);

    UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Mana, Category="Vital Attribute")
    FGameplayAttributeData MaxMana;
    ATTRIBUTE_ACCESSORS(URPGAttributeSet, MaxMana);

    UFUNCTION()
    void OnRep_Health(const FGameplayAttributeData& OldHealth) const;

    UFUNCTION()
    void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth) const ;

    UFUNCTION()
    void OnRep_Mana(const FGameplayAttributeData& OldMana) const;

    UFUNCTION()
    void OnRep_MaxMana(const FGameplayAttributeData& OldMaxMana) const;

private:
    //设置FEffectProperties类数据,Props内有很多信息,参照上面的结构体声明
    void SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const;
};
.cpp文件
// Copyright KimiLiu


#include "AbilitySytstem/RPGAttributeSet.h"

#include "AbilitySystemBlueprintLibrary.h"
#include "Net/UnrealNetwork.h"
#include "GameplayEffectExtension.h"
#include "GameFramework/Character.h"

URPGAttributeSet::URPGAttributeSet()
{
    InitHealth(50.f);
    InitMaxHealth(100.f);
    InitMana(50.f);
    InitMaxMana(100.f);
}

void URPGAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    //注册需要复制的属性Health,没有复制条件,不论复制的结果是否等于客户端原有结果,都进行复制调用
    DOREPLIFETIME_CONDITION_NOTIFY(URPGAttributeSet, Health, COND_None, REPNOTIFY_Always);
    DOREPLIFETIME_CONDITION_NOTIFY(URPGAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
    DOREPLIFETIME_CONDITION_NOTIFY(URPGAttributeSet, Mana, COND_None, REPNOTIFY_Always);
    DOREPLIFETIME_CONDITION_NOTIFY(URPGAttributeSet, MaxMana, COND_None, REPNOTIFY_Always);
}

// 此处的Clamp只能更改CurrentValue,不能更改BaseValue,因此还需要在Post函数中做一次Clamp
void URPGAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
    Super::PreAttributeChange(Attribute, NewValue);

    if (Attribute == GetHealthAttribute())
    {
       NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
    }
    if (Attribute == GetMaxHealthAttribute())
    {
       
    }
    if (Attribute == GetManaAttribute())
    {
       NewValue = FMath::Clamp(NewValue, 0.f, GetMaxMana());
    }
    if (Attribute == GetMaxManaAttribute())
    {
       
    }
}

void URPGAttributeSet::SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const
{
    // Source = causer of the effect, Target = target of the effect (owner of this AS)
    // 拿到源头的上下文,上下文内拥有指向ASC的指针
    Props.EffectContextHandle = Data.EffectSpec.GetContext();
    Props.SourceASC = Props.EffectContextHandle.GetOriginalInstigatorAbilitySystemComponent();

    if (IsValid(Props.SourceASC) && Props.SourceASC->AbilityActorInfo.IsValid() && Props.SourceASC->AbilityActorInfo->AvatarActor.IsValid())
    {
       // 获取逻辑拥有者
       Props.SourceAvatarActor = Props.SourceASC->AbilityActorInfo->AvatarActor.Get();
       // 获取控制器
       Props.SourceController = Props.SourceASC->AbilityActorInfo->PlayerController.Get();
       // 若ASC组件没有控制器,则从逻辑拥有者那里获取控制器
       if (Props.SourceController == nullptr && Props.SourceAvatarActor != nullptr)
       {
          // Source有可能没有Controller
          if (const APawn* Pawn = Cast<APawn>(Props.SourceAvatarActor))
          {
             Props.SourceAvatarActor = Pawn->GetController();
          }
       }
       // 获取来源角色
       if (Props.SourceController)
       {
          Props.SourceCharacter = Cast<ACharacter>(Props.SourceController->GetPawn());
       }
    }

    // 拿到目标Actor
    if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid())
    {
       Props.TargetAvatarActor = Data.Target.AbilityActorInfo->AvatarActor.Get();
       Props.TargetController = Data.Target.AbilityActorInfo->PlayerController.Get();
       Props.TargetCharacter = Cast<ACharacter>(Props.TargetAvatarActor);
       Props.TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Props.TargetAvatarActor);
    }
}

// Data 很强大,我们可以从中拿到任何我们想拿到的东西,但是得注意是否是空指针,需要做判断
void URPGAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
    Super::PostGameplayEffectExecute(Data);

    // Source = causer of the effect, Target = target of the effect (owner of this AS)

    FEffectProperties Props;
    SetEffectProperties(Data, Props);
    // 可以用Prps来取到我们想要的值,处理大部分逻辑

    if (Data.EvaluatedData.Attribute == GetHealthAttribute())
    {
       GEngine->AddOnScreenDebugMessage(1, 3.f, FColor::Red, FString::Printf(TEXT("Health: %f"), GetHealth()));
       SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
    }
    if (Data.EvaluatedData.Attribute == GetManaAttribute())
    {
       GEngine->AddOnScreenDebugMessage(1, 3.f, FColor::Blue, FString::Printf(TEXT("Mana: %f"), GetHealth()));
       SetMana(FMath::Clamp(GetMana(), 0.f, GetMaxMana()));
    }
    
    
}

void URPGAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth) const 
{
    // 当Health属性被调用,此函数被调用,传入OldHealth作为旧值,该旧值将会被保存以免服务端通知客户端该属性需要回滚
    GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, Health, OldHealth);
}

void URPGAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth) const 
{
    GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, Health, OldMaxHealth);
}

void URPGAttributeSet::OnRep_Mana(const FGameplayAttributeData& OldMana) const
{
    GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, Mana, OldMana);
}

void URPGAttributeSet::OnRep_MaxMana(const FGameplayAttributeData& OldMaxMana) const
{
    GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, MaxMana, OldMaxMana);
}

OverlayWidgetController

.h文件
// Copyright KimiLiu

#pragma once

#include "CoreMinimal.h"
#include "AbilitySytstem/RPGAttributeSet.h"
#include "UI/WidgetController/RPGWidgetController.h"
#include "OverlayWidgetController.generated.h"

USTRUCT(BlueprintType)
struct FUIWidgetRow : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FGameplayTag MessageTag = FGameplayTag();
    
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FText Message = FText();

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TSubclassOf<class URPGUserWidget> MessageData;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    UTexture2D* Image = nullptr;
};

class URPGUserWidget;

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAttributeChangeSignature, float, NewValue);

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMessageWidgetRowSignature, FUIWidgetRow, Row);



/**
 * 
 */
UCLASS(BlueprintType, Blueprintable)
class AURA_API UOverlayWidgetController : public URPGWidgetController
{
    GENERATED_BODY()

public:
    virtual void BroadcastInitialValues() override;

    virtual void BindCallbacksToDependences() override;

    UPROPERTY(BlueprintAssignable, Category="GAS|Attribute")
    FOnAttributeChangeSignature OnHealthChanged;

    UPROPERTY(BlueprintAssignable, Category="GAS|Attribute")
    FOnAttributeChangeSignature OnMaxHealthChanged;

    UPROPERTY(BlueprintAssignable, Category="GAS|Attribute")
    FOnAttributeChangeSignature OnManaChanged;

    UPROPERTY(BlueprintAssignable, Category="GAS|Attribute")
    FOnAttributeChangeSignature OnMaxManaChanged;

    UPROPERTY(BlueprintAssignable, Category="GAS|Messages")
    FMessageWidgetRowSignature MessageWidgetRowDelegate;

protected:

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Widget Data")
    TObjectPtr<UDataTable> MessageWidgetDataTable;
    
    // ~Start Delegate Function
    // ~End Delegate Function
    
    // 自定义带<>的模板函数,以后可以常用,指定父类可以获得多个子类的相关函数
    template <typename T>
    T* GetDataTableRowByTag(UDataTable* DataTable, const FGameplayTag& Tag);
    
};

// 模板函数实现
template <typename T>
T* UOverlayWidgetController::GetDataTableRowByTag(UDataTable* DataTable, const FGameplayTag& Tag)
{
    T* Row = DataTable->FindRow<T>(Tag.GetTagName(), TEXT(""));
    return Row;
}
.cpp文件

.AddLambda可以把函数直接写在()内,不需要多写一个函数,方便了很多,可以后期一次性看到我们绑定的函数是啥,以及这个函数干嘛用的

// Copyright KimiLiu


#include "UI/WidgetController/OverlayWidgetController.h"

#include "AbilitySytstem/RPGAbilitySystemComponent.h"
#include "AbilitySytstem/RPGAttributeSet.h"

//初始化多播执行事件
void UOverlayWidgetController::BroadcastInitialValues()
{
    //父类的AttributeSet类型是UAttributeSet不是RPGAttributeSet需要自行Cast
    const URPGAttributeSet* RPGAttributeSet = CastChecked<URPGAttributeSet>(AttributeSet);

    //执行初始化多播通知
    OnHealthChanged.Broadcast(RPGAttributeSet->GetHealth());
    OnMaxHealthChanged.Broadcast(RPGAttributeSet->GetMaxHealth());
    OnManaChanged.Broadcast(RPGAttributeSet->GetMana());
    OnMaxManaChanged.Broadcast(RPGAttributeSet->GetMaxMana());
    
}

// 绑定(注册)Attribute改变事件,当Attribute的基础值改变的时候,会执行相应Attribute的对应的函数
void UOverlayWidgetController::BindCallbacksToDependences()
{
    const URPGAttributeSet* RPGAttributeSet = CastChecked<URPGAttributeSet>(AttributeSet);

    AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
       RPGAttributeSet->GetHealthAttribute()).AddLambda(
       [this](const FOnAttributeChangeData& Data)
          {
             OnHealthChanged.Broadcast(Data.NewValue);
          }
       );

    AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
       RPGAttributeSet->GetMaxHealthAttribute()).AddLambda(
       [this](const FOnAttributeChangeData& Data)
          {
             OnMaxHealthChanged.Broadcast(Data.NewValue);
          }
       );

    AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
       RPGAttributeSet->GetManaAttribute()).AddLambda(
       [this](const FOnAttributeChangeData& Data)
          {
             OnManaChanged.Broadcast(Data.NewValue);
          }
       );

    AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
       RPGAttributeSet->GetMaxManaAttribute()).AddLambda(
       [this](const FOnAttributeChangeData& Data)
          {
             OnMaxManaChanged.Broadcast(Data.NewValue);
          }
       );

    Cast<URPGAbilitySystemComponent>(AbilitySystemComponent)->EffectAssetTags.AddLambda(
       [this](const FGameplayTagContainer& AssetTags)
       {
          for(FGameplayTag Tag : AssetTags)
          {
             // "A.1".MatchesTag("A") will return True, "A".MatchesTag("A.1") will return False
             FGameplayTag MessageTag = FGameplayTag::RequestGameplayTag(FName("Message"));
             if (Tag.MatchesTag(MessageTag))
             {
                FUIWidgetRow* Row = GetDataTableRowByTag<FUIWidgetRow>(MessageWidgetDataTable, Tag);
                MessageWidgetRowDelegate.Broadcast(*Row);
             }
          }
       }
    );
}


// ~Delegate Function Start 
// ~Delegate Function End
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

楚江_wog1st

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值