GameplayBuffSystem框架

设计

首先我们需要一个 BuffManager ,管理所有的 BuffComponent
(实现的时候,可以让 BuffManager 作为一个 GameStateComponentBuffComponent 作为一个 PlayerStateComponent

对于每个 Player,需要一个 BuffComponent 管理该 Player 身上持有的 Buff

对于 Buff,需要一个 BuffBase 作为 Buff 的基类;

classDiagram

	UGameplayBuffManager..>FGameplayBuffSetting
	UGameplayBuffManager..>UGameplayBuffComponent
	UGameplayBuffManager..>FGameplayBuffParams
    class UGameplayBuffManager {
        BuffSettings : TMap~int|FGameplayBuffSetting~
        +CommitBuff(Target, BuffID, Params)
        +QueryBuff(Target, BuffID)
        +CombineBuffParams(Target, BuffID, Params)
        +RemoveBuff(Target, BuffID, bRemoveImmediately)
        +RemoveBuff(Target, BuffHandle, bRemoveImmediately)
        +ClearBuff(Target)
        -Tick()
        -CreateBuff(Target, BuffID, Params)
    }
    
    UGameplayBuffComponent..>UGameplayBuffBase
    class UGameplayBuffComponent {
    	BuffGroup : TArray~UGameplayBuffBase*~
        BuffIDArray : int (For Sync)
        AddBuff(InBuff)
        RemoveBuff(InBuff, bRemoveImmediately)
    }
	
	class FGameplayBuffSetting {
        BuffName : FString
        bNeedToMerge : bool
        BuffAsset : TSubclassOf~UGameplayBuffBase~
    }
    
	class FGameplayBuffParams {
        # ValueMap : TMap~FString, FVariant~ ValueMap
        FGameplayBuffParams(std::initializer_list ~TPairInitializer[const FString&, FVariant]~ ValuePairs)
        SetValue(const FString& FieldName, FVariant Value)
        Contains(const FString& FieldName)
        IsEmpty()
        Merge(const FGameplayBuffParams& OtherParams)
        GetValueMap() const
        operator+(const FGameplayBuffParams& OtherParams)
    }
    
    class FGameplayCountDownData {
    	StartTime
    	TotalTime
    	LeftTime
    	IsPause
    	SpeedFactor
    }
	
	UGameplayBuffBase..>FGameplayBuffParams
	class UGameplayBuffBase {
		Owner : TWeakObjectPtr~UGameBuffComponent~
		BuffID : int
		BuffName : FString
		BuffParams : FGameplayBuffParams
		+Create(Owner, BuffID, Params)
		+Remove()
		+Merge(BuffHandle, Params)
		+CombineParams(Params)
		+Tick(DeltaTime)
		+CheckNeetToStop()
		+SetNeedToRemove(bEnable)
		#OnCreate()
		#OnRemove()
		#OnMerge()
		#OnTick()
	}
		
	UGameplayBuffBase<|--UGameplayBuff_TimeDuration
	UGameplayBuff_TimeDuration..>FGameplayCountDownData
	class UGameplayBuff_TimeDuration {
		# TimeDuration : float
		# PassDuration : float
		# TickInternal : float
		# LastTickTime : float
		- SpeedFactor : float
		- bInPause : false
		- PauseReasons : TArray~FString~
		# Tick(DeltaTime)
		+ Refresh(InPassDuration, InTimeDuration)
		+ BeginPauseTime(Reason)
		+ StopPauseTime(Reason)
		+ UpdateSpeedFactor(InFactor)
		+ GetTimeDuration()
		+ GetLeftTime()
		+ GetCountDownData()
		# OnBeginPauseTime()
		# OnStopPauseTime()
		# OnUpdateSpeedFactor()
		# OnRefresh()
		# OnTimeStateChanged()
	}
		
	UGameplayBuffUtils..>UGameplayBuffManager

BuffManager

  1. Commit:判断 BuffSettings 里是否有对应 BuffIDBuff,若存在则创建该实例;

    判断一下 BuffComponent 里是否原本已经有相同 BuffIDBuff,若存在,并且该 Buff 需要 Merge 的话,执行 OldBuffMerge ,并将新创建的 BuffRemove

    否则直接执行 BuffComponentAddBuff

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
UGameplayBuffBase* UGameplayBuffManager::CommitBuff(UGameplayBuffComponent* Target, int BuffID, const FGameplayBuffParams& Params)
{
if (Target == nullptr)
{
LogD(TEXT("UGameplayBuffManager::CommitBuff, Target is nullptr! BuffID=%d"), BuffID);
return nullptr;
}

// MergedParams 的作用:将代码中传入的参数与额外配置表中的参数 Merge,实现 BuffParams 的配置化
FGameplayBuffParams MergedParams = GetMergedParams( BuffID, Params );

UGameplayBuffBase* Buff = CreateBuff(Target, BuffID, MergedParams);
if (Buff == nullptr)
{
LogD(TEXT("UGameplayBuffManager::CommitBuff, Buff is nullptr! UID=%llu, BuffID=%d"), Target->GetUID(), BuffID);
return nullptr;
}

auto SpecBuffGroup = Target->GetBuffGroupByBuffID(BuffID);
if (SpecBuffGroup.Num() && BuffSettings[BuffID].bNeedToMerge == true)
{
UGameplayBuffBase* OldBuff = SpecBuffGroup[0];
OldBuff->Merge(Buff, MergedParams);
Buff->Remove();
return OldBuff;
}
else
{
Target->AddBuff(Buff);
return Buff;
}
}

UGameplayBuffBase* UGameplayBuffManager::CreateBuff(UGameplayBuffComponent* Target, int BuffID, const FGameplayBuffParams& Params)
{
if (!BuffSettings.Contains(BuffID) || !IsValid(BuffSettings[BuffID].BuffAsset))
{
LogW(TEXT("UGameplayBuffManager::CreateBuff, Miss BuffSetting! UID=%llu, BuffID=%d"), Target->GetUID(), BuffID);
return nullptr;
}

UGameplayBuffBase* BuffInst = NewObject<UGameplayBuffBase>( Target, BuffSettings[BuffID].BuffAsset );
if (BuffInst == nullptr)
{
LogW(TEXT("UGameplayBuffManager::CreateBuff, BuffInst is nullptr! UID=%llu, BuffID=%d"), Target->GetUID(), BuffID);
return nullptr;
}


BuffInst->Create(Target, BuffID, Params);

return BuffInst;
}
  1. Query:判断BuffComponent上是否有对应ID的Buff,返回对应 Handle
1
2
3
4
5
6
7
8
9
10
11
TArray<UGameplayBuffBase*> UGameplayBuffManager::QueryBuff(UGameplayBuffComponent* Target, int BuffID)
{
if (Target == nullptr)
{
LogD(TEXT("UGameplayBuffManager::QueryBuff, Target is nullptr! BuffID=%d"), BuffID);
return TArray<UGameplayBuffBase*>();
}

auto SpecBuffGroup = Target->GetBuffGroupByBuffID(BuffID);
return SpecBuffGroup;
}
  1. Remove:通过 BuffID 或者 HandleBuffComponent 上存在的对应 BuffRemove
    特别的,为了解决 Buff 之间的依赖问题(比如 Buff(A->B),在 A、BRemove 都调用到了另一个 BuffRemove,会导致循环 Remove问题),维护一个 bRemoveImmediately (默认为false),每次调用 Remove 时只是 MarkDirty(把移除标记设为 true),在下一次 Tick 才会实际移除。
    这样就可以一次 Tick 移除一个 Buff,通过时间来解开了这个循环依赖的链条。
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
void UGameplayBuffManager::RemoveBuff(UGameplayBuffComponent* Target, int BuffID, bool bRemoveImmediately)
{
if (Target == nullptr)
{
LogD(TEXT("UGameplayBuffManager::RemoveBuff, Target is nullptr! BuffID=%d"), BuffID);
return;
}

auto SpecBuffGroup = Target->GetBuffGroupByBuffID(BuffID);
if (!SpecBuffGroup.Num())
{
LogD(TEXT("UGameplayBuffManager::RemoveBuff, Buff is not exist! UID=%llu, BuffID=%d"), Target->GetUID(), BuffID);
return;
}

for (int Index = SpecBuffGroup.Num() - 1; Index >= 0; Index--)
{
Target->RemoveBuff(SpecBuffGroup[Index], bRemoveImmediately);
}
}

void UGameplayBuffManager::RemoveBuff(UGameplayBuffComponent* Target, UGameplayBuffBase* Buff, bool bRemoveImmediately)
{
if (Target == nullptr)
{
LogD(TEXT("UGameplayBuffManager::RemoveBuff, Target is nullptr!"));
return;
}
Target->RemoveBuff(Buff, bRemoveImmediately);
}

void UGameplayBuffManager::ClearBuff(UGameplayBuffComponent* Target)
{
if (Target == nullptr)
{
LogD(TEXT("UGameplayBuffManager::RemoveBuff, Target is nullptr!"));
return;
}

auto BuffGroup = Target->GetBuffGroup();
for (int Index = BuffGroup.Num() - 1; Index >= 0; Index--)
{
if (Index >= BuffGroup.Num()) continue;
auto Buff = BuffGroup[Index];
if (Buff == nullptr) continue;
Target->RemoveBuff(Buff);
}
}
  1. CombineBuff 显然需要支持传入参数,在 Create 的时候传参,或者通过 CombineParams 将参数传入 Buff 中:
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
void UGameplayBuffManager::CombineBuffParams(UGameplayBuffComponent* Target, int BuffID, const FGameplayBuffParams& Params)
{
if (Target == nullptr)
{
LogD(TEXT("UGameplayBuffManager::CombineBuffParams, Target is nullptr! BuffID=%d"), BuffID);
return;
}

auto SpecBuffGroup = Target->GetBuffGroupByBuffID(BuffID);
if (!SpecBuffGroup.Num())
{
LogD(TEXT("UGameplayBuffManager::CombineBuffParams, Buff is not exist! UID=%llu, BuffID=%d"), Target->GetUID(), BuffID);
return;
}

for (auto Buff : SpecBuffGroup)
{
if (IsValid(Buff))
{
Buff->CombineParams(Params);
}
}
}

void UGameplayBuffManager::CombineBuffParams(UGameplayBuffBase* Buff, const FGameplayBuffParams& Params)
{
if (Buff == nullptr)
{
LogD(TEXT("UGameplayBuffManager::CombineBuffParams, Buff is nullptr!"));
return;
}

Buff->CombineParams(Params);
}
  1. Tick:遍历BuffComponent,遍历其中的 BuffGroup;对每一个Buff 执行 Tick,并且检查是否需要 Stop
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
void UGameplayBuffManager::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
if (IsClient(this)) return;
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

auto GS = GetOwner<AGameStateBase>();
auto PlayerArray = GS->GetAllPlayerState();
for (auto Player : PlayerArray)
{
auto Target = UFunctionLibrary::GetPlayerStateComponent<UGameplayBuffComponent>(Player);
if (Target == nullptr) continue;

const auto BuffGroup = Target->GetBuffGroup();

// Tick
for (auto Buff : BuffGroup)
{
if (Buff == nullptr) continue;
if (!Buff->CheckNeedToStop())
{
Buff->Tick(DeltaTime);
}
}

// Remove
int TotalCount = BuffGroup.Num();
for (int Index = TotalCount - 1; Index >= 0; Index--)
{
auto& Buff = BuffGroup[Index];
if (Buff->CheckNeedToStop() || Buff->CheckNeedToRemove())
{
LogD(TEXT("UGameplayBuffManager::TickComponent, Remove Buff (UID=%llu, BuffID=%d)"), Player->GetActorStateUID(), Buff->GetBuffID());
Target->RemoveBuff(Buff, true);
}
}
}
}

