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

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

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

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

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

这是因为委托(delegate)在复制发生后被调用...

UPROPERTY(ReplicatedUsing=OnRep_MyAttribute)
FGameplayAttributeData MyAttribute;

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

(这是 ActionRPG 的默认 GAS 设置)

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

  1. 如何获取服务器更新之前的属性值?
  2. 如何将此值转发给委托(delegate)人?

最佳答案

获取旧值(问题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 channel 的@Dan。

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

想法

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

  1. 在属性集 OnRep 函数中缓存每个属性的值。
  2. 在委托(delegate)中使用缓存的值,但仅当它有效时。由于该值是在 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);
}

访问委托(delegate)中的先前值

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.
}

关于c++ - 如何在客户端获取 GameplayAbility 属性的增量值(属性值更改后)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54170464/

相关文章:

c++ - 为 std::string 释放内存的异常(尝试在 UE4 中使用 YOLO/Darknet)

unreal-engine4 - 虚幻气体 : Influence of the GameplayEffect aggregator on gameplay attribute values

c++ - 使用宏定义字段,但字段不能具有类型 'void' 和/或预期 ')'

c++ - 虚幻气体 : Print out the current value of an attribute to UI when it changes

c++ - 在循环中使用 stringstream 从几个字符串中提取数字

c++ - 使用传递函数指针调用 _beginthreadx

c++ - 允许将 "const char*"分配给 std::string,但分配给 std::wstring 不会编译。为什么?

ubuntu - 虚幻引擎是否将其依赖项存储在文件本身中?

c++ - UE4中简单坐标变换结果不正确

c++ - Qt QGraphicsScene 添加项目缓慢