Unreal Engine (UE) のサブシステムは、管理されたライフタイムを持つ自動的にインスタンス化されたクラスです。このクラスを利用すると、拡張ポイントを簡単に使用できるため、プログラマはエンジン クラスの変更または上書きするといった複雑な作業を回避しつつ、ブループリントや Python を公開できます。
現在サポートされているサブシステムのライフタイムは以下になります。
サブシステム | 継承元 |
---|---|
Engine | UEngineSubsystem クラス |
Editor | UEditorSubsystem クラス |
GameInstance | UGameInstanceSubsystem クラス |
LocalPlayer | ULocalPlayerSubsystem クラス |
例えば、次の基本クラスから派生するクラスを作成する場合、
class UMyGamesSubsystem : public UGameInstanceSubsystem
結果は次のようになります。
UGameInstance
が作成された後、UMyGamesSubsystem
というインスタンスも作成されます。UGameInstance
が初期化されるとき、サブシステムでInitialize()
が呼び出されます。UGameInstance
が終了するとき、サブシステムでDeinitialize()
が呼び出されます。- この時点で、サブシステムへの参照は削除されます。サブシステムへの参照がなくなると、サブシステムはガベージ コレクションされます。
サブシステムを使用する理由
プログラミング サブシステムを使用する理由は、以下をはじめ、いくつかあります。
- プログラミング時間が短縮される。
- エンジン クラスのオーバーライド回避に役立つ。
- ただでさえ入り組んだクラスにさらに API を追加しなくてすむ。
- ユーザー フレンドリなタイプのノードを通じてブループリントにアクセスできる。
- エディタ スクリプティングやテスト コードの記述のために Python スクリプトにアクセスできる。
- コードベースのモジュール性と一貫性を実現する。
サブシステムは、プラグインを作成する際に特に役立ちます。プラグインを機能させるのに必要なコードに関する指示を用意する必要がないからです。ユーザーはプラグインをゲームに追加するだけでよく、デベロッパーはプラグインがインスタンス化され、初期化されるタイミングが正確にわかります。その結果、デベロッパーは API と UE4 で提供されている機能の使用方法に注力できます。
ブループリントでサブシステムにアクセスする
サブシステムは、コンテキストを理解し、キャストを必要としないスマート ノードによって、自動的にブループリントに公開されます。標準の UFUNCTION()
マークアップとルールを使用して、ブループリントで使用可能な API を管理しています。
ブループリント グラフで右クリックしてコンテキスト メニューを表示し、「subsystems」を検索すると、以下の画像のような表示になります。主要なタイプそれぞれにカテゴリがあり、具体的なサブシステムそれぞれに個別のエントリがあります。
上記からノードを追加すると、次のような結果が得られます。
Python でサブシステムにアクセスする
Python を使用している場合、ビルトインのアクセサーを使用して、以下の例のようにサブシステムにアクセスできます。
my_engine_subsystem = unreal.get_engine_subsystem(unreal.MyEngineSubsystem)
my_editor_subsystem = unreal.get_editor_subsystem(unreal.MyEditorSubsystem)
Python は現在実験的な機能です。
サブシステムのライフタイムの詳細
Engine サブシステム
class UMyEngineSubsystem : public UEngineSubsystem { ... };
Engine サブシステムのモジュールがロードされると、サブシステムはモジュールの Startup()
関数から戻った後に Initialize()
を実行します。
また、モジュールの Shutdown()
関数から戻った後に Deinitialize()
を実行します。
UMyEngineSubsystem* MySubsystem = GEngine->GetEngineSubsystem<UMyEngineSubsystem>();
Editor サブシステム
class UMyEditorSubsystem : public UEditorSubsystem { ... };
Editor サブシステムのモジュールがロードされると、サブシステムはモジュールの Startup()
関数から戻った後に Initialize()
を実行します。
また、モジュールの Shutdown()
関数から戻った後に Deinitialize()
を実行します。
UMyEditorSubsystem* MySubsystem = GEditor->GetEditorSubsystem<UMyEditorSubsystem>();
GameInstance Subsystems
class UMyGameSubsystem : public UGameInstanceSubsystem { ... };
これらのサブシステムは、以下のように UGameInstance を通してアクセスされます。
UGameInstance* GameInstance = ...;
UMyGameSubsystem* MySubsystem = GameInstance->GetSubsystem<UMyGameSubsystem>();
LocalPlayer サブシステム
class UMyPlayerSubsystem : public ULocalPlayerSubsystem { ... };
これらのサブシステムは、以下のように ULocalPlayer を通してアクセスされます。
ULocalPlayer* LocalPlayer = ...;
UMyPlayerSubsystem * MySubsystem = LocalPlayer->GetSubsystem<UMyPlayerSubsystem>();
サブシステムの例
次の例では、収集したリソースの数を追跡する統計システムをゲームに追加します。
UGameInstance
から派生させて UMyGamesGameInstance
を作成した後、それに IncrementResourceStat()
関数を追加します。しかし、ゆくゆくは他の統計や、統計の集計、統計の保存/読み込み機能などを追加した方が良いでしょう。そのため、UMyGamesStatsSubsystem
など、1 つのクラスにそのすべてを配置することにします。
再び UMyGamesGameInstance
を作成して、UMyGamesStatsSubsystem
型のメンバーを追加します。その後、そこにアクセサーを追加して、Initialize 関数と Deinitialize 関数をフックします。しかし、これにはいくつかの問題があります。
UGameInstance
のゲーム固有の派生物がない。UMyGamesGameInstance
は存在するが、既に大量の関数があり、そこにさらに追加するのは最適性に欠ける。
十分に複雑なゲームで UGameInstance
から派生させるのが妥当な理由は多数あります。しかし、サブシステムがある場合、それを使う必要はありません。なんと言っても、サブシステムを使用すると、他の方法よりも必要なコーディングが少なくなります。
そのため、最終的に使用するコードを以下の例に示します。
UCLASS()
class UMyGamesStatsSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// USubsystem を開始する
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// USubsystem を終了する
void IncrementResourceStat();
private:
// All my variables
};