GameplayRequestSystem框架

在联网游戏中经常需要有多个玩家之间请求交互的操作,提供一套 GameplayRequestSystem 用于快速自定义出一类新的 Request,自定义可发起请求交互对象、操作回调;

基本框架

从一次 Request 基本的流程出发:

对于某一类 Request,首先 A 需要筛选出关心的 B (其它玩家) ,判断这些玩家是否可以发起该类请求,如果不可以的话,还可能需要知道无法发起请求的原因;

然后由 A 发起请求,并等待一系列请求交互操作:

  1. SendRequest :由 A 向 B 发起某个请求,等待请求回复;
  2. AcceptRequest:B / Manager 接受某个请求;
  3. RejectRequest:B / Manager 拒绝某个请求;
  4. CancelRequest:A / B / Manager 取消某个请求;

每个操作需要通知给请求交互的双方 A、B;

对于一个新的 Request,两个玩家的 Client 需要同步到该 Request 信息,获取请求当前状态与一些同步属性等;

  1. PlayerRequestComponent 执行对应 Request 交互、持有 RequestFilter、同步 Request

  2. PlayerRequestFilter 进行 可以发起请求的目标 的注册与更新等;

  3. RequestMananger 持有实际的 Request,管理其生命周期;

对于 Request 的数据与操作,需要一些自定义参数,通过 CommonParams 实现;

classDiagram
	direction TD
	
	UGameplayRequestUtils ..> UGameplayRequestManager
	UGameplayRequestUtils ..> UGameplayRequestComponent

    class UGameplayRequestManager {
        CreateRequest()
        CancelRequest()
        ResponseRequest()
        ActiveRequests : TMap~uint64|UGameplayRequestBase*~
    }
    
    class UGameplayRequestComponent {
        SendRequest()
        CancelRequest()
        AcceptRequest()
        RejectRequest()
        GetFilter()
        Requests : TArray~UGameplayRequestBase*~
        Filters : FGameplaySubSystemCollection~UGameplayRequestFilterBase~
    }
    
    UGameplayRequestManager --> UGameplayRequestBase
    UGameplayRequestComponent --> UGameplayRequestBase
    class UGameplayRequestBase {
        GetType()
        GetFilterType()
        State : EGameplayRequestState
        Result : EGameplayRequestResult
    }
    
    UGameplayRequestComponent --> UGameplayRequestFilterBase
    UGameplayRequestFilterBase --> FGameplayRequestFilterTargetData
    class UGameplayRequestFilterBase {
    	GetType()
        IsTargetValid()
        RegisterPossibleTarget()
        UnregisterPossibleTarget()
        FilterTargets()
        FilterTargetDatas : TArray~FGameplayRequestFilterTargetData~
    }
    
    class FGameplayRequestFilterTargetData {
        PlayerUID : uint64
        bValid : bool
        Params : FCommonVariantParams
    }

Request Manager

作为全局的 Manager 管理所有 Request 的生命周期,持有 Request 实例;

在这里控制 Request 增加/移除,将其同步 增加/移除 到 SenderTargetRequestComponent 上,通过 RequestComponent 同步 Request 给对应的 Client

根据 UGameplayRequestBase 注册所有的 RequestCDO 用于生成 Request 实例;

特别的,为了 Request 状态能正确同步与通知,当其 Finish 时,并不会直接将其 Remove,而是 KeepAlive,到 KeepAliveDuration 时间结束才将其实例移除;

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
// RequestManager.h
UCLASS(meta = (BlueprintSpawnableComponent))
class UGameplayRequestManager : public UGameStateComponentBase
{
GENERATED_BODY()

#pragma region Base

protected:
UGameplayRequestManager();
virtual void OnInitSelf(AGameStateBase* Owner) override;
virtual void OnUninit() override;

#pragma endregion Base


#pragma region Interact

public:
uint64 CreateRequest(uint32 Type, uint64 SenderUID, uint64 TargetUID, const FCommonVariantParams& Params = {});
bool CancelRequest(uint64 RequestID, uint64 CancelUID = 0, FCommonVariantParams Params = {});
bool ResponseRequest(uint64 RequestID, bool bAccept, uint64 ResponseUID = 0, FCommonVariantParams Params = {});

#pragma endregion Interact


#pragma region Request

public:
UGameplayRequestBase* GetRequest(uint64 RequestID) const;
TArray <UGameplayRequestBase*> GetAllRequests() const;

private:
void InitRequests();
void UninitRequests();

private:
void AddRequest(UGameplayRequestBase* Request);
void RemoveRequest(uint64 RequestID);
void ClearRequests();

private:
UPROPERTY()
TMap <uint32, UGameplayRequestBase*> RequestCDOs;

private:
UPROPERTY()
TMap<uint64, UGameplayRequestBase*> ActiveRequests;
UPROPERTY()
TMap<uint64, FTimerHandle> RequestKeepAliveTimers;

#pragma endregion Request


#pragma region Helper

private:
void ExecuteFunctionForRequestPlayers(UGameplayRequestBase* Request, TFunction<void(UGameplayRequestComponent*)> Callback);

#pragma endregion Helper

};
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// RequestManager.cpp


#pragma region Base

UGameplayRequestManager::UGameplayRequestManager()
{
SetIsReplicatedByDefault(true);
PrimaryComponentTick.bCanEverTick = false;
}

void UGameplayRequestManager::OnInitSelf(AGameStateBase* Owner)
{
Super::OnInitSelf(Owner);

InitRequests();
}

void UGameplayRequestManager::OnUninit()
{
Super::OnUninit();

UninitRequests();
}

#pragma endregion Base


#pragma region Interact

