UE4の勉強記録

UE4の勉強の記録です。個人用です。

8章15節 新しいアセットタイプを作る

<前文>

f:id:kazuhironagai77:20180225113310p:plain

で勉強しています。

今回は、「8章15節 新しいアセットタイプの作成」を勉強します。

***このブログは、このブログの読者が、教科書(Unreal Engine 4 Scripting with C++)を読んでいる前提で書かれています。この教科書を読まないで、このブログを読んでも理解不可能であると考えられます。ご了承ください。***

<本文>

<目的>

新しいアセットタイプの作成をします。

<方法>

1. UObjectクラスを継承したカスタムアセットを作ります。

f:id:kazuhironagai77:20180408121230p:plain

教科書には、これしか説明がありません。以下に私なりの解釈で、カスタムアセットを作りました。

1.1 まず、前回、紹介した方法で、前回のプロジェクトを別名保存しておきます。要らない作業かもしれませんが、保険です。

f:id:kazuhironagai77:20180408121326p:plain

カスタムアセットとありますが、教科書に表示されているコードを観ると、Uobjectクラスを継承した普通のクラスです。教科書のサンプルコードのプロジェクト名は、UE4COOKBOOKですから。なので、UMyCustomAssetという名前の新しいクラスを作成します。いろいろな方法で作成出来ますが、今回は以下の方法で作成しました。

1.2 UE4エディターを起動させ、Content Brower内で、マウスを右クリックして、New C++ Classを選択します。

f:id:kazuhironagai77:20180408121413p:plain

1.3 Show All Classをチェックし、Objectを選択します。そしてNextを選択します。

f:id:kazuhironagai77:20180408121440p:plain

1.4 名前をMyCustomAssetとします。教科書のサンプルコードのプロジェクト名は、UE4COOKBOOKですから。Packet Publish社のサンプルコードを見てみると、このサンプルコードは、Sourceファイルのエディターフォルダー内に保存されています。なので、PathをSource/UECookBookEditorに変更します。私は、新しいクラスをUE4エディターから作成する場合、通常、publicを選択しますので、今回もそうします。

f:id:kazuhironagai77:20180408121507p:plain

1.5 作られたクラスに、足りないコードを足します。

f:id:kazuhironagai77:20180408121539p:plain

2. UFactoryクラスを基に、FactoryCreateNewをオーバーライドして、UCustomAsssetFactoryという名前のクラスを作成します。

f:id:kazuhironagai77:20180408121613p:plain

f:id:kazuhironagai77:20180408121628p:plain

と教科書にあります。その通りに、作ってみました。クラスは、1で紹介したのと同じ方法で、作りました。足りないコードを作成されたクラスに足すと、以下のように成りました。

f:id:kazuhironagai77:20180408121656p:plain

f:id:kazuhironagai77:20180408121709p:plain

3.コードをコンパイルして、エディターを開いてください。

4.Content Browserを右クリックして、Create Advanced AssetセクションにあるMiscellaneousタブを選択してください。あなたが作った新しいクラスがあるはずです。あなたの新しいカスタムタイプのインスタンスが作成できるはずです。

<結果>

方法の4にあるように、Content Browserを右クリックして、Create Advanced AssetセクションにあるMiscellaneousタブを選択すると、Miscellaneous下に直接、MyCustomAssetが作成されています。

f:id:kazuhironagai77:20180408121758p:plain

すこし、教科書の表記と違いますが、出来たと考えていいと思います。

<考察>

とりあえず、出来ましたが、全く理解していません。まず、教科書のHow it works…を読んでみました。

f:id:kazuhironagai77:20180408121834p:plain

1. 最初のクラスは、実行中のゲーム内に存在する、実際のオブジェクトです。そのオブジェクトは、テクスチャー、データファイル、カーブデータ、あなたが必要とする何にでも成ります

最初のクラスとは、MyCustomAssetクラスの事です。以下にそのコードを示します。

f:id:kazuhironagai77:20180408121958p:plain

UObjectを継承している事、UPROPERTYを使用している事を除けば、普通のC++のクラスですし、その二つを含めても、普通のUE4のクラスです。このクラスがアセットにどうやってなるのでしょうか?