BuffComponent

  1. AddBuff:将 Buff 添加到 BuffGroup / BuffIDArray,通过 BuffIDArray 做客户端的同步(仅同步 BuffID 到客户端)
  2. RemoveBuff:移除 Buff,并执行该 BuffRemove 方法,同步对应信息;
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
62
63
64

void UGameplayBuffComponent::AddBuff(UGameplayBuffBase* InBuff)
{
BuffGroup.Add( InBuff );
GetBuffIDArray_Mutable().Add( InBuff->GetBuffID() );

if (IsStandaloneOrDS(this))
{
OnRep_BuffIDArray();
}
}

void UGameplayBuffComponent::RemoveBuff(UGameplayBuffBase* InBuff, bool bRemoveImmediately)
{
if (InBuff == nullptr) return;

int TotalCount = BuffGroup.Num();
for (int Index = TotalCount - 1; Index >= 0; Index--)
{
if (BuffGroup[Index] == InBuff)
{
if (bRemoveImmediately == true)
{
BuffGroup.RemoveAt( Index );
GetBuffIDArray_Mutable().RemoveAt( Index );
InBuff->SetNeedToRemove(true);
InBuff->Remove();

if (IsStandaloneOrDS(this))
{
OnRep_BuffIDArray();
}
}
else
{
InBuff->SetNeedToRemove(true);
}

break;
}
}
}