uint64 UGameplayRequestManager::CreateRequest(uint32 Type, uint64 SenderUID, uint64 TargetUID, const FCommonVariantParams& Params)
{
auto RequestCDO = RequestCDOs.FindRef(Type);
if (!IsValid(RequestCDO))
{
return 0;
}

if (SenderUID == TargetUID)
{
return 0;
}

UGameplayRequestBase* NewRequest = NewObject<UGameplayRequestBase>(this, RequestCDO->GetClass());
if (!IsValid(NewRequest)) return 0;

uint64 RequestID = NewRequest->Init(SenderUID, TargetUID, Params);
if (RequestID <= 0) return 0;

AddRequest( NewRequest );

// RegisterKeepAliveTimer
float KeepAliveDuration = FMath::Max( 10.0f, NewRequest->GetKeepAliveTime());

FTimerHandle& TimerHandle = RequestKeepAliveTimers.FindOrAdd(RequestID);
UWorldTimerManager::SetTimer(GetWorld(), TimerHandle,
[WeakSelfPtr = TWeakObjectPtr<UGameplayRequestManager>(this), RequestID = RequestID]()
{
if (!WeakSelfPtr.IsValid()) return;
WeakSelfPtr->RemoveRequest( RequestID );
}, KeepAliveDuration, false);

NewRequest->NotifyCreated();

return RequestID;
}

bool UGameplayRequestManager::CancelRequest(uint64 RequestID, uint64 CancelUID, FCommonVariantParams Params)
{
auto Request = GetRequest(RequestID);
if (!IsValid(Request)) return false;

// 由 Manager / Sender / Target 发起
if ( !TSet<uint64>{ 0LL, Request->GetSenderUID(), Request->GetTargetUID() }.Contains( CancelUID ))
{
return false;
}

if (Request->IsFinished())
{
return false;
}

Params.SetValue( "CancelUID", CancelUID );
Request->Cancel( Params );

return true;
}


bool UGameplayRequestManager::ResponseRequest(uint64 RequestID, bool bAccept, uint64 ResponseUID, FCommonVariantParams Params)
{
auto Request = GetRequest(RequestID);
if (!IsValid(Request)) return false;

// 由 Manager / TargetUID 发起
if ( !TSet<uint64>{ 0LL, Request->GetTargetUID() }.Contains( ResponseUID ))
{
return false;
}

if (Request->IsFinished())
{
return false;
}

Params.SetValue( "ResponseUID", ResponseUID );

if (bAccept)
{
Request->Accept( Params );
}
else
{
Request->Reject( Params );
}

return true;
}


#pragma endregion Interact



#pragma region Request

UGameplayRequestBase* UGameplayRequestManager::GetRequest(uint64 RequestID) const
{
return ActiveRequests.FindRef( RequestID );
}

TArray<UGameplayRequestBase*> UGameplayRequestManager::GetAllRequests() const
{
TArray <UGameplayRequestBase*> OutArray;
for (const auto& [RequestID, Request] : ActiveRequests)
{
if (!IsValid(Request)) continue;;
OutArray.Add( Request );
}
return OutArray;
}

// -----

void UGameplayRequestManager::InitRequests()
{
if (!IsStandaloneOrDS(this)) return;

TArray<UClass*> RequestClasses;
GetDerivedClasses(UGameplayRequestBase::StaticClass(), RequestClasses, true);

for (auto Class : RequestClasses)
{
if (!IsValid(Class)) continue;
if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
auto CDO = Class->GetDefaultObject<UGameplayRequestBase>();
if (!IsValid(CDO)) continue;

RequestCDOs.Add( CDO->GetType(), CDO );
}
}

void UGameplayRequestManager::UninitRequests()
{
if (!IsStandaloneOrDS(this)) return;

ClearRequests();
}

// ----

void UGameplayRequestManager::AddRequest(UGameplayRequestBase* Request)
{
if (!IsValid(Request)) return;
ActiveRequests.Add( Request->GetRequestID(), Request );

ExecuteFunctionForRequestPlayers(Request, [Request](UGameplayRequestComponent* Component)
{
if (IsValid(Component))
{
Component->AddRequest( Request );
}
});
}

void UGameplayRequestManager::RemoveRequest(uint64 RequestID)
{
auto Request = GetRequest(RequestID);

if (IsValid(Request))
{
ExecuteFunctionForRequestPlayers(Request, [RequestID](UGameplayRequestComponent* Component)
{
if (IsValid(Component))
{
Component->RemoveRequest( RequestID );
}
});

Request->Uninit();
}

if (auto* TimerHandle = RequestKeepAliveTimers.Find(RequestID))
{
UWorldTimerManager::ClearTimer(GetWorld(), *TimerHandle);
RequestKeepAliveTimers.Remove(RequestID);
}

ActiveRequests.Remove(RequestID);
}

void UGameplayRequestManager::ClearRequests()
{
TArray <uint64> RequestIDs;
ActiveRequests.GetKeys(RequestIDs);

for (auto RequestID : RequestIDs)
{
RemoveRequest( RequestID );
}
}

#pragma endregion Request


#pragma region Helper

void UGameplayRequestManager::ExecuteFunctionForRequestPlayers(UGameplayRequestBase* Request, TFunction<void(UGameplayRequestComponent*)> Callback)
{
if (!IsValid(Request)) return;

if (auto Sender = UGameplayRequestUtils::GetComponentByUID(GetWorld(), Request->GetSenderUID()); IsValid(Sender))
{
Callback(Sender);
}

if (auto Target = UGameplayRequestUtils::GetComponentByUID(GetWorld(), Request->GetTargetUID()); IsValid(Target))
{
Callback(Target);
}
}

#pragma endregion Helper

RequestComponent

作为 Player 组件进行 Request 操作(请求、同意、拒绝、取消);

将与 Player 相关的 Request 同步(当 PlayerSender / Target 时认为其相关);

