CommonParams解决方案

在实现业务框架的时候,经常需要支持传参的功能(比如 Buff 系统中,AddBuff 的时候,通常需要支持外部传入一个 BuffParams ,在内部解析出各种 Param 使用。)

这个参数需要支持基本的数据保存、同步等。

初始的想法

一个最简单的想法,是打包一个结构体,支持各种类型的传入,比如:

1
2
3
4
5
6
7
8
9
10
USTRUCT()
struct Params
{
UPROPERTY()
uint64 uVar0;
UPROPERTY()
int iVar0;
UPROPERTY()
float fVar0;
}

这样虽然很简单,同时可以通过反射来同步。

但是有一个巨大的问题,那就是在解析参数的时候,只知道 Var0 这种没有名字的抽象的概念,使用者需要记住 Var0 对应的是什么数据,很不直观。

CommonVariantParams

维护一个 CommonVariantParams,通过 TMap <FString, FVariant> ValueMap 来保存数据,同时自定义 NetSerialize 来支持网络同步。

为了方便使用,还可以自定义一些构造函数,比如 std::initializer_list<TPairInitializer<const FString&, FVariant>> ValuePairs ,这样就可以支持 { {Key0, Val0}, {Key1, Val1} } 形式的构造。

由于 TMap 以及 FVariant 实际上在引擎内部都已经有了合适的重载 operator << Archive,所以自定义 NetSerialize 也很简单。

使用方法

1
2
3
4
5
6
// 创建Params:
FCommonVariantParams Params = { {"ParamA", (float)A}, {"ParamsB", (int)B }, {"ParamsC", (FString)C } /* ... */ });
// 解析Params:
float A = BuffParams.GetValue<float>("ParamA");
int B = BuffParams.GetValue<int>("ParamB");
FString C = BuffParams.GetValue<FString>("ParamC");

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// CommonVariantParams.h
USTRUCT(BlueprintType)
struct FCommonVariantParams
{
GENERATED_BODY()

FCommonVariantParams() = default;
FCommonVariantParams(const FString& Key, FVariant Value);
FCommonVariantParams(std::initializer_list<TPairInitializer<const FString&, FVariant>> ValuePairs);

void SetValue(const FString& FieldName, FVariant Value);
bool Contains(const FString& FieldName) const;
bool IsEmpty() const;
template <typename ValueType = float> ValueType GetValue(const FString& FieldName, ValueType Default = {}) const
{
if (!ValueMap.Contains(FieldName)) return Default;
if (TVariantTraits<ValueType>::GetType() != ValueMap[FieldName].GetType()) return Default;
return ValueMap[FieldName].GetValue<ValueType>();
}

FString ToString() const;
const TMap<FString, FVariant>& GetValueMap() const;

bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);

FCommonVariantParams operator+(const FCommonVariantParams& OtherParams);
FCommonVariantParams& operator=(const FCommonVariantParams& OtherParams)
{
ValueMap = OtherParams.GetValueMap();
return *this;
}

void Merge(const FCommonVariantParams& OtherParams);
void Clear();

decltype(auto) begin() { return ValueMap.begin(); }
decltype(auto) begin() const { return ValueMap.begin(); }
decltype(auto) end() { return ValueMap.end(); }
decltype(auto) end() const { return ValueMap.end(); }

protected:
TMap<FString, FVariant> ValueMap;
};

template<>
struct TStructOpsTypeTraits<FCommonVariantParams> : TStructOpsTypeTraitsBase2<FCommonVariantParams>
{
enum
{
WithNetSerializer = true,
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// CommonVariantParams.cpp
FCommonVariantParams::FCommonVariantParams(const FString& Key, FVariant Value)
{
ValueMap.Add(Key, Value);
}

FCommonVariantParams::FCommonVariantParams(std::initializer_list<TPairInitializer<const FString&, FVariant>> ValuePairs)
{
for (const auto& Pair : ValuePairs)
{
ValueMap.Add(Pair.Key, Pair.Value);
}
}

void FCommonVariantParams::SetValue(const FString& FieldName, FVariant Value)
{
ValueMap.Add(FieldName, FVariant(Value));
}

bool FCommonVariantParams::Contains(const FString& FieldName) const
{
return ValueMap.Contains(FieldName);
}

bool FCommonVariantParams::IsEmpty() const
{
return ValueMap.IsEmpty();
}

const TMap<FString, FVariant>& FCommonVariantParams::GetValueMap() const
{
return ValueMap;
}

bool FCommonVariantParams::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
bOutSuccess = true;
Ar << ValueMap;
return true;
}

FCommonVariantParams FCommonVariantParams::operator+(const FCommonVariantParams& OtherParams)
{
FCommonVariantParams CombinedParams = *this;
CombinedParams.Merge(OtherParams);
return CombinedParams;
}

void FCommonVariantParams::Merge(const FCommonVariantParams& OtherParams)
{
for (const auto& [Key, Value] : OtherParams.GetValueMap())
{
// 覆盖
SetValue(Key, Value);
}
}

void FCommonVariantParams::Clear()
{
ValueMap.Empty();
}

Lua

对于这个 CommonVariantParams,还可以扩展一些方法,让这些参数可以通过 UnluaLua 脚本中设置与访问。

1
2
3
4
5
6
7
void FCommonVariantParams::SetValue(const FString& FieldName, FVariant Value)
{
ValueMap.Add(FieldName, FVariant(Value));
}

void FCommonVariantParams::SetIntValue(const FString& FieldName, int32 Value) { SetValue(FieldName, Value); }
int32 FCommonVariantParams::GetIntValue(const FString& FieldName, int32 Default /*= {}*/) { return GetValue<int32>(FieldName, Default); }
1
2
3
4
5
6
7
// 导出:
// define 在 UnluaEx.h 中
BEGIN_EXPORT_REFLECTED_CLASS(FCommonVariantParams)
ADD_FUNCTION(SetIntValue)
ADD_FUNCTION(GetIntValue)
END_EXPORT_CLASS()
IMPLEMENT_EXPORTED_CLASS(FCommonVariantParams)

具体业务

特别地,具体业务使用的时候,可以继承一个自己的 Params 来使用,以 FGameplayBuffParams 为例:

1
2
3
4
5
6
USTRUCT()
struct FGameplayBuffParams : public FCommonVariantParams
{
GENERATED_BODY()
using FCommonVariantParams::FCommonVariantParams;
}