代码之家  ›  专栏  ›  技术社区  ›  Richard

如何获取客户端上可玩性属性的delta值(属性值更改后)?

  •  0
  • Richard  · 技术社区  · 6 年前

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

    另外,我不仅要得到新值,还要得到值的变化量( delta = new value - old value )。这应该可以使用 attribute value change delegate ,因为它包含 FOnAttributeChangeData 和它的成员 NewValue OldValue .

    在服务器上,两个值都是正确的。然而,在客户身上, FOnAttributeChangeData::NewValue == FOnAttributeChangeData::OldValue 两者的价值相同 新值 在服务器上。

    这是因为委托被调用 之后 复制已发生…

    UPROPERTY(ReplicatedUsing=OnRep_MyAttribute)
    FGameplayAttributeData MyAttribute;
    
    void UAttributeSetBase::OnRep_MyAttribute()
    {
        GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MyAttribute);
    }
    

    (这是的默认气体设置 ActionRPG )

    …所以客户机不知道它在复制之前的价值。

    1. 如何获取属性的值,该属性在被服务器更新之前已具有该值?
    2. 如何将此值转发给代理?
    1 回复  |  直到 6 年前
        1
  •  1
  •   Richard    6 年前

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

    将值转发给代表(问题2)

    想法

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

    1. 为属性集中的每个属性缓存该值 OnReP 功能。
    2. 使用委托中的缓存值,但是 只有当它是有效的 . 因为该值是在 OnReP 函数,它将不存在于服务器上。这非常好,因为我们希望保留服务器上的行为,它使用 FOnAttributeChangeData::OldValue (只有在服务器上才有正确的值)。

    示例实现

    缓存上一个值

    属性数据库.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*;
    }
    

    属性数据库.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);
    }
    

    访问委托中的上一个值

    无杆菌碱.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.
    }