Как получить дельта-значение атрибута GameplayAbility на клиенте (после изменения значения атрибута)? - PullRequest
0 голосов
/ 13 января 2019

В моем проекте Unreal Engine, в котором используется GameplayAbilitySystem и архитектура по умолчанию сервер-> клиент, клиент получает уведомление об изменениях значения атрибута, произошедших на сервере.

Кроме того, я пытаюсь получить не только новое значение, но и сумму, которую изменило значение (delta = new value - old value). Это должно быть возможно при использовании делегата для изменения значения атрибута *1007*, поскольку он содержит FOnAttributeChangeData со своими членами NewValue и OldValue.

На сервере оба значения верны. Однако на клиенте FOnAttributeChangeData::NewValue == FOnAttributeChangeData::OldValue и оба имеют значение, идентичное NewValue на сервере.

Это потому, что делегат называется после репликация произошла ...

UPROPERTY(ReplicatedUsing=OnRep_MyAttribute)
FGameplayAttributeData MyAttribute;

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

(это настройка GAS по умолчанию ActionRPG )

... поэтому клиент не знает о значении, которое он имел до репликации.

  1. Как получить значение атрибута, которое было до обновления на сервере?
  2. Как мне переслать это значение делегату?

1 Ответ

0 голосов
/ 13 января 2019

Получение старого значения (вопрос 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);
}

Спасибо @Dan с канала Unreal GAS.

Переслать значение делегату (вопрос 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.
}
...