虚幻引擎 GAS(GameplayAbilitySystem) 基础概念02 ...

2

主题

4

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2022-12-22 18:52:32 | 显示全部楼层
前记:

很多翻译都是用的机器翻译不是很准确,我认真看了英文文档,再加上中文翻译做了该摘要,方便后来的学习者,也方便自己后续查阅。
字字不易,希望多多点赞收藏。也希望多多提意见
正文:

1、AttributeSet说明

/**
* Defines the set of all GameplayAttributes for your game
* Games should subclass this and add FGameplayAttributeData properties to represent attributes like health, damage, etc
* AttributeSets are added to the actors as subobjects, and then registered with the AbilitySystemComponent
* It often desired to have several sets per project that inherit from each other
* You could make a base health set, then have a player set that inherits from it and adds more attributes
*/
UCLASS(DefaultToInstanced, Blueprintable)
class GAMEPLAYABILITIES_API UAttributeSet : public UObject
{
        GENERATED_UCLASS_BODY()

public:

        /** Override to disable initialization for specific properties */
        virtual bool ShouldInitProperty(bool FirstInit, FProperty* PropertyToInit) const { return true; }
      属性定义在AttributeSet中,由AttributeSet管理。开发者应该从UAttributeSet继承出自己的AttributeSet 。AttributeSet定义在需要属性的Actor上 面。在构造AttributeSet的时候,AttributeSet会自动的把自己注册在Owner的AbilitySystemComponent(ASC)里面。
2、怎么设计AttributeSet

      一个ASC可以拥有一个或多个AttributeSets。在游戏中所有Actor共享一个巨大的AttributeSet也是可行的,每个Actor仅使用需要的属性即可。开发者也可以根据游戏设计需求,提取多个通用的AttributeSet,互相组合给不同类型的Actor用。
Attributes在内部以AttributeSetClassName.AttributeName的方式引用。当继承了AttributeSet后,所有父类的属性也必须通过父类作为前缀引用(ParentClassName.AttributeName)。
      所以在一个ASC中可以有多个不同的AttributeSet但是同一类AttributeSet在一个ASC中最多只能有一个。
3、在运行时添加和删除AttributeSet

      ASC可以在运行时添加和删除AttributeSet,但是这个操作很危险。如果一个客户端先于服务器删除了一个AttributeSet,之后服务器同步这个AttributeSet的属性的时候,会导致客户端crash。
运行时添加AttributeSet的用例:
AbilitySystemComponent->GetSpawnedAttributes_Mutable().AddUnique(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();运行时删除AttributeSet的用例:
AbilitySystemComponent->GetSpawnedAttributes_Mutable().Remove(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();4、定义属性

Attributes只能在C++的AttributeSet头文件中定义。在我们的AttributeSet头文件中定义如下的宏
// 定义一个增加各种Getter和Setter方法的宏
#define ATTRIBUTE_ACCESSORS( , PropertyName) \
        GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
        GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
        GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
        GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)然后我们通过这个宏就可以为每个属性定义一套Get,Set,Initial的方法。
        UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing = OnRep_Health)
        FGameplayAttributeData Health;
        ATTRIBUTE_ACCESSORS(UJMZTpAttributeSet, Health);


        UFUNCTION()
        void OnRep_Health(const FGameplayAttributeData& OldValue);上面定义了一个可以同步的属性,如果需要该属性支持预测需要做如下的操作:
void UJMZTpAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{
        GAMEPLAYATTRIBUTE_REPNOTIFY(USampleAttributeSet, Health, OldValue);
}

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

        DOREPLIFETIME(UJMZTpAttributeSet, Health, COND_None, REPNOTIFY_Always);
        //DOREPLIFETIME(UJMZTpAttributeSet, MaxHealth);
        //DOREPLIFETIME(UJMZTpAttributeSet, Physical);
        //DOREPLIFETIME(UJMZTpAttributeSet, MaxPhysical);
}REPNOTIFY_Always,只有在客户端预测的值和服务器同步的值相等之后才触发回调函数OnRep_Health。如果客户端值本来就可以服务器的值一样不会触发回调函数OnRep_Health。
      如果属性不需要同步,就不需要设置回调函数OnRep,和上面这些操作。
5、属性的初始化

      属性初始化的方式有多种,Epic 推荐使用instant类型的GameplayEffect 。ATTRIBUTE_ACCESSORS这个宏会为每个Attribute生成一个,可以在C++里面直接调用的初始化函数。样例如下:
// InitHealth(float InitialValue) is an automatically generated function for an Attribute 'Health' defined with the `ATTRIBUTE_ACCESSORS` macro
AttributeSet->InitHealth(100.0f);6、属性改变前的回调函数

