【UE4 C++】編程子系統 Subsystem


概述

定義

  • Subsystems 是一套可以定義、自動實例化和釋放的類的框架。可以將其理解為 GamePlay 級別的 Component
  • 不支持網絡賦值
  • 4.22開始引入,4.24完善。(可以移植源碼到更早之前的版本使用。源碼在手,為所欲為)

五類 Subsystems 及其生命周期

image

  • 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
      • 更模塊化,代碼顯得優雅
      • 解耦性高,易於維護、分工協作;易於移植復用
      • 模塊間的數據訪問具有更好的封裝性
  • 更友好的訪問接口
    • 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;
    }
    

    image

用法二

  • 支持創建抽象類,多個派生類,支持藍圖繼承,支持遍歷訪問

    • 注意UCLASS(Type)
      • 父類為 Abstract抽象類,防止實例化
      • Blueprintable 藍圖繼承
      • BlueprintType 可定義藍圖變量
    • 注意每種類型的 Subsystem 只能創建一個實例
  • 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();
    		}
    	}
    };
    
  • 藍圖調用

    image

    image

其他用法

  • 支持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;
    }
    

    image

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;
    
    };
    

    image

系統自帶 Subsystem 使用藍圖示例

Asset Editor Subsystem

image

Asset Tag Subsystem

image

Editor Utility Subsystem

image

Layer Subsystem

image

Import Subsystem

image

Editor Validator Subsystem

  • 檢查、驗證資產

  • 從 EditorUtilityBlueprint 中選擇父類 EditorValidatorBase

    image

    image

    image


參考


附錄

  • 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);
    }
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM