UE编程子系统 Subsystem

编程子系统 Subsystem

content

概述
定义

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

参考

编程子系统
【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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值