<前文>
で勉強しています。
前回、体調を崩してしまい、「8章17節 新しいコンソールコマンドを作成する」の途中で終わってしまったので、今回は、前回の続きをします。
***このブログは、このブログの読者が、教科書(Unreal Engine 4 Scripting with C++)を読んでいる前提で書かれています。この教科書を読まないで、このブログを読んでも理解不可能であると考えられます。ご了承ください。***
<本文>
<目的>
今回は、「8章17節 新しいコンソールコマンドを作成する」の考察を行います。
<考察>
今回も、教科書のhow it works…を読んでいきます。
まずは、StartupModule関数内のお話しのようです。以下に、StartupModule関数内に追加した、全てのコードを示します。
ここで、初期化されているオブジェクト変数、DisplayTestCommandとDisplayUserSpecifiedWindowは、以下のように、ヘッダーファイルで、宣言しています。
UE4のAPIを見てみると、IConsoleCommandは、Remarkに以下のように述べられています。
コンソールコマンドのためのインターフェイス
ここで、コンソールコマンドを、作成しているのは間違いないようです。
IConsoleManagerは、以下の箇所で使われています。
Unreal Engine 4 のAPIのIConsoleManager のremarkを見てみると、
コンソールコマンドと変数を操作する。破壊される時(on destruction)に、登録(registered)されたコンソール変数は、開放される。
とあります。ここで、新しく登録されたコンソールコマンドのメモリーの開放はいらないと言う事でしょうか?
IConsoleManagerは、モジュールであると、2.で述べています。であるので、何かのモジュールのサブモジュールであっても、おかしくはないですが、APIの階層には、以下のようにしか示されていません。
コアモジュールとは、何でしょうか?UE4のモジュールの一つなのでしょうか? ググってみても、はっきりとは、説明された文に出会えませんでした。ただ、Module list?によると、コマンドmodule listで、全てのモジュールが表示されるとあるので、試してみると、以下の結果となりました。
ここで、表示されているCoreがコアモジュールのようです。このCoreを調べて見ると、
とありました。これらのFiltersは、Coreのサブモジュールなのでしょう。ここで、IConsoleManagerのAPIをもう一度みてみると、
とありました。まず、CoreがIConsoleManagerのモジュールであるとはっきり書かれていました。更に、HeaderにHALの中にこのモジュールはありますと書かれています。今度は、CoreのHALを見てみると、
ありました。ただし、HALのモジュールの一つとしてではなく、クラスの一つとして、紹介されていました。APIから、教科書の説明にあるコア―>IModuleManagerの関係は確認出来ましたが、APIでは、IModuleManagerはモジュールではなくクラスと紹介されていました。更に、UE4CookbookEditor.build.csクラスにこのプロジェクトで使用出来るすべてのモジュールが、入っているはずですが、以下に示すように、IModuleMangaerというモジュールはありません。
ここら辺から結論づけると、やはり、IModuleManangerは、クラスであって、モジュールではないと考えられます。
「新たな情報を追加する必要・・・」については、具体的になにを述べているのかは不明です。
ここで、いろいろと述べられていますが、結局、Get関数を使う必要があると言っているだけです。以下にGet関数が使われている箇所を示します。
4.によれば、Get関数は、IConsoleManagerのインスタンスのレファレンスを返すとあります。UE4のAPIでは、どう述べているのでしょうか?
コンソールマネジャーのために、シングルトンを返す。
とあります。さらに戻り値をみてみると、IConsoleManagerとなっています。ただし、APIによれば、この戻り値は、シングルトンそのものとなっています。しかし、この二つの解説で、Get関数の機能については、良く分かりました。IConsoleManagerのインスタンスもしくは、シングルトンへのレファレンスを返す、いわば普通のget関数と同じと言う事です。
だたし、気を付けないといけないのは、このIConsoleManagerへのレファレンスに対して、さらに、RegisterConsoleCommand関数が使われている点です。
5.では、RegisterConsoleCommand関数の機能は、新しいコンソールコマンドを追加出来る事、と解説されています。以下に示すRegisterConsoleCommand関数は、DisplayTestCommandWindowと、DisplayWindowと言う新しいコンソールコマンドを、IConsoleManagerのインスタンスのレファレンスに追加しているのでしょうか?
ついでに、UE4のAPIも見てみましょう。
と7種類もあってどれか分からないので、最初の方(DisplayTestCommandWindowの方)を、Visual Studioで確認しました。
ここから、確認すると、三番目のパラメーターが、const FConsoleCommandDelegate &Commandになっています。ので、
アギュメントを取らないコンソールコマンドを登録する。
これが、正解でしょう。そして、「アギュメントを取らないコンソールコマンドを登録する。」とあり、RegisterConsoleCommand関数の機能が、新しいコマンドを追加出来る事である事は、間違いないようです。
更に、今回のRegisterConsoleCommand関数は、4つのパラメーターを持っている事も確認出来ました。次の6.から、それぞれのパラメーターについての解説があります。
まず、以下に、実際のコードを示します。
Nameは、実際に、ユーザーがタイプするコマンドとなっています。コードには、DisplayTestCommandWindowとなっています。この、コマンドを変えてみましょう。
ユーザーがタイプするコマンドも、以下に示すように、同じように変化しました。
2番目のパラメーターは、良く分かりません。ツールチップならば、タイプしている時に、現れる文字だと思うのですが、testと言う文字は、何処にも現れません。よってスキップします。
三番目のパラメーターは、デリゲートをパスしていますね。ここで、ユーザーが、我々が決めたコマンド(今回は、DisplayTestCommandWindow)をタイプした場合、何が起きるのかを決めます。細かい内容については、7.で解説しているようなので、ここでは、省きます。
4番目のパラメーターは、フラグの指定に使われるようです。具体的に、ECVF_defaultが、どのように使われるかは、細かくは分かりません。
CreateRaw関数についての説明です。CreateRaw関数については、もう何回か勉強していますが復習も兼ねてもう一度勉強してみます。以下に実際のコードを示します。
まず、生のC++の関数をデリゲートとして、呼び出す時に、CreateRaw関数が使われます。その次のパラメーターは良く分かりません。
コマンドを実行した時に表示されるウィンドウのタイトルの事でしょうか?試してみます。以下のように、FStringを変更してみます。
見事に変わりました。コマンドを実行した時に表示されるウィンドウのタイトルの事のようです。
ここから、次のコードの解説に入っていきます。今度のコードは、アギュメントを使うコマンドの作り方です。アギュメントとパラメーターは、同じと理解していたのですが、このサイトによると、実は違う事がわかりました。
パラメーターは、メソッドの定義に使用される変数です。アギュメントは、そのパラメーターに、パスするデータです。
ですので、
と直しました。が、それでも訳として可笑しいので、更に、以下のように訳しました。
以下にコードを示します。
以下に示す部分が、コンソールコマンドとの違いであると述べています。
FConsoleCommandWithArgsDelegate関数の機能について、簡単に説明されています。が正直、訳に自信がないです。So its signatureが、adapt a functionにのみかかっているのか、wrapにもかかっているのかが分からないです。「匿名の関数とは、何を指すのでしょうか?特定のデリゲートの宣言と一致するために、適応する事」とは、一体何のことでしょうか?
全く分からないので、まずFConsoleCommandWithArgsDelegate関数のUE4のAPIを見てみます。
コンソールコマンドデリゲートタイプ(アギュメント付き)
これは、voidなコールバックの関数です。この関数は必ず、アギュメントのリストを受け取ります。
とだけあります。このAPIのremarkの説明は、だいたい8.で説明した内容と一致していますが、それだけです。
この説明を、読んで気が付いたのですが、DisplayWindowのコマンドは試していませんでした。今、ここで試してみます。
DisplayWindow test test testと打ち込んでみました。
タイトルがtest test testとなりました。
では、11.の解説を考察していきます。解説を考察するにあたって、11.が解説している部分のコードを以下に示しておきます。
CreateLambda関数は、TArray<FString>を受け取ります。これは、11.の解説の「その関数は、FstringのConst Tarrayを、取ると指定します。」の部分です。その後の、「DisplayWindow関数は、ウィンドウのタイトルを指定するために、シングルのFstringを受け取ります。」は、最後のthis->DisplayWindow(WindowTitle)の部分の説明です。この関数は、パラメーターが一つしかなく、全てのFstringのConst Tarrayをパスする事は出来ません。そのため、11.で説明しているように、「全てのコンソールコマンドのパラメーターを一つのFstring内に、連結させる」必要があります。これを行っているのが、for loop の部分です。
ああ、やっと分かりました!
これが、10.で説明していた、「特定のデリゲートの宣言と一致するために、適応したりする事」の意味する事だったようです。この場合の特定のデリゲートの宣言では、パラメーターは、FString一つです。がパスされたラムダ関数は、FStringの配列を持って言います。この場合、CreateLambda関数内で、FStringの配列を、一つのFStringに適応する必要があります。FConsoleCommandWithArgsDelegate関数、もしくは、CreateLambda関数は、これが出来る関数であると、10.で説明されていたのです。
12.によると、このCreateLambda関数が、11.で解説した内容を可能にする関数と宣言しています。12.では、更に親切にDisplayWindow関数にパスする前にと、どこで、それが可能なのか?まで説明しています。
まず、訳がうまくいってないので、どう訳そうとしたのから、説明します。まず、Ampersand in the capture optionsの意味は、[&]だと思うんですが、は、通常は、square bracketsと言います。 Capture options を調べてみると、そんな言葉はなく、Wire SharkにCapture optionsというダイアログがあると出てくるだけです。色々探してみると、このサイトに、の事を、 C++の世界では、capture clause と呼んでいる場合がある事が分かります。それだけです。でも、文脈から推測するに、Ampersand in the capture optionsの意味は、[&]と考えるのが自然だと思います。で、次に、wants to capture the context of the declaring functionの意味が曖昧なのです。文字通り訳せば、宣言した関数の前後関係を手に入れたい。つまり、宣言した関数の前後関係を知りたいと言う事です。この前後関係とは何でしょうか?関数やラムダ式に前後関係があるとは思えません。あったとしても、それが重要であるとも思えません。このcontextは、computer scienceの専門用語なはずです。関数かラムダ式内の何かを指しているはずですが、それが分からないのです。そこが分からないため、その続きの文章であるcapture …function by reference by including the ampersand in the capture options [&]. のby …by…の関係が不明になってしまいます。 つまり[&]によるレファレンス化によって手に入れるのか、[&]とレファレンスのどちらか by …(or)by…、もしくは両方by…(and)by…によって手に入れるのかが不明なのです。で、訳がうまくいかなくなってしまったのです。
ここまで、説明して、一つ閃きました。[&]の意味です。[&]の意味が分かれば、このby…by…の関係も分かるはずです。通常、C++で&を付けると、レファレンス化します。[&]を付ける事でも、レファレンスするのなら、capture …by …by…の関係は、おのずと、[&]によるレファレンス化によって手に入れるになるからです。[&]が、何か特別なリファレンス化する方法である可能性があるのでしょか?調べてみました。このサイトによると、[&]の意味は、
[&]の意味は、あなたが、そのラムダ関数のスコープ内の全ての変数をレファレンスとしてアクセスしたいという事です。
と説明してありました。は!ここで、全ての意味が繋がりました。capture …by …by…の関係は、前述したもののどれでもなく、capture the context of the declaring function by referenceまでがひとくくりの文だったのです。更に、この文脈におけるcontextの意味も判明しました。この場合、contextは、宣言した関数のスコープ内の全ての変数という意味です。つまり、capture every variable in the scope of the declaring function by referenceという意味だったのです。とすると、訳は、「宣言した関数のスコープ内の全ての変数をレファレンスとして獲得する。」となります。そして、その後のby including the ampersand in the capture options [&].が方法を示していたのです。訳としては、単純に、「[&]を使って」と言う事でしょう。
ここまでを、もう一度、まとめますと、capture the context of the declaring function by reference by including the ampersand in the capture options [&].は、capture every variable in the scope of the declaring function by reference by including the ampersand in the capture options [&].と同じ意味で、訳は、「[&]を使って、宣言した関数のスコープ内の全ての変数をレファレンスとして獲得したい。」となります。これを踏まえてもう一度、以下に訳してみます。
どうでしょうか?意味ははっきりして、かつ正しい訳になったと思います。
14.は以下に示す部分について解説しています。通常の関数のパラメーターの宣言と同じと述べています。
この宣言方法については、前に勉強したはずなので、今回はスキップします。
That make up our arguments togetherの部分が、今一上手く訳せませんが、15.は、以下に示すコードの解説を行っているだけです。
まず、「ラムダ関数の中で、我々は新しいFStringを作成します。」がFString WindowTitileを示しています。次の文はうまく訳せませんでしたが、forループの事を解説しています。ここでは、アギュメントでパスされたFStringの配列を、ひとつのFStringにしています。更に、連結する際にスペースを入れる事で、タイトルが、スペースを保持出来るように配慮しています。
ここでは、単に、range-based for ループを使用している理由が説明されています。
説明しやすいように。最初にコードを以下に示しておきます。
17.の解説は、Thisポインターについてにです。一見簡単そうな部分ですが、ちょっと考えてみると、結構良く分からない所があります。「thisポインターは、先に述べた&オペレーターによって得られている。」とありますが、別に何もなくても、同じ、EditorModuleクラス内のメンバー関数同士なので、Thisポインター使えるじゃないの?と考えてしまうのですが。筆者のいた大学は、JAVAが授業の中心で、C++は研究室に入った時、急に必要になり、夏休みの間、毎日、8時間、ほぼ独学で勉強して、とりあえず使えるようになったのが基礎になっているので、やや理解が乏しい部分がないとは言えないのです。C++の専門家からみると少し変な疑問かもしれませんが、とりあえず、ここに書き残しておきます。更に、私にとって良く分からないのが、WindowTitleをパスしている事です。これってよかったんですか?WindowTitleはCreateLambda関数内で宣言されているので、この関数のスコープが外れた時に、メモリーは開放されてしまうのでは?と考えてしまいます。C++の専門家の方にとっては、これらの事は自明の事なんでしょうが、正直に言いますと、私はこの辺が全然分からなくなる時があります。
ここから、ShutdownModule関数の解説となります。モジュールがアンロードされた場合、ShutdownModule関数内で、そのモジュールに追加していた我々のコンソールコマンドを除く必要がありますが、そのためには、ShutdownModule関数内で、コンソールコマンドのオブジェクトへのレファレンスを保持する必要があります。と述べています。2.でUnreal Engine 4 のAPIのIConsoleManager のremarkの内容から、コンソールコマンドの開放は、勝手にやってくれるのかと推理したのですが、ここで、自分でやらないといけないようです。
以下に、ShutdownModule関数内に付足したコードを示しました。
そのため、とありますが、そのためとは何の事でしょうか?「ShutdownModule関数内で、コンソールコマンドのオブジェクトへのレファレンスを保持する必要があります。」の事です。DisplayTestCommand変数を作る必要があると述べています。このDisplayTestCommand変数は、解説の1.から7.で散々勉強したやつですので、何一つ、ここで新しく作る必要はありません。
DisplayTestCommand変数が、20.で述べた事を可能にするそうです。20.の文をそのまま、読めば、このコンソールコマンドをゲーム中は使えなく出来ると、解釈出来るのですが、そうなんでしょうか?
21.でやっとShutdownModule関数の話になりました。21.では、以下に示したコードの解説をしています。
まず「DIsplayTestCommandが、いろいろなコンソールコマンドに対して、レファレンスするかを確認します。」についてですが、このコード内のif(DisplayTestCommand)についての解説です。DisplayTestCommandがTrueを返す時は、レファレンスがあると言う事らしいです。そして、Get関数を用いて、「IConsoleManagerオブジェクトに対して、レファレンスを獲得します。」。さらに、「UnregisterConsoleCommandを、ポインターにパスします…」のあたりは、UnregisterConsoleObject関数がDisplayTestCommandを受け取る部分の説明と考えられます。
Unregisterした後、DisplayTestCommand変数をnullptrにリセットするだけで、メモリーを開放出来ると述べています。以下に示すコードでも、それしかしていません。
ここで、メモリーの開放に詳しい人は、この個所について鋭い考察が出来るのでしょうが、筆者はあんまり詳しくありません。ので、教科書の通りにやるしかないので、これ以上、深くは考えません。メモリーの開放に詳しい人なら、2.で解説したUnreal Engine 4 のAPIのIConsoleManager のremarkの内容についての整合性についての解説も出来るのかもしれません。
これから解説するDisplayWindow関数についての簡単な説明です。
ここから、DisplayWindow関数についての解説です。以下にコードを示します。
この関数内で、ウィンドウを表示するので、SNew関数を使用します。SNew関数そのものについては、すでに勉強済みなので、ここでは省略します。更に、25.から最後まで、ウィンドウをSNew関数を用いて作る方法についての解説で、これも前に勉強した事の繰り返しなので、ここでの解説は省略します。
<まとめ>
今回は、独自のコマンドを作る2つの方法を習いました。一つは、コマンドのみ、もう一つは、コマンドにアギュメントを付けるタイプのものです。
- コマンドを作成するには、StartupModule関数とShutdownModule関数を使用する。
- アギュメントを付けるタイプのコマンドを作成する場合、CreateLambda関数を使用する。
- CreateLambda関数は、関数内で、パラメーターの数を変えたり出来るので、パスされたアギュメントとデリゲートする関数のパラメーターの数が合わない場合も関数内で調整が出来る。
- ウィンドウを作成したり、開いたりする方法は、ここでも同じだった。
<おまけ>
今まで、パラメーターとアギュメントを同じ意味で使っていましたが、このサイトによると、パラメーターは、メソッドの定義に使用される変数で、アギュメントは、そのパラメーターに、パスするデータだそうです。
先週、体調を崩してしまいました。今週はこれだけです。