如何在客户端上获取GameplayAbility属性的增量值(属性值更改后)?

时间:2019-01-13 15:44:21

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

在我的使用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. 如何将该值转发给委托人?

1 个答案:

答案 0 :(得分: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不和谐频道的@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.
}