通过 GameplaySubSystem 持有每一种 RequestType 对应的 RequestFilter ,进行 FilterTargetData (可能发起请求的人的相关信息)的更新与同步;

后续每次对 Request 进行 Verify 时,需要通过 Player 上对应 RequestFilter 进行校验是否 IsTargetValid

同时客户端可以根据 RequestFilter 上的 FilterTargetDatas 信息,提前进行部分操作(比如 Client 知道该类请求无可以请求对象时、自己 / 对方处于不可请求状态时,进行提示)

这里 ReplicateRequest 时,在 Client 创建对应 UObject,需要通过 ReplicateSubobjects_OwnerOnly 来进行同步:

Request 作为 Subobject 同步下去时,会在 Client 找到其对应的 Outer(也就是 UGameplayRequestManager,所以 UGameplayRequestManager 需要开启同步)创建在其 ActorChannelCreateSubObjects 中;

RequestComponent 中,由一个同步字段 Requests 持有对应指针;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool UPlayerStateComponentBase::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);

if (APlayerStateBase* PS = GetPlayerState(); IsValid(PS) && IsValid(Channel) && IsValid(Channel->Connection))
{
if (Channel->Connection->PlayerController == PS->GetPlayerController())
{
WroteSomething |= ReplicateSubobjects_OwnerOnly(Channel, Bunch, RepFlags);
}
}

return WroteSomething;
}

以下为具体实现:

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// RequestComponent.h

UCLASS(meta=(BlueprintSpawnableComponent))
class UGameplayRequestComponent : public UPlayerStateComponentBase, public IGameplaySubSystemCollectionOwnerInterface
{
GENERATED_BODY()


#pragma region Base

public:
UGameplayRequestComponent();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

protected:
virtual void OnInitSelf() override;
virtual void OnUninit() override;

#pragma endregion Base



#pragma region Interact


public:
bool SendRequest(uint32 Type, uint64 TargetUID, const FCommonVariantParams& Params = {});
bool CancelRequest(uint64 RequestID, const FCommonVariantParams& Params = {});
bool AcceptRequest(uint64 RequestID, const FCommonVariantParams& Params = {});
bool RejectRequest(uint64 RequestID, const FCommonVariantParams& Params = {});


#pragma region C2S
public:
UFUNCTION(Reliable, Server)
void C2S_SendRequest(uint32 Type, uint64 TargetUID, const FCommonVariantParams& Params = {});
UFUNCTION(Reliable, Server)
void C2S_AcceptRequest(uint64 RequestID, const FCommonVariantParams& Params = {});
UFUNCTION(Reliable, Server)
void C2S_RejectRequest(uint64 RequestID, const FCommonVariantParams& Params = {});
UFUNCTION(Reliable, Server)
void C2S_CancelRequest(uint64 RequestID, const FCommonVariantParams& Params = {});
#pragma endregion C2S


#pragma endregion Interact


#pragma region Request

public:
UFUNCTION()
UGameplayRequestBase* GetRequest(uint64 RequestID);
UFUNCTION()
TArray <UGameplayRequestBase*> GetRequestsByType(uint32 Type);


public:
void AddRequest(UGameplayRequestBase* Request);
void RemoveRequest(uint64 RequestID);
void ClearRequests();

private:
UFUNCTION()
void OnRep_Requests();

private:
UPROPERTY(ReplicatedUsing=OnRep_Requests)
TArray <UGameplayRequestBase*> Requests;

#pragma endregion Request


#pragma region Filter

protected:
virtual TArray<UClass*> GetFilterClasses() const { return {}; }

public:
UFUNCTION()
UGameplayRequestFilterBase* GetFilter(UClass* InClass);

protected:
virtual bool ReplicateSubobjects_OwnerOnly(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags) override;

public:
DECLARE_GAMEPLAYSUBSYSTEM_ACCESSORS_BY_NAME(Filter, UGameplayRequestFilterBase, Filters);
virtual TArray<FGameplaySubSystemCollectionBase*> GetSubSystemCollectionPtrs() override { return { &Filters }; }

private:
FGameplaySubSystemCollection <UGameplayRequestFilterBase> Filters;

#pragma endregion Filter


#pragma region Debug

public:
UFUNCTION(Reliable, Server)
void C2S_DebugSendRequest(uint32 Type, uint64 SenderUID, uint64 TargetUID);
UFUNCTION(Reliable, Server)
void C2S_DebugAcceptRequests(uint64 PlayerUID);
UFUNCTION(Reliable, Server)
void C2S_DebugRejectRequests(uint64 PlayerUID);
UFUNCTION(Reliable, Server)
void C2S_DebugCancelRequests(uint64 PlayerUID);

#pragma endregion Debug

};
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
// RequestComponent.cpp


#pragma region Base

UGameplayRequestComponent::UGameplayRequestComponent()
{
SetIsReplicatedByDefault(true);
PrimaryComponentTick.bCanEverTick = false;
}

void UGameplayRequestComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);

// --------------------------------------------------------------------------------------------------------------------
// --- OwnerOnly

FDoRepLifetimeParams OwnerOnlyRepParams;
OwnerOnlyRepParams.bIsPushBased = true;
OwnerOnlyRepParams.Condition = COND_OwnerOnly;

DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestComponent, Requests, OwnerOnlyRepParams);
}

// -----

void UGameplayRequestComponent::OnInitSelf()
{
Super::OnInitSelf();

if (IsStandaloneOrDS(this))
{
Filters.Init(this, GetFilterClasses());
}
}

void UGameplayRequestComponent::OnUninit()
{
Super::OnUninit();

Filters.Uninit();
ClearRequests();
}

#pragma endregion Base


#pragma region Interact


