Unreal Engine で使用する全てのゲームプレイ クラスは、クラス ヘッダ ファイル (.h
) とクラス ソース ファイル (.cpp
) で構成
されています。クラス ソース ファイルは、クラスに属する関数を 実装 することによってクラスの機能を定義するファイルである一方で、
クラス ヘッダ ファイルには変数や関数など、クラスの宣言とそのメンバーが
含まれています。
Unreal Engine のクラスには標準化した命名スキームがあり、クラスの最初の文字 (プレフィックス) を見ただけで、 瞬時にクラスの種類が判断できます。以下はゲームプレイ クラスのプレフィックスです。
プレフィックス |
意味 |
---|---|
|
スポーン可能 なゲームプレイ オブジェクトの基底クラスからの拡張です。これらはアクタで、ワールド内へ直接スポーンすることができます。 |
|
全ゲームプレイ オブジェクトの基底クラスからの拡張です。このクラスはワールド内へ直接インスタンス化することはできません。アクタに属さなければいけません。通常は コンポーネント のようなオブジェクトです。 |
クラスの追加
C++ クラス ウィザード は、新規クラスに必要なヘッダ ファイルとソース ファイルを設定し、それに応じてゲーム モジュールを更新します。 ヘッダ ファイルとソース ファイルには、 UCLASS() マクロのようなアンリアル エンジン固有のコードに加えて、クラス宣言とクラス コンストラクタを自動的にインクルードします。
クラスヘッダ
Unreal Engine のゲームプレイクラスにはそれぞれ、固有のクラス ヘッダ ファイルがあります。ファイルには、その中で定義されているクラスと一致する名前を付けなければなりません。
A
または U
のプレフィックスを差し引いて「.h
」ファイル拡張子を使用します。例えば AActor
クラスのクラス ヘッダ ファイルの名前は Actor.h
となります。
エピック ゲームズのコードはこうしたガイドラインに従いますが、現在のエンジンではクラス名とソースファイル名との間には正式な関係は存在しません。
ゲーム プレイ クラスのクラス ヘッダ ファイルは、クラス、変数、関数の宣言プロセスを単純化するために、特殊マクロと標準の C++ 記法を併用します。
各ゲームプレイ クラスのヘッダ ファイル最上部には、以下のようにクラス用に (自動) 生成されたヘッダ ファイルをインクルードしなくてはいけません。従って ClassName.h
の上には次の行が表示されます。
#include "ClassName.generated.h"
クラスの宣言
クラスの宣言はクラス名、継承元のクラス、継承した関数や変数があれば定義します。クラスの宣言では、クラス指定子 やメタデータ経由で望まれる 他のエンジンやエディタ固有の動作も定義します。
クラスの宣言の記法は以下の通りです。
UCLASS([specifier, specifier, ...], [meta(key=value, key=value, ...)])
class ClassName : public ParentName
{
GENERATED_BODY()
}
宣言は、クラスのための標準の C++ クラス宣言で構成されています。標準の宣言の上で、クラス指定子やメタデータなどの記述子が UCLASS マクロへ
渡されます。これらは宣言されているクラスに対して UClass
を作成するために使用され、エンジンの特別なクラス表現とみなされます。また、GENERATED_BODY()
マクロを
マクロはクラス本体の冒頭に配置しなくてはいけません。
クラス指定子
クラスを宣言するときに、エンジンやエディタの様々な側面でクラスがどのように動作するかを制御する クラス指定子 を宣言に追加することができます。
クラス指定子 |
効果 |
---|---|
|
Abstract 指定子によって、このクラスを「抽象基本クラス」として宣言し、このクラスのアクタがレベルに追加されないようにします。これは、それ自体では意味をもたないクラスで役立ちます。例えば、 |
|
AdvancedClassDisplay 指定子は、クラスのすべてのプロパティを、[Details (詳細)] パネルの [詳細セクション] でのみ見えるようにします。個々のプロパティでこれをオーバーライドするには、プロパティで |
|
AutoCollapseCategories 指定子は、親クラスの AutoExpandCategories 指定子のリストされているカテゴリの効果を無効にします。 |
|
このクラスのオブジェクトに対し、アンリアル エディタのプロパティ ウィンドウで自動的に展開するカテゴリを 1 つ以上指定します。カテゴリなしで宣言された変数を自動展開するには、変数を宣言するクラス名を使用します。 |
|
ブループリントの作成に使用できる基本クラスとしてこのクラスを公開します。他のものを継承していない場合、初期設定は |
BlueprintType |
ブループリントの変数に使用できるタイプとしてクラスを公開します。 |
|
アクタのブラウザで Group View が有効な場合、指定された GroupName` 内でクラスとサブクラスを含みます。 |
|
このクラスのプロパティは、アンリアル エディタのプロパティ ウィンドウのカテゴリでグループ化されません。この指定子は子クラスに受け継がれますが、 |
|
このクラスがコンフィギュレーション ファイル ( |
|
このクラスの全プロパティと関数は |
|
ルートの変換は、サブクラスの変換を階層を上に進み、最初のルートクラスの子クラスのみに制限します。 |
|
コンストラクタ宣言の自動生成を防ぎます。 |
|
このクラスの全インスタンスは「インスタンス化された」とみなされます。インスタンス化されたクラス (コンポーネント) は構成時に複製されます。この指定子はサブクラスで継承されます。 |
|
リストされている全クラスは、このクラスより前にコンパイルされます。クラス名は同じ (あるいは以前の) パッケージのクラスを指定しなければなりません。1 行の |
|
このクラスは非推奨とし、クラスのオブジェクトはシリアル化時に保存されません。この指定子はサブクラスで継承されます。 |
|
親クラスから継承したリスト化されたカテゴリの |
|
基本クラスから継承した |
|
このクラスのオブジェクトは、既存のアセットから参照されるのではなく、アンリアル エディタのプロパティ ウィンドウから作成できることを示しています。デフォルトの挙動は、既存のオブジェクトのみの参照をプロパティ ウィンドウで割り当てることができます。この指定子はすべての子クラスに受け継がれますが、子クラスは |
|
ユーザー エンティティから非表示にするカテゴリを 1 つ以上リスト化します。カテゴリなしで宣言されたプロパティを非表示にするには、変数を宣言するクラス名を使います。この指定子は子クラスに受け継がれます。 |
|
プロパティ ウィンドウのコンボ ボックスにこのクラスが表示されないようにします。 |
|
指定されたカテゴリのすべての関数をユーザー エンティティから非表示にします。 |
|
名前付き関数をユーザー エンティティで非表示にします。 |
|
クラスは直接 C++ で宣言され、 UnrealHeaderTool はボイラープレートを生成しないことを示します。新規のクラスにこの指定子を使用しないでください。 |
|
他のモジュールで使用するために、クラスの型情報のみエクスポートさせます。クラスはキャスト可能ですが、クラスの関数を呼び出すことはできません (インライン メソッドは除く)。これにより、他のモジュールからすべての関数にアクセス可能である必要のないクラスで何もかもをエクスポートしないことでコンパイル時間を短縮できます。 |
|
このクラスの宣言は、ヘッダ ジェネレータで自動生成された C++ ヘッダファイルに含まれてはいけないことを示します。C++ クラス宣言は、別のヘッダファイルで手書きのコードによって定義されなければなりません。ネイティブ クラスに対してのみ有効です。新規のクラスにこの指定子を使用しないでください。 |
|
基本クラスから継承した |
|
ブループリントの作成に使用できない基本クラスに指定します。これはデフォルトであり、サブクラスで継承されます。 |
|
基本クラスから継承した |
|
このクラスのコンフィギュレーション情報はオブジェクト毎に保存されます。この場合、各オブジェクトは |
|
アンリアル エディタで作成可能であり、レベル、 UI シーン、ブループリント (クラスのタイプにより異なります) で作成置可能なクラスであることを示します。このフラグは子クラスに受け継がれますが、子クラスは |
|
リストされているカテゴリの |
|
リストされているカテゴリ内の全関数をプロパティ ビューアに表示します。 |
|
名前付けした関数をプロパティ ビューアに表示します。 |
|
このクラスに属するオブジェクトがディスクに保存されることはありません。プレイヤーやウィンドウなど、本質的に非永続的な、一定のネイティブ クラスと併用する場合に役立ちます。この指定子は子クラスに受け継がれますが、 |
|
このクラスのオブジェクトは |
メタデータ指定子
クラスの実装
すべてのゲームプレイ クラスは、適切に実装するために GENERATED_BODY
マクロを使用しなければなりません。これは、クラスとそのすべての変数と関数を定義するクラスヘッダ (.h) ファイルで
行われます。クラス ソース ファイルとヘッダー ファイルのベスト プラクティスは、A
または U
のプレフィックスを取り除いて、実行中のクラスに一致する名前をつけることです。従って、AActor
クラスのソース ファイルは
Actor.cpp
という名前になります。そのヘッダ ファイルは Actor.h
という名前になります。これは、エディタ内の "Add C++ Class" メニュー オプションによって作成されるクラスを自動的に処理します。
このソース ファイル (.cpp) には C++ クラス宣言を含むヘッダ ファイル (.h) がインクルードされていなければなりません。これは通常自動生成されますが、必要に応じて手書きのコードでも作成可能です。例えば、AActor
クラスに対する C++ 宣言は
EngineClasses.h
ヘッダ ファイルで自動生成されます。つまり Actor.cpp
ファイルには、EngineClasses.h
ファイルかこのファイルをインクルードする
別のファイルがインクルードされていなければなりません。一般的には、ゲーム プロジェクト用のヘッダ ファイルをインクルードし、それがゲーム プロジェクトのゲームプレイ クラスのヘッダをインクルードします。AActor
と EngineClasses.h
の場合、
Engine プロジェクト用のヘッダ ファイルである Engine.h をインクルードする EnginePrivate.h
ヘッダがインクルードされ、それが `Engine.h`ヘッダ ファイルをインクルードします。
#include "EnginePrivate.h"
インクルードされていないクラスの関数の実装で他のクラスを参照する場合、ファイルを追加でインクルードする必要があるかもしれません。単にそのファイルをインクルードして追加します。
クラス コンストラクタ
UObjects
は コンストラクタ を使ってプロパティおよびその他の変数のデフォルト値の設定や、その他必要な初期化を実行します。クラス コンストラクタは通常、クラス実装ファイル内に置かれます。
例えば、AActor::AActor
コンストラクタであれば、 Actor.cpp
の中です。
一部のコンストラクタは、モジュール別に特別な「コンストラクタ」ファイルに置かれる場合もあります。例えば、AActor::AActor
コンストラクタは EngineConstructors.cpp
内に存在します。これは
以前に、コンストラクタの採用に DEFAULTS
ブロックを使用した自動変換プロセスの結果です。これらは時間と共にそれぞれのクラス ソース ファイルに移動していきます。
コンストラクタ インラインをクラス ヘッダ ファイルに配置することも可能です。ただし、コンストラクタがクラス ヘッダ ファイル内にある場合、
自動コードジェネレータがヘッダにコンストラクタ宣言を生成することを防ぐために、 UClass は CustomConstructor
指定子で宣言されなければなりません。
コンストラクタの形式
以下が、 UObject コンストラクタの最も基本的な形式です。
UMyObject::UMyObject()
{
// Initialize Class Default Object properties here.
}
このコンストラクタが Class Default Object (CDO) を初期化し、クラスのその後のインスタンスがベースとするマスター コピーになります。特別なプロパティ変更構造体をサポートする セカンダリ コンストラクタもあります。
UMyObject::UMyObject(const FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer)
{
// Initialize CDO properties here.
}
上記のコンストラクタは両方とも実際には初期化は実行しませんが、エンジンがすべてのフィールドをゼロ、NULL、またはデフォルト コンストラクタが実装する値にしているはずです。
ただし、コンストラクタに書き込まれた初期化コードは CDO に適用されるので、
CreateNewObject
や SpawnActor
のように、エンジン内で正しく作成されたオブジェクトの新規インスタンスにコピーされます。
コンストラクタに渡される FObjectInitializer
パラメータは、const とマークされていますが、ビルトインの「可変」 (mutable) 関数経由で設定してプロパティとサブオブジェクトをオーバーライドすることができます。作成されている UObject
にこれらの値が反映されるので、
それを使って登録されたプロパティまたはコンポーネントの値を変更することができます。
AUDKEmitterPool::AUDKEmitterPool(const FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer.DoNotCreateDefaultSubobject(TEXT("SomeComponent")).DoNotCreateDefaultSubobject(TEXT("SomeOtherComponent")))
{
// Initialize CDO properties here.
}
上記のケースでは、スーパークラスは "SomeComponent" および "SomeOtherComponent" という名前のサブオブジェクトをコンストラクタ内に作成しようとしますが、FObjectInitializer があるので作成はしません。
次のケースでは、SomeProperty
が CDO でデフォルトで 26 になるので、AUTDemoHUD の各インスタンスが新しくなります。
AUTDemoHUD::AUTDemoHUD()
{
// Initialize CDO properties here.
SomeProperty = 26;
}
Constructor Statics と Helpers
さらに複雑なデータ型、特にクラスの参照、名前、アセットの参照のために値を設定する場合、
必要な様々なプロパティ値を保持するためにコンストラクタ内で ConstructorStatics 構造体を定義およびインスタンス化することが求められます。ConstructorStatics
構造体は、コンストラクタの初回実行時のみ作成されます。次の実行時にはポインタをコピーするだけなので、
処理速度が超高速になります。ConstructorStatics
構造体が作成されると、後にコンストラクタで実際のプロパティに値を代入する時に、
アクセス手段として構造体のメンバに値が代入されます。
ContructorHelpers は、コンストラクタ特有の共通のアクションを実行するために使用するヘルパー テンプレートを含む ObjectBase.h
で定義される特別な名前空間です。例えばヘルパー テンプレートには、
アセットやクラスへの参照を検索するためだけでなく、コンポーネントを作成したり検索するものもあります。
アセットの参照
クラスにアセットの参照が存在しないことが理想的です。ハードコード化されたアセットの参照は壊れやすいので、アセット プロパティの設定にはブループリントの使用が推奨されてきました。しかし、 ハードコード化された参照は完全にサポートされています。オブジェクトを構築するたびにアセットの検索を行いたくないため、検索は 1 度だけ行います。静的な構造体を使用して、アセット検索は必ず 1 回だけ 実行するようにできます。
ConstructorHelpers::FObjectFinder
は StaticLoadObject
を使って指定された UObject
への参照を検索します。一般的にこれはコンテンツ パッケージに格納されているアセットの参照に使用します。オブジェクトが見つからない場合、
失敗したことが報告されます。
ATimelineTestActor::ATimelineTestActor()
{
// Structure to hold one-time initialization
struct FConstructorStatics
{
ConstructorHelpers::FObjectFinder<UStaticMesh> Object0;
FConstructorStatics()
:Object0(TEXT("StaticMesh'/Game/UT3/Pickups/Pickups/Health_Large/Mesh/S_Pickups_Base_Health_Large.S_Pickups_Base_Health_Large'"))
{
}
};
static FConstructorStatics ConstructorStatics;
// Property initialization
StaticMesh = ConstructorStatics.Object0.Object;
}
クラスの参照
ConstructorHelpers::FClassFinder
は指定された UClass
への参照を検索し、見つからない場合は失敗したことを報告します。
APylon::APylon(const class FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer)
{
// Structure to hold one-time initialization
static FClassFinder<UNavigationMeshBase> ClassFinder(TEXT("class'Engine.NavigationMeshBase'"));
if (ClassFinder.Succeeded())
{
NavMeshClass = ClassFinder.Class;
}
else
{
NavMeshClass = nullptr;
}
}
たいていの場合、 USomeClass::StaticClass()
を使って ClassFinder の複雑さをまとめてスキップすることができます。例えば、ほとんどの状況で次のメソッドを利用できます。
NavMeshClass = UNavigationMeshBase::StaticClass();
モジュール間の参照の場合は、ClassFinder メソッドを使用した方が良いかもしれません。
コンポーネントとサブ オブジェクト
コンポーネント サブオブジェクトの作成およびアクタの階層へのアタッチも、コンストラクタ内で行うことができます。アクタのスポーン時に、そのコンポーネントは CDO からクローンされます。コンポーネントが毎回正しく作成、破棄、ガーベジコレクション処理されるように、 コンストラクタで作成された各コンポーネントへのポインタは、所有クラスの UPROPERTY に格納されます。
UCLASS()
class AWindPointSource : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
UWindPointSourceComponent* WindPointSource;
UPROPERTY()
UDrawSphereComponent* DisplaySphere;
};
AWindPointSource::AWindPointSource()
{
// Create a new component and give it a name.
WindPointSource = CreateDefaultSubobject<UWindPointSourceComponent>(TEXT("WindPointSourceComponent0"));
// Set our new component as the RootComponent of this actor, or attach it to the root if one already exists.
if (RootComponent == nullptr)
{
RootComponent = WindPointSource;
}
else
{
WindPointSource->AttachTo(RootComponent);
}
// Create a second component.This will be attached to the component we just created.
DisplaySphere = CreateDefaultSubobject<UDrawSphereComponent>(TEXT("DrawSphereComponent0"));
DisplaySphere->AttachTo(RootComponent);
// Set some properties on the new component.
DisplaySphere->ShapeColor.R = 173;
DisplaySphere->ShapeColor.G = 239;
DisplaySphere->ShapeColor.B = 231;
DisplaySphere->ShapeColor.A = 255;
DisplaySphere->AlwaysLoadOnClient = false;
DisplaySphere->AlwaysLoadOnServer = false;
DisplaySphere->bAbsoluteScale = true;
}
親クラスに属するコンポーネントの修正は通常必要ありません。ただし、親クラスによって作成されたコンポーネントを含め、アタッチされたすべてのコンポーネントの現在のリストが、
ルート コンポーネントを含め、USceneComponent
の GetAttachParent
、GetParentComponents
、GetNumChildrenComponents
、GetChildrenComponents
、GetChildComponent
を
呼び出すことで利用することができます。