<前文>
で勉強しています。
今回は、「8章16節 アセットのための新しいカスタムコンテンツメニューのエントリーを作成する」を勉強します。
***このブログは、このブログの読者が、教科書(Unreal Engine 4 Scripting with C++)を読んでいる前提で書かれています。この教科書を読まないで、このブログを読んでも理解不可能であると考えられます。ご了承ください。***
<本文>
<目的>
アセットのための新しいカスタムコンテンツメニューのエントリー(entry)を作成する。とありますが、エントリー(entry)って何ですかね。正直、良く分からないですね。教科書の先を読むと、
こんな図が紹介されていました。この図から推測するに、カスタムアセットのインスタンスを右クリックした時に、表示されるメニューの項目を追加出来る事のようです。この項目をエントリーと呼ぶみたいですね。
<方法>
0.失敗した時の保険のために、前回のプロジェクトを別名保存しておきます。やり方は、前に説明した方法です。*
1. FAssetTypeAction_Baseを基に、新しいクラスを作ってください。そのヘッダーファイルにhをインクルードする必要があります。
1.1 前回と同じように新しいクラスを作ります。
と思ったら、FAssetTypeActions_Base.クラスがありません。Visual studio から作るしかないようです。
Packet Publish社が提供するサンプルコードを見ると、Chapter8Editorフォルダー内にある、MyCustomAssetActions.hとMyCustomAssetActions.cppがここのサンプルコードのようです。このサンプルコードを参考に、Visual studio側 から作ります。
1.1.1 プロジェクトを右クリックしてAdd->New Item…を開き、名前にMyCustomAssetActions.hと書き、LocationにCookbookEditor\Publicを選びます。
1.1.2 cppファイルも同様にして作成します。Locationは、CookbookEditor\Privateにします。
1.2 作ったヘッダーファイルに、Packet Publish社が提供するサンプルコード内のMyCustomActions.hを参考にして、コードを足していきます。
1.3 同様に、cppファイルにも、Packet Publish社が提供するサンプルコード内のMyCustomActions.cppを参考にして、コードを足していきます。
Packet Publish社が提供するサンプルコードは、MyCustomAssetActions.hを最後にインクルードしていますが、最初にインクルードします。*
ここで、一端ビルドしてみます。
一応、ここまでは成功しているみたいです。念のため、実行もしてみましたが、問題なく動きました。
2. そのクラス内の以下のバーチャル関数をオーバーライドします。
2.1 Packet Publish社が提供するサンプルコードも参考にしつつ、以下のようにコードを書きました。
3. 以下の関数を宣言します。
3.1 以下のように宣言しました。
4.Cppファイルに宣言した関数を実装します。
4.1以下のようになりました。
5. エディターモジュール内のStartupMode()関数に、以下のコードを足します。
5.1 Packet Publish社が提供するサンプルコードも参考にしつつ、UE4CookbookEditorModule.cppのStartupModule()関数に、以下のようにコードを書きました。
CreatedAssetTypeActionsがエラーになっています。Packet Publish社が提供するサンプルコード内のUE4CookbookEditorModule.hファイル内に、
CreatedAssetTypeActionsオブジェクトの宣言があるので、
5.2そのまま、CreatedAssetTypeActionsオブジェクトの宣言をコピーします。
エラーが消えました。
この部分は、教科書には、全く記載がないので、Packet Publish社が提供するサンプルコードなしでは、再現は無理だと思われます。
6. ShutdownModule()関数に以下のコードを足します。
6.1 Packet Publish社が提供するサンプルコードのUE4CookbookEditorModule.cppファイルを参考にして以下のコードを作りました。
<結果>
7. プロジェクトをコンパイルしてください。そしてエディターを実行してください。
7.1 ビルトをしたらエラーになってしまいました。
スラッシュの向きを直してくださいとあったので、上記のように変えました。その後、もう一度ビルドしたら、成功しました。
7.2 実行しました。
8.コンテンツブラウザーからカスタムアセットのインスタンスを作成してください。
9.コンテンツメニューにあるカスタムコマンドを見るために、新しいアセットを右クリックしてください。
見事に出来てました。
<考察>
とりあえず、今回もHow it works…を読んでいきます。今回から、教科書から抜き出したHow it works…とその訳は、考察部分と区別がしやすいように、枠で囲むようにします。
まず、FAssetTypeActions_Baseクラスの話からです。ここは、何より、APIを見るべきでしょうか?
全てのAssetTypeActionsの基底クラスです。いろいろなタイプに有用なヘルパー関数を提供します。このクラスから派生するかどうかは、選択出来ます。
とあります。両方とも、FAssetTypeActions_Baseクラスについて説明していますが、教科書と、APIは、微妙に違っているように思えます。教科書は、FAssetTypeActions_Baseクラスの機能は、アセットタイプ用のコンテンツメニューコマンドとなっていますが、APIは、シンプルにAssetTypeActionsとなっています。教科書のアセットタイプ用のコンテンツメニューコマンドが何であるかが、まず分からないので、ここはとりあえず飛ばして、次を見てみます。
FAssetTypeActions_BaseクラスのAPIに、IAssetTypeActions.hの中のバーチャル関数についての解説があります。
サンプルコード内、MyCustomAssetActions.hファイル内でオーバーライドしているバーチャル関数は、以下に示す通りです。
この中で、FAssetTypeActions_BaseクラスのAPI 中に紹介されているIAssetTypeActions.hの中のバーチャル関数は、
の二つです。ちなみに、UE4のコード規約では、以下に示すように、Iはインターフェイスを表しています。Fは、T,U,A,S,Iで表せない場合に使用します。
ここで、カスタムコンテンツメニューエントリー(custom context menu entry)とバインドする関数も宣言するとあります。サンプルコードのMyCustomAssetActions.hには、以下の関数がまだありますが、多分、MyCustomAssetContext_Clicked関数のみがこれに当たると思います。
IAssetTypeActions.hの中のバーチャル関数の一つであるHasActions関数について説明しています。まず、HasAction関数がエンジンコードから呼ばれる関数であると言っています。更に、エンジンコードから呼ばれた後、アクションを保持しているかどうかをチェックするための関数とあります。と言う事は、True かFalseを返すはずです。APIを見ても、サンプルコードを見ても、返す変数は、boolとなっていました。では、どんなアクションを持っている場合なのでしょうか?ここでは、選択されたオブジェクトに対応するアクションとあります。この選択されたオブジェクトが良く分かりません。パラメーターに、UObjectをパスしていますが、これの事でしょうか?
次は、GetAction関数の説明がされています。まずこの関数は、先程のHasAction関数がTrueを返した時のみ、働くとあります。次に、もしHasAction関数がTrueを返したら、MenuBuilderにある関数を呼ぶとあります。MenuBuilderは、FMenuBuilderクラスから作成されたオブジェクトで、この関数にパスされています。FMenuBuilderクラスのAPIを見てみると、remarkには、以下の説明があります。
垂直なメニューのためのビルダー
このオブジェクトを呼ぶ理由は、メニューオプションを作成するためとありますので、見事にマッチしています。このクラスの関数を使って我々が新しく作った項目をメニューに表示させ、かつ我々が提供するアクションをこの項目が選択された場合に、起動するのでしょう。
ここで、GetName関数について説明しています。驚いたことに、GetName関数は、IAssetTypeActionsクラスの関数でした。FAssetTypeActions_BaseのAPIには、IAssetTypeActionsクラスからオーバーライドした関数が紹介されていますが、GetName関数はありませんでした。IAssetTypeActionクラスそのもののAPIを見てみます。
このタイプの名前を返す
GetName関数がありました。どうも、この関数で返す名前の指定をするようです。通常のGet関数と同じと考えると、このクラスに名前を指定する変数があり、その名前を返す関数と考えられます。
以下に、MyCustomAssetActions.hファイル中のGetName関数の宣言を示します。
GetSupportedClass関数もIAssetTypeActionsクラスの関数でした。
特定のオブジェクトが、このタイプに操作されるかどうかをチェックする。
APIのremarkは、間違っていると思います。少なくとも、この関数が返す値はUClassでBoolではありません。教科書の説明にある我々のアクションクラスがサポートするUClassのインスタンスを返すが正しいようにみえます。
GetTypeColor関数もIAssetTypeActionsクラスから調べて見ます。
このタイプに関連する色を返します。
クラスはともかく、このタイプとか、アクションに色があるのかが良く分からないですが、兎に角、色を返す事だけは分かりました。
最後に紹介されてるのが、GetGategories関数です。
そのアセットタイプのカテゴリーを返します。
これも、IAssetTypeActionsクラスの関数でした。あらゆるアセットタイプは、何らかのカテゴリーに属しているので、そのアセットタイプのカテゴリーを調べる関数であると理解しました。9の訳は少し変ですね。直そうと思ったのですが、The category under which the actions show in the context menuが良く分からないのです。The actions show under the category in the context menuなのか、The action show in the context menu under the categoryなのがまず分かりません。アセットタイプにカテゴリーがある事は分かります。でも、アクションやコンテンツメニューにカテゴリーってあるんでしょうか? 段々、訳がおかしくなってきたのですが、原因は、このアクションが実際、何を示しているかが分からないからです。現状、改善しようがないですので、このまま行きます。
ここから、4.から9.までで、説明された関数の実装部分についての説明に入ります。
10.の説明は、MyCustomAssetActions.cppのHasActions関数の実装についてです。以下に実際のコードを示しますが、Trueしか返しません。
4.で「・・・アクションを保持しているかどうかをチェックするための関数とあります。・・・」とありましたが、少なくともこのサンプルコード内では、HasAction関数は、アクションを保持しているかどうかをチェックはせず、闇雲に、全ての条件に対してTrueを返しています。
本来は、
If(InObject.something.something)
{
return ture;
}
else
{
return false;
}
みたくなるのでしょうか?
恐らく、この教科書では、サンプルコードは必要最低限することで、学習者が理解しやすいようにしたのでしょう。その方針は、正しいと思いますが、このInObject.something.somethingの部分が、ここで、紹介されていれば、この節におけるアクションが、具体的に何を示すのか分かったので、少し残念です。
GetActions関数の実装部分について説明しています。以下にGetAction関数のコードを示します。
MenuBuilderオブジェクトは、そのメンバー関数であるAddMenuEntryを呼んでいます。この関数については、次の12以降で詳しく説明しているので、ここでは、踏み込みません。レファレンス云々はC++の基本なのでこれも踏み込みません。
12.13.で述べられている事の再確認をしましょう。現在、カスタムコンテンツメニューのエントリーは以下のように、表示されています。
これを、AddMenuEntry関数の最初と二番目の関数を変えて、実際に表示が変わるか試してみたいと思います。以下に示すように変えてみました。
その結果、以下に示すように、教科書の説明通りの変化を示しました。
AddMenuEntry関数の三番目の関数についてですが、アイコンのイメージを指定しています。コードを以下に示します。
このアイコンについては、新しいツールボタンを作るで、さんざん勉強したので、ここでは、このままにしておきます。
ここで、MyCustomAssetContext_Clicked関数をデリゲートとしてパスしています。
このデリゲートとして、ある関数を、CreateRaw関数を使用して、バインドする方法も、新しいツールボタンを作るで勉強したので、ここでは、深く検証はしません。
―追加文ー
一言だけ、追加しておきたい事があったので、ここに書いておきます。新しいツールボタンを作るで、CreatRaw関数を使用した理由は、デリゲートする関数のあるクラスが生のC++クラスだからと説明されていました。今回は、どうなんでしょうか?MyCustomAssetContext_Clicked関数があるFMyCUstomAssetActionクラスは、FAssetTypeActions_Baseクラスを継承していますし、生のC++クラスとは、言えないのじゃないのでしょうか?
この疑問について、今日は時間がないので、確認できませんが、時間が許されるときに、検証してみます。検証結果は、ここに記載します。
―追加文終ー
これは、知らなかったです。試してみましょう。
Cookbook Windowが開かれました。MyCustomAssetContext_Clicked()のコードを以下に示します。
これを見ると、ウィンドウが開かれるのが分かります。個々のコードは、「8章14節 新しい編集用ウィンドウの作成」で勉強した内容とほぼ同じですので、ここでは、省略します。
以下にGetName関数のコードを示します。GetName関数が、アセットタイプの名前を返すように、実装されています。
6.で、「このクラスに名前を指定する変数があり、その名前を返す関数と考えられます。」と述べましたが、サンプルコードでは、名前そのものも、ここで、決定していますね。これは、サンプルコードを出来るだけシンプルにするための便宜的なものと理解しておきます。
サムネイル云々については良く分かりませんが以下の事でしょうか?
試しに、StringをMy Custom Assetから以下に示すように、My Custom Asset11111に変更してみます。
その結果、
となりました。ここでは、説明されていませんが、サムネイルは、ぜひ作りたいです。後で、やり方を研究してみましょう。
後、分からないのは、メニューセクションのタイトルだけです。
これの事でしょか?
以下にGetSupportedClass関数の実装を示します。
我々がアクションに操作してほしいアセットタイプは、UMyCustomAsset::StaticClass()であると述べていますが、今一、実感がありません。
7.の宣言部分の説明と合わせて、読んでみても、このクラスの中で、もっとも意味不明な、get関数です。このオブジェクト変数が一体何なのかが分からないだけでなく、この関数から返されたオブジェクトを、何に、使用するのかも見当が付きません。
以下にGetTypeColor関数の実装を示します。教科書に述べてあるように、エメラルド色を返しています。
どんな色でも良いとあるので、色を変えてみましょう。白くしてみました。
サムネイルも白くなりました。エメラルド色の方がきれいですね。
8.でアクションやタイプに色があるのか?とこの関数の解説に対して沢山の疑問をかけていましたが、それらに対する回答は得られなかったですね。
GetName関数で述べたのと、同じように、ここでも、色そのものを、この関数内で決定していますが、実際は、このクラスに色を指定する変数を作り、その色をこの関数にパスすると思います。
その通りですが、もう一回、「8章14節 新しい編集用ウィンドウの作成」で勉強した、ウィンドウの作り方をここで、復習するつもりは、ありません。軽く流していきます。
はい、作っていますね。(下に実際のコードを示します。)
SWindowについては、さんざん勉強したので、ここでは、復習しません。
All the functions that are chained…ではなく、All the functions are chained と思うのですが。ここでは、SWindowの全ての関数が、チェーン化して使えると言っています。
この事を言っています。
上記に示した、チェーン化されたSWindowクラスの個々の関数についての説明です。
26から30までは、「8章14節 新しい編集用ウィンドウの作成」で勉強した内容とほぼ一緒なので、今回はスキップします。
But we need to actually tell the engine that …の文の訳がグドグドですが、ここから次のクラス、EditorModuleクラスに追加したコードについての解説が始まります。
我々が、前に作成したFUE4CookbookEditorModuleクラスのStartupModule関数とShutdownModule関数を用います。
以下に実際のコードを示します。
アセットツールモジュール(Asset Tools Module)クラスと、IAssetToolsクラスの関係が良く分かりませんが、こういうものと覚えます。兎に角、ロードは出来ました。
実際のコード:
前述の我々が作成したMyCusntomAssetActionクラスのインスタンスを指す新しいSharedポインターを、作成します。
実際のコード:
35.に36.をResigerAssetTypeActions関数を使ってパスします。
実際のコード:
37。の説明を読む限り、CreateAssetTypeActionsが、アクションインスタンスですが、アクションインスタンスの定義自体が不明です。ひょっとしたら、UE4の基底クラスにアクションというのがあって、そこから派生したクラスは、全てアクションクラスで、そのクラスのインスタンスは、アクションインスタンスなのかもしれません。
CreateAssetTypeActionsは、IAssetTypeActionsクラスのインスタンスです。UE4のAPIによると、
AssetTypeActionsは、アセットタイプのアクションとその他の情報を提供する。
とあります。兎に角、このIAssetTypeActionsクラスのインスタンスであるCreateAssetTypeActionsをAdd関数を使ってパスします。
この33.から37.までの流れは何故こうやるのかを考えるのではなく、これが正しいやり方なので、覚えるようにと考えるべきかもしれません。
まず、配列を使って作られたオブジェクトは、CreateAssetTypeActionsです。38.は、CreateAssetTypeActionsの事を述べているのでしょうか?CreateAssetTypeActionsは、IAssetTypeActionsクラスのインスタンスであるので、その通りだと思います。ここで、分からないのは、an array of all the created asset action です。全ての作成されたアセットアクションの配列、と訳しましたが、まずその意味が不明です。アセットアクションですら、具体的に何を指しているのか不明なのですから。しかし、分かる事もあります。将来的に、他のクラスのために、カスタムアクションを追加したい場合、この方法で出来るという事です。
ここから、ShutdownModule内の話になります。以下にShutdownModule内に追加したコードを示します。
39.で、アセットツールモジュールのインスタンスをもう一度、検索しますとあります。以下に示すコードの事でしょう。
大分、ごちゃごちゃした訳ですが意味は通ります。以下に、ここで解説している部分のコードを示します。
レンジベースのforループとは、for (auto Action : CreatedAssetTypeActions)の事です。40.では、設置されたアクションクラスとなっていますが、CreateAssetTypeActionsの配列にセットされたのは、インスタンスです。StartupModule関数で、レジスターしたので、ここでアンレジスターする必要があります。
話は全然変わりますが、今まで、翻訳を、英語―>日本語とやっていました。これが、大変時間がかかるのと、訳した文が大変おかしい場合が多く、どうしてこうなるのか不思議でした。41.は、どう訳しても、変になるので、やり方を変えて、英語で理解―>日本語で説明としてみました。41.の訳は、学校の英語のクラスの英文和訳なら0点かもしれませんが、日本語がおかしくないですし、英文と日本文の意味する処は、大変近くなっていると感じます。いわゆる超訳というやつですね。
41.は、クラスがレジスターされた場合、エディターがそのレジスターされたクラスに、何をするのかについて説明しています。ShutdownModule内に追加したコードについての説明は、40.でお終いでした。41.から最後までは、レジスターに関する解説みたいですね。個々のレジスターの説明に関しては、私は特に知識がありませんので、ただ、はい分かりました。としか言えません。ただ、ここでも、レジスターされるのは、クラスとなっていて、インスタンスではありませんね。ここで、アセットといっているのは、
これらの事です。
42.の説明文に対する私の解釈はこうです。まず、エディターは、アセットが、右クリックされたら、そのアセットが、カスタムアセットクラスから作られたかどうかチェックします。その方法は、そのアセットのStaticClassと、GetSupportedClass関数が返す値と比較し、両者が一致するかです。もし一致した場合は、そのアセットはカスタムアセットクラスから作成されたアセットであると判断されます。もし一致した場合、エディターは、GetAction関数を呼び出し実行します。
以下に我々が作成したFMyCustomAssetActions クラス内のGetSupportedClass関数を示します。
単に、StaticClassを返しているので、このクラスから作成されたアセットのstaticClassとこの関数が返す値は一致するのは確実です。
―追加文ー
7.で、GetSupportedClass関数の「APIのremarkは、間違っている。」と、断言しました。7.を読んだ時点では、完全には、分かっていなかったですが、
特定のオブジェクトが、このタイプに操作されるかどうかをチェックする。
とAPIがチェックすると断言している事に対して、UClass のオブジェクトを返しているのに、チェックすると言うのは、おかしいと述べました。この私の指摘は、正しいですし、APIが間違っているのは、確実ですが、このAPIが、完全に間違っていたわけでもない事も、ここで、分かりました。
このAPIは、GetSupportedClass関数について説明していたのでは、なくて、ResigerAssetTypeActions関数内で、GetSupportedClass関数が果たす役割について説明していたのです。
勿論、それは、GetSupportedClass関数のAPIとしては、間違いですし、私の主張が正しい事に代わりはないですが、この説明文にも、意味があったのです。
―追加文終ー
さらに、FMyCustomAssetActions クラス内のGetAction関数を下に示します。
この関数の中身については、11.から16.に詳しく解説していますので、省略しますが、兎に角、この関数が実行されます。
アセットを右クリックしましたが、表示されたメニューが、見事に、GetActions関数で指定した内容になっています。
我々のサンプルの場合、CustomAssetActionボタンの名前は、CustomAssetAction1111なので、それをクリックすると、MyCustomAssetContext_Clicked関数が呼ばれ、MyCustomAssetContext_Clicked関数が指定したウィンドウが開きました。
<まとめ>
- MyCustomAssetActionsクラスが、カスタムアセットの名前、サムネイルの形状、右クリックした時に現れる、メニューバーの内容、実際にクリックした時に発動する機能など、カスタムアセットに関する総てを司っています。このクラスは、FAssetTypeAction_Baseクラスを継承する事により、HasActions関数、 GetActions関数、 MyCustomAssetContext_Clicked関数、 GetName関数、GetSupportedClass関数、 GetTypeColor関数などのカスタムアセットの全てを決定する関数が使えるようになります。
- ただし、そのクラスを実際にエディターから使用するためには、EdiorModuleクラスから、そのクラスを呼ぶ必要があります。今回は、IAssetToolsクラス、FModuleManagerクラス、FAssetToolsModuleクラス、RegisterAssetTypeActions関数、IAssetTypeActionsクラスなどを用い、MyCustomAssetActionsクラスを実際にエディターから呼ぶようにしました。
- 以下に示す、アセットのための新しいカスタムコンテンツメニューのエントリー(項目)の設定は、MyCustomAssetActionsクラスのGetActions関数によってコントロールされています。
<おまけ>
*別名保存は念のためにやっているので、時間を取られる事自体が不本意なのですが、今回、何回やってもエラーになって保存出来ない事態に成ってしまいました。具体的にいうと、
この表示が出た後に、はい(Y)を、押すと、
上記の表示になってエラーになります。30分近く格闘して原因が判明したのですが、原因は、CustomAssetFactory.cppファイルのインクルードの順番が、以下の様に成っていて、ビルトをするとエラーになるからでした。
CustomAssetFactory.hファイルは最初にインクルードしないといけないという大原則を忘れていました。
以上のように直した処、正常の別名で保存出来ました。
なぜ、前回は、間違った順番で、ヘッダーファイルをインクルードしたにも関わらず、正常にビルド出来たのか不思議ですが、こんなつまらないミスで30分以上無駄にしてしまいました。