f:id:kazuhironagai77:20180408122039p:plain

2.このレシピの目的のための、最も簡単な例は、名前を記憶するためのFStringプロパティだけを持つアセットです。

MyCustomAssetクラスは、FStringタイプのプロパティ、ColorNameを持ちます。前述の文章に、“そのオブジェクトは、テクスチャー、データファイル、カーブデータ、あなたが必要とする何にでも成ります。”とありましたが、このプロパティにその様に指定するのだと思います。

f:id:kazuhironagai77:20180408122129p:plain

3.このプロパティは、UPORPPERTYであるとマークされています。それ故に、このプロパティは、メモリーに記録されます。さらにEditAnywhereとマークされているので、デファルトオブジェクトとデファルトオブジェクトのインスタンスの両方から編集する事が出来ます。

実際のサンプルコード内で確認すると、FString ColorNameは、UPROPERTYでEditAnywhereと指定されています。8章の7節で、UPROPERTYの書き方について勉強した通りの説明だと思います。

f:id:kazuhironagai77:20180408122218p:plain

4.二番目のクラスはFactoryです。UnrealはFactoryデザインパターンをアセットのインスタンスを作るために使います。

以下に、2番目のクラス、CustomAssetFactoryを示します。

f:id:kazuhironagai77:20180408122316p:plain

f:id:kazuhironagai77:20180408122333p:plain

UFactoryクラスを継承していますが、Factoryデザインパターンとは、何でしょうか?4の説明によると、このクラスが、前のクラス、MyCustomAssetをアセットに変換するみたいです。

f:id:kazuhironagai77:20180408122357p:plain

5.これは、オブジェクト作成のインターファイスを宣言するためのバーチャルなメソッドを使用するgeneric baseのFactoryが存在するという意味です。そして、Factoryのサブクラスは、実際の、論議中のオブジェクトの作成を担当します。

かなり混乱した訳になってしまいましたが、結論は、UFactoryクラスがベースとなって、新たに作成しているオブジェクトを担当するクラスは、UFactoryのサブクラスとして作られるという事だと思います。

f:id:kazuhironagai77:20180408122456p:plain

6.この方法の長所は、ユーザーが作成したサブクラスが、必要なら、それ自身のサブクラスの一つとして、インスタンス化出来る事です。その事は、実装時に、作成を要求されているオブジェクトから、どっちのオブジェクトを作成するのか決定する事に関してのディテールを隠します。

もう訳が大分崩壊していますが、言いたい事は何となく想像出来ます。恐らく、クラスをインスタンス化する際に、シングルトンとして、一個だけインスタンスする場合と、通常のオブジェクト作成のように、一つのオブジェクトを作成して、そのオブジェクトを初期化する2つの方法があり、Factoryクラスを継承したサブクラスは、クラスの作成時には、そのどちらを選ぶかを、決定する必要がないと言うメリットがある。と言いたいのだと思います。

f:id:kazuhironagai77:20180408122548p:plain

7.UFactoryを我々のベースクラスとして、適切なヘッダーをインクルードします。

f:id:kazuhironagai77:20180408122634p:plain

以上のヘッダーファイルをインクルードしました。ただし、cppファイルの方も以下に示すヘッダーファイルをインクルードしています。

f:id:kazuhironagai77:20180408122655p:plain

f:id:kazuhironagai77:20180408122708p:plain

8.そのコンストラクターはオーバーライドします。なぜなら、新しいデファルトのコンストラクターか実行した後で、我々の新しいFactoryのために設置したい沢山のプロパティがあるからです。

f:id:kazuhironagai77:20180408122825p:plain

新しいFactoryのための新しいプロパティは、bCreateNew, bEditAfterNew, そしてSupportedClassの三つでしょうか?

f:id:kazuhironagai77:20180408122854p:plain

9.bCreateNewは、そのfactoryは、現状では、論議中のオブジェクト(the object in question)の新しいインスタンスを、最初から作成する事が出来ると言う意味である。

うーん。なんだか分かったような分からないような説明です。UE4のAPIを見てみましょう。

f:id:kazuhironagai77:20180408123010p:plain

CanCreateNew()から返されるデファルト値

