Unreal Engine にはゲーム オブジェクトを扱うための堅牢なシステムがあります。Unreal でのオブジェクトの基本クラスは UObject
です。UCLASS
マクロを使用して、UObject
から派生したクラスにタグ付けすると、UObject を扱うシステムが
その派生クラスを認識できるようになります。
UCLASS マクロ
UCLASS マクロは、UObject
にその Unreal ベースの型を記述する UCLASS
への参照を与えます。各 UCLASS
では、クラス デフォルト オブジェクト (CDO) と呼ばれる 1 つのオブジェクトが維持されます。CDO は実質的には、クラス コンストラクタによって生成されてそれ以降は変更されない、デフォルトの「テンプレート」オブジェクトです。UCLASS と CDO はどちらも特定のオブジェクト インスタンスに対して取得できますが、通常は読み取り専用とみなされます。
オブジェクト インスタンスに対する UCLASS には、GetClass()
関数を使用していつでもアクセスできます。
UCLASS
には、そのクラスを規定する一連のプロパティと関数が入っています。それらは標準的な C++ コードで使用できる通常の C++ 関数と変数ですが、オブジェクト システム内でどのように動作するかを制御する、Unreal Engine 特有のメタデータでタグ付けされています。
タグ付けの構文の詳細については、「プログラミング リファレンス」を参照してください。
UObject クラスには、UFUNCTION または UPROPERTY 指定子でリフレクション用にマークされていない、ネイティブのみのプロパティを含めることができます。ただし、対応する UCLASS 内でリストに記載されるのは、指定子 マクロでマークされている関数とプロパティのみです。
プロパティと関数の型
UObject には、任意の型のメンバー変数 (プロパティと呼ばれる) または関数を含めることができます。ただし、Unreal Engine がそれらの変数を認識および維持するには、それらの変数が特殊なマクロでマークされていて、特定の型規格に適合している必要があります。そのような規格の詳細については、プロパティ および UFunction のリファレンス ページを参照してください。
UObject の作成
UObject ではコンストラクタ引数はサポートされていません。すべての C++ UObject はエンジン起動時に初期化され、エンジンはそれぞれのデフォルトのコンストラクタを呼び出します。デフォルトのコンストラクタがなければ、その UObject はコンパイルされません。
UObject のコンストラクタは軽量であり、デフォルト値とサブオブジェクトを設定するためにのみ使用され、作成時にはその他の機能は呼び出されないようにすべきです。Actor および ActorComponent の場合、初期化機能はその代わりに BeginPlay()
メソッドに置かれるべきです。
UObjects の作成は、ランタイム時の NewObject メソッド、またはコンストラクタでの CreateDefaultSubobject メソッドでのみ行われるべきです。
メソッド | 説明 |
---|---|
NewObject<class> |
使用可能なすべての作成オプションに対してオプションのパラメータを指定して新規インスタンスを作成します。自動生成される名前を使用した単純なユースケースなど、さまざまな柔軟性が提供されます。 |
CreateDefaultSubobject<class> |
子クラスを作成するメソッドや親クラスを返すメソッドを提供する、コンポーネントまたはサブオブジェクトを作成します。 |
UObject では new
演算子を使用すべきではありません。すべての UObject のメモリは Unreal Engine によって管理されていて、ガベージ コレクションが行われます。new または delete 演算子を使用してメモリを手動で管理すると、メモリ内のデータが破損することがあります。
UObject によって提供される機能
このシステムを使用するのは必須ではなく、使用するのが適切ではない場合もありますが、使用することで次のような多くのメリットがあります。
- ガベージ コレクション
- 参照の更新
- リフレクション
- シリアル化
- デフォルト プロパティの変更の自動更新
- プロパティの自動初期化
- エディタの自動統合
- ランタイム時に使用可能な型情報
- ネットワークのレプリケーション
上記のメリットのほとんどは、UObject と同じリフレクションとシリアル化の機能がある UStruct にも当てはまります。UStruct は値の型と見なされているので、ガベージ コレクションは行われません。これらの各システムの詳細については、「Unreal でのオブジェクト処理」のドキュメントを参照してください。
Unreal Header Tool
UObject の派生型によって提供される機能を利用するには、必要となる情報を収集するために、それらの型のヘッダ ファイルで前処理手順を実行する必要があります。 この前処理手順は UnrealHeaderTool (略して「UHT」) によって行われます。UObject の派生型には、順守すべき特定の構造があります。
ヘッダ ファイルの形式
ソース (.cpp) ファイルでの UObject の実装は他の C++ クラスと同様ですが、Unreal Engine で正常に機能するためには、ヘッダ (.h) ファイルでの定義が特定の基本構造を順守している必要があります。エディタの [New C++ Class (新規 C++ クラス)] コマンドを使用するのが、書式が正しいヘッダ ファイルをセットアップするための最も簡単な方法です。UObject の派生クラスの基本ヘッダ ファイルは次のようになります。この例では、UObject の派生物は UMyObject という名前であり、MyProject というプロジェクトで作成されていると仮定しています。
#pragma once
#include 'Object.h'
#include 'MyObject.generated.h'
UCLASS()
class MYPROJECT_API UMyObject : public UObject
{
GENERATED_BODY()
};
上記の中で Unreal に特有の部分を以下に示します。
#include "MyObject.generated.h"
この行は、このファイル内にある最後の #include
ディレクティブとなるはずです。このヘッダ ファイルで他のクラスについて知っておく必要があれば、このファイルのどこかでそれらのクラスを前方宣言するか、MyObject.generated.h
の前でインクルードすることができます。
UCLASS()
UCLASS
マクロによって、UMyObject
が Unreal Engine で認識されるようになります。このマクロでは、そのクラスでどの機能が有効または無効になっているかを決定する、さまざまな クラス指定子 がサポートされています。
class MYPROJECT_API UMyObject : public UObject
MyProject で UMyObject クラスを他のモジュールに公開する場合は、MYPROJECT_API
を指定する必要があります。これは、ゲーム プロジェクトによってインクルードされ、複数のプロジェクトにわたって移植可能で自己完結型の機能を提供するクラスを意図的に公開するモジュールやプラグインで最も有用です。
GENERATED_BODY()
GENERATED_BODY
マクロは引数を取りませんが、エンジンで必要とされるインフラストラクチャをサポートするようにクラスを設定します。これはすべての UCLASS
と USTRUCT
で必要です。
Unreal Header Tool では最小限のセットの C++ がサポートされています。カスタム #ifdefs
マクロを使用して UCLASS の一部をラップする場合、UHT は WITH_EDITOR
または WITHEDITORONLY_DATA
が含まれていないマクロを無視します。
オブジェクトを更新する
ティックは、Unreal Engine でオブジェクトがどのように更新されるかということです。すべてのアクタにはフレームごとにティックされる能力が備わっていて、更新で必要とされる計算やアクションを実行する手段が提供されます。
アクタと ActorComponent には、そうするように登録されている場合に呼び出されるティック関数がありますが、UObjects
には組み込み更新機能はありません。ティック機能がプロジェクトで必要である場合は、継承クラス指定子を使用して FTickableGameObject
クラスから継承することで、ティック機能を付加できます。
そして、Tick()
関数を実装すると、エンジンによってフレームごとにそれが呼び出されます。
ほとんどのゲーム内オブジェクトは アクタ であるため、フレームごとではなくユーザーが設定した最小間隔でティックできます。
オブジェクトを破棄する
オブジェクトの破棄は、オブジェクトが参照されなくなったときに、ガベージ コレクション システムによって自動的に処理されます。したがって、UPROPERTY
ポインタ、エンジン コンテナ、TStrongObjectPtr
、クラス インスタンスではそれらのオブジェクトへの強い参照を使用してはいけません。
弱いポインタは、参照しているオブジェクトに対してガベージ コレクションが行われるかどうかに影響を及ぼしません。
ガベージ コレクションが実行されたときに、参照されていないオブジェクトが見つかるとメモリから削除されます。また、MarkPendingKill()
関数をオブジェクトに対して直接呼び出すことはできません。この関数は、そのオブジェクトへのすべてのポインタを NULL
に設定し、そのオブジェクトをグローバル検索から取り除きます。そのオブジェクトは次回のガベージ コレクションのパスで完全に削除されます。
スマート ポインタは、UObject で使用されることは想定されていません。
リリース 5.1 での変更点
-
Object->MarkPendingKill()
がObj->MarkAsGarbage()
で置き換えられました。この新しい関数は、古くなったオブジェクトの追従のみを行うようになり、gc.PendingKillEnabled=true
であれば、使用されなくなったオブジェクトへの参照をクリアしません。以前のリリースではクリアしていました。 -
強い参照を使用していると、参照されている UObject は存続し続けます。そのような参照によって UObject が存続し続けないようにするには、それらの参照を、弱いポインタを使用するように変換するか、(パフォーマンスが極めて重要である場合は) プログラマーが手動でクリアする通常のポインタに変換します。
強いポインタを弱いポインタと置き換えて、ゲームプレイ操作中にそれらをいったん逆参照し、ガベージ コレクションがフレーム間でのみ実行されるようにできます。
-
IsValid()
は null であるか使用されていないかどうかをチェックする関数ですが、IsValid の使用箇所のほとんどは、アクタのOnDestroy
イベントが呼び出されたときにそのアクタへのポインタをクリアするなどの適切なプログラミング技法で置き換えることができます。 -
PendingKill()
が無効になっていれば、MarkGarbage()
はそのオブジェクトを破棄する必要があることをオブジェクトのオーナーに通知しますが、そのオブジェクトへのすべての参照が解放されるまで、そのオブジェクト自体はガベージ コレクションの対象になりません。 -
アクタの場合、アクタに対して
Destroy()
が呼び出されて、そのアクタがレベルから取り除かれても、そのアクタへのすべての参照が解放されるまで、そのアクタはガベージ コレクションの対象になりません。 -
ライセンシーにとっての主な違いは、
MarkPendingKill()
関数を使用して高コストのオブジェクトに対してガベージ コレクションを強制することは機能しなくなることです。 -
MarkPendingKill()
によるガベージ コレクションでポインタは自動的にはクリアされなくなるので、nullptr を手動でクリアしていない場合、nullptr の既存のチェックはIsValid()
呼び出しで置き換えるべきです。