bool UGameplayRequestComponent::SendRequest(uint32 Type, uint64 TargetUID, const FCommonVariantParams& Params)
{
if (!IsStandaloneOrDS(this)) return false;

auto Manager = UGameplayRequestUtils::GetManager(GetWorld());
if (!IsValid(Manager)) return false;

bool bSucceed = Manager->CreateRequest(Type, GetUID(), TargetUID, Params);
return bSucceed;
}


bool UGameplayRequestComponent::AcceptRequest(uint64 RequestID, const FCommonVariantParams& Params)
{
if (!IsStandaloneOrDS(this)) return false;

auto Manager = UGameplayRequestUtils::GetManager(GetWorld());
if (!IsValid(Manager)) return false;
bool bSucceed = Manager->ResponseRequest(RequestID, true, GetUID(), Params);

return bSucceed;
}

bool UGameplayRequestComponent::RejectRequest(uint64 RequestID, const FCommonVariantParams& Params)
{
if (!IsStandaloneOrDS(this)) return false;

auto Manager = UGameplayRequestUtils::GetManager(GetWorld());
if (!IsValid(Manager)) return false;
bool bSucceed = Manager->ResponseRequest(RequestID, false, GetUID(), Params);

return bSucceed;
}

bool UGameplayRequestComponent::CancelRequest(uint64 RequestID, const FCommonVariantParams& Params)
{
if (!IsStandaloneOrDS(this)) return false;

auto Manager = UGameplayRequestUtils::GetManager(GetWorld());
if (!IsValid(Manager)) return false;
bool bSucceed = Manager->CancelRequest(RequestID, GetUID(), Params);

return bSucceed;
}


#pragma region C2S

void UGameplayRequestComponent::C2S_SendRequest_Implementation(uint32 Type, uint64 TargetUID, const FCommonVariantParams& Params)
{
SendRequest(Type, TargetUID, Params);
}


void UGameplayRequestComponent::C2S_AcceptRequest_Implementation(uint64 RequestID, const FCommonVariantParams& Params)
{
AcceptRequest(RequestID, Params);
}

void UGameplayRequestComponent::C2S_RejectRequest_Implementation(uint64 RequestID, const FCommonVariantParams& Params)
{
RejectRequest(RequestID, Params);
}

void UGameplayRequestComponent::C2S_CancelRequest_Implementation(uint64 RequestID, const FCommonVariantParams& Params)
{
CancelRequest(RequestID, Params);
}

#pragma endregion C2S


#pragma endregion Interact


#pragma region Request

UGameplayRequestBase* UGameplayRequestComponent::GetRequest(uint64 RequestID)
{
for (auto Request : Requests)
{
if (!IsValid(Request)) continue;
if (Request->GetRequestID() == RequestID)
{
return Request;
}
}
return nullptr;
}

TArray<UGameplayRequestBase*> UGameplayRequestComponent::GetRequestsByType(uint32 Type)
{
TArray <UGameplayRequestBase*> OutArray;
for (auto Request : Requests)
{
if (!IsValid(Request)) continue;
if (Request->GetType() == Type)
{
OutArray.Add( Request );
}
}
return OutArray;
}

void UGameplayRequestComponent::AddRequest(UGameplayRequestBase* Request)
{
if (!IsValid(Request)) return;

GetRequests_Mutable().AddUnique( Request );
}

void UGameplayRequestComponent::RemoveRequest(uint64 RequestID)
{
auto Request = GetRequest( RequestID );
if (!IsValid(Request)) return;

GetRequests_Mutable().Remove( Request );
}

void UGameplayRequestComponent::ClearRequests()
{
GetRequests_Mutable().Empty();
}

// ----

void UGameplayRequestComponent::OnRep_Requests()
{
for (auto Request : Requests)
{
if (!IsValid(Request)) continue;
Request->NotifyCreated();
}
}


#pragma endregion Request


#pragma region Filter

bool UGameplayRequestComponent::ReplicateSubobjects_OwnerOnly(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
bool WroteSomething = false;

WroteSomething |= Super::ReplicateSubobjects_OwnerOnly(Channel, Bunch, RepFlags);
WroteSomething |= Filters.ReplicateSubSystems( Channel, Bunch, RepFlags );

if (IsValid(Channel) && RepFlags->bNetInitial == false)
{
for (auto Request : Requests)
{
if (!IsValid(Request)) continue;
WroteSomething |= Channel->ReplicateSubobject(Request, *Bunch, *RepFlags);
}
}

return WroteSomething;
}

UGameplayRequestFilterBase* UGameplayRequestComponent::GetFilter(UClass* InClass)
{
for (auto Filter : GetFilters())
{
if (IsValid(Filter) && Filter->GetClass() == InClass)
{
return Filter;
}
}
return nullptr;
}

#pragma endregion Filter


#pragma region Debug

void UGameplayRequestComponent::C2S_DebugSendRequest_Implementation(uint32 Type, uint64 SenderUID, uint64 TargetUID)
{
#if UE_BUILD_SHIPPING
return;
#endif

SenderUID = SenderUID > 0 ? SenderUID : GetUID();
TargetUID = TargetUID > 0 ? TargetUID : GetUID();

auto Component = UGameplayRequestUtils::GetComponentByUID(GetWorld(), SenderUID);
if (!IsValid(Component)) return;

Component->SendRequest( Type, TargetUID );
}


void UGameplayRequestComponent::C2S_DebugAcceptRequests_Implementation(uint64 PlayerUID)
{
#if UE_BUILD_SHIPPING
return;
#endif
PlayerUID = PlayerUID > 0 ? PlayerUID : GetUID();
auto Component = UGameplayRequestUtils::GetComponentByUID(GetWorld(), PlayerUID);
if (!IsValid(Component)) return;

TArray <UGameplayRequestBase*> PlayerRequests = Component->GetRequests();

for (auto Request : PlayerRequests)
{
if (!IsValid(Request)) continue;
Component->AcceptRequest(Request->GetRequestID());
}
}