となっており、ますます分からないです。まあAPIは役に立たないですね。

結局、Object in questionが具体的に何を指しているのかが不明なのです。この場合、FactoryクラスのサブクラスであるUCustomAssetFactoryクラスの事なのか、ゲーム中に実際に存在するMyCustomAssetクラスの事なのか。とにかく、このパラメーターをTrueにすれば、何かのクラスのインスタンス化を白紙の段階から作れるようになるという事だけは、理解出来ます。でも、それって普通じゃないとも言えなくはないと思うのですが。Factoryデザインパターンに則る場合、その様に成るのでしょうか?

すこし、試してみましょう。

f:id:kazuhironagai77:20180408123054p:plain

bCreateNewの設定をfalseに変えてみます。その結果、

f:id:kazuhironagai77:20180408123126p:plain

MyCustomAssetが消えてしまいました。

この結果から、bCreateNewの設定をtrueにしないと、custom asset は表示されない事は確認できました。

f:id:kazuhironagai77:20180408123202p:plain

10.bEditAfterNewは、その新しく作られたオブジェクトが、作成された直後からすぐに編集出来る事を示します。

編集可能にしたいインスタンスは、このパラメーターをTrueにすればいいと言う事でしょうか? 編集可能なパラメーターは、以下に示すように、

f:id:kazuhironagai77:20180408123300p:plain

UPROPERTY(EditAnywhere)とすればいいとパラメーターの設定で習いましたが、ここでは、クラスのインスタンスであるからそこは違うのでしょうか?それとも、Factoryクラスのサブクラスとして編集可能にする場合は、dEditAfterNewの設定をtrueにするという事なのでしょうね。となると、Object in questionに該当するクラスは、UCustomAssetFactoryクラスなのかも知れません。

UE4APIも一応、確認しておきましょう。

f:id:kazuhironagai77:20180408123325p:plain

新しいオブジェクトを作成した後にアソシエイトエディターを開きたいのならTure.

APIを読んで、疑問が解決に近づくのではなく、更に、混乱の度合いが深まるというやりがいの余りない結果です。bEditAfterNewをtrueに設定したクラスのオブジェクトは、UE4エディター側から、アソシエイトエディターを開く事が可能という事でしょう。

もう訳が分からないと嘆く前に、APIの言っている事を試してみるましょう。UE4エディターをVisual studioから起動させ、MyCustomAssetのインスタンスを作成します。名前は、test1とします。Test1をダブルクリックすると、アソシエイトエディターが見事に開きました。更に、Color Nameのパラメーターが表示され、編集可能になっています。

f:id:kazuhironagai77:20180408123425p:plain

次に、bEditAfterNewの設定をfalseに変更して、同様の実験を行ってみます。

f:id:kazuhironagai77:20180408123450p:plain

f:id:kazuhironagai77:20180408123502p:plain

普通にアソシエイトエディターを開く事が出来ました。全く訳わからないですね。

f:id:kazuhironagai77:20180408123528p:plain

11.SupportedClass変数は、factoryが作成するオブジェクトのタイプについての反射された情報(reflection information)を保持しているUClassインスタンスである。

f:id:kazuhironagai77:20180408123602p:plain

コードを見る限り、SupportedClassが実際に保持する情報は、StaticClass()のようです。MyCustomAssetクラスには、そんな関数はないので、ペアレントクラスであるUObjectクラスの関数と考えられます。UE4のAPIで調べてみると、

f:id:kazuhironagai77:20180408123625p:plain

ランタイム時にこのクラスを再現しているUClass オブジェクトを返します。

となっています。UClassは、UObjectのサブクラスです。

f:id:kazuhironagai77:20180408123708p:plain

これがないと、何か問題があるのでしょうか?このオブジェクトのメモリーの開放は必要ないのでしょか?などの疑問は沸きますが、こういう風に、カスタムアセットは作るのだ。と考え、このまま行きます。

f:id:kazuhironagai77:20180408123734p:plain

12.UFactoryのサブクラスにおける、最も重要な機能は、実際のfacotryのメソッド、―FactoryCreateNew―である。

