【UE4 C++】编程子系统 Subsystem
阅读原文时间:2023年07月12日阅读:3

概述

  • 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)。

  • 更适用的生命周期

    • 引擎只支持一个 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++ 访问

    // 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 访问方法

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

用法一

  • 支持委托

  • 支持普通变量和函数

  • 支持蓝图调用

    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; }; 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 只能创建一个实例

  • C++访问

    • 访问单个 GetWorld()->GetGameInstance()->GetSubsystem<T>()
    • 访问多个 GetWorld()->GetGameInstance()->GetSubsystemArray<T>()
  • 使用示例

    /**
     * 抽象类 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&amp; 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&amp; 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()-&gt;GetGameInstance()-&gt;GetSubsystem&lt;UGitSubsystem&gt;();
        GitSubsystem-&gt;Help();
    
        // 获取多个 Subsystem,继承自同个抽象类
        const TArray&lt;USourceControlSubsystem*&gt; SourceControlSubsystems = GetWorld()-&gt;GetGameInstance()-&gt;GetSubsystemArray&lt;USourceControlSubsystem&gt;();
        UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"********************* 遍历USourceControlSubsystem ********************"));
        for (USourceControlSubsystem* ItemSubSystem : SourceControlSubsystems)
        {
            ItemSubSystem-&gt;GetPlatformName();
        }
    }
    };
  • 蓝图调用

其他用法

  • 支持Tick, 需继承 FTickableGameObject,参考 UAutoDestroySubsystem 写法

    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());


  • 可以再编辑器 和 运行时 使用

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

  • 需要再 .build.cs 添加 EditorSubsystem 模块

    if (Target.bBuildEditor)
    {
        PublicDependencyModuleNames.AddRange(new string[] { "EditorSubsystem" });
    }
  • 需要添加头文件

  • 编译模式记得选Editor 模式

    #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&amp; Collection) override;
    // 释放
    virtual void Deinitialize() override;
    };

Asset Editor Subsystem

Asset Tag Subsystem

Editor Utility Subsystem

Layer Subsystem

Import Subsystem

Editor Validator Subsystem

  • 检查、验证资产

  • 从 EditorUtilityBlueprint 中选择父类 EditorValidatorBase


参考


附录

  • 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&lt;bool&gt; PopulatingGuard(bPopulating, true);
      
      if (BaseType-&gt;IsChildOf(UDynamicSubsystem::StaticClass())) // 判断是否是 UDynamicSubsystem 的子类
      {
          for (const TPair&lt;FName, TArray&lt;TSubclassOf&lt;UDynamicSubsystem&gt;&gt;&gt;&amp; SubsystemClasses : DynamicSystemModuleMap)
          {
              for (const TSubclassOf&lt;UDynamicSubsystem&gt;&amp; SubsystemClass : SubsystemClasses.Value)
              {
                  if (SubsystemClass-&gt;IsChildOf(BaseType))
                  {
                      AddAndInitializeSubsystem(SubsystemClass);
                  }
              }
          }
      }
      else // 不是 UDynamicSubsystem 的子类
      {
          TArray&lt;UClass*&gt; 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-&gt;GetDefaultObject&lt;USubsystem&gt;();
          if (CDO-&gt;ShouldCreateSubsystem(Outer)) // 从CDO调用ShouldCreateSubsystem来判断是否要创建
          {
              USubsystem* Subsystem = NewObject&lt;USubsystem&gt;(Outer, SubsystemClass); //创建
              SubsystemMap.Add(SubsystemClass,Subsystem); // 添加到 SubsystemMap
              Subsystem-&gt;InternalOwningSubsystem = this;
              Subsystem-&gt;Initialize(*this); //调用Initialize
              return Subsystem;
          }
      }
      return nullptr;
      } return SubsystemMap.FindRef(SubsystemClass);

      }

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章