夺旗模式框架

描述

夺旗模式:

场景里会按照一定的规则刷新出不同类型的旗帜(不同类型的旗帜有不同的得分、血量、表现等);

玩家会被分成若干个队伍,抢夺刷新出来的旗帜,将旗帜带到自己队伍目标点进行销毁,则可以获得分数;

玩家持有旗帜时,旗帜可能会掉落或被抢夺:被其它玩家撞击到则旗帜被抢夺到其它玩家身上;旗帜的血量归零则会掉落回场景;

有正式赛与加时赛两种类型的比赛,首先会开启正式赛;对于正式赛,率先达到目标分的队伍获胜;如果平局则开启加时赛,否则进行结算;对于加时赛,率先得分的队伍获胜,如果平局则以两队平局进行结算;

要点总结:

  1. 旗帜实体:旗帜本身有自己的类型、得分、血量;
  2. 旗帜刷新:场景内根据一定规则(倒计时刷新、旗子上缴)会刷出不同类型旗帜;
  3. 旗帜传递:玩家可以拾取场景里的旗帜、相互抢夺旗帜、掉落旗帜(持有旗帜血量归零)、将旗帜送给终点得分等;玩家可以组队,共享队伍得分;同队伍之间的玩家可以传递旗帜;
  4. 单局流程:所有玩家就绪分队后,传送到起始点准备开赛;如果一局比赛平分,一定条件下可以触发下一场比赛继续;

模式基础

单局流程

首先,一个完整的夺旗赛,由若干个子比赛构成,子比赛逐渐推进;

flowchart LR

M[MainProcess]
R0[RaceProcess_0]
R1[RaceProcess_1]
R2[RaceProcess_2]

M-->R0
M-->R1
M-->R2
R0-.->R1
R1-.->R2

比赛胜负规则:

单场比赛结束时,判定是否有一方队伍得分更高:

如果有,则全局结束;

如果没有,尝试进入下一场比赛(如果有配置的话),若没有下一场则全局结束;

  1. 正式赛:比赛结束条件:有一队达到目标分 / 倒计时结束
  2. 加时赛:比赛结束条件:有一队得到分数 / 倒计时结束
  3. 其它类型比赛……

局内玩法

从业务出发,先明确有哪些相关逻辑:

最基本的交互显然是:

flowchart LR
F0[场景旗帜_0]
F1[场景旗帜_1]
G0([目标点_0])
G1([目标点_1])
P0[[Player_0]]
P1[[Player_1]]

F0--PickUp--->P0--Drop-->F0
P0--Reach--->G0
P0<--Strike--->P1

PickUpPlayer 触碰到 场景旗帜 ,就可以拾取该旗帜到自己身上;

DropPlayer 持有的旗帜存在血量,其血量归零(可能是被其它 Player 攻击导致扣血),就会触发旗帜的掉落,在对应位置生成场景旗帜;

ReachPlayer 将持有的旗帜护送到 目标点,获得分数,同时该旗帜会被销毁;

StrikePlayer 撞击另一个 Player,可以抢夺对方的旗帜;

业务拆分

首先,先给出基本的一些元素定义;

游戏玩法的本质是由刷新点刷新出旗帜,旗帜在各个部分之间交互传递,旗帜本身定义为 FlagItem

旗帜在场景(不在 Player 身上)里的表现为,存在对应的 FlagSceneItem

场景里存在若干个终点,定义该终点为 GoalPointSceneItem

这里说的 交互 的本质实际上是一个 FlagInstance各个部分 之间的 传递 。想一下所谓的 各个部分,到底是什么?

本质可以抽象为一个个持有 Flag 的容器,将其定义为 FlagContainer

这里显然有三种 Container

Container_WorldFlag:定义 WorldFlag没有被 Player 持有的旗帜

Container_GoalPoint:定义 GoalPoint 为目标点

Container_Player:每个 Player 持有一个 Container

细想一下, WorldFlagGoalPoint 的具体业务逻辑和具体的某个 Instance 没有太大关系,只需要通过 ID 串联即可,所以对于 WorldFlagGoalPoint,不需要真的在每一个 Instance 都创建对应 Container,只需要一个单局管理的唯一 Manager 用来代替管理即可。但显然对于每个 Player 都需要有自己的 Container

可以在单局内创建全局唯一的 Container_WorldFlagContainer_GoalPoint 实例。

flowchart TD


F0[FlagSceneItem_0]
F1[FlagSceneItem_1]
F2[FlagSceneItem_2]
subgraph WorldFlag[Container_WorldFlag]
    direction LR
    F0-.-F1-.-F2