void UGameplayBuffComponent::OnUninit()
{
Super::OnUninit();
if (!IsStandaloneOrDS(this)) return;

for (int Index = BuffGroup.Num() - 1; Index >= 0; Index--)
{
if (Index >= BuffGroup.Num()) continue;
auto Buff = BuffGroup[Index];
if (Buff == nullptr) continue;
BuffGroup.RemoveAt( Index );
Buff->Remove();
}
}


void UGameplayBuffComponent::OnRep_BuffIDArray()
{
// ...
}

Buff

BuffBase

每个 Buff 的实际持有者为 BuffComponent

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
void UGameplayBuffBase::Create(UGameplayBuffComponent* InOwner, int InBuffID, const FGameplayBuffParams& Params)
{
if (InOwner == nullptr) return;

Owner = InOwner;
BuffID = InBuffID;
BuffName = UGameplayBuffUtils::GetBuffName(BuffID);
BuffParams = Params;
LogD(TEXT("UGameplayBuffBase::Create, UID=%llu, BuffID=%d, BuffName=%s, Params=%s"), Owner->GetUID(), BuffID, *BuffName, *BuffParams.ToString());
OnCreate();
}

void UGameplayBuffBase::Remove()
{
if (Owner == nullptr) return;

LogD(TEXT("UGameplayBuffBase::Remove, UID=%llu, BuffID=%d, BuffName=%s"), Owner->GetUID(), BuffID, *BuffName);
OnRemove();

Owner = nullptr;
BuffID = 0;
}

