GameSubSystem实现

维护 SubSystem 的解决方案;

在维护各个业务时,经常需要将 Manager,下面再拆出若干个子系统,需要一种快捷的方法快速扩展出一套 SubSystem 系统;

  1. 基本结构:由 Manager 持有 SubSystemCollectionCollection 中持有若干个继承于 SubSystemBase 的自定义 SubSystem
  2. 同步方案:当 Manager IsReplicatedCollection 中的 SubSystem 需要同步数据时,提供 SubSystem 的同步策略,保证双端统一;

基本结构

classDiagram

	direction LR

	IGameSubSystemCollectionOwnerInterface<|--Manager
	class IGameSubSystemCollectionOwnerInterface {
		GetSubSystemCollectionBase()
	}

	Manager..*FGameSubSystemCollection
	
	
	class Manager {
	SubSystemCollections : FGameSubSystemCollection~UGameSubSystem~
	}
	
	
	class FGameSubSystemCollectionBase {
        Outer : TWeakObjectPtr~UObject~
        SubSystemMap : TMap~UClass*,TStrongObjectPtr[UGameSubSystemBase]~
        BaseType : UClass*
        
        Init()
        SetOuter()
        Uninit()
        Tick()
        
        
        AddSubSystem()
        RemoveSubSystem()
        GetSubSystem()
        ReplicateSubSystems()
    }
    

	FGameSubSystemCollectionBase<|--FGameSubSystemCollection
    FGameSubSystemCollectionBase..>UGameSubSystemBase
	class FGameSubSystemCollection {
	FGameSubSystemCollection()
	
	}
	
	
   
    IInterface_ActorSubobject<|--UGameSubSystemBase
    class IInterface_ActorSubobject {
    	+ OnCreatedFromReplication()
    	+ OnDestroyedFromReplication()
    }
    
    class UGameSubSystemBase {
    	LastTickTime : float
    	
    	+ Init()
    	+ Uninit()
    	
    	# OnInit()
    	# OnUnInit()
    	
    	+ Tick()
    	# OnTick(float DeltaTime)
    	# GetTickInternal()
    	# GetTimeNow()
    	
    	+ IsSupportedForNetworking()
    	+ OnCreatedFromReplication()
    	+ OnDestroyedFromReplication()
    }
    
    UGameSubSystemBase<|--UGameSubSystem
    class UGameSubSystem {
    	# OnInit()
    	# OnUnInit()
    	# OnTick(float DeltaTime)
    	
    	# GetTickInternal()
    }

Manager

Manager 持有对应类型的 SubSystemCollection,主动调用 InitUninitTick

同时,为了后续的同步, Manager 需要重载 IGameSubSystemCollectionOwnerInterfaceGetSubSystemCollectionBase,用于找到对应的 Collection(如果不需要同步,也可以不实现);

提供一个宏,方便地进行注册:

1
2
3
4
5
6
7
8
9
10
11
12
#define DECLARE_GAMESUBSYSTEM_ACCESSORS(BaseClass, SubSystemCollections) \
template<class T> \
typename TEnableIf<TIsDerivedFrom<T, BaseClass>::Value, T*>::Type \
GetSubSystem() \
{ \
return SubSystemCollections.GetSubSystem<T>(); \
} \
\
TArray<BaseClass*> GetSubSystems() \
{ \
return SubSystemCollections.GetSubSystems(); \
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// IGameSubSystemCollectionOwnerInterface

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

class IGameSubSystemCollectionOwnerInterface
{
GENERATED_BODY()

public:
virtual FGameSubSystemCollectionBase& GetSubSystemCollectionBase() = 0;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Manager.h
{
public:
DECLARE_GAMESUBSYSTEM_ACCESSORS(UGameSubSystem, SubSystemCollections);
virtual FGameSubSystemCollectionBase& GetSubSystemCollectionBase() override { return SubSystemCollections; }

private:
FGameSubSystemCollection <UGameSubSystem> SubSystemCollections;
}

// Manager.cpp
// 在合适的时机:
SubSystemCollections.Init()
SubSystemCollections.Uninit()
SubSystemCollections.Tick()

SubSystemCollection

SubSystemCollection 负责收集与管理所有的 SubSystem

SubSystemCollectionBase 中,提供一个 BaseClass,这个 BaseClass 由对应的 SubSystemCollection 在初始化的时候传入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename TBaseType>
struct FGameSubSystemCollection : FGameSubSystemCollectionBase
{
static_assert(TIsDerivedFrom<TBaseType, UGameSubSystemBase>::Value, "TBaseType must inherit from UGameSubSystemBase");

FGameSubSystemCollection() : FGameSubSystemCollectionBase(TBaseType::StaticClass())
{
}
}


FGameSubSystemCollectionBase::FGameSubSystemCollectionBase(UClass* InBaseType) : BaseType(InBaseType)
{
check(BaseType);
}

提供两种 Init 方法:

  1. 根据 BaseClass,找到所有继承于该类的 Class 创建 SubSystem
  2. 指定 SubSystemClasses 进行创建 SubSystem
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
void FGameSubSystemCollectionBase::Init(UObject* InOuter, bool bCreatedDerivedClasses)
{
if (!SetOuter(InOuter)) return;

if (bCreatedDerivedClasses)
{
TArray<UClass*> SubSystemClasses;
GetDerivedClasses(BaseType, SubSystemClasses, true);
AddSubSystemByClasses( SubSystemClasses );
}
}

void FGameSubSystemCollectionBase::Init(UObject* InOuter, const TArray<UClass*>& InSubSystemClasses)
{
if (!SetOuter(InOuter)) return;
AddSubSystemByClasses(InSubSystemClasses);
}

bool FGameSubSystemCollectionBase::SetOuter(UObject* InOuter)
{
if (Outer.IsValid()) return false; // already initialized
if (!IsValid(InOuter)) return false; // invalid Outer

Outer = InOuter;
return true;
}

TMap < UClass*, TStrongObjectPtr<UGameSubSystemBase> > SubSystemMap 将所有的 SubSystem 实例保存下来;

1
2
3
4
5
6
void FGameSubSystemCollectionBase::AddSubSystem(UGameSubSystemBase* SubSystem)
{
if (!IsValid(SubSystem)) return;
SubSystemMap.Add( SubSystem->GetClass(), TStrongObjectPtr(SubSystem) );
SubSystem->Init();
}

由于期望 FGameSubSystemCollection 可以在编译期决定类型,使用了 template<typename TBaseType> ,这也导致无法走 UHT 的反射挂上UPROPERTY 标记来保证其生命周期。

所以这里需要用 TStrongObjectPtr 保证 Collection 内部的 SubSystem 不会被 GC

完整实现

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

struct FGameSubSystemCollectionBase
{
public:
void Init(UObject* InOuter, bool bCreatedDerivedClasses = true);
void Init(UObject* InOuter, const TArray<UClass*>& InSubSystemClasses);
bool SetOuter(UObject* InOuter);

void Uninit();
void Tick();

protected:
FGameSubSystemCollectionBase(UClass* InBaseType);

public:
TArray<UGameSubSystemBase*> AddSubSystemByClasses(const TArray<UClass*>& SubSystemClasses);
UGameSubSystemBase* AddSubSystemByClass(UClass* SubSystemClass);
void AddSubSystem(UGameSubSystemBase* SubSystem);

void RemoveSubSystemByClass(UClass* SubSystemClass);
void RemoveSubSystem(UGameSubSystemBase* SubSystem);

protected:
UGameSubSystemBase* GetSubSystemInternal(UClass* SubSystemClass) const;
TArray<UGameSubSystemBase*> GetSubSystemsInternal() const;

private:
TWeakObjectPtr <UObject> Outer = nullptr;
TMap < UClass*, TStrongObjectPtr<UGameSubSystemBase> > SubSystemMap;
UClass* BaseType = 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
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
// FGameSubSystemCollectionBase.cpp

FGameSubSystemCollectionBase::FGameSubSystemCollectionBase(UClass* InBaseType)
: BaseType(InBaseType)
{
check(BaseType);
}

void FGameSubSystemCollectionBase::Init(UObject* InOuter, bool bCreatedDerivedClasses)
{
if (!SetOuter(InOuter)) return;

if (bCreatedDerivedClasses)
{
TArray<UClass*> SubSystemClasses;
GetDerivedClasses(BaseType, SubSystemClasses, true);
AddSubSystemByClasses( SubSystemClasses );
}
}

void FGameSubSystemCollectionBase::Init(UObject* InOuter, const TArray<UClass*>& InSubSystemClasses)
{
if (!SetOuter(InOuter)) return;
AddSubSystemByClasses(InSubSystemClasses);
}

bool FGameSubSystemCollectionBase::SetOuter(UObject* InOuter)
{
if (Outer.IsValid()) return false; // already initialized
if (!IsValid(InOuter)) return false; // invalid Outer

Outer = InOuter;
return true;
}


void FGameSubSystemCollectionBase::Tick()
{
for (auto& Pair : SubSystemMap)
{
auto SubSystem = Pair.Value;
if (SubSystem.IsValid())
{
SubSystem->Tick();
}
}
}

void FGameSubSystemCollectionBase::Uninit()
{
TArray <UClass*> SubSystemClasses;
SubSystemMap.GetKeys(SubSystemClasses);
for (auto SubSystemClass : SubSystemClasses)
{
RemoveSubSystemByClass(SubSystemClass);
}
}

// -----

TArray<UGameSubSystemBase*> FGameSubSystemCollectionBase::AddSubSystemByClasses(const TArray<UClass*>& SubSystemClasses)
{
TArray<UGameSubSystemBase*> SubSystems;

for (auto SubSystemClass : SubSystemClasses)
{
auto SubSystem = AddSubSystemByClass( SubSystemClass );
if (IsValid(SubSystem))
{
SubSystems.Add( SubSystem );
}
}

return SubSystems;
}


UGameSubSystemBase* FGameSubSystemCollectionBase::AddSubSystemByClass(UClass* SubSystemClass)
{
if (SubSystemClass == nullptr || !Outer.IsValid()) return nullptr;
if (SubSystemClass->HasAnyClassFlags(CLASS_Abstract)) return nullptr;
if (!SubSystemClass->IsChildOf(BaseType)) return nullptr;

if (SubSystemMap.Contains( SubSystemClass ))
{
return SubSystemMap.FindRef(SubSystemClass).Get();
}

UGameSubSystemBase* SubSystem = NewObject<UGameSubSystemBase>(Outer.Get(), SubSystemClass);
AddSubSystem(SubSystem);
return SubSystem;
}

void FGameSubSystemCollectionBase::AddSubSystem(UGameSubSystemBase* SubSystem)
{
if (!IsValid(SubSystem)) return;
SubSystemMap.Add( SubSystem->GetClass(), TStrongObjectPtr(SubSystem) );
SubSystem->Init();
}

void FGameSubSystemCollectionBase::RemoveSubSystemByClass(UClass* SubSystemClass)
{
if (SubSystemClass == nullptr || !Outer.IsValid()) return;
if (!SubSystemMap.Contains( SubSystemClass )) return;

auto SubSystem = SubSystemMap.FindAndRemoveChecked(SubSystemClass);
if (!SubSystem.IsValid()) return;
SubSystem->Uninit();
}

void FGameSubSystemCollectionBase::RemoveSubSystem(UGameSubSystemBase* SubSystem)
{
if (!IsValid(SubSystem)) return;

auto SubSystemClass = SubSystem->GetClass();
if (!SubSystemMap.Contains( SubSystemClass ) ||SubSystemMap.FindRef(SubSystemClass).Get() != SubSystem) return;

RemoveSubSystemByClass( SubSystemClass );
}

// -----

UGameSubSystemBase* FGameSubSystemCollectionBase::GetSubSystemInternal(UClass* SubSystemClass) const
{
if (SubSystemClass == nullptr) return nullptr;

if (auto SubSystem = SubSystemMap.FindRef( SubSystemClass ); SubSystem.IsValid())
{
return SubSystem.Get();
}

return nullptr;
}

TArray<UGameSubSystemBase*> FGameSubSystemCollectionBase::GetSubSystemsInternal() const
{
TArray <UGameSubSystemBase*> OutArray;

for (auto [Class, SubSystem] : SubSystemMap)
{
if (!SubSystem.IsValid()) continue;
OutArray.Add( SubSystem.Get() );
}
return OutArray;
}

对于 FGameSubSystemCollection,提供几个特化的方法,主要是 GetSubSystem

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

template<typename TBaseType>
struct FGameSubSystemCollection : FGameSubSystemCollectionBase
{
static_assert(TIsDerivedFrom<TBaseType, UGameSubSystemBase>::Value, "TBaseType must inherit from UGameSubSystemBase");

FGameSubSystemCollection() : FGameSubSystemCollectionBase(TBaseType::StaticClass())
{
}

public:
template<class TSubSystemClass = TBaseType>
typename TEnableIf<TIsDerivedFrom<TSubSystemClass, TBaseType>::Value, TSubSystemClass*>::Type
GetSubSystem() const
{
return static_cast<TSubSystemClass*>(GetSubSystemInternal(TSubSystemClass::StaticClass()));
}

template<class TSubSystemClass = TBaseType>
typename TEnableIf<TIsDerivedFrom<TSubSystemClass, TBaseType>::Value, TArray<TSubSystemClass*>>::Type
GetSubSystems() const
{
const TArray<UGameSubSystemBase*>& Array = GetSubSystemsInternal();
const TArray<TSubSystemClass*>* SpecificArray = reinterpret_cast<const TArray<TSubSystemClass*>*>(&Array);
return *SpecificArray;
}
};

特别地,这里的 FGameSubSystemCollection::GetSubSystems,由于 FGameSubSystemCollection::GetSubSystemsInternal 返回的也是 UGameSubSystemBase 指针,内存大小和 TSubSystemClass 指针一样,所以可以使用 reinterpret_cast 直接将整个数组的类型转化,节省遍历 cast 开销。

SubSystemBase

需要一个 SubSystemBase

提供一些基础的方法,InitUninitTickSubSystemCollection 中调用。

同时子类只需要关心:OnInitOnUninitOnTick

对于各自的业务,各自继承自定义的 SubSystemBase ,然后再自定义各自的 SubSytem 继承于这个业务扩展出来的 SubSystemBase 即可。

SubSystemBase->GetOuter() 就可以拿到对应的 Manager

对于 Tick ,维护一个 LastTickTime,用于计算 DeltaTime

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

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

#pragma region Init

public:
void Init();
void Uninit();

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

#pragma endregion Init

#pragma region Tick

public:
void Tick();

protected:
virtual void OnTick(float DeltaTime);

protected:
virtual float GetTickInternal() { return -1.0f; }
virtual float GetWorldTimeNow();

private:
float LastTickTime;


#pragma endregion Tick

};
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
// GameSubSystemBase.cpp

#pragma region Init

void UGameSubSystemBase::Init()
{
LastTickTime = GetWorldTimeNow();
OnInit();
}

void UGameSubSystemBase::OnInit()
{
}


// ------------------------------

void UGameSubSystemBase::Uninit()
{
OnUninit();
}

void UGameSubSystemBase::OnUninit()
{
}


#pragma endregion Init



#pragma region Tick


void UGameSubSystemBase::Tick()
{
float TickInternal = GetTickInternal();
if (TickInternal < 0) return;

float CurrentTickTime = GetWorldTimeNow();
if (CurrentTickTime - LastTickTime > TickInternal)
{
OnTick(CurrentTickTime - LastTickTime);
LastTickTime = CurrentTickTime;
}
}

void UGameSubSystemBase::OnTick(float DeltaTime)
{

}

#pragma endregion Tick

同步方案

由于各业务拆分到 SubSystem 中,其中

  1. 可能存在需要同步的数据;
  2. SubSystem 本身需要在 Client 有对应实例;

一种简单的方案是将这种需要同步的数据,存储在 Manager 中,SubSystem 也跟着 Manager 创建,但显然这样不够优雅;

于是可以实现一套 SubSystem 的同步方案,核心思路是:

SubSystem 作为 ManagerSubObj ,通过同步在 Client 创建出来,创建后注册进 Manager 对应的 Collection 中;

同步基建

显然,这里的 Manager 可能是 Actor,也可能是 ActorComponent

SubSystem 作为 ManagerSubObj 进行同步创建,通过 ReplicateSubObjects 来同步;

SubSystem 实现 IInterface_ActorSubobject,在其中 OnOnCreatedFromReplication 的回调中,通过 GetOuter 获取 Manager

Manager 实现 IGameSubSystemCollectionOwnerInterface,获取 Collection

这样就可以在 ClientSubSystem 注册进对应 ManagerCollection

Create SubObject

flowchart LR
UActorChannel::ProcessBunch
-->UActorChannel::ReadContentBlockPayload
-->UActorChannel::ReadContentBlockHeader


UActorChannel::ProcessBunch
-->UActorChannel::FindOrCreateReplicator

UActorChannel::ProcessBunch
-->FObjectReplicator::ReceivedBunch

Client 收到同步包时,首先进行 UActorChannel::ReadContentBlockHeader

在这里会从 Bunch 中反序列出对应的数据,对应着 UActorChannel::WriteContentBlockHeader 中序列化的数据;

可以观察到,对于 SubObj,会在这里进行 NewObject 并且由 ActorChannel->CreateSubObjects 持有;

并且最后返回 SubObj 作为 RepObj,在 UActorChannel::ProcessBunchFindOrCreateReplicator 并记录在 ReplicationMap 中,后续进行 FObjectReplicator::ReceivedBunch

FObjectReplicator::PostReceivedBunchCallRepNotifies 执行一系列 OnRep

特别地,如果在一个 Object 中同步了一个 SubObjectArray,该 Array 标记有 ReplicatedUsing 方法,当 SubObjectClient 创建出时,会立马调用到该 OnRep 方法;(每次 ProcessBunch 都会对所有的 ReplicationMap 调用 PostReceivedBunch);

一致的 Outer

Client 创建出 SubSystem 时,通过 GetOuter 找到 Manager,从而找到 Collection 注册进该 SubSystem

所以需要保证这里 NewObject 时,和 DS 保持一致的 Outer,这样才能找到正确的 Manager

实际上,在 UE4 中这里 NewObject 时,直接将 Actor 作为 Outer

(但实际上 SubObj 可能由 ActorComponentSubObj 持有;错误的 Actor 会丢失索引路由)

参考 UE5,在 UActorChannel::ReadContentBlockHeader 时:

新写入一个 Bit(0/1) 表示 bActorIsOuter,仅当 (ObjOuter == Actor) || (!Obj->IsSupportedForNetworking()) 时,该值为 true

为了节省流量,仅当 bActorIsOuter = false 时候向 Bunch 中写入 ObjOuter

1
2
3
4
5
6
7
UObject* ObjOuter = Obj->GetOuter();
const bool bActorIsOuter = (ObjOuter == Actor) || (!Obj->IsSupportedForNetworking());
Bunch.WriteBit(bActorIsOuter ? 1 : 0);
if (!bActorIsOuter)
{
Bunch << ObjOuter;
}

在创建完 SubObj 后,会调用 Actor->OnSubobjectCreatedFromReplication( SubObj ),找到对应的 Interface 执行回调;

1
2
3
4
5
6
7
8
if ( UActorComponent * Component = Cast<UActorComponent>(NewSubobject) )
{
Component->OnCreatedFromReplication();
}
else if (IInterface_ActorSubobject* SubojectInterface = Cast<IInterface_ActorSubobject>(NewSubobject))
{
SubojectInterface->OnCreatedFromReplication();
}

在这里可以进行 SubSystem 的注册;

具体实现

Manager

对于 Manager,重载 ManagerReplicateSubobjects,调用 Collection 提供的 ReplicateSubSystems 方法;

1
2
3
4
5
6
7
8
9
10
11
// Manager.cpp

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

WroteSomething |= Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
WroteSomething |= SubSystemCollections.ReplicateSubSystems( Channel, Bunch, RepFlags );

return WroteSomething;
}

同时,Manager 需要实现 IGameSubSystemCollectionOwnerInterface 接口,用于后续 SubSystem 的注册;

1
2
3
// Manager.h

virtual FGameCollectionBase& GetSubSystemCollectionBase() override { return SubSystemCollections; }

SubSystemCollection

对于 SubSystemCollection 实现 ReplicateSubSystems,将需要同步的(IsSupportedForNetworking == trueSubSystem 写入到 Bunch 中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool FGameCollectionBase::ReplicateSubSystems(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
if (!Outer.IsValid()) return false;
if (!IsValid(Channel)) return false;

if (RepFlags->bNetInitial) return false;

bool WroteSomething = false;
const auto& SubSystems = GetSubSystemsInternal();
for (auto SubSystem : SubSystems)
{
if (!IsValid(SubSystem)) continue;

if (SubSystem->IsSupportedForNetworking())
{
WroteSomething |= Channel->ReplicateSubobject( SubSystem, *Bunch, *RepFlags );
}
}

return WroteSomething;
}

特别地,当 RepFlags 初始化时,先不进行 SubSystem 的同步;

这是因为在 AActor::ReplicateSubobjects 时,递归进行的 ReplicateSubobjects

1
2
3
4
5
6
7
8
for (UActorComponent* ActorComp : ReplicatedComponents)
{
if (ActorComp && ActorComp->GetIsReplicated())
{
WroteSomething |= ActorComp->ReplicateSubobjects(Channel, Bunch, RepFlags);
WroteSomething |= Channel->ReplicateSubobject(ActorComp, *Bunch, *RepFlags);
}
}

同步一般会先进行 Actor 的同步,之后进行 ActorComponent 的同步,也就是从上到下的设置同步参数,然后 StartReplicating

flowchart LR

UActorChannel::ReplicateSubobject
--> UActorChannel::CreateReplicator
--> FObjectReplicator::StartReplicating
--> UNetDriver::GetReplicationChangeListMgr
--> FRepLayout::CreateReplicationChangelistMgr

但是如果 SubSystem 作为一个 Manager(ActorComponent)SubSystem 时,由于递归的顺序,会先调用 ActorComponentSubSystemStartReplicating,然后再调用 ActorComponentStartReplicating

可能会导致时序的问题,所以延迟一帧开始同步 SubSystem(判断 RepFlags->bNetInitial);

SubSystemBase

首先对于需要同步的 SubSystem,重载 IsSupportedForNetworking 标记为 true

然后实现 IInterface_ActorSubobject 接口;

这里正常 GetOuter,就可以获取到 Manager

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

UCLASS(Abstract)
class UGameSubSystemBase : public UObject, public IInterface_ActorSubobject
{
GENERATED_BODY()

#pragma region Replicate

public:
virtual bool IsSupportedForNetworking() const override { return false; }

virtual void OnCreatedFromReplication() override;
virtual void OnDestroyedFromReplication() override;

#pragma endregion Replicate

};

CreatedFromReplication 时,通过 GetOuter 获取 Manager,再找到 Collection 将自己注册进去;

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

void UGameSubSystemBase::OnCreatedFromReplication()
{
auto CollectionOwner = Cast<IGameCollectionOwnerInterface>(GetOuter());
if (CollectionOwner == nullptr) return;

auto& Collection = CollectionOwner->GetSubSystemCollectionBase();
Collection.SetOuter(GetOuter());
Collection.AddSubSystem(this);
}

void UGameSubSystemBase::OnDestroyedFromReplication()
{
auto CollectionOwner = Cast<IGameCollectionOwnerInterface>(GetOuter());
if (CollectionOwner == nullptr) return;

auto& Collection = CollectionOwner->GetSubSystemCollectionBase();
Collection.RemoveSubSystem(this);
}

这样,对于一个 SubSytem 就可以正常的进行数据同步;

特别地,在 UE4 中,没有提供直接 RemoveSubObject 机制,对于一个同步的 SubObj,会一直在 ActorChannel->CreateSubObjects 中被持有;

UActorChannel::DestroyActorAndComponents 时,会进行 Destroy;或者主动构造 Bunch,让其 Connection->PackageMap->SerializeObject( Bunch, UObject::StaticClass(), SubObjClassObj, &ClassNetGUID ) 获取的 ClassNetGUID invalid,从而在 CreateSubObjectsRemove 掉对应的 SubObj

UE5 中相关 SubObj 的同步进行了重构,正常支持动态的 AddSubObject/RemoveSubObject