PreAttributeChange & PreAttributeBaseChange
just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
         *        There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
         *        This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc".
         *       
         *        NewValue is a mutable reference so you are able to clamp the newly applied value as well.
         */
        virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) {
                if (Attribute == GetMoveSpeedAttribute())
                {
                        // Cannot slow less than 150 units/s and cannot boost more than 1000 units/s
                        NewValue = FMath::Clamp<float>(NewValue, 150, 1000);
                }
        }

        /**
         *        This is called just before any modification happens to an attribute's base value when an attribute aggregator exists.
         *        This function should enforce clamping (presuming you wish to clamp the base value along with the final value in PreAttributeChange)
         *        This function should NOT invoke gameplay related events or callbacks. Do those in PreAttributeChange() which will be called prior to the
         *        final value of the attribute actually changing.
         */
        virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }

      AttributeSet中的任何属性的CurrentValue改变之前,都会调用PreAttributeChange。这个回调一般情况下是用来对属性的值做约束,比如Speed必须在150到1000之间。但是属性改变需要触发的事件不建议用这个回调。关于具体属性改变需要触发的事件,建议用下面的方式,更准确和高效。
UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute) (Responding to Attribute Changes).
      其实我个人感觉对具体属性做合理值的修正,用PreAttributeChange效率也有点低。应该通过手写具体属性的Setter函数,在具体属性的Setter函数里做限制,这样会更高效。
      PreAttributeBaseChange 略……
7、属性改变后的回调

        /**
         *        Called just before a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
         *        Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
         */
        virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }
      当instant类型的GameplayEffect (GE)修改属性的BaseValue之后会触发PostGameplayEffectExecute的回调。通过这个回调我们可以做很多属性改变后的处理,比如命中的受击动画,伤害飘字等。
      当PostGameplayEffectExecute被调用的时候,属性值已经被改变了,当时改变的属性值还没有同步到客户端。这个时候如果我们对属性做二次修改,客户端收到的将是修改后的值。
8、TODO

关于UAttributeSet 还有很多更复杂的操作,因为暂时还没有更详细的接触,所以没有做更进一步的阐述。将来对GAS接触的更深入的时候,再补。

UCLASS(DefaultToInstanced, Blueprintable)
class GAMEPLAYABILITIES_API UAttributeSet : public UObject
{
        GENERATED_UCLASS_BODY()

public:

        /** Override to disable initialization for specific properties */
        virtual bool ShouldInitProperty(bool FirstInit, FProperty* PropertyToInit) const { return true; }

        /**
         *        Called just before modifying the value of an attribute. AttributeSet can make additional modifications here. Return true to continue, or false to throw out the modification.
         *        Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
         */       
        virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData &Data) { return true; }
       
        /**
         *        Called just before a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
         *        Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
         */
        virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }

        /**
         *        An "On Aggregator Change" type of event could go here, and that could be called when active gameplay effects are added or removed to an attribute aggregator.
         *        It is difficult to give all the information in these cases though - aggregators can change for many reasons: being added, being removed, being modified, having a modifier change, immunity, stacking rules, etc.
         */

        /**
         *        Called just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
         *        There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
         *        This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc".
         *       
         *        NewValue is a mutable reference so you are able to clamp the newly applied value as well.
         */
        virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { }

        /**
         *        This is called just before any modification happens to an attribute's base value when an attribute aggregator exists.
         *        This function should enforce clamping (presuming you wish to clamp the base value along with the final value in PreAttributeChange)
         *        This function should NOT invoke gameplay related events or callbacks. Do those in PreAttributeChange() which will be called prior to the
         *        final value of the attribute actually changing.
         */
        virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }

        /** Callback for when an FAggregator is created for an attribute in this set. Allows custom setup of FAggregator::EvaluationMetaData */
        virtual void OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const { }

        /** This signifies the attribute set can be ID'd by name over the network. */
        void SetNetAddressable();

        /** Initializes attribute data from a meta DataTable */
        virtual void InitFromMetaDataTable(const UDataTable* DataTable);

        /** Gets information about owning actor */
        FORCEINLINE AActor* GetOwningActor() const { return CastChecked<AActor>(GetOuter()); }
        UAbilitySystemComponent* GetOwningAbilitySystemComponent() const;
        FGameplayAbilityActorInfo* GetActorInfo() const;

        /** Print debug information to the log */
        virtual void PrintDebug();

        // Overrides
        virtual bool IsNameStableForNetworking() const override;
        virtual bool IsSupportedForNetworking() const override;
        virtual void PreNetReceive() override;
        virtual void PostNetReceive() override;

protected:
        /** Is this attribute set safe to ID over the network by name?  */
        uint32 bNetAddressable : 1;
};
回复

举报 使用道具

您需要登录后才可以回帖 登录 | 立即注册
快速回复 返回顶部 返回列表