void UGameplayBuffBase::Merge(UGameplayBuffBase* InBuff, const FGameplayBuffParams& Params)
{
if (Owner == nullptr) return;
LogD(TEXT("UGameplayBuffBase::Merge, UID=%llu, BuffID=%d, BuffName=%s"), Owner->GetUID(), BuffID, *BuffName);
OnMerge(InBuff, Params);
}

void UGameplayBuffBase::Tick(float DeltaTime)
{
OnTick();
}

void UGameplayBuffBase::CombineParams(const FGameplayBuffParams& Params)
{
BuffParams = BuffParams + Params;
}

TimeDurationBuff

Time时间流逝的 Buff,需要支持 RefreshPauseUpdateSpeedFactor 等操作;

  1. 基础的 TimeDurationBuff 的实现:
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
void UGameplayBuff_TimeDuration::OnCreate()
{
Super::OnCreate();

TimeDuration = BaseTimeDuration;

float InDuration = BuffParams.GetValue("Duration");
if (InDuration != 0) TimeDuration = InDuration;

PassDuration = 0.0f;
LastTickTime = GetWorldTimeNow();
}

void UGameplayBuff_TimeDuration::Tick(float DeltaTime)
{
if (GetWorld() == nullptr || GetWorld()->GetGameState() == nullptr) return;

float CurrentTickTime = GetWorldTimeNow();
TickInternal = CurrentTickTime - LastTickTime;
LastTickTime = CurrentTickTime;

if (CheckInPause() == false && CheckNeedToStop() == false)
{
PassDuration += TickInternal * SpeedFactor;
OnTick();
}
}
  1. Refresh:刷新倒计时时间:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void UGameplayBuff_TimeDuration::Refresh(float InPassDuration, float InTimeDuration)
{
if (InPassDuration >= 0)
{
PassDuration = InPassDuration;
}

if (InTimeDuration >= 0)
{
TimeDuration = InTimeDuration;
}

OnRefresh();
}
  1. Pause:根据不同的 Reason 暂停/重启 时间:
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
void UGameplayBuff_TimeDuration::BeginPauseTime(FString Reason)
{
if (Owner == nullptr) return;

LogD(TEXT("UGameplayBuff_TimeDuration::BeginPauseTime, UID=%llu, BuffID=%d, TimeDuration=%.2f, bInPause=%d, Reason=%s"),
Owner->GetUID(), BuffID, TimeDuration, bInPause, *Reason);

PauseReasons.AddUnique(Reason);

if (bInPause == true) return;
if (PauseReasons.Num() <= 0) return;
bInPause = true;

OnBeginPauseTime();
}