void UGameplayRequestComponent::C2S_DebugRejectRequests_Implementation(uint64 PlayerUID)
{
#if UE_BUILD_SHIPPING
return;
#endif

PlayerUID = PlayerUID > 0 ? PlayerUID : GetUID();
auto Component = UGameplayRequestUtils::GetComponentByUID(GetWorld(), PlayerUID);
if (!IsValid(Component)) return;

TArray <UGameplayRequestBase*> PlayerRequests = Component->GetRequests();

for (auto Request : PlayerRequests)
{
if (!IsValid(Request)) continue;
Component->RejectRequest(Request->GetRequestID());
}
}

void UGameplayRequestComponent::C2S_DebugCancelRequests_Implementation(uint64 PlayerUID)
{
#if UE_BUILD_SHIPPING
return;
#endif

PlayerUID = PlayerUID > 0 ? PlayerUID : GetUID();
auto Component = UGameplayRequestUtils::GetComponentByUID(GetWorld(), PlayerUID);
if (!IsValid(Component)) return;

TArray <UGameplayRequestBase*> PlayerRequests = Component->GetRequests();

for (auto Request : PlayerRequests)
{
if (!IsValid(Request)) continue;
Component->CancelRequest(Request->GetRequestID());
}
}


#pragma endregion Debug

RequestFilter

对于每一类 Request,每个玩家身上会有一个对应的 RequestFilter,用来更新可能可以发起请求的目标;

通过 Register / Unregister PossibleTarget,然后更新对应的 FilterTargetData 并同步给 Client

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
// FilterTargetData 

USTRUCT()
struct FGameplayRequestFilterTargetData
{
GENERATED_BODY()

FGameplayRequestFilterTargetData() = default;
FGameplayRequestFilterTargetData(uint64 PlayerUID) : PlayerUID(PlayerUID) {}

public:
bool operator==(const FGameplayRequestFilterTargetData& Other) const
{
if (PlayerUID != Other.PlayerUID) return false;
return true;
}
bool operator !=(const FGameplayRequestFilterTargetData& Other) const { return !(*this == Other); }

public:
FString ToString() const;


public:
UPROPERTY()
uint64 PlayerUID;

UPROPERTY()
bool bValid = false;

UPROPERTY()
FCommonVariantParams Params = {};
};

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// RequestFilterBase.h

UCLASS(Abstract)
class UGameplayRequestFilterBase : public UGameplaySubSystemBase
{
GENERATED_BODY()

#pragma region Config

public:
virtual uint32 GetType() const PURE_VIRTUAL(UGameplayRequestFilterBase::GetType, return 0;);

#pragma endregion Config


#pragma region Base

protected:
virtual void OnInit() override;
virtual void OnUninit() override;
virtual FString ToString() const;

private:
TWeakObjectPtr <class UGameplayRequestComponent> Owner;

#pragma endregion Base


#pragma region Replicate

protected:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty> & OutLifetimeProps) const override;
protected:
virtual bool IsSupportedForNetworking() const override { return true; }

#pragma endregion Replicate


#pragma region FilterTarget

public:
UFUNCTION()
virtual bool IsTargetValid(uint64 TargetUID) const;
UFUNCTION()
FGameplayRequestFilterTargetData GetFilterTargetData(uint64 PlayerUID) const;

protected:
void FilterTargets(); // 主动调用

protected:
void ClearPossibleTargets();
void RegisterPossibleTarget(uint64 TargetUID);
void UnregisterPossibleTarget(uint64 TargetUID);

protected:
virtual bool CheckSelfConditionValid() { return true; }
virtual bool CheckTargetConditionValid(uint64 TargetUID) { return true; }

private:
UFUNCTION()
void OnRep_FilterTargetDatas();

private:
UPROPERTY(Replicated, ReplicatedUsing = OnRep_FilterTargetDatas)
TArray <FGameplayRequestFilterTargetData> FilterTargetDatas;

#pragma endregion FilterTarget


#pragma region Helper

protected:
uint64 GetUID() const;
APlayerStateBase* GetPlayerState() const;
float GetTimeNow() const;

#pragma endregion Helper

};

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// RequestFilterBase.cpp

#pragma region Base

void UGameplayRequestFilterBase::OnInit()
{
Super::OnInit();
Owner = Cast<UGameplayRequestComponent>(GetOuter());
}

void UGameplayRequestFilterBase::OnUninit()
{
Super::OnUninit();
}

FString UGameplayRequestFilterBase::ToString() const
{
return FString::Printf(TEXT("[%llu|%s]"), GetUID(), *GetName());
}


#pragma endregion Base


#pragma region Replicate

void UGameplayRequestFilterBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// --------------------------------------------------------------------------------------------------------------------
// --- OwnerOnly

FDoRepLifetimeParams OwnerOnlyRepParams;
OwnerOnlyRepParams.bIsPushBased = true;
OwnerOnlyRepParams.Condition = COND_OwnerOnly;

DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestFilterBase, FilterTargetDatas, OwnerOnlyRepParams);
}

#pragma endregion Replicate


#pragma region FilterTarget

bool UGameplayRequestFilterBase::IsTargetValid(uint64 TargetUID) const
{
const auto& Data = GetFilterTargetData(TargetUID);
return Data.bValid;
}

FGameplayRequestFilterTargetData UGameplayRequestFilterBase::GetFilterTargetData(uint64 PlayerUID) const
{
for (const auto& Data : FilterTargetDatas)
{
if (Data.PlayerUID == PlayerUID)
{
return Data;
}
}
return {};
}

void UGameplayRequestFilterBase::ClearPossibleTargets()
{
if (!IsStandaloneOrDS(this)) return;

GetFilterTargetDatas_Mutable().Empty();
}