end


G0([GoalPoint_0])
G1([GoalPoint_1])
G2([GoalPoint_2])
subgraph GoalPoint[Container_GoalPoint]
    direction LR
    G0-.-G1-.-G2
end


P0[[Container_Player_0]]
P1[[Container_Player_1]]

WorldFlag--PickUp--->P0--Drop--->WorldFlag
P0--Reach--->GoalPoint
P0<--Strike--->P1

这样就可以将 交互 的逻辑拆得非常简单了:

PickUp :由 FlagSceneItemPlayerOverlap 触发,将 Container_WorldFlag 持有的对应该 FlagSceneItemFlagItem ,传递给这个 PlayerConatiner_Player

Drop : 由 Player 的血量变化归零触发,将 Container_Player 上的所有 FlagItem 转移给 Cotainer_WorldFlag

Reach:由 PlayerGoalPointSceneItemOverlap 触发,将 Container_Player 上的所有 FlagItem 转移给 Cotainer_GoalPonit

Strike:由 Player0Player1 的撞击触发,将 Container_Player 上的所有 FlagItem 转移给 Cotainer_Player1

这里的 PickUpDropReachStrike 是这张图的 ,将其定义为 TransferPolicy

对于 Container,有各自的 Add(FlagItem)Remove(FlagItem),一些基本逻辑:

Container_WorldFlag

  1. AddItem :从 FlagItem 上取出位置,将 Item 血量设置为满血,通过调用 FlagSceneItemManager 创建出 FlagItem 对应的 FlagSceneItem

  2. RemoveItem:调用 FlagSceneItemManager 删除对应的 FlagItem 对应的 FlagSceneItem

Container_GoalPoint

  1. AddItem:判断是从哪个 Container 来的,给对应 Player 加分,并且 RemoveItem

  2. RemoveItem :销毁对应的 FlagItem

Container_Player

  1. AddItem:从 FlagItem 获得 FlagSyncData 同步给自己,并设置 Player 血量为 Item 的血量等逻辑;

  2. RemoveItem:将 Player 血量设置给 Item 血量,清空同步信息等逻辑;

完整的业务拆分完毕,这下需要哪些系统显而易见:

ProcessSystem:负责管理单局流程,创建比赛与推动玩法进程;

TeamSystem :负责 Player 的组队,管理 MemebrScore 等;

FlagItemSystem:负责管理所有 FlagItem 实例,提供 CreateDestroy 等方法,维护 Item 基本的生命周期;

ContainerSystem:负责提供基本的 CreateContainerDestoryContainer 方法,对于具体的 Container,需要支持 AddItemRemoveItemGetItem 等;

TransferSystem:负责管理所有的 TransferPolicy ,提供 TransferItem 方法;

FlagSpawnerSystem:旗帜的刷新点,负责 WorldFlag 的生成,生成 FlagItemAddContainer_WorldFlag

SceneItemSystem :维护场景里 SceneItem 的生成,提供 CreateDestroy 对应的 SceneItem 的方法;对于某个具体的 SceneItem,需要实现基础的 OverlapSync

具体实现

ProcesSystem

首先需要一个挂在 GameState 上的 MainProcess 负责管理全局的流程,串联多个 RaceProcess 推进玩法;

显然,一个最简单的流程可以这样:

flowchart LR
M[MainProcess]
R0[RaceProcess_0]
R1[RaceProcess_1]
F[Finish]

M---->|1. 开赛|R0
R0-.->|2. Result_0|M
M-->|3. Check_Result_0|R1
R1-.->|4. Result_1|M
M-->|5. Check_Result_1|F
R0-.->R1
R1-.->F

但是这样循环流程需要在 Main 里面关注 RaceProcess 给出的 Result 事件,显然有点不够优雅。

发现这里的主要由两部分组成:

  1. RaceProcess 管理子比赛流程,给出 Result
  2. MainProcess 接受子比赛 Result,校验并判断开启下一场 Race 或者结束比赛

所以可以将其拆成两个 Controller

MainProcess 负责提供 Dispatch 的功能,

  1. FinishRace_Controller:监听 Dispatcher开赛 事件,并向 Dispatcher 分发 单局结束事件;
  2. CreateRace_Controller:监听 单局结束 事件,进行处理并向 Dispatcher 分发开赛 事件;
flowchart TB

M[MainProcess]
D[Dispatcher]
F[FinishRace_Controller]
C[CreateRace_Controller]