void UGameplayBuff_TimeDuration::StopPauseTime(FString Reason)
{
if (Owner == nullptr) return;

LogD(TEXT("UGameplayBuff_TimeDuration::StopPauseTime, UID=%llu, BuffID=%d, TimeDuration=%.2f, bInPause=%d, Reason=%s"),
Owner->GetUID(), BuffID, TimeDuration, bInPause, *Reason);
PauseReasons.Remove(Reason);

if (bInPause == false) return;
if (PauseReasons.Num() > 0) return;
bInPause = false;

OnStopPauseTime();
}
  1. UpdateSpeedFactor : 更新 Buff 的速度
1
2
3
4
5
6
7
8
9
void UGameplayBuff_TimeDuration::UpdateSpeedFactor(float InFactor)
{
if (Owner == nullptr) return;
if (SpeedFactor == InFactor) return;
SpeedFactor = InFactor;
LogD(TEXT("UGameplayBuff_TimeDuration::UpdateSpeedFactor, UID=%llu, BuffID=%d, TimeDuration=%.2f, SpeedFactor=%.2f"),
Owner->GetUID(), BuffID, TimeDuration, SpeedFactor);
OnUpdateSpeedFactor();
}

BuffUtils

暴露给外部系统使用的 Utils,期望外部的调用都从这里走,实际上是一些胶水代码。

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
UFUNCTION()
static int GetBuffID( FString BuffName );
UFUNCTION()
static FString GetBuffName( int BuffID );
UFUNCTION()
static float GetBuffParam(FString BuffName, FString ParamName);
static float GetBuffParam(int BuffID, FString ParamName);

UFUNCTION()
static TArray<UGameplayBuffBase*> GetGameplayBuff(APlayerStateBase* PS, FString BuffName);
static TArray<UGameplayBuffBase*> GetGameplayBuff(APlayerStateBase* PS, int ID);

UFUNCTION(BlueprintCallable)
static UGameplayBuffBase* AddGameplayBuff(APlayerStateBase* PS, FString BuffName);
static UGameplayBuffBase* AddGameplayBuff(APlayerStateBase* PS, int BuffID);
static UGameplayBuffBase* AddGameplayBuff(APlayerStateBase* PS, FString BuffName, const FGameplayBuffParams& Params);
static UGameplayBuffBase* AddGameplayBuff(APlayerStateBase* PS, int BuffID, const FGameplayBuffParams& Params);

// bRemoveImmediately = true 可能会导致依赖问题,非必要时序依赖,建议使用 false
UFUNCTION(BlueprintCallable)
static void RemoveGameplayBuff(APlayerStateBase* PS, FString BuffName, bool bRemoveImediately = false);
static void RemoveGameplayBuff(APlayerStateBase* PS, FString BuffName, bool bRemoveImediately, const FGameplayBuffParams& Params);
static void RemoveGameplayBuff(APlayerStateBase* PS, int ID, bool bRemoveImediately = false, const FGameplayBuffParams& Params = {});
static void RemoveGameplayBuff(APlayerStateBase* PS, UGameplayBuffBase* Buff, bool bRemoveImediately = false, const FGameplayBuffParams& Params = {});