void UGameplayRequestFilterBase::RegisterPossibleTarget(uint64 TargetUID)
{
if (!IsStandaloneOrDS(this)) return;
if (FilterTargetDatas.Contains( TargetUID )) return;
FilterTargetDatas.Add( { TargetUID } );

}

void UGameplayRequestFilterBase::UnregisterPossibleTarget(uint64 TargetUID)
{
if (!IsStandaloneOrDS(this)) return;
if (!FilterTargetDatas.Contains( TargetUID )) return;
FilterTargetDatas.Remove( TargetUID );

}

void UGameplayRequestFilterBase::FilterTargets()
{
bool bSelfConditionValid = CheckSelfConditionValid();
for (auto& Data : FilterTargetDatas)
{
bool bTargetConditionValid = CheckTargetConditionValid(Data.PlayerUID);
Data.bValid = (bSelfConditionValid && bTargetConditionValid);
// TODO : More Params
Data.Params.Clear();
Data.Params.SetValue( "bSelfConditionValid", bSelfConditionValid );
Data.Params.SetValue( "bTargetConditionValid", bTargetConditionValid );
}

MARK_PROPERTY_DIRTY_FROM_NAME(UGameplayRequestFilterBase, FilterTargetDatas, this);
OnRep_FilterTargetDatas();
}


void UGameplayRequestFilterBase::OnRep_FilterTargetDatas()
{
}


#pragma endregion FilterTarget



#pragma region Helper

uint64 UGameplayRequestFilterBase::GetUID() const
{
if (!Owner.IsValid()) return 0;
return Owner->GetUID();
}

APlayerStateBase* UGameplayRequestFilterBase::GetPlayerState() const
{
if (!Owner.IsValid()) return nullptr;
return Owner->GetPlayerState<APlayerStateBase>();
}

float UGameplayRequestFilterBase::GetTimeNow() const
{
return UFunctionLibrary::GetGameServerTimeWithSecond(GetWorld());
}

#pragma endregion Helper

Request

一个 RequestRequestManager 持有实际的 Request

通过 SenderTargetRequestComponent 将其同步到对应 Client

对于 Request,定义基本属性、状态以及提供生命周期相关回调;

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// RequestBase.h

UCLASS(Abstract)
class UGameplayRequestBase : public UObject
{
GENERATED_BODY()

#pragma region Config

public:
UFUNCTION()
virtual uint32 GetType() const PURE_VIRTUAL(UGameplayRequestBase::GetType, return 0;);
virtual UClass* GetFilterType() const PURE_VIRTUAL(UGameplayRequestBase::GetFilterType, return UGameplayRequestFilterBase::StaticClass(););

#pragma endregion Config


#pragma region Base

public:
virtual FString ToString() const;
uint64 Init(uint64 SenderUID, uint64 TargetUID, const FCommonVariantParams& InParams);
void Uninit();

private:
inline static uint64 REQUEST_ID_COUNTER = 0;

#pragma endregion Base


#pragma region Replicate

protected:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

private:
virtual bool IsSupportedForNetworking() const override { return true; }

#pragma endregion Replicate


#pragma region Validate

public:
bool ValidateRequest();

#pragma endregion Validata


#pragma region Timeout

private:
void RegisterTimeoutTimer();

private:
FTimerHandle TimeoutTimer;

#pragma endregion Timeout


#pragma region Interact

private:
friend class UGameplayRequestManager;

private:
void Accept(const FCommonVariantParams& Params = {});
void Reject(const FCommonVariantParams& Params = {});
void Cancel(const FCommonVariantParams& Params = {});

void Timeout();
void Interrupt();

protected:
virtual void OnAccepted() {};
virtual void OnRejected() {};
virtual void OnCanceled() {};

#pragma endregion Interact


#pragma region Create

public:
void NotifyCreated();

private:
bool bHasNotifyCreated = false;

#pragma endregion Create


#pragma region Complete

private:
void NotifyCompleted(EGameplayRequestResult InResult);

protected:
virtual void OnCompleted(EGameplayRequestResult InResult) {}

private:
UFUNCTION()
void OnRep_Result();

private:
UPROPERTY(Replicated, ReplicatedUsing=OnRep_Result)
EGameplayRequestResult Result = EGameplayRequestResult::None;

#pragma endregion Complete


#pragma region State

private:
void UpdateState(EGameplayRequestState InState);

protected:
virtual void OnStateChanged(EGameplayRequestState) {}

private:
UFUNCTION()
virtual void OnRep_State();

private:
UPROPERTY(Replicated, ReplicatedUsing=OnRep_State)
EGameplayRequestState State = EGameplayRequestState::Invalid;

#pragma endregion State



#pragma region KeepAlive

public:
float GetKeepAliveTime() const { return KeepAliveTime; }

private:
// 最大保留时间 (完成后也可能保留)
float KeepAliveTime = 60.0f;

#pragma endregion KeepAlive


#pragma region Params

public:
void CombineParams(const FCommonVariantParams& Params);

private:
UPROPERTY(Replicated)
FCommonVariantParams RequestParams;

#pragma endregion Params


#pragma region Data
private:
UFUNCTION()
void OnRep_RequestID();

private:
UPROPERTY(Replicated)
uint64 SenderUID = 0;
UPROPERTY(Replicated)
uint64 TargetUID = 0;

UPROPERTY(Replicated)
float CreateTime = 0.0f;
UPROPERTY(Replicated)
float TimeoutDuration = 30.0f;

UPROPERTY(Replicated, ReplicatedUsing=OnRep_RequestID)
uint64 RequestID = 0;

#pragma endregion Data



#pragma region Helper

public:
bool IsFinished() const;

protected:
float GetTimeNow() const;
float GetRunningTime() const;

#pragma endregion Helper

};
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// RequestBase.cpp