ついに、CustomAssetFactoryクラスの唯一のメンバー関数、親クラスの、Factoryクラスからオーバーライドしたメンバー関数であるFactoryCreateNew()についての説明です。以下にFactoryCreateNew()関数のコードを示します。

f:id:kazuhironagai77:20180408123827p:plain

f:id:kazuhironagai77:20180408123845p:plain

13.FactoryCreatNewは、作成されるオブジェクトのタイプの決定を担当します。そのタイプのインスタンスを作成(construct)するためにNewObjectを使います。それは、NewObjectの呼びたしを通して以下のパラメーターをパスします。

サンプルコードを見ながら13を解釈すると、このメンバー関数から返されるオブジェクトのタイプは、この関数内で決定される(今回は、MyCustomAsset)。そのため、どんなタイプのオブジェクトでも返せるように、NewObject関数が使用されます。NewObject関数が使用するパラメーターは以下の通りです。と成りました。UE4のAPIを観ると、

f:id:kazuhironagai77:20180408124022p:plain

ゲームプレイのオブジェクトをコンストラクトするための便利なテンプレート

特定されたクラスの新しいオブジェクト用のタイプTポインター

とありました。ここは、こういうやり方でやるのだと理解しておき、これ以上深くは入りません。以下に、それぞれのパラメーターについての説明がありますが、そのままにしておきます。

f:id:kazuhironagai77:20180408124108p:plain

14.InClassは作成(construct)されるオブジェクトのクラスです。

f:id:kazuhironagai77:20180408124158p:plain

15.InParentは、新しく作られるオブジェクトを保持するオブジェクトです。InParentは特定しなければ、そのオブジェクトは、トランジストパッケージ(transient package)であると決めてかかります。その意味するところは、自動的にセーブはされないと言う事です。

f:id:kazuhironagai77:20180408124233p:plain

16.Nameは作成されるオブジェクトの名前です。

f:id:kazuhironagai77:20180408124314p:plain

17.Flagsは、そのオブジェクトを、そのオブジェクトを保持しているパッケージの外側から見えるようにする事などをコントロールするフラグ(creation flags)のビットマスク(bitmask)です。

f:id:kazuhironagai77:20180408124358p:plain

18.FactoryCreateNewの内側では、どちらのサブクラスがインスタンス化されるかに関して決定される。他の初期化も行われる。例えば、マニュアルのインスタンス化や初期化が要求されるサブクラスがあった場合、ここで加えられます。

f:id:kazuhironagai77:20180408124437p:plain

f:id:kazuhironagai77:20180408124448p:plain

19.この関数のためのエンジンのコードからの一つの例を以下に示す。

ここでは、この関数内で、初期化され、かつ返されるオブジェクトが、プロパティを持っていて、そのプロパティの値を指定するのに、あるクラスを初期化しなければならない場合を紹介している。教科書のサンプルでは、UCameraAnimというクラスが、インスタンス化されるが、そのオブジェクトは、InterGroupCameraクラスと、GroupNameプロパティを持っていて、どちらもこの関数内で、初期化やインスタンス化が必要である。

f:id:kazuhironagai77:20180408124601p:plain

20.ここに見えるように、NewCamAnimインスタンスのCameraInterpGroupメンバーを追加するためのNewObjectへの二番目のコールがある。

<まとめ>

1.独自のアセットタイプの作成方法を習いました。アセットタイプは以下に示す2つのクラスから作成する事が、可能です。

f:id:kazuhironagai77:20180408124642p:plain

UObjectを継承して作られるクラス、MyCustomAssetは、基本的には、何でも、プロパティとして持つ事が出来ます。今回のサンプルでは、最も簡単な例として、FStringを持つクラスを作りました。もう一つのクラス、CustomAssetFactoryが、前のクラスをアセットタイプにするクラスです。CustomAssetFactoryクラスは、UFactoryクラスを継承していて、オーバーライドしたコンストラクターとFactoryCreateNew()メンバー関数を使う事によって、MyCustomAssetを新しいアセットタイプにする事が出来ます。

2.教科書の説明に基づいて解釈すると、bEditAfterNewの設定は、アソシエイトエディターを開けるかどうかなのですが、Falseにしてもアソシエイトエディターは開けて、良く分からないです。