UFUNCTION()
static void CombineGameplayBuffParams(APlayerStateBase* PS, FString BuffName, const FGameplayBuffParams& Params = {});
static void CombineGameplayBuffParams(APlayerStateBase* PS, int ID, const FGameplayBuffParams& Params = {});
static void CombineGameplayBuffParams(UGameplayBuffBase* Buff, const FGameplayBuffParams& Params = {});

static void ClearGameplayBuff(APlayerStateBase* PS);

UFUNCTION()
static bool CheckGameplayBuffExist(APlayerStateBase* PS, FString BuffName);
static bool CheckGameplayBuffExist(APlayerStateBase* PS, int BuffID);
static bool CheckGameplayBuffHandleExist(APlayerStateBase* PS, UGameplayBuffBase* Buff);

其它信息

FGameplayBuffParams

BuffParams 部分,实现一个 ValueMap 记录各种类型的参数;

这样使用的时候就可以这样使用 Params

1
2
3
4
5
// 外部调用创建Buff时:
UGameplayBuffUtils::AddGameplayBuff(Target, BuffID, { {"ParamA", (float)A}, {"ParamsB", (int)B } });
// Buff内部 (BuffParams 已经传入 Buff 中)
float A = BuffParams.GetValue<float>("ParamA");
int B = BuffParams.GetValue<int>("ParamB");
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
USTRUCT(BlueprintType)
struct FGameplayBuffParams
{
GENERATED_BODY()

FGameplayBuffParams() = default;
FGameplayBuffParams(const FString& Key, FVariant Value);
FGameplayBuffParams(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);

FGameplayBuffParams operator+(const FGameplayBuffParams& OtherParams);
void Merge(const FGameplayBuffParams& OtherParams);

protected:
TMap<FString, FVariant> ValueMap;
};
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
62
63
64
65
66

FGameplayBuffParams::FGameplayBuffParams(const FString& Key, FVariant Value)
{
ValueMap.Add(Key, Value);
}

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

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

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

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

FString FGameplayBuffParams::ToString() const
{
FString DebugString = FString::Printf(TEXT("Params:"));
for (const auto& [Key, Value] : ValueMap)
{
DebugString += FString::Printf(TEXT("[%s=%s]"), *Key, *UStringUtils::ToString(Value));
}
return DebugString;
}

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

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

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

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

FGameplayCountDownData

提供给 UGameplayBuff_TimeDuration 使用,一份记录倒计时的数据

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
USTRUCT()
struct FGameplayCountDownData
{
GENERATED_USTRUCT_BODY()

FGameplayCountDownData() = default;
FGameplayCountDownData(float StartTime, float TotalTime = 0.0f, float LeftTime = 0.0f, bool IsPause = false, float SpeedFactor = 1.0f)
: StartTime(StartTime), TotalTime(TotalTime), LeftTime(LeftTime), IsPause(IsPause), SpeedFactor(SpeedFactor)
{}
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
Ar << StartTime;
Ar << TotalTime;
Ar << LeftTime;
Ar << IsPause;
Ar << SpeedFactor;

bOutSuccess = true;
return true;
}

bool operator==(const FGameplayCountDownData& Other) const
{
if (StartTime != Other.StartTime) return false;
if (TotalTime != Other.TotalTime) return false;
if (LeftTime != Other.LeftTime) return false;
if (IsPause != Other.IsPause) return false;
if (SpeedFactor != Other.SpeedFactor) return false;
return true;
}
bool operator !=(const FGameplayCountDownData& Other) const { return !(*this == Other); }

FString ToString() const;

UPROPERTY()
float StartTime = 0;
UPROPERTY()
float TotalTime = 0;
UPROPERTY()
float LeftTime = 0;
UPROPERTY()
bool IsPause = false;
UPROPERTY()
float SpeedFactor = 1.0;
};