#pragma region Base

FString UGameplayRequestBase::ToString() const
{
return FString::Printf(TEXT("{[ID=%llu][Type=%d][UID=%llu->%llu][Params=%s][%s][Time=%.f+%.f(s)][%.f(s), %.f(s)]}"),
RequestID,
GetType(), SenderUID, TargetUID, *RequestParams.ToString(),
*UStringUtils::ToString(State),
CreateTime, GetRunningTime(),
TimeoutDuration, KeepAliveTime
);
}

uint64 UGameplayRequestBase::Init(uint64 InSenderUID, uint64 InTargetUID, const FCommonVariantParams& InParams)
{
if (!IsStandaloneOrDS(this)) return 0;

SetRequestID( ++REQUEST_ID_COUNTER );

SetSenderUID( InSenderUID );
SetTargetUID( InTargetUID );
SetRequestParams( InParams );
SetState( EGameplayRequestState::Waiting );

SetCreateTime( GetTimeNow() );
SetTimeoutDuration( 30.0f );

KeepAliveTime = FMath::Max(KeepAliveTime, TimeoutDuration);

bool bValid = ValidateRequest();


if (bValid == false)
{
return 0;
}

RegisterTimeoutTimer();

return RequestID;
}

void UGameplayRequestBase::Uninit()
{

UWorldTimerManager::ClearTimer(GetWorld(), TimeoutTimer);
}

#pragma endregion Base


#pragma region Replicate


void UGameplayRequestBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);

FDoRepLifetimeParams SharedParams;
SharedParams.bIsPushBased = true;

DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, RequestID, SharedParams);

DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, SenderUID, SharedParams);
DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, TargetUID, SharedParams);
DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, RequestParams, SharedParams);
DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, CreateTime, SharedParams);
DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, TimeoutDuration, SharedParams);
DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, RequestID, SharedParams);

DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, State, SharedParams);
DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayRequestBase, Result, SharedParams);
}

#pragma endregion Replicate


#pragma region Validate

bool UGameplayRequestBase::ValidateRequest()
{
if (GetType() <= 0) return false;

// 如果没有需要的 Filter 则默认为 true
if (auto Filter = UGameplayRequestUtils::GetPlayerFilterByUID(GetWorld(), SenderUID, GetFilterType()); IsValid(Filter))
{
if (!Filter->IsTargetValid(TargetUID))
return false;
}

// TODO : 重复 Request

return true;
}

#pragma endregion Validate


#pragma region Timeout

void UGameplayRequestBase::RegisterTimeoutTimer()
{
if (TimeoutDuration <= 0.0f) return;

UWorldTimerManager::SetTimer(GetWorld(), TimeoutTimer,
[WeakSelfPtr = TWeakObjectPtr<UGameplayRequestBase>(this)]()
{
if (!WeakSelfPtr.IsValid()) return;
WeakSelfPtr->Timeout();
}, TimeoutDuration, false);
}

#pragma endregion Timeout


#pragma region Interact

void UGameplayRequestBase::Accept(const FCommonVariantParams& Params)
{
if (IsFinished()) return;


CombineParams(Params);
UpdateState(EGameplayRequestState::Accepted);
OnAccepted();
NotifyCompleted(EGameplayRequestResult::Success);
}

void UGameplayRequestBase::Reject(const FCommonVariantParams& Params)
{
if (IsFinished()) return;

CombineParams(Params);
UpdateState(EGameplayRequestState::Rejected);
OnRejected();
NotifyCompleted(EGameplayRequestResult::Failed);
}

void UGameplayRequestBase::Cancel(const FCommonVariantParams& Params)
{
if (IsFinished()) return;

CombineParams(Params);
UpdateState(EGameplayRequestState::Canceled);
OnCanceled();
NotifyCompleted(EGameplayRequestResult::Canceled);
}

void UGameplayRequestBase::Timeout()
{
if (IsFinished()) return;
Cancel( { "CancelReason", EGameplayRequestCancelReason::Timeout } );
}

void UGameplayRequestBase::Interrupt()
{
if (IsFinished()) return;
Cancel( {"CancelReason", EGameplayRequestCancelReason::Interrupt} );
}


#pragma endregion Interact



#pragma region Create

void UGameplayRequestBase::NotifyCreated()
{
if (bHasNotifyCreated) return;
bHasNotifyCreated = true;
}


#pragma endregion Create


#pragma region Complete

void UGameplayRequestBase::NotifyCompleted(EGameplayRequestResult InResult)
{

SetResult(InResult);
OnCompleted(InResult);
OnRep_Result();
}

void UGameplayRequestBase::OnRep_Result()
{
}


#pragma endregion Complete


#pragma region State

void UGameplayRequestBase::UpdateState(EGameplayRequestState InState)
{
if (InState == State) return;

SetState(InState);
OnStateChanged(InState);
OnRep_State();
}

void UGameplayRequestBase::OnRep_State()
{
}


#pragma endregion State


#pragma region Params

void UGameplayRequestBase::CombineParams(const FCommonVariantParams& Params)
{
if (Params.IsEmpty()) return;
SetRequestParams( RequestParams + Params );

}

#pragma endregion Params


#pragma region Data

void UGameplayRequestBase::OnRep_RequestID()
{

}

#pragma endregion Data


#pragma region Helper

bool UGameplayRequestBase::IsFinished() const
{
return State != EGameplayRequestState::Waiting && State != EGameplayRequestState::Invalid;
}

float UGameplayRequestBase::GetTimeNow() const
{
return UFunctionLibrary::GetGameServerTimeWithSecond(GetWorld());
}

float UGameplayRequestBase::GetRunningTime() const
{
float TimeNow = GetTimeNow();
return TimeNow - CreateTime;
}


