c++ - 如何获取客户端上 GameplayAbility 属性的增量值(属性值更改后)?
问题描述
在我使用GameplayAbilitySystem和默认服务器->客户端架构的虚幻引擎项目中,客户端会收到服务器上发生的属性值更改的通知。
此外,我不仅要获取新值,还要获取值更改的数量(delta = new value - old value
)。这应该可以使用属性值更改委托,因为它包含FOnAttributeChangeData
其成员NewValue
和OldValue
。
在服务器上,这两个值都是正确的。但是,在客户端上,FOnAttributeChangeData::NewValue == FOnAttributeChangeData::OldValue
两者都具有与NewValue
服务器上相同的值。
这是因为在复制发生后调用了委托......
UPROPERTY(ReplicatedUsing=OnRep_MyAttribute)
FGameplayAttributeData MyAttribute;
void UAttributeSetBase::OnRep_MyAttribute()
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MyAttribute);
}
(这是ActionRPG的默认 GAS 设置)
...所以客户端不知道它在复制之前的价值。
- 我如何获取属性的值,它在服务器更新之前具有的值?
- 如何将此值转发给委托人?
解决方案
获取旧值(问题 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 源代码时,一种可能性是在属性集中缓存以前的值,以便您能够从外部访问它。
OnRep
为属性集函数中的每个属性缓存该值。- 在委托中使用缓存的值,但前提是它是有效的。由于该值是在
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.
}
推荐阅读
- sql-server-2012 - 将字节四舍五入到下一个最接近的 GB 并以字节为单位返回值
- python-3.x - 每次调用 Asyncio 代码时都会显示递增的参数数量
- r - 带动态条件的条件累加和
- java - 如何更改 MySQL 会话数据库?
- api - 如何在Vue中使用html传感器api
- python - 随机森林准确率太低
- c - 进程内存的虚拟地址是如何计算的?
- reactjs - 编译android时出现React-native-camera错误
- python-3.x - 从 pandas 列中删除 unicode '\xa0'
- java - 在 javafx 中正确调整 BorderLayout 内的 ScrollPane