M-->D

F-->|Send_Finsih|D
D-.->|Register_Create|F

C-->|Send_Create|D
D-.->|Register_Finish|C

TeamSystem

参考 [UE] TeamSystem 框架

支持玩家的 加入退出 队伍,以及维护队伍的各种数据(比如 MembersScore),并维护数据,进行同步。

SceneItemSystem

classDiagram

    UCFSceneItemUtils..>UCFSceneItemManager
    
    UCFSceneItemManager..>UCFScecneItemBase
    class UCFSceneItemManager {
    	frien UCFSceneItemUtils
    	- CreateItem(Type, UID, Params)
    	- DestroyItem(UID)
    }
    
    UCFScecneItemBase..*UCFScecneItemSyncComponent
    UCFScecneItemBase..*UCFScecneItemDisplayComponent
    class UCFScecneItemBase {
    	SyncComponent : UCFScecneItemSyncComponent
    	OverlapDelegate
    	# OnInit()
    	# OnUninit()
    	# BeginDetect()
    	# EndDetect()
    	+ CollectSyncData()
    }
    
    UCFScecneItem_WorldFlag--|>UCFScecneItemBase
    UCFScecneItem_GoalPoint--|>UCFScecneItemBase

首先,需要 UCFSceneItemManager 负责管理所有的 SceneItem,同时通过 UCFSceneItemUtils 提供方法给外部调用。

对于一个具体的 SceneItem,额外对其提供两个 Component

  1. SyncComponent:在 DS/Client 生成,维护 SceneItem 的同步数据,这一部分数据不会被 AOI 裁剪,保证远距离的数据同步;
  2. DisplayComponent:仅在 Client 生成,随着 SceneItemAOI,用于实现客户端表现。

FlagItemSystemContainerSystem

classDiagram
	direction LR

	UCaptureFlagItemSystem..>FCaptureFlagItem
    class UCaptureFlagItemSystem {
    	ItemInstances : TMap~uint64|TSharedPtr~FCaptureFlagItem~~
    	+ CreateItem(ItemID)
    	+ DestroyItem(ItemUID)
    	+ GetItem(ItemUID)
    	- GenerateUID()
    	- RegisterClearTimer()
    	- UnregisterClearTimer()
    	- ClearAllItems(bCheckUnused)
    }
    
    %% ----- FlagItem -----
    
    namespace FlagItem {
        class FCaptureFlagItem
        class FCaptureFlagItemSyncData
    }
    
    FCaptureFlagItem..FCaptureFlagItemSyncData
    FCaptureFlagItem..UCaptureFlagContainerBase
    class FCaptureFlagItem {
    	+ InitItem(InUID, ItemID)
    	+ GetSyncData() : FCaptureFlagItemSyncData
    	+ UID / ItemID / Type / Health / MaxHealth...
    	+ Container : TWeakObjectPtr~UCaptureFlagContainerBase~
    	+ TransferReason : ECFTransferReason
    }
    
    class FCaptureFlagItemSyncData {
    	+ UID / ItemID / Type / Health / MaxHealth / TransferReason
    }
    
    %% ----- FlagContainer -----
    
    namespace FlagContainer {
    	class UCaptureFlagContainerBase
    	class UCaptureFlagContainer_WorldFlag
    	class UCaptureFlagContainer_GoalPoint
    	class UCaptureFlagContainer_Player
    }
    
    class UCaptureFlagContainerBase {
    	Items : TMap ~uint64|TWeakPtr~FCaptureFlagItem~~
    	Owner : TWeakObjectPtr~UObject~
    	+ Init(InOwner)
    	+ Uninit()
    	+ AddItem(TWeakPtr~FCaptureFlagItem~Item, Params)
    	+ RemoveItem(TWeakPtr~FCaptureFlagItem~ Item)
    	+ VerifyCanAddItem()
    	+ GetType() : ECFContainerType
    	# OnInit()
    	# OnUninit()
    	# OnAddItem(TWeakPtr~FCaptureFlagItem~Item, Params)
    	# OnRemoveItem(TWeakPtr~FCaptureFlagItem~ Item)
    }
    
    UCaptureFlagContainer_WorldFlag--|>UCaptureFlagContainerBase
    UCaptureFlagContainer_GoalPoint--|>UCaptureFlagContainerBase
    
    UCaptureFlagContainer_Player--|>UCaptureFlagContainerBase
    class UCaptureFlagContainer_Player{
    	# GetType() : ECFContainerType
    	
    }
    
    %% ----- ContainerOwner -----
    
    namespace ContainerOwner {
    	class UCaptureFlagManager
    	class UCaptureFlagComponent
    	class ICaptureFlagContainerOwnerInterface
    }
    
    
    UCaptureFlagManager..>UCaptureFlagContainer_GoalPoint
    UCaptureFlagManager..>UCaptureFlagContainer_WorldFlag
    UCaptureFlagManager..|>ICaptureFlagContainerOwnerInterface
    class UCaptureFlagManager{
    	- WorldFlagContainer
    	- GoalPointContainer
    	+ GetWorldFlagContainer()
    	+ GetGoalPointContainer()
    }
    
    UCaptureFlagComponent..|>ICaptureFlagContainerOwnerInterface
    UCaptureFlagComponent..>UCaptureFlagContainer_Player
    UCaptureFlagComponent..>FCaptureFlagItemSyncData
    class UCaptureFlagComponent{
    	- FlagContainer_Player
    	- FlagItemSyncData : FCaptureFlagItemSyncData
    	+ GetCaptureFlagContainer()
    	+ UpdateFlagItemSyncData(SyncData)
    }
    
    ICaptureFlagContainerOwnerInterface..>UCaptureFlagContainerBase
    class ICaptureFlagContainerOwnerInterface{
    	CreateFlagContainer(InOwner)
    	DestroyFlagContainer(Container)
    }