#pragma endregion Helper


GameplayRequestUtils

通过该 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// GameplayRequestUtils.h

UCLASS()
class UGameplayRequestUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()

#pragma region Base

public:

UFUNCTION()
static UGameplayRequestManager* GetManager(UWorld* InWorld);
UFUNCTION()
static UGameplayRequestComponent* GetComponent(APlayerStateBase* PS);
UFUNCTION()
static UGameplayRequestComponent* GetComponentByUID(UWorld* InWorld, uint64 UID);


#pragma endregion Base


#pragma region Filter

public:
UFUNCTION()
static UGameplayRequestFilterBase* GetPlayerFilter(APlayerStateBase* PS, UClass* FilterType);

UFUNCTION()
static UGameplayRequestFilterBase* GetPlayerFilterByUID(UWorld* InWorld, uint64 UID, UClass* FilterType);

#pragma endregion Filter


#pragma region Request

public:
UFUNCTION()
static UGameplayRequestBase* GetRequest(UWorld* InWorld, uint64 RequestID);

UFUNCTION()
static UGameplayRequestBase* GetPlayerRequest(APlayerStateBase* PS, uint64 RequestID);

UFUNCTION()
static TArray<UGameplayRequestBase*> GetPlayerRequests(APlayerStateBase* PS);

UFUNCTION()
static TArray<UGameplayRequestBase*> GetPlayerRequestsByType(APlayerStateBase* PS, uint32 Type);

#pragma endregion Request

};

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// GameplayRequestUtils.cpp


#pragma region Base

UGameplayRequestManager* UGameplayRequestUtils::GetManager(UWorld* InWorld)
{
if (InWorld == nullptr) return nullptr;
return UFunctionLibrary::GetGameStateComponentFromWorld<UGameplayRequestManager>(InWorld);
}

UGameplayRequestComponent* UGameplayRequestUtils::GetComponent(APlayerStateBase* PS)
{
if (!IsValid(PS)) return nullptr;
return UFunctionLibrary::GetPlayerStateComponent<UGameplayRequestComponent>(PS);
}

UGameplayRequestComponent* UGameplayRequestUtils::GetComponentByUID(UWorld* InWorld, uint64 UID)
{
if (!IsValid(InWorld)) return nullptr;
auto PS = UFunctionLibrary::GetPlayerStateByUID(InWorld, UID);
if (!IsValid(PS)) return nullptr;
return GetComponent(PS);
}


#pragma endregion Base


#pragma region Filter

UGameplayRequestFilterBase* UGameplayRequestUtils::GetPlayerFilter(APlayerStateBase* PS, UClass* FilterType)
{
auto Component = GetComponent(PS);
if (!IsValid(Component)) return nullptr;
return Component->GetFilter(FilterType);
}

UGameplayRequestFilterBase* UGameplayRequestUtils::GetPlayerFilterByUID(UWorld* InWorld, uint64 UID, UClass* FilterType)
{
auto Component = GetComponentByUID(InWorld, UID);
if (!IsValid(Component)) return nullptr;
return Component->GetFilter(FilterType);
}

#pragma endregion Filter


#pragma region Request

UGameplayRequestBase* UGameplayRequestUtils::GetRequest(UWorld* InWorld, uint64 RequestID)
{
if (!IsValid(InWorld)) return nullptr;

if (IsStandaloneOrDS(InWorld))
{
auto Manager = GetManager(InWorld);
if (IsValid(Manager))
{
return Manager->GetRequest( RequestID );
}
}
else if (IsClient(InWorld))
{
APlayerStateBase* HostPS = UFunctionLibrary::GetPlayerStateByUID(InWorld, UFunctionLibrary::GetMainPlayerUID(InWorld));
if (IsValid(HostPS))
{
return GetPlayerRequest( HostPS, RequestID );
}
}

return nullptr;
}


UGameplayRequestBase* UGameplayRequestUtils::GetPlayerRequest(APlayerStateBase* PS, uint64 RequestID)
{
auto Component = GetComponent(PS);
if (!IsValid(Component)) return nullptr;
return Component->GetRequest( RequestID );
}

TArray<UGameplayRequestBase*> UGameplayRequestUtils::GetPlayerRequests(APlayerStateBase* PS)
{
auto Component = GetComponent(PS);
if (!IsValid(Component)) return {};
return Component->GetRequests();
}

TArray<UGameplayRequestBase*> UGameplayRequestUtils::GetPlayerRequestsByType(APlayerStateBase* PS, uint32 Type)
{
auto Requests = GetPlayerRequests(PS);
TArray <UGameplayRequestBase*> OutRequests;
for (auto Request : Requests)
{
if (!IsValid(Request)) continue;
if (Request->GetType() == Type)
{
OutRequests.Add( Request );
}
}
return OutRequests;
}


#pragma endregion Request

Sample

创建一个请求时,只需要创建该类型对应的 RequestFilterRequest 即可;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Request_Test.h

UCLASS()
class UGameplayRequest_Test : public UGameplayRequestBase
{
GENERATED_BODY()

#pragma region Config

public:
virtual uint32 GetType() const override { return 1001; }
virtual UClass* GetFilterType() const override { return UGameplayRequestFilter_Test::StaticClass(); }

#pragma endregion Config


private:
virtual void OnAccepted() override {};
virtual void OnRejected() override {};
virtual void OnCanceled() override {};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// RequestFilter_Test.h

UCLASS()
class UGameplayRequestFilter_Test : public UGameplayRequestFilterBase
{
GENERATED_BODY()

#pragma region Config

public:
virtual uint32 GetType() const override { return 1001; }

#pragma endregion Config

public:
virtual bool IsTargetValid(uint64 TargetUID) const override { return true; };
};