UE4の勉強記録

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

新しいツールバーボタンを作る(リバースエンジニア編)Part 3

<前文>

f:id:kazuhironagai77:20180225113310p:plain

で勉強しています。

今回も、ツールバーボタンのコードを分析していきます。前回、UE4CookbookEditorクラスの分析が終わらなかったので、今回は、続きをやります。

<本文>

<個々の検証の続き>

f:id:kazuhironagai77:20180305152728p:plain

ToolbarExtender の宣言(ヘッダーファイルより)

f:id:kazuhironagai77:20180305152811p:plain

ToolbarExtender は、UE4CookbookEditor.hファイルで宣言したFExtenderクラスのオブジェクトでこの行で初期化しています。前回の検証では、2つの事が予測されました。

  1. メニューバーを拡張するのに必要と考えられている。
  2. FExtenderのメンバー関数のパラメーターとして、FExtensionBaseのオブジェクト変数が必要になる。

この初期化の場合、FExtensionBaseのオブジェクト変数は必要とされていません。教科書のHow it works…には、

f:id:kazuhironagai77:20180305152850p:plain

34.我々は、メニューやツールバーを拡大するために使われるFExtenderクラスを通して、これが出来る。

35.我々は、最初にFExtenderのインスタンスをSharedポインターとして作る。Sharedポインターとして作れば、このモジュールがシャットダウンした時、この拡張も非初期化されるからである。

と、かなり意訳だけど訳して見ました。まず、34のこれがのこれとは何なのか?と言う事で、33を見てみると、

f:id:kazuhironagai77:20180305153021p:plain

33.コマンドとアクションとの間に作られた提携を使って、我々は、今、新しいコマンドをツールバーに加えたいと、実際に拡張システムに告げる事が必要です。

まず、順番に問題を解決していきたいと思うので、最初の疑問、「34のこれがのこれとは何なのか?」について解答します。「新しいコマンドをツールバーに加えたいと、実際に拡張システムに告げる事」です。ですから、34は、

34.我々は、メニューやツールバーを拡大するために使われるFExtenderクラスを通して、新しいコマンドをツールバーに加えたいと、実際に拡張システムに告げる事が出来る。

34の解説文は非常に分かり易くなりました。だけど、ここで新しい疑問も出て来ます。それは、33の説明文にある、「コマンドとアクションとの間に作られた提携を使って」とは、前回検証したMapActionメンバー関数の事ではないのでしょうか?

f:id:kazuhironagai77:20180305153220p:plain

これ以外に、コマンドとアクションとの間に作られた提携はないです。この疑問の回答は、先を読んでから考えてみます。35は意訳ですが、その言わんとするところは、明白です。Sharedポインターを使っているから、メモリーの開放はいらないよ。と言う事です。

では、次の行を見てみましょう。

f:id:kazuhironagai77:20180305153331p:plain

まず、Extensionは、前回、ヘッダーファイルで検証した、

f:id:kazuhironagai77:20180305153401p:plain

FExtensionBaseクラスのオブジェクト変数であるExtensionの初期化を行っています。「FExtenderのメンバー関数のパラメーターとして、FExtensionBaseのオブジェクト変数が必要になる。」と前回予測しましたが、完全に外れました。FExtensionBaseのオブジェクト変数を初期化するのに、FExtenderのメンバー関数が使用されています。前回、FExtenderクラスのヘッダーファイルに以下の記述があったから、「FExtenderのメンバー関数のパラメーターとして、FExtensionBaseのオブジェクト変数が必要になる。」と予測しましたが、外れました。

f:id:kazuhironagai77:20180305153427p:plain

予測なので、外れる可能性は必ずあるのですが、外れると結構ショックです。では、正解は、FExtensionBaseのオブジェクト変数の初期化は、FExtenderのメンバー関数, AddToolBarExtension(), が行うでした。UE4のAPIによると、

f:id:kazuhironagai77:20180305153455p:plain

  • ツールバーを指定された拡張点から拡張します。
  • その新しく拡張したオブジェクトへのポインター。後でこの拡張を取り外すのに使用出来ます

と説明されています。最初のツールバーを拡張するのは当然の機能ですが、指定された拡張点とは、どういう事でしょうか?これは、拡張する場所をツールバーの中から自由に動かせると言う事だと思います。

