はじめに
※確認UEバージョン:5.0.3
自作したクラスにUEのUSubsystemを継承したサブシステムクラスのインスタンスを載せる方法を解説します。
搭載後は、自作したクラスのヘッダーやソースに手を加えずに新しいサブシステムを載せることができます。
USubsystemとは
サブシステムはUE4.22から追加された機能です。
エンジン側でサポートされているサブシステムとしては、GameInstanceSubsystemやEngineSubsystemなどがあります。
例えば以下のようなGameInstanceSubsystemを継承した新規クラスを作成した場合、GameInstance生成時にUMyGameSubSystemも生成されます。
/**
 * @brief 自作ゲームインスタンスサブシステム
 */
UCLASS()
class UMyGameSubSystem : public UGameInstanceSubsystem {
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable,DisplayName = "My処理")
	void DoAnything();
};UMyGameSubSystemを使用すると、プロジェクトなどで用意したGameInstanceを継承したクラスに#includeせずUGameInstaceとして作成したサブシステムにアクセスできるので、過度なincludeを軽減することができます。
// UGameInstanceからアクセスできる。
const UGameInstance* pGameInstance = GetWorld()->GetGameInstance();
// ※ただし、アクセスするSubSystemクラスヘッダーのincludeは必要です。
UMyGameSubSystem* pGameMySubSystem = pGameInstance->GetSubsystem<UMyGameSubSystem>();
pGameMySubSystem->DoAnything();
USubsystemの仕組み
サブシステムを搭載するクラスには専用のUSubsystemを継承したサブシステム基本クラスが存在します。
サブシステムを搭載するクラスには必ず、FSubsystemCollection<サブシステム基本クラス> をメンバ変数として持たせています。
例えば、エンジン側で実装されているFSubsystemCollectionは以下の箇所で宣言されています。
| クラス | サブシステム基本クラス | 宣言箇所 | 
| UGameInstance | UGameInstanceSubsystem | Engine/Source/Runtime/Engine/Classes/Engine/GameInstance.h (567行目) | 
| UWorld | UWorldSubsystem | Engine/Source/Runtime/Engine/Classes/Engine/World.h (4056行目) | 
| UEngine | UEngineSubsystem | Engine/Source/Runtime/Engine/Classes/Engine/Engine.h (3376行目) | 
FSubsystemCollectionは「サブシステム基本クラス」を継承したクラスの情報を収集して、その情報をもとにサブシステムを生成、破棄するコレクション型です。
FSubsystemCollection 宣言ヘッダー:Engine/Source/Runtime/Engine/Public/Subsystems/SubsystemCollection.h (100行目)
サブシステムを搭載するクラスの初期化時にFSubsystemCollectionBase::Initialize()を呼ぶことで、サブシステムを生成します。
FSubsystemCollectionBase::Initialize()内ではFUObjectHashTablesからクラス継承情報を取得して生成するクラス情報をすべて収集した後、FSubsystemCollectionBase::AddAndInitializeSubsystem()でNewObjectを行い、サブシステムインスタンスを生成します。
またサブシステムを搭載するクラスの終了処理関数内でFSubsystemCollectionBase::Deinitialize()を呼びサブシステムを開放します。
自作したクラスにサブシステムを搭載する手順
以上を踏まえて、サブシステムを自作クラスに搭載していきたいと思います。
今回手順で使用する自作クラスは下記クラスです。
/**
 * @brief サブシステムを搭載するクラス
 */
UCLASS()
class MYTESTPROJECT_API UMyObject : public UObject {
   GENERATED_BODY()
   
};自作クラス専用のサブシステムクラスを作成する
専用サブシステムクラスをヘッダーファイルに宣言します。
/**
 * @brief 専用サブシステム
 */
UCLASS()
class UMyObjectSubsystem : public USubsystem {
   GENERATED_BODY()
};自作クラスにサブシステムコレクションをメンバ変数として持たせる
自作クラスにFSubsystemCollectionを持たせます。
class UMyObjectSubsystem;
/**
 * @brief サブシステムを搭載するクラス
 */
UCLASS()
class MYTESTPROJECT_API UMyObject : public UObject {
   GENERATED_BODY()
   // ↓↓追加
private:
   FSubsystemCollection<UMyObjectSubsystem> SubsystemCollection;
   // ↑↑
};自作クラスに初期化、終了処理を追加する
自作クラスに初期化、終了処理を追加します。
今回はInitialize、Finalize関数を追加します。
ヘッダーにメソッドを宣言します。
/**
 * @brief サブシステムを搭載するクラス
 */
UCLASS()
class MYTESTPROJECT_API UMyObject : public UObject {
   GENERATED_BODY()
private:
   FSubsystemCollection<UMyObjectSubsystem> SubsystemCollection;
public:
// ↓↓追加
   /**
    * @brief 初期化処理
    */
   void Initialize();
   
   /**
    * @brief 終了処理
    */
   void Finalize();
// ↑↑
};初期化処理・終了処理それぞれにSubsystemCollectionの初期化・終了処理を追加します。
void UMyObject::Initialize()
{
   // サブシステム初期化 
   SubsystemCollection.Initialize(this);
}
void UMyObject::Finalize()
{
   // サブシステム終了処理 
   SubsystemCollection.Deinitialize();
}サブシステム取得関数を追加する
外からアクセスできるように、サブシステム取得関数をpublicで宣言します。
/**
 * @brief サブシステムを搭載するクラス
 */
UCLASS()
class MYTESTPROJECT_API UMyObject : public UObject {
   GENERATED_BODY()
private:
   FSubsystemCollection<UMyObjectSubsystem> SubsystemCollection;
public:
   /**
    * @brief 初期化処理
    */
   void Initialize();
   
   /**
    * @brief 終了処理
    */
   void Finalize();
// ↓↓追加
   /**
    * @brief サブシステムを取得
    */
   template <typename TSubsystemClass>
   TSubsystemClass* GetSubsystem() const
   {
      return SubsystemCollection.GetSubsystem<TSubsystemClass>(TSubsystemClass::StaticClass());
   }
// ↑↑
};以上で、自作サブシステムクラスの搭載は完了です。
あとはUMyObjectSubsystemを継承した新規サブシステムを作成することで、新しいサブシステムがUMyObjectで追加されます。
かなり簡単に搭載できますので、継承関係の深いクラスなどで活用いただけると幸いです。
