【问题标题】:How to get the delta value of an GameplayAbility attribute on client (after attribute value change)?如何获取客户端上 GameplayAbility 属性的增量值(属性值更改后)?
【发布时间】:2019-01-13 15:44:20
【问题描述】:

在我使用GameplayAbilitySystem 和默认服务器->客户端架构的虚幻引擎项目中,客户端会收到服务器上发生的属性值更改的通知。

此外,我不仅要获取新值,还要获取值更改的数量 (delta = new value - old value)。这应该可以使用attribute value change delegate 来实现,因为它包含FOnAttributeChangeData 及其成员NewValueOldValue

在服务器上,这两个值都是正确的。但是,在客户端,FOnAttributeChangeData::NewValue == FOnAttributeChangeData::OldValue 和两者的值都与服务器上的 NewValue 相同。

这是因为在复制发生之后 调用了委托...

UPROPERTY(ReplicatedUsing=OnRep_MyAttribute)
FGameplayAttributeData MyAttribute;

void UAttributeSetBase::OnRep_MyAttribute()
{
    GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MyAttribute);
}

(这是ActionRPG的默认GAS设置)

...所以客户端不知道它在复制之前的值。

  1. 如何获取属性值,它在被服务器更新之前具有的值?
  2. 如何将此值转发给委托人?

【问题讨论】:

    标签: c++ unreal-engine4 unreal-gameplay-ability-system


    【解决方案1】:

    获取旧值(问题 1)

    UnrealEngine OnRep 函数提供复制变量的先前状态作为OnRep 函数中的第一个参数。所以添加参数

    void UAttributeSetBase::OnRep_MyAttribute(const FGameplayAttributeData& Previous)
    {
        const auto PreviousValue = Previous.GetCurrentValue(); // See below for possible usage.
        GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MyAttribute);
    }
    

    感谢 Unreal GAS discord 频道的@Dan。

    将值转发给委托人(问题 2)

    想法

    当您的目标是不修改 UE4 源代码时,一种可能性是在属性集中缓存以前的值,以便您可以从外部访问它。

    1. 为属性集OnRep 函数中的每个属性缓存该值。
    2. 在委托中使用缓存的值,但仅当它有效时。由于该值是在 OnRep 函数中分配的,因此它不会存在于服务器上。这很好,因为我们希望保留服务器上的行为,它使用FOnAttributeChangeData::OldValue(仅在服务器上具有正确的值)。

    示例实现

    缓存上一个值

    AttributeSetBase.h:

    // Wrapper for a TMap. If you need thread safety, use another container or allocator.
    class CachePreviousDataFromReplication
    {
        TMap<FName, FGameplayAttributeData> CachedPreviousData;
    public:
        void Add(const FName, const FGameplayAttributeData&);
        auto Find(const FName) const -> const FGameplayAttributeData*;
    };
    class YOUR_API UAttributeSetBase : public UAttributeSet
    {
        // ...
    private:
        UFUNCTION() void OnRep_MyAttribute(const FGameplayAttributeData& Previous);
        // ...
    private:
        CachePreviousDataFromReplication CachedDataFromReplication;
    public:
        // \param[in]   AttributeName   Use GET_MEMBER_NAME_CHECKED() to retrieve the name.
        auto GetPreviousDataFromReplication(const FName AttributeName) const -> const FGameplayAttributeData*;
    }
    

    AttributeSetBase.cpp:

    void CachePreviousDataFromReplication::Add(const FName AttributeName, const FGameplayAttributeData& AttributeData)
    {
        this->CachedPreviousData.Add(AttributeName, AttributeData);
    }
    
    auto CachePreviousDataFromReplication::Find(const FName AttributeName) const -> const FGameplayAttributeData*
    {
        return CachedPreviousData.Find(AttributeName);
    }
    
    void UAttributeSetBase::OnRep_MyAttribute(const FGameplayAttributeData& Previous)
    {
        CachedDataFromReplication.Add(GET_MEMBER_NAME_CHECKED(UAttributeSetBase, MyAttribute), Previous); // Add this to every OnRep function.
        GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MyAttribute);
    }
    
    auto UAttributeSetBase::GetPreviousDataFromReplication(const FName AttributeName) const -> const FGameplayAttributeData*
    {
        return CachedDataFromReplication.Find(AttributeName);
    }
    

    访问委托中的前一个值

    ACharacterBase.h:

    class YOUR_API ACharacterBase : public ACharacter, public IAbilitySystemInterface
    {
        // ...
        void OnMyAttributeValueChange(const FOnAttributeChangeData& Data); // The callback to be registered within GAS.
        // ...
    }
    

    ACharacterBase.cpp:

    void ACharacterBase::OnMyAttributeValueChange(const FOnAttributeChangeData& Data)
    {
        // This delegate is fired either from
        // 1. `SetBaseAttributeValueFromReplication` or from
        // 2. `InternalUpdateNumericalAttribute`
        // #1 is called on clients, after the attribute has changed its value. This implies,
        // that the previous value is not present on the client anymore. Therefore, the
        // value of `Data.OldValue` is erroneously identical to `Data.NewValue`.
        // In that case (and only in that case), the previous value is retrieved from a cache
        // in the AttributeSet. This cache will be only present on client, after it had
        // received an update from replication.
        auto deltaValue = 0.f;
        if (Data.NewValue == Data.OldValue)
        {
            const auto attributeName = GET_MEMBER_NAME_CHECKED(UAttributeSetBase, MyAttribute);
            if (auto previousData = AttributeSetComponent->GetPreviousDataFromReplication(attributeName))
            {
                // This will be called on the client, when coming from replication.
                deltaValue = Data.NewValue - previousData->GetCurrentValue();
            }
        }
        else
        {
            // This might be called on the server or clients, when coming from
            // `InternalUpdateNumericalAttribute`.
            deltaValue = Data.NewValue - Data.OldValue;
        }
        // Use deltaValue as you like.
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-04
      • 1970-01-01
      • 2014-10-19
      • 2014-05-29
      • 1970-01-01
      相关资源
      最近更新 更多