f:id:kazuhironagai77:20180305153602p:plain

f:id:kazuhironagai77:20180305153616p:plain

では、まずAddToolBarExtension()のそれぞれのパラメーターを見ていきましょう。

ExtensionHook:拡張するメニューの一部分。同じ場所を何回も拡張できる。その拡張は、それらが登録された順番に適用される。

HookPosition: 拡張されたフックに関して、フックを適用出来る場所

CommnadList: あなたが拡張するツールバーアイテムのアクションを担当するUIコマンドリスト

ToolbarExtensionDelegate: あなたが拡張しようとするツールバーの一部を配置するために呼ばれる

それぞれを、かなりの直訳で訳してみましたが、何となく意味が分かったような分からないような内容です。教科書のHow it works…も読んでみます。

f:id:kazuhironagai77:20180305153713p:plain

37.AddToolBarExtensionの最初のパラメーターは、我々が拡張したい場所の拡張点の名前です。

38.我々の拡張を配置したい場所を見つけるためには、まず、エディターUIの拡張点の表示のスイッチを入れる必要があります。

39.そのためには、まずエディターの編集メニューのエディターの環境設定を開けてください。

40.一般・未分類を開け、次にDisplay UIExtension Pointsを選択します。

41.エディターを再起動します。そうすると、下記に記したスクリーンショットのようなエディターUI上の上書きされた緑のテキストが見えます。

f:id:kazuhironagai77:20180305153756p:plain

42.その緑のテキストがUIExtensionPointを示します。そしてテキストの内容が、AddToolBarExtension関数に提供すべき文字列です。

43.このレシピでは、拡張をコンパイル拡張点に加えます。ただし、あなたが望むどの拡張点でも選ぶ事が出来ます。

44.メニュー拡張点にツールバー拡張を加える事は出来ません。反対にツールバー拡張点にメニュー拡張を加える事も出来ません。

拙速な訳を足しました。大変分かり易いです。というか、この説明以上の内容がいるでしょうか?まず、拡張点(extension point)と言う言葉が、今まで何回も出て来ましたが、それが具体的に何を意味しているのかが不明でした。ここでこれ以上出来ないほど明快に説明されています。Compileが単に、一つの拡張点を示す名称だったとは、全然、APIの説明だけでは分からなかったです。

f:id:kazuhironagai77:20180305153848p:plain

上記に実際のエディターのスクリーンショットを示しました。ここで、少し遊んでみましょう。AddToolBarExtensionの最初のパラメーターをCompileからSettingsに変えて見ました。

f:id:kazuhironagai77:20180305153918p:plain

見事に、My Buttonの位置がCompileの前からSettingsの前に、変わっています。

正直に告白すると、このブログを始めた最大の理由が、この教科書があまり信用できないからだったのですが(最初のブログで教科書通りにやっても、実行出来なかった例を紹介しています。)、ここで、教科書への信頼が急速に回復しました。やっぱりこの教科書はすごいです。勉強する価値があります。8章までは、教科書の説明不足な処は、Packet Publish社の提供するサンプルコードで補う事が出来たのですが、8章で初めて、サンプルコードを参考にするだけでは解けない問題にぶち当たりました。結局、ヒストリアさんのブログで解決出来ましたが、大変苦労しましたし、教科書のこの部分が絶対的に説明不足で、Packet Publish社の提供するサンプルコードは間違っていたのは変わらないので、実際の処、私の教科書に対しての信頼度はかなり低下してました。

次に、AddToolBarExtensionの2番目のパラメーターを検証します。APIの説明は、

     HookPosition: 拡張されたフックに関して、フックを適用出来る場所

と訳しましたが、はっきり言って意味不明です。まず、フックとは何なのかが分かりません。とりあえず、教科書のhow it works…を見てみましょう。

f:id:kazuhironagai77:20180305154027p:plain

45.AddToolBarExtensionの2番目のパラメーターは、指定された拡張点からの場所アンカーです。我々は、FExtensionHook::Beforeを選択したので、我々のアイコンはコンパイル点の前に表示されます。

