概述
定義
- Subsystems 是一套可以定義、自動實例化和釋放的類的框架。可以將其理解為 GamePlay 級別的 Component
- 不支持網絡賦值
- 4.22開始引入,4.24完善。(可以移植源碼到更早之前的版本使用。源碼在手,為所欲為)
五類 Subsystems 及其生命周期
-
UEngineSubsystem(繼承自 UDynamicSubsystem,UDynamicSubsystem繼承自 USubsystem)
UEngine* GEngine
代表引擎,數量1。 Editor或Runtime模式都是全局唯一,從進程啟動開始創建,進程退出時銷毀。UEngine::Init()
-
UEditorSubsystem(繼承自 UDynamicSubsystem,UDynamicSubsystem繼承自 USubsystem)
UEditorEngine* GEditor
代表編輯器,數量1。 顧名思義,只在編輯器下存在且全局唯一,從編輯器啟動開始創建,到編輯器退出時銷毀。 -
UGameInstanceSubsystem (繼承自 USubsystem)
UGameInstance* GameInstance
代表一場游戲,數量1。 從游戲的啟動開始創建,游戲退出時銷毀。這里的一場游戲指的是Runtime或PIE模式的運行的都算,一場游戲里可能會創建多個World切換。 -
UWorldSubsystem (繼承自 USubsystem)
UWorld* World
代表一個世界,數量可能>1。其生命周期,跟GameMode是一起的。(EWorldType:Game,Editor,PIE,EditorPreview,GamePreview等 ) -
ULocalPlayerSubsystem (繼承自 USubsystem)
ULocalPlayer* LocalPlayer:代表本地玩家,數量可能>1。
UE支持本地分屏多玩家類型的游戲,但往往最常見的是就只有一個。LocalPlayer雖然往往跟PlayerController一起訪問,但是其生命周期其實是跟UGameInstance一起的(默認一開始的時候就創建好一定數量的本地玩家),或者更准確的說是跟LocalPlayer的具體數量掛鈎(當然你也可以運行時動態調用AddLocalPlayer)。
為什么要使用 Subsystems
- 更適用的生命周期
- 引擎只支持一個 GameInstance ,運行周期是整個引擎的生命周期
- 自定義 ManagerActor,生命周期一般為當前 level 的生命周期
- Subsystems 的生命周期可以依存於Engine,Editor,World,LocalPlayer
- 更簡
- GameInstance 或者自定義 ManagerActor,需要手動維控制創建、釋放
- Subsystems 自動創建、釋放,提供 Initialize()、Deinitialize(),並且可重載
- 更模塊化、更優雅、更封裝、更易於維護、移植復用
- GameInstance 中將任務系統,計分系統,經濟系統、對話系統等多個Manager 寫在一起,會變得臃腫
- 模塊間的數據訪問封裝不夠良好,容易污染
- 不利於業務邏輯模塊的復用,特別是需要進行移植的時候,以及多個插件同時都有自己的 GameInstance
- Subsystems 可以為不同的 Manager 創建對應的Subsystems
- 如 Manager 划分,
- 任務系統Subsystem : UGameInstanceSubsystem
- 計分系統Subsystem : UGameInstanceSubsystem
- 經濟系統Subsystem : UGameInstanceSubsystem
- 對話系統Subsystem : UGameInstanceSubsystem
- 更模塊化,代碼顯得優雅
- 解耦性高,易於維護、分工協作;易於移植復用
- 模塊間的數據訪問具有更好的封裝性
- 如 Manager 划分,
- GameInstance 中將任務系統,計分系統,經濟系統、對話系統等多個Manager 寫在一起,會變得臃腫
- 更友好的訪問接口
- Subsystem 更像以全局變量的形式來訪問
- 提供了 Python 腳本的訪問,用於編寫編輯器腳本或編寫測試代碼
- Subsystem 無需覆蓋引擎類。
Subsystems 的使用
-
創建過程,涉及 FSubsystemCollectionBase
FSubsystemCollectionBase::Initialize() FSubsystemCollectionBase::AddAndInitializeSubsystem() FSubsystemCollectionBase::Deinitialize()
參考附錄源碼 或者
EngineDir\Engine\Source\Runtime\Engine\Private\Subsystems\SubsystemCollection.cpp
-
默認重載函數
- ShouldCreateSubsystem
- Initialize()
- Deinitialize()
-
C++ 訪問
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html // UMyEngineSubsystem 獲取 UMyEngineSubsystem* MyEngineSubsystem = GEngine->GetEngineSubsystem<UMyEngineSubsystem>(); // UMyEditorSubsystem 獲取 UMyEditorSubsystem* MyEditorSubsystem = GEditor->GetEditorSubsystem<UMyEditorSubsystem>(); // UMyGameInstanceSubsystem 獲取 //UGameInstance* GameInstance = GetWorld()->GetGameInstance(); UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(); UMyGameInstanceSubsystem* MyGameInstanceSubsystem = GameInstance->GetSubsystem<UMyGameInstanceSubsystem>(); // UMyWorldSubsystem 獲取 UMyWorldSubsystem* MyWorldSubsystem = GetWorld()->GetSubsystem<UMyWorldSubsystem>(); // UMyLocalPlayerSubsystem 獲取 ULocalPlayer* LocalPlayer = UGameplayStatics::GetPlayerController()->GetLocalPlayer(); UMyLocalPlayerSubsystem* MyLocalPlayerSubsystem = LocalPlayer->GetSubsystem<UMyLocalPlayerSubsystem>();
- 引擎自帶 USubsystemBlueprintLibrary 訪問方法
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html UCLASS() class ENGINE_API USubsystemBlueprintLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: /** Get a Game Instance Subsystem from the Game Instance associated with the provided context */ UFUNCTION(BlueprintPure, Category = "Engine Subsystems", meta = (BlueprintInternalUseOnly = "true")) static UEngineSubsystem* GetEngineSubsystem(TSubclassOf<UEngineSubsystem> Class); /** Get a Game Instance Subsystem from the Game Instance associated with the provided context */ UFUNCTION(BlueprintPure, Category = "GameInstance Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true")) static UGameInstanceSubsystem* GetGameInstanceSubsystem(UObject* ContextObject, TSubclassOf<UGameInstanceSubsystem> Class); /** Get a Local Player Subsystem from the Local Player associated with the provided context */ UFUNCTION(BlueprintPure, Category = "LocalPlayer Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true")) static ULocalPlayerSubsystem* GetLocalPlayerSubsystem(UObject* ContextObject, TSubclassOf<ULocalPlayerSubsystem> Class); /** Get a World Subsystem from the World associated with the provided context */ UFUNCTION(BlueprintPure, Category = "GameInstance Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true")) static UWorldSubsystem* GetWorldSubsystem(UObject* ContextObject, TSubclassOf<UWorldSubsystem> Class); /** * Get a Local Player Subsystem from the LocalPlayer associated with the provided context * If the player controller isn't associated to a LocalPlayer nullptr is returned */ UFUNCTION(BlueprintPure, Category = "LocalPlayer Subsystems", meta = (BlueprintInternalUseOnly = "true")) static ULocalPlayerSubsystem* GetLocalPlayerSubSystemFromPlayerController(APlayerController* PlayerController, TSubclassOf<ULocalPlayerSubsystem> Class); private: static UWorld* GetWorldFrom(UObject* ContextObject); };
UGameInstanceSubsystem
用法一
-
支持委托
-
支持普通變量和函數
-
支持藍圖調用
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html UCLASS() class DESIGNPATTERNS_API UScoreGameInsSubsystem : public UGameInstanceSubsystem { GENERATED_BODY() public: // 是否允許被創建 virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; } // 初始化 virtual void Initialize(FSubsystemCollectionBase& Collection) override; // 釋放 virtual void Deinitialize() override; // 聲明委托 DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FScoreChanged, int32, CurrentScore); UPROPERTY(BlueprintAssignable) FScoreChanged ScoreChange; UFUNCTION(BlueprintCallable, Category = "MySubsystem | ScoreGameInsSubsystem") int32 AddScore(int32 BaseScore); private: int32 Score; };
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html void UScoreGameInsSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"")); } void UScoreGameInsSubsystem::Deinitialize() { Super::Deinitialize(); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"")); } int32 UScoreGameInsSubsystem::AddScore(int32 BaseScore) { Score = UKismetMathLibrary::Max(0, Score + BaseScore); // 調用委托 ScoreChange.Broadcast(Score); return Score; }
用法二
-
支持創建抽象類,多個派生類,支持藍圖繼承,支持遍歷訪問
- 注意UCLASS(Type)
- 父類為 Abstract抽象類,防止實例化
- Blueprintable 藍圖繼承
- BlueprintType 可定義藍圖變量
- 注意每種類型的 Subsystem 只能創建一個實例
- 注意UCLASS(Type)
-
C++訪問
- 訪問單個
GetWorld()->GetGameInstance()->GetSubsystem<T>()
- 訪問多個
GetWorld()->GetGameInstance()->GetSubsystemArray<T>()
- 訪問單個
-
使用示例
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html /** * 抽象類 USourceControlSubsystem */ UCLASS(Abstract, Blueprintable, BlueprintType) class DESIGNPATTERNS_API USourceControlSubsystem : public UGameInstanceSubsystem { GENERATED_BODY() public: // ShouldCreateSubsystem 默認返回 True // 可重載 UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "MySubsystem | SourceControl") FString GetPlatformName(); }; /** * 派生類 UGitSubsystem */ UCLASS() class DESIGNPATTERNS_API UGitSubsystem : public USourceControlSubsystem { GENERATED_BODY() public: // 初始化 virtual void Initialize(FSubsystemCollectionBase& Collection) override { Super::Initialize(Collection); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"),*GetName()); } // 釋放 virtual void Deinitialize() override { Super::Deinitialize(); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"),*GetName()); } virtual FString GetPlatformName_Implementation()override { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【Git】")); return TEXT("Git"); } void Help() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Help Command")); } }; /** * 派生類 USVNSubsystem */ UCLASS() class DESIGNPATTERNS_API USVNSubsystem : public USourceControlSubsystem { GENERATED_BODY() public: // 初始化 virtual void Initialize(FSubsystemCollectionBase& Collection) override { Super::Initialize(Collection); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"), *GetName()); } // 釋放 virtual void Deinitialize() override { Super::Deinitialize(); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"), *GetName()); } virtual FString GetPlatformName_Implementation()override { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【SVN】")); return TEXT("SVN"); } }; /** * 用於測試 ASourceControlActor */ UCLASS(Blueprintable, BlueprintType) class DESIGNPATTERNS_API ASourceControlActor : public AActor { GENERATED_BODY() public: ASourceControlActor(){} virtual void BeginPlay()override { Super::BeginPlay(); // 獲取單個 Subsystem UGitSubsystem* GitSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UGitSubsystem>(); GitSubsystem->Help(); // 獲取多個 Subsystem,繼承自同個抽象類 const TArray<USourceControlSubsystem*> SourceControlSubsystems = GetWorld()->GetGameInstance()->GetSubsystemArray<USourceControlSubsystem>(); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"********************* 遍歷USourceControlSubsystem ********************")); for (USourceControlSubsystem* ItemSubSystem : SourceControlSubsystems) { ItemSubSystem->GetPlatformName(); } } };
-
藍圖調用
其他用法
-
支持Tick, 需繼承 FTickableGameObject,參考 UAutoDestroySubsystem 寫法
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html UCLASS() class DESIGNPATTERNS_API UScoreGameInsSubsystem : public UGameInstanceSubsystem, public FTickableGameObject { GENERATED_BODY() public: virtual void Tick(float DeltaTime)override{ UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"Ping Pong"));} virtual bool IsTickable()const override { return !IsTemplate(); } //判斷是否是 CDO,避免執行兩次 Tick virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UMyScoreSubsystem, STATGROUP_Tickables); } };
-
支持 Subsystem 之間的訪問
-
支持多個Subsystem定義依賴順序,再初始化時調用
Collection.InitializeDependency(UScoreGameInsSubsystem::StaticClass());
UWorldSubsystem
-
可以再編輯器 和 運行時 使用
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html UCLASS() class DESIGNPATTERNS_API UMyWorldSubsystem : public UWorldSubsystem { GENERATED_BODY() public: // 是否允許被創建 virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; } // 初始化 virtual void Initialize(FSubsystemCollectionBase& Collection) override; // 釋放 virtual void Deinitialize() override; FString GetWorldType(); };
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html void UMyWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" [World] %-20s\t[Type] %s\t"), *GetWorld()->GetName(), *GetWorldType()); } void UMyWorldSubsystem::Deinitialize() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" [World] %-20s\t[Type] %s\t"), *GetWorld()->GetName(), *GetWorldType()); } FString UMyWorldSubsystem::GetWorldType() { FString WorldTypeName; switch (GetWorld()->WorldType) { case EWorldType::None: {WorldTypeName = TEXT("None"); }break; case EWorldType::Game: {WorldTypeName = TEXT("Game"); }break; case EWorldType::Editor: {WorldTypeName = TEXT("Editor"); }break; case EWorldType::PIE: {WorldTypeName = TEXT("PIE"); }break; case EWorldType::EditorPreview: {WorldTypeName = TEXT("EditorPreview"); }break; case EWorldType::GamePreview: {WorldTypeName = TEXT("GamePreview"); }break; case EWorldType::GameRPC: {WorldTypeName = TEXT("GameRPC"); }break; case EWorldType::Inactive: {WorldTypeName = TEXT("Inactive"); }break; default: WorldTypeName = TEXT("default"); }; return WorldTypeName; }
UEditorSubsystem
-
需要再 .build.cs 添加 EditorSubsystem 模塊
if (Target.bBuildEditor) { PublicDependencyModuleNames.AddRange(new string[] { "EditorSubsystem" }); }
-
需要添加頭文件
-
編譯模式記得選Editor 模式
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14819721.html #include "EditorSubsystem.h" UCLASS() class DESIGNPATTERNS_API UMyEditorSubsystem : public UEditorSubsystem { GENERATED_BODY() public: // 是否允許被創建 virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; } // 初始化 virtual void Initialize(FSubsystemCollectionBase& Collection) override; // 釋放 virtual void Deinitialize() override; };
系統自帶 Subsystem 使用藍圖示例
Asset Editor Subsystem
Asset Tag Subsystem
Editor Utility Subsystem
Layer Subsystem
Import Subsystem
Editor Validator Subsystem
-
檢查、驗證資產
-
從 EditorUtilityBlueprint 中選擇父類 EditorValidatorBase
參考
- 編程子系統
- 【B站】[英文直播]Programming Subsystems(真實字幕組)
- 《InsideUE4》GamePlay架構(十一)Subsystems
- 【PPT】貓でも分かるUE4.22から入ったSubsystem
- Does 'Data Validation' not work for Blueprints?
- 原博客地址 https://www.cnblogs.com/shiroe/p/14819721.html
附錄
-
SubsystemCollection.cpp
- FSubsystemCollectionBase::Initialize()
- FSubsystemCollectionBase::AddAndInitializeSubsystem()
- FSubsystemCollectionBase::Deinitialize()
void FSubsystemCollectionBase::Initialize(UObject* NewOuter) { if (Outer != nullptr) { // already initialized return; } Outer = NewOuter; check(Outer); if (ensure(BaseType) && ensureMsgf(SubsystemMap.Num() == 0, TEXT("Currently don't support repopulation of Subsystem Collections."))) { check(!bPopulating); //Populating collections on multiple threads? if (SubsystemCollections.Num() == 0) { FSubsystemModuleWatcher::InitializeModuleWatcher(); } TGuardValue<bool> PopulatingGuard(bPopulating, true); if (BaseType->IsChildOf(UDynamicSubsystem::StaticClass())) // 判斷是否是 UDynamicSubsystem 的子類 { for (const TPair<FName, TArray<TSubclassOf<UDynamicSubsystem>>>& SubsystemClasses : DynamicSystemModuleMap) { for (const TSubclassOf<UDynamicSubsystem>& SubsystemClass : SubsystemClasses.Value) { if (SubsystemClass->IsChildOf(BaseType)) { AddAndInitializeSubsystem(SubsystemClass); } } } } else // 不是 UDynamicSubsystem 的子類 { TArray<UClass*> SubsystemClasses; GetDerivedClasses(BaseType, SubsystemClasses, true); for (UClass* SubsystemClass : SubsystemClasses) { AddAndInitializeSubsystem(SubsystemClass); } } // Statically track collections SubsystemCollections.Add(this); } } void FSubsystemCollectionBase::Deinitialize() { // Remove static tracking SubsystemCollections.Remove(this); if (SubsystemCollections.Num() == 0) { FSubsystemModuleWatcher::DeinitializeModuleWatcher(); } // Deinit and clean up existing systems SubsystemArrayMap.Empty(); for (auto Iter = SubsystemMap.CreateIterator(); Iter; ++Iter) // 遍歷 SubsystemMap { UClass* KeyClass = Iter.Key(); USubsystem* Subsystem = Iter.Value(); if (Subsystem->GetClass() == KeyClass) { Subsystem->Deinitialize(); // 清理、釋放 Subsystem->InternalOwningSubsystem = nullptr; } } SubsystemMap.Empty(); Outer = nullptr; } USubsystem* FSubsystemCollectionBase::AddAndInitializeSubsystem(UClass* SubsystemClass) { if (!SubsystemMap.Contains(SubsystemClass)) { // Only add instances for non abstract Subsystems if (SubsystemClass && !SubsystemClass->HasAllClassFlags(CLASS_Abstract)) { // Catch any attempt to add a subsystem of the wrong type checkf(SubsystemClass->IsChildOf(BaseType), TEXT("ClassType (%s) must be a subclass of BaseType(%s)."), *SubsystemClass->GetName(), *BaseType->GetName()); // Do not create instances of classes aren't authoritative if (SubsystemClass->GetAuthoritativeClass() != SubsystemClass) { return nullptr; } const USubsystem* CDO = SubsystemClass->GetDefaultObject<USubsystem>(); if (CDO->ShouldCreateSubsystem(Outer)) // 從CDO調用ShouldCreateSubsystem來判斷是否要創建 { USubsystem* Subsystem = NewObject<USubsystem>(Outer, SubsystemClass); //創建 SubsystemMap.Add(SubsystemClass,Subsystem); // 添加到 SubsystemMap Subsystem->InternalOwningSubsystem = this; Subsystem->Initialize(*this); //調用Initialize return Subsystem; } } return nullptr; } return SubsystemMap.FindRef(SubsystemClass); }