GameSubSystem实现
维护 SubSystem
的解决方案;
在维护各个业务时,经常需要将 Manager
,下面再拆出若干个子系统,需要一种快捷的方法快速扩展出一套 SubSystem
系统;
- 基本结构:由
Manager
持有 SubSystemCollection
,Collection
中持有若干个继承于 SubSystemBase
的自定义 SubSystem
;
- 同步方案:当
Manager
IsReplicated
且 Collection
中的 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
,主动调用 Init
、Uninit
、Tick
。
同时,为了后续的同步, Manager
需要重载 IGameSubSystemCollectionOwnerInterface
的 GetSubSystemCollectionBase
,用于找到对应的 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
|
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
| { public: DECLARE_GAMESUBSYSTEM_ACCESSORS(UGameSubSystem, SubSystemCollections); virtual FGameSubSystemCollectionBase& GetSubSystemCollectionBase() override { return SubSystemCollections; } private: FGameSubSystemCollection <UGameSubSystem> SubSystemCollections; }
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
方法:
- 根据
BaseClass
,找到所有继承于该类的 Class
创建 SubSystem
。
- 指定
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; if (!IsValid(InOuter)) return false; 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
|
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::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; if (!IsValid(InOuter)) return false; 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
|
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
。
提供一些基础的方法,Init
、Uninit
、Tick
在 SubSystemCollection
中调用。
同时子类只需要关心:OnInit
、OnUninit
、OnTick
。
对于各自的业务,各自继承自定义的 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
|
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
|
#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
中,其中
- 可能存在需要同步的数据;
SubSystem
本身需要在 Client
有对应实例;
一种简单的方案是将这种需要同步的数据,存储在 Manager
中,SubSystem
也跟着 Manager
创建,但显然这样不够优雅;
于是可以实现一套 SubSystem
的同步方案,核心思路是:
将 SubSystem
作为 Manager
的 SubObj
,通过同步在 Client
创建出来,创建后注册进 Manager
对应的 Collection
中;
同步基建
显然,这里的 Manager
可能是 Actor
,也可能是 ActorComponent
;
将 SubSystem
作为 Manager
的 SubObj
进行同步创建,通过 ReplicateSubObjects
来同步;
SubSystem
实现 IInterface_ActorSubobject
,在其中 OnOnCreatedFromReplication
的回调中,通过 GetOuter
获取 Manager
;
Manager
实现 IGameSubSystemCollectionOwnerInterface
,获取 Collection
;
这样就可以在 Client
将 SubSystem
注册进对应 Manager
的 Collection
;
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::ProcessBunch
中 FindOrCreateReplicator
并记录在 ReplicationMap
中,后续进行 FObjectReplicator::ReceivedBunch
;
在 FObjectReplicator::PostReceivedBunch
中 CallRepNotifies
执行一系列 OnRep
;
特别地,如果在一个 Object
中同步了一个 SubObjectArray
,该 Array
标记有 ReplicatedUsing
方法,当 SubObject
在 Client
创建出时,会立马调用到该 OnRep
方法;(每次 ProcessBunch
都会对所有的 ReplicationMap
调用 PostReceivedBunch
);
一致的 Outer
Client
创建出 SubSystem
时,通过 GetOuter
找到 Manager
,从而找到 Collection
注册进该 SubSystem
;
所以需要保证这里 NewObject
时,和 DS
保持一致的 Outer
,这样才能找到正确的 Manager
;
实际上,在 UE4
中这里 NewObject
时,直接将 Actor
作为 Outer
;
(但实际上 SubObj
可能由 ActorComponent
等 SubObj
持有;错误的 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
,重载 Manager
的 ReplicateSubobjects
,调用 Collection
提供的 ReplicateSubSystems
方法;
1 2 3 4 5 6 7 8 9 10 11
|
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
|
virtual FGameCollectionBase& GetSubSystemCollectionBase() override { return SubSystemCollections; }
|
SubSystemCollection
对于 SubSystemCollection
实现 ReplicateSubSystems
,将需要同步的(IsSupportedForNetworking == true
)SubSystem
写入到 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
时,由于递归的顺序,会先调用 ActorComponent
的 SubSystem
的 StartReplicating
,然后再调用 ActorComponent
的 StartReplicating
;
可能会导致时序的问题,所以延迟一帧开始同步 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
|
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
|
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
,从而在 CreateSubObjects
中 Remove
掉对应的 SubObj
;
在 UE5
中相关 SubObj
的同步进行了重构,正常支持动态的 AddSubObject/RemoveSubObject
;