location anchorについて一言、言わせてもらいます。この教科書の筆者の名詞を二つ並べて最初の名詞を形容詞として使用する書き方は、英語で読んでいるときは、別に気にしないのですが、日本語の翻訳する時に、最適な訳が分からなくて大変イライラさせられます。Locationとは、場所のことです。Anchorとは、一義的には、船の錨の事です。そこから転じて、支えという意味で使われたりします。場所の支えとは、どういう事でしょうか?場所は面ですので、その中の一点を指す事をここで表現したいのでしょうか?もしくは、location anchorという言葉が英語にあって私が知らないと言う事でしょうか?Googleで検索してみると一個も引っかかりません。Unreal Enigne 4のエディターでのみ使われる専門用語でしょうか?全く分かりません。すごくイライラさせられます。

ただし、この説明文は、AddToolBarExtensionの2番目のパラメーターについて、これ以上出来ないくらい分かり易く説明しています。最初のパラメーターで拡張する場所(拡張点)を指定したのですが、その場所の中にも、更に細かく指定出来て、2番目のパラメーターで、それを指定できますと説明しています。さらに、今回このサンプルでは、Beforeを指定したと書かれています。

ここで、APIの2番目のパラメーターの説明をもう一度、読んでみると、最初のフックとは、拡張点の事で、その次のフックは、location anchorの事を指している事が分かります。なのでAPIの意味も分かりました。さらに、EExtensionHook::PositionのAPIを見てみると、

f:id:kazuhironagai77:20180305154117p:plain

と、説明されており、Beforeの他に、AfterとFirstがある事が分かります。試してみましょう。まず、EExtensionHook::Afterとしてみます。

f:id:kazuhironagai77:20180305154145p:plain

見事に、Compileの最後に移動しました。次に、Firstを試して見ましょう。

f:id:kazuhironagai77:20180305154210p:plain

見事にAPIの説明通りになりました。

では、AddToolBarExtensionの3番目のパラメーターを検証しましょう。教科書のHow it works…には、

f:id:kazuhironagai77:20180305154238p:plain

46.次のパラメーターは、マップアクションを保持した我々のコマンドリストです。

と、単に、前回作成した、CommandListをここで、パスしろと言ってます。ここで、今回の最初の疑問であるFExtensionBase 変数の説明の処で、「CommandListのMapActionをパスしろと、教科書のhow it works…の33と34が言っているようしか読めない。」との疑問が解けました。その通り、言っていたのです。

最後のパラメーターについて検証します。教科書のhow it works…には

f:id:kazuhironagai77:20180305154326p:plain

47. 最後のパラメーターは、実際にUIのコントロールツールバーの拡張点と我々が前に特定したアンカーに加える事を担当するデリゲートです。

拙速な訳で申し訳ないが、ここで言いたい事は、新しく加えるアイコンの形状はここでパスされるデリゲートで決めてくださいという事だと思います。実際のコードを見れば、明白ですが、

f:id:kazuhironagai77:20180305154431p:plain

AddToolBarExtensionと言う、関数をdelegateの形でパスする役割を果たしています。前回、今回作成するツールボタンが、クリックされた場合の機能を決定するために、FExecuteActionクラスのCreateRawに関数をデリゲートを用いてパスしましたが、ここでは、FToolBarExtensionDelegateクラスに、CreateRawに関数をデリゲートを用いてパスしています。教科書のhow it works…に、

f:id:kazuhironagai77:20180305154501p:plain

48.そのデリゲートは、void(*func)(FToolBarBuilder and builder)のフォームの関数に繋がっています。例えば、私たちのモジュールクラスで定義したAddToolBarExtensionと呼ばれる関数です。

AddToolBarExtension関数を見てみると、

f:id:kazuhironagai77:20180305154543p:plain

となっていて、確かに、void(*func)(FToolBarBuilder and builder)のフォームの関数になっています。さらに、

f:id:kazuhironagai77:20180305154615p:plain

49.この関数が呼ばれた時、その呼ばれた、UIエレメントを加えたビルダーにあるコマンドが そのエレメントを、UI上にある、我々が特定した場所に適用します。

と説明されています。UIエレメントとビルダーが、何なのか、いまいち、不明ですが、その関数(デリゲートで指定している関数)が指定しているアイコンを表示することだと思います。

次の行のコードは、

f:id:kazuhironagai77:20180305154725p:plain

となっています。本来ならば、APIから見ていくのですが、教科書のHow it works…の説明が素晴らしいので、先に読んでみます。

f:id:kazuhironagai77:20180305154752p:plain

50.最後に、我々は、この関数内にある、そのレベルエディターモジュールをロードする事が必要になります。そうする事で、我々は、そのレベルエディター内のメインツールバーに、我々の拡張を加える事が出来ます。

もうこれ以上無いくらい分かり易いですね。解説の必要が全くないくらいの明快な説明です。次にいきます。

f:id:kazuhironagai77:20180305154837p:plain

StartupModule()関数の最後の行です。ToolbarExtenderをモジュールに加えています。上記の説明通りです。

f:id:kazuhironagai77:20180305154903p:plain

51.いつもどおりにModuleManagerを、モジュールをロードするためとリファレンスをそれに返すのに使える。

52.手にあるリファレンスとともに、我々は、そのモジュールのためにToolbar Extensibility Managerを手に出来る。そして我々は、我々の拡張を加えよとそれに告げる事が出来る。

51は、この行の説明でなく、前の行の説明です。これ以上何の説明が必要なのか?と思ったら、モジュールのリファレンスを手に入れるためには、ModuleManager関数を使えばいいんです。と言う、UE4C++に関する一般的な話です。

52は、50、51で手に入れたレベルエディターモジュールに、我々が作成したToolbarExtenderオブジェクト変数をバスして下さい。そうすれば、我々がToolbarExtenderオブジェクト変数で指定した拡張を加えますと、だたし、パスするには、レベルエディターモジュールにあるExtensibilityManager()にパスしてください。具体的な方法は、GetToolBarExtensibilityManager()->AddExtender(ここにパス)です。と説明しています。

この二つの解説は、UE4におけるC++プログラミングではこうやり方です。と言っているようにも取れるので、そのまま覚えるしかないと思います。

ここで、とうとうメインディッシュであるStartupModule()関数のリバースエンジニアが終わりました。今回は少し短いですが、重要な区切りなので、ここで終にし、まとめに入ります。

 

<まとめ>

とうとう、メインディッシュであるStartupModule()関数のリバースエンジニアが終わりました。ここでは、2つのまとめを行います。まず、今回のまとめとしてToolbarExtenderオブジェクト変数を中心に、ツールバーボタンの形状をどのように、モジュールに伝えるのかを、まとめます。その後で、StartupModule()関数のまとめを行います。StartupModule()関数は、仮説で述べたように、ツールボタンの作成をしているだけではなく、いやむしろ、作成していません。ほかの関数で作成したツールボタンの機能と形状を、CookbookCommandクラスで作成したコマンドとバイントしています。これについてStartupModule()関数の総括として、StartupModule()関数をまとめます。

 

<まとめ1.今回のまとめ>

今回検証したコードは、たった一つの目的のために書かれています。それはツールバーボタンの位置、形状を決める事と、どのようにそれをモジュールに伝えるのかです。以下の手順で行っています。

  • FExtender クラスのToolbarExtenderオブジェクト変数を使って、ツールボタンのアイコンをツールバーに加える。
  • やり方は、
    • ToolbarExtenderオブジェクト変数を初期化
    • FExtensionBaseクラスのオブジェクト変数をToolbarExtenderオブジェクト変数のAddToolBarExtensionで初期化
    • レベルエディターモジュールのリファレンスをModuleManager関数を使って獲得し、ToolbarExtenderオブジェクト変数をパスする。
    • ToolbarExtenderオブジェクト変数をパスする時は、GetToolBarExtensibilityManager()->AddExtenderを使用する。

 

<まとめ2.StartupModule()関数のまとめ>

StartupModule()関数は、仮説で述べたように、ツールボタンの作成をしているだけではなく(いやむしろ、作成していません。)、ほかの関数で作成したツールボタンの機能と形状を、CookbookCommandクラスで作成したコマンドとバイントしています。以下に、StartupModule()関数の役割をまとめました。

  • StartupModule()は、ツールボタンの機能と形状を指定する。
  • 指定方法は、機能や形状を表した関数をデリゲートを用いてUICommandやモジュールにバインドする。
  • Slateモジュールのコマンドがツールボタンの形状や機能を指定していると仮定していたが、実際は、UE4APIが担当していた。

f:id:kazuhironagai77:20180305155030p:plain

と仮定していましたが、実際は、

f:id:kazuhironagai77:20180305155059p:plain

上記のようにするのが、正しいと考えられます。