首先,需要一个 FlagItemSystem 负责管理所有的 FlagItem 实例。提供基本的创建、销毁、查询功能的同时,这个 FlagItemSystem需要 RegisterClearTimer,每隔一段时间,Clear 不被任何一个 Container 持有的 Item

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

TWeakPtr<FCaptureFlagItem> UCaptureFlagItemManager::CreateItem(int ItemID)
{
auto NewItem = MakeShared<FCaptureFlagItem>();
NewItem->Init( GenerateUID(), ItemID );
ItemInstances.Add( NewItem->GetUID(), NewItem );

NotifyItemCreated(NewItem);
return NewItem;
}

void UCaptureFlagItemManager::DestoryItem(uint64 ItemUID)
{
if (!ItemInstances.Contains(ItemUID)) return;

auto Item = ItemInstances.FindRef(ItemUID);
if (Item.IsValid())
{
if (Item->GetContainer().IsValid())
{
// 先从 Container 上移除
Item->GetContainer()->RemoveItem(Item);
}
}
ItemInstances.Remove(ItemUID);
}

void UCaptureFlagItemManager::ClearAllItems(bool bUnused)
{
TArray <uint64> ItemUIDs;
ItemInstances.GetKeys(ItemUIDs);
for (auto ItemUID : ItemUIDs)
{
auto Item = ItemInstances.FindRef(ItemUID);
if (!Item.IsValid()) return;

if ( bUnused == false
|| /*未被任何 Container 持有*/ Item->GetContainer().IsValid() == false)
{
DestoryItem(ItemUID);
}
}
}

对于 FlagItem,保存 Flag 最基本的信息,同时提供一个 CollectSyncData,用于生成对应的 FlagSyncData,表示需要同步的数据,在 ContainerOwner 需要的时候进行数据同步。

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
// FlagItem.h

#define GENERATE_CAPTUREFLAG_ITEM_PROPERTY(Visibility, Type, Name) \
public: Type Get##Name() const { return Name; } \
public: void Set##Name(const Type& Value) { Name = Value; } \
Visibility: Type Name


// 需要在 FCaptureFlagItem::GetSyncData 中打包数据

USTRUCT()
struct FCaptureFlagItemSyncData
{
GENERATED_BODY()

FCaptureFlagItemSyncData() = default;

public:
FString ToString() const;
bool operator==(const FCaptureFlagItemSyncData& Other) const;
bool operator!=(const FCaptureFlagItemSyncData& Other) const
{
return !(*this == Other);
}

public:
UPROPERTY()
uint64 UID = 0;
UPROPERTY()
int ItemID = 0;
UPROPERTY()
int Type = 0;
UPROPERTY()
float Health = 0.0f;
UPROPERTY()
float MaxHealth = 0.0f;
UPROPERTY()
ECFTransferReason TransferReason;
};


struct FCaptureFlagItem
{

virtual ~FCaptureFlagItem() = default;

public:
void Init(uint64 InUID, int ItemID);
FCaptureFlagItemSyncData GetSyncData() const;

public:
FString ToString() const;

public:
GENERATE_CAPTUREFLAG_ITEM_PROPERTY(private, uint64, UID);

GENERATE_CAPTUREFLAG_ITEM_PROPERTY(private, int, ItemID);
GENERATE_CAPTUREFLAG_ITEM_PROPERTY(private, int, Type);
GENERATE_CAPTUREFLAG_ITEM_PROPERTY(private, float, Health);
GENERATE_CAPTUREFLAG_ITEM_PROPERTY(private, float, MaxHealth);
GENERATE_CAPTUREFLAG_ITEM_PROPERTY(private, int, Score);

GENERATE_CAPTUREFLAG_ITEM_PROPERTY(private, ECFTransferReason, TransferReason);
GENERATE_CAPTUREFLAG_ITEM_PROPERTY(private, TWeakObjectPtr<class UCaptureFlagContainerBase>, Container);
};

需要 Container 来持有 FlagItem,在 AddItemRemoveItem 内写具体的逻辑。

每种 Container 通过重载 UCaptureFlagContainerBasePURE_VIRTUAL 方法 GetType 来定义其类型。

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
// ContainerBase.h

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

friend class UCaptureFlagItemUtils;

public:
virtual ECFContainerType GetType() PURE_VIRTUAL( UCaptureFlagContainerBase::GetType, return ECFContainerType::None; ) // 设置 ContainerType
virtual bool VerifyCanAddItem();
virtual void CollectParams(FCaptureFlagParams& Params);

private:
bool CheckCapcityValid();

public:
void Init(UObject* InOwner);
void Uninit();

public:
bool AddItem(TWeakPtr<FCaptureFlagItem> Item, const FCaptureFlagParams& Params = {});
bool RemoveItem(TWeakPtr<FCaptureFlagItem> Item);
TArray <TWeakPtr<FCaptureFlagItem>> GetAllItems();
TWeakPtr<FCaptureFlagItem> GetItem(uint64 ItemUID) const;
void ClearItems();

protected:
// 各个 Container 自定义逻辑
virtual void OnInit() {}
virtual void OnUninit() {}
virtual void OnAddItem(TWeakPtr<FCaptureFlagItem> Item, const FCaptureFlagParams& Params) {};
virtual void OnRemoveItem(TWeakPtr<FCaptureFlagItem> Item) {};

protected:
TMap <uint64, TWeakPtr<FCaptureFlagItem>> Items {};
TWeakObjectPtr <UObject> Owner = nullptr;
};

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
// ContainerBase.cpp

bool UCaptureFlagContainerBase::AddItem(TWeakPtr<FCaptureFlagItem> Item, const FCaptureFlagParams& Params)
{
if (!Owner.IsValid() || !Item.IsValid()) return false;

uint64 ItemUID = Item.Pin()->GetUID();
if (Item.Pin()->GetContainer().IsValid())
{
// Item 只能被放在一个 Container 内
return false;
}

// 检查是否可以获得
if (VerifyCanAddItem() == false)
{
return false;
}

Item.Pin()->SetContainer( this );
Items.Add( ItemUID, Item );

OnAddItem(Item, Params);
return true;
}

bool UCaptureFlagContainerBase::RemoveItem(TWeakPtr<FCaptureFlagItem> Item)
{
if (!Owner.IsValid() || !Item.IsValid()) return false;

uint64 ItemUID = Item.Pin()->GetUID();
if (!Items.Contains(ItemUID))
{
return false;
}

Item.Pin()->SetContainer(nullptr);
Items.Remove(ItemUID);
OnRemoveItem(Item);

return true;
}

每个 Container 需要一个 ContainerOwner 来持有,这里有三种 Container

  1. Container_WorldFlag: 由 CaptureFlagManager(GS) 持有,一场子比赛持有一个;
  2. Container_GoalPoint:由 CaptureFlagManager(GS) 持有,一场子比赛持有一个;
  3. Container_Player:由 CaptureFlagComponent(PS) 持有,每个 Player 持有一个;

提供一个 ICaptureFlagContainerOwnerInterface 用于支持 CreateDestroy 对应的 Container 方法;

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
// ContainerOwner.h

UINTERFACE(BlueprintType, Blueprintable)
class UCaptureFlagContainerOwnerInterface : public UInterface
{
GENERATED_BODY()
};

class ICaptureFlagContainerOwnerInterface
{
GENERATED_BODY()

public:
template<class T>
static typename TEnableIf<TIsDerivedFrom<T, UCaptureFlagContainerBase>::Value, T*>::Type
CreateFlagContainer(UObject* InOwner) // 传入 this
{
if (!IsValid(InOwner)) return nullptr;
T* Container = NewObject<T>(InOwner);
Container->Init(InOwner);
return Container;
}

void DestroyFlagContainer(UCaptureFlagContainerBase* Container)
{
if (!IsValid(Container)) return;
Container->Uninit();
Container = nullptr;
}
};

TransferSystem

classDiagram
    direction LR
    
    UCFTransferSystem..>UCFTransferPolicyBase
    UCFTransferSystem..UCaptureFlagContainerBase
    class UCFTransferSystem {
        Policies : FGameSubSystemCollection~UCFTransferPolicyBase~
        friend UCFTransferPolicyBase
        - TransferItem(Item, TargetContainer, TransferReason)
    }
    
    
    UCFTransferPolicyBase..UCaptureFlagContainerBase
    class UCFTransferPolicyBase{
    	# OnInit()
    	# OnUninit()
    	# TransferItem(Item, TargetContainer)
    	# GetTransferReason() : ECFTransferReason
    }
    
    UCFTansferPolicy_PickUp--|>UCFTransferPolicyBase
    UCFTansferPolicy_Drop--|>UCFTransferPolicyBase
    UCFTansferPolicy_Reach--|>UCFTransferPolicyBase
    UCFTansferPolicy_Strike--|>UCFTransferPolicyBase

首先需要一个 UCFTransferSystem 持有 Polices,管理并维护所有 TransferPolicy 的生命周期,具体可以参考 [UE] GameSubSystem 简单实现

对于一个具体的 TransferPolicy,通过重载 TransferPolicyBasePURE_VIRTUAL 方法 GetTransferReason ,明确其对应的 TransferReason,在其 OnInitOnUninit,监听该类型需要的事件,通过对应的 Callback 调用到 Base 提供的 TransferItem 方法,执行 Item 的转移。

对于一次成功的 Transfer,显然会调用到 SourceContainerRemove ,以及 TargetContainerAdd

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

bool UCaptureFlagTransferManager::TransferItem(TWeakPtr<FCaptureFlagItem> Item, UCaptureFlagContainerBase* TargetContainer, ECFTransferReason TransferReason)
{
if (!Item.IsValid() || !IsValid(TargetContainer)) return false;

auto SharedItem = Item.Pin();

// 检查是否可以获得
if (TargetContainer->VerifyCanAddItem() == false) return false;
// Source 可以为空
auto SourceContainer = SharedItem->GetContainer();

if (SourceContainer == TargetContainer) return false;

ECFTransferReason LastTransferReason = Item.Pin()->GetTransferReason();
Item.Pin()->SetTransferReason(TransferReason);

// 若有 Source,则尝试移除
if (SourceContainer.IsValid() && SourceContainer->RemoveItem(Item) == false)
{
Item.Pin()->SetTransferReason(LastTransferReason);
return false;
}

FCaptureFlagParams Params{};
if (SourceContainer.IsValid())
{
SourceContainer->CollectParams(Params);
}

// 尝试添加
if (TargetContainer->AddItem(Item, Params) == false)
{
Item.Pin()->SetTransferReason(LastTransferReason);
// 失败则还给 Source
if (SourceContainer.IsValid())
{
SourceContainer->AddItem(Item);
}

return false;
}

return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TransferPolicyBase.h
UCLASS(Abstract)
class UCFTransferPolicyBase : public UGameModeSubSystemBase
{
GENERATED_BODY()

protected:
virtual ECFTransferReason GetTransferReason() const PURE_VIRTUAL(UCFTransferPolicyBase::GetTransferReason, return ECFTransferReason::None;);

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

protected:
bool TransferItem(TWeakPtr<FCaptureFlagItem> Item, UCaptureFlagContainerBase* TargetContainer) const;


protected:
TWeakObjectPtr <class UCaptureFlagTransferManager> System = nullptr
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// TransferPolicy.cpp
void UCFTransferPolicyBase::OnInit()
{
System = Cast<UCaptureFlagTransferManager>(GetOuter());
}

bool UCFTransferPolicyBase::TransferItem(TWeakPtr<FCaptureFlagItem> Item, UCaptureFlagContainerBase* TargetContainer) const
{
if (!System.IsValid() || !Item.IsValid() || !IsValid(TargetContainer)) return false;

bool bSucceed = false;
bSucceed = System->TransferItem( Item, TargetContainer, GetTransferReason() );

return bSucceed;
}