<前文>
で勉強しています。今回は、「9章5節 Slateのイベントに関数の呼び出しをつける」を勉強します。
<本文>
<目的>
「Slateのイベントに関数の呼び出しをつける」を勉強します。
<方法>
Step.0
今回も新しいプロジェクトを作成します。プロジェクト名は、Chapter9part5_0702とします。最初に、build.csファイルのSlate,SlateCoreをアンコメントしておきます。
Step.1
前回と同じように、クラスウィザードから作成します。
GameModeを選択します。
名前をClickEventGameModeにします。このモジュールは他のモジュールから利用される予定はないので、Privateにします。
Step.2
以下に示すように加えました。
エラー表示なので、以下に示すヘッダーファイルをインクルードしました。
Step.3
以下に示すように加えました。
Step.4
以下に示すようにしました。
エラー表示を消していきます。
Engineヘッダーファイルをインクルードしました。
SetLockMouseToViewport()関数がエラーを示しています。詳しく見てみると
「FInputMoucseUIOnlyは、SetLockMouseToViewport()メンバーを持っていません。」とあります。UE4のAPIでFInputMoucseUIOnlyを見てみると、
変数、関数などのどこにも、SetLockMouseToViewportはありませんでした。ただ、何となくですが、この中のSetLockMouseToViewportBehavior関数が正しい奴な気がします。とりあえず、SetLockMouseToViewportBehavior関数を変わりに使用してみます。
とりあえず、上記のように変えました。
Step.5
以下のように実装しました。
<結果>
Step.6
ビルドしました。
エディターを開きました。
Step.7
しました。
Step.8
変わりました。
<考察>
今回も、How it works…を読んでいきます。
To minimize the number of classes extraneous to the point of the recipe that you need to createの訳は、「作成しなければならないレシピのポイントと関係のないクラスの数を最小にするために」としましたが、これがGameModeにかかっているのか自信はないです。もしGamemodeにかかっているのなら、to create…, to display…, and to minimize …と書くと思いますので。でも結局、ここで言っているのは「他のレシピと同じようにGameModeを新しく作成します。」といい事なので、細かい事は気にしないで、次に行きましょう。
2.では以下の事が述べられています。
(a) Slate Widgetを作成した後、そのSlate Widgetと対話する必要がある。
(b) そのためには、Slate Widgetのレファレンスを残す必要がある。
どのようにSlate Widgetのレファレンスを保存するのかについては次以降に説明があると思います。
3.では以下の事が述べられています。
(a) 2.で述べた事をするために、2つのSharedポインターをメンバーデータとして作成。
(b) 一つ目のメンバーデータは、UIの親、もしくはルートのウィジェットを参照。
(c) 二つ目のメンバーデータは、ボタンのラベルを参照
(d) 二つ目のメンバーデータは、ボタンのラベルをゲームを実行中に変化させるのに必要。
以下に、ClickEventGameModeのヘッダーファイルから、2つのSharedポインターを示します。
3.の(b)で述べているように、一つ目のSharedポインターは、UIの親に当たるであろうウィジェットを参照出来るようにSVerticalBoxタイプに成っています。二つ目は、3.の(c)で述べているように、ボタンのラベルへの参照を保存できるように、STextBlockになっています。
ClickEventGameModeのソースファイルを見てみると、
一つ目のSharedポインターであるWidgetの初期化は前回までのSlate ウィジェットと変わりなく、一見するとゲームの実行中にプレイヤーがそのウィジェットと対話するために作られたものではなさそうです。ただし、よく見るとOnClicked()でゲームの実行中にボタンがクリックされた場合のイベント内容を定義しています。ゲーム実行中に、プレイヤーがウィジェットと対話出来るようにすると言われると、何か大層な事を行うのかと思いましたが、実際はイベントとデリゲートで対応するので、特に新しい内容ではなかったと言う事でしょうか。確かにイベントとデリゲートはゲーム実行中にプレイヤーがウィジェットと対話する一つの方法であります。しかしこれは、ウィジェットの参照が必要となってないないので、厳密には2.の内容とは異なります。
以下に示すButtonClickedの
ButtonLabelが参照される事で、ボタン上のテキストが変化する場合こそが、2.で述べている内容に当たるようです。3.でも(c)、(d)で2.と同じような事が述べられています。
ここでは、AClickEventGameModeクラスのメンバー変数をメンバーデータと呼んでいます。メンバーデータと言う言い方は初めて聞いたので、少し調べて見ました。
メンバー変数の別名のようです。IBMが使っていた言い方のようで特別な意味はなさそうです。
4.では以下の事が述べられています。
(a) BeginPlayをオーバーライドします。
(b) Player Controllerのレファレンスを得ます。
プレイヤーコントローラーのレファレンスを得るためには、ゲームを始める必要があるのでしょうか?多分そうなのでしょう。ので、BeginPlay関数内でそのレファレンスを得る必要があるのでしょう。
GetFirstLocalPlayerFromController()でPlayer Controllerのレファレンスを得ています。この部分のコードに関しては、他の9章のレシピで散々使用していますので、ここではもう考察しません。
5.では以下の事が述べられています。
(a) ButtonClickedと呼ばれるFReplyを返す関数を作成する。
(b) FReplyはイベントがハンドルされるかどうかを示すStruct。
(c) ButtonClickedのsignatureはFOnClickedのsignatureで決まる。
(d) FOnClickedはその時に使用されるデリゲート。
まず、ButtonClicked関数を見てみましょう。
FReplyを返しています。(a)で述べてある通りです。(b)についてですが、FReplyをAPIで調べて見ると、
返事(Reply)は、どのようにイベントがハンドルさせるかのある程度の様相を連絡するためにSlateのイベントがシステムに返す何かである。例えば、特定のウィジェットにマウスキャプチャーを与えるかシステムに聞く事でOnMouseDownイベントを、ウィジェットがハンドルする場合、FReply::CaputureMouse(NewMouseCapture)を返却する。
と書かれていてFReplyがどのようにイベントをハンドルするかが良く分かります。ただ、気になるのが、FReplyは本当にstructなのかと言う事です。APIには、FReplyの関数が沢山紹介されています。Structならば関数はないはずです。SlateCoreモジュールにあるReply.hファイルを見ても、コンストラクター(ただしhidden)はあるし、関数はあるし、変数もあるのでクラスに見えます。サンプルに使われているコードを見てもFReply::Handled()となっていて、( )が付いていますから関数にあたるはずです。なのにFReplyはstructと書かれています。
(c)、(d)については以下のコードについての解説です。
ButtonClickedをデリゲートとしてFOnClickedのメンバー関数であるCreateUObject()にパスしています。この関係から考えると、FOnClickedのsignature は、ButtonClickedのsignatureで決まり、ButtonClickedがその時のデリゲートとなります。5.(c), (d) の解説と逆です。ウーン訳を間違えたかともう一度読み直しましたが、訳はあっていると思います。とりあえずここの考察はここまでとして、次に移りたいと思います。
以下に示すコードの部分を解説しています。
以前、Super::BeginPlay()の役割が良く分からないと述べていましたが、6.ではっきり説明されていました。クラスが正しく初期化するために、Super::BeginPlay()を呼ぶとありました。
次に以下の部分のコードを説明しています。
この部分は、今までと全く同じなので、考察はスキップします。
以下の部分のコードを説明しています。
Valueを追加とありますが、結局はデリゲートのButtonClickedメンバー関数の事のようです。OnClicked()関数は初めての使用なので、OnClicked()関数のAPIを調べて見ました。
ボタンが押された時に、実行するためのデリゲート
まず、OnClickedはSButtonクラスの関数ではなく変数でした。タイプはFOnClickedです。FOnClickedのAPI を見てみると、
ウェジェットがユーザーにそれらがクリックされた事を知らせたいときに呼ばれるデリゲート。
ボタンやボタンのようなウィジェットに使用される傾向にある。
更に、CreateUObjectのAPIを見てみると
スタテック:UObjectに基づくメンバー関数のデリゲートを作成
UObjectのデリゲートはあなたのオブジェクトへの弱いレファレンスを維持します。
あなたはそれを呼ぶためにExcuteIfBound()を使用出来る。
となっていました。この部分のコードの書き方は、APIからでは良く分からなかったです。とりあえず、このまま覚える事にします。
これは、8.で考察したOnClickedの解説ですね。OnClickedはデリゲートプロパティとなっています。そしてボタンがクリックされるとすぐにデリゲートをBroadcastするとあります。
以下にしめす、コードの
OnClickedのパラメーターの解説をしていると考えられます。
OnClicked内のデリゲート関数は、今回のサンプルでは、UObject関数とバインドするためにCreateUObjectデリゲート関数を使用していますが、CreateStatic、CreateLamdaデリゲート関数も使えると述べています。
CreateUObjectデリゲート関数のパラメーターについての解説です。これらのパラメーターは、一般的なデリゲートを使用するために必要なパラメーターと同じです。
この関数とは、CreateUObjectデリゲート関数の事を指していると考えられます。ので13.もCreateUObjectデリゲート関数とそのパラメーターについての解説です。
FOnClickedOnClickedについては何が言いたい事なのか良く分かりません。
FOnClickedはAPIによると、
となっています。つまりタイプはFReplyです。ButtonClickedメンバー関数の返し値のタイプもFReplyです。この事を同じsignatureを持っているといっているのでしょうか。
はい。
以下に示す部分のコードの解説です。
Content()の中身の解説です。私が覚えている限りでは、Content()の中身は常に、SNew()を使用していたはずです。SAssignNew()とは何でしょうか?
色々調べたのですが以下の説明が最も分かり易かったのでそのまま引用します。ここからコピーしました。
今までは、TextBlockのテキストを表示するのに、SNewを使用していました。例えば以下に示す前回のコードでは、
STextBlockを作成するのに、SNewが使用されています。
今回は、テキストの文字を変更するため、STextBlockのポインターが必要です。ので、以下に示すように、予め、ClickEventGameModeのヘッダーファイルで、
STextBlockのポインター用の変数を宣言してありました。これをSNewを使用して初期化すると、
ButtonLabel = SNew(STextBlock);
と書かなければなりません。しかし、今回のSTextBlockはContent()内のウィジェットなのでこの書き方は出来ません。ではどうするかと言うと、SAssignNewを使用するのです。SAssignNewを使用すると、
と書く事が出来ます。
これで、このSTextBlockにButtonLabel 変数を使用する事で、他のメンバー関数からもアクセス出来るようになりました。これが、SNewの代わりにSAssignNewを使用する意味と、SAssignNewが存在する意義です。
まずは、解説文についてですが、
variable to point -> variable to the pointer
じゃないかなーと思います。特定の子供のウィジェットを指す変数と言う言い方は、理解は出来ますが、かなり曖昧な言い方です。SAssignNewの使用方法並びに目的、SNewとの相違を理解した今では、この曖昧な言い方でも正確に18、の内容を理解出来ますが、最初読んだ時は何が言いたいのかはっきりしませんでした。その時は、variables to the pointerが正しい文で、校正の最中に間違えてvariable to pointに成ってしまったのかな?と思って訳しました。ポインターの変数、特定の子供のウィジェットのためのポインターの変数と言う言い方は、少なくと曖昧ではありません。
個々で書かれている事は、17.で考察した内容の繰り返しです。まあ、17.の考察が正しいかったと言う事の証明にはなりました。
以下に、SAssignNewの実際のコードを示します。19.はSAssignNewの最初のargumentについての解説です。
SAssignNewはSNewと同じくマクロです。関数ではないです。そのせいなのでしょうか。ここではargumentと呼んでいます。パラメーターとargumentの違いについては、既にこのブログで勉強したので、簡単に述べますが、実際のデータをargumentと呼び、タイプを指定するための変数やオブジェクトを使用する場合(関数の初期化の時など)はパラメーターと言います。ButtonLabelは実際のデータですから、argumentであります。あれ?ここではargumentと呼んでいる事と、SassignNewがマクロである事は関係なさそうです。
18.でpoint を the pointerとあえて変更して訳しましたが、今回はそのままPointing at で訳しました。20.で言っている事は、ButtonLabel変数が我々のボタンのTextBlockのポインターを保存しているので、そのTextBlockのテキストをいつでもButtonLabel変数を通して変えられますと言う事です。
以下に示す部分のコードの解説です。
AddIViewportWidgetForPlayer関数については散々、まえのレシピで考察したので、今回はスキップします。
GetFirstLocalPlayerFromController()についての解説です。この関数についての考察も同じ9章の他のレシピで散々したので、今回はスキップします。
この23.は何を言っているのか良く分かりません。スキップします。
以下に示す部分のコードの解説をしています。
これを行う事で、最初のプレイヤーのゲーム画面にカーソルを表示します。
以下に示す部分のコードについての解説です。
25.は以下の事を述べています。
(a) SetInputModeは我々にあるウィジェットに集中する事を許可します。
(b) ので、プレイヤーは他のUIに関連する機能の中からそのウィジェットと対話出来ます。
(c) 他のUIに関連する機能とは、例えばゲームのviewportに対するマウスをロックする事です。
まず、(a)のウィジェットに集中するとは何のことでしょうか?解説文を読んだだけでは全く分かりませんが、コードを見れば分かります。SetWidgetToFocus()関数について解説しているのです。では、SetWidgetToFocus()関数のAPIを見てみましょう。
集中するウィジェット
とだけ解説され何のための関数なのか更に分からなくなってしまいました。のでSetWidgetToFocus()関数を持つFInputModeUIOnlyのAPIを見てみます。
そのUIのみにユーザーのインプットに反応する事を許可するインプットのモードをセットするために使用されるデータストラクチャー
とありました。ので、ウィジェットに集中するとは、そのウィジェットのみがユーザーのインプットに対して反応する事を指していると考えられます。
ここで、一つ大事な事に気が付きましたが、APIでは、FInputModeUIOnlyクラスがストラクチャーとして紹介されてました。クラスじゃないのと思って、実際のコードを見てみると、
ストラクチャーとしてしっかり定義されていました。Fで始まる名前は、ストラクチャーであるとここに書かれていましたが、すっかり忘れていました。
次に、(b)に関する解説ですが、「ので、プレイヤーは他のUIに関連する機能の中からそのウィジェットと対話出来ます。」とありますが、これもSetWidgetToFocus()関数の機能であるそのウィジェットのみがユーザーのインプットに対して反応する事を解説していると考えられます。
最後の(c)ですが、「他のUIに関連する機能とは、例えばゲームのviewportに対するマウスをロックする事です。」とありますが、ここが問題なのです。「他のUIに関連する機能」は、プレイヤーの動きに関するインプット、ゲームの実行に関するインプットなどを指しているはずです。なのに、「例えば」の後に続く例が、「ゲームのviewportに対するマウスをロックする事です。」になっています。これは、FInputModeUIOnlyのもう一つの関数であるSetLockMouseToViewportBehavior関数に対しての解説です。
なので、訳を以下のように直しました。
SetLockMouseToViewportBehavior関数については、もう一つ解説しておかなければならない事があります。
教科書とPacketPulish社のサンプルコードでは、SetLockMouseToViewportBehavior関数は使われず、SetLockMouseToViewport関数が使われていました。この関数は、FInputModeUIOnlyのAPIを見ても、実際のコードを見ても、存在していず、使用出来ません。ので、実際に存在し、かつFInputModeUIOnlyの変数であるEMouseLockMode MouseLockModeを正しく定義出来るSetLockMouseToViewportBehavior関数をここでは、使用しました。
これも、25.と同じFInputModeのパラメーターに関する解説です。Builderバターン…に関しては良く分かりませんが。
ここも、25.で既に解説した内容の繰り返しですが、FInputModeUIOnlyについては更に詳しく解説しているので、少しだけ考察します。
以下の事が、27.では述べられています。
(a) FInputModeUIOnlyクラスはFInputModeのサブクラスです
(b) このサブクラスは、ある設定が出来ます。
(c) その設定とは、「全てのインプットのイベントがプレイヤーのコントローラーや他のインプットのハンドリングでなくUIのレイヤーにリダイレクトするようにする。」事です。
まず(a)に関してですがFinputModeUIOnlyはストラクチャーでクラスではないと25.で述べたばかりですが、27.ではクラスと紹介されています。何なのでしょうか?思わず、「ほげぇー」と言ってしまいました。でも、FInputModeUIOnlyのコードやAPIを見ると、確かにコンストラクターが合って、メンバー関数があって、クラスと言われればクラスです。
FInputModeUIOnlyがクラスなのか、ストラクチャーなのか本筋と全く関係ない処で大変な事になってしまいました。この件に関してはおまけで続きを行いたいと思います。
最後の、(c)ですが、「…インプットのイベントがプレイヤーのコントローラーや他のインプットのハンドリングでなく…」と述べています。このプレイヤーのコントローラーや他のインプットが、25.で述べている「…他のUIに関連する機能の中…」の具体的な例を説明しています。
以下に示す事を28.は述べています。
(a) builderのパターンはある事を我々に許可します。
(b) ある事とは、オブジェクトのインスタンスをカスタム化するメソッドを要求する事。
(c) しかもそれは、オブジェクトのインスタンスがパラメーターとしてその関数内に送られる前に行えます。
まず、(a)のBuilderのパターンが何かが分かりません。調べて見ます。wikiによれば、オブジェクト言語におけるデザインの4大パターンの内の一つとありました。うーん。これはひょっとするとプログラマーならば、必ず知っていなければならないレベルの知識みたいです。全く記憶にありませんでした。今から、勉強するしかありません。YouTubeにBuilder Design Pattern In C++と言うビデオがありました。これをまず勉強します。
<Builder Design Pattern In C++のまとめ>
- 飛行機を作る。
- 飛行機は、本体、エンジンなどのそれぞれ複雑な部分から構成されている。さらにそれらの部分は複雑に他の部分と関係しあっている。
- どうやって飛行機を作るか?
- この作り方の具体的な手順をデザインパターンと言います。Builderデザインパターンは、そのデザインパターンの一つです。
- Builderデザインパターンでは、それぞれの部分、この場合、飛行機の本体やエンジンを順番(step by step)に作成していきます。
8分間のビデオにしては大変簡潔にまとまっていました。基本的なデザインパターン並びにBuilderパターンの考え方は理解出来ました。もっと勉強するためには、以下に示す
本を読めばいい事も分かりました。後で、軽くは読んでおきます。とりあえず、UE4の勉強とは関係ないので、この話題はここで打ち切ります。
(b)と(c)についてはもうはっきりしました。Builderパターンでデザインしているからパラメーターとしてパスされるそれぞれのオブジェクトがパスされる前に作成されるので、それらをカスタム化してからパスする事も可能です。と言う事です。
SetLockMouseToViewport関数は、FInputModeUIOnlyには存在しないので、代わりにSetLockMouseToViewportBehavior関数を使用しました。他の解説については、25.で既に考察した内容の繰り返しですので、スキップします。
やっと次の部分のコードに移りました。以下にその部分のコードを示します。
はい。
この関数が実行されると、FReplyが返されます。その結果、起きる事を32.は解説しています。特に不思議な事はないと思います。
33.はFReply::Handled()についての解説です。FReply::HandledのAPIには、以下のように示しています。
あるイベントはシステムがそのイベントがハンドルされた事を知れるように、FReply::Handled()を返すべきです。
特に付け足す事はありません。
34.はFReply::Handled()の代わりにFReply::Unhandled()を使用した場合の弊害についての解説です。FReply::UnhandledはAPIによると、
あるイベントはシステムがそのイベントがアンハンドルされた事を知れるように、FReply::UnHandled()を返すべきです。
とあります。34.の解説で述べられている事に対する確認は、APIからは得られませんでした。
<まとめ>
Slateのイベントに関数の呼び出しをつける方法を今回は勉強しました。今回のレシピでは、ボタン上のSTextBlockをイベント(ボタンをマウスでクリック)中に呼び出してテキストの表示を変えました。これを可能にするためには、ボタン上のSTextBlockに対してのポインターが必要でした。このポインターはSNewマクロの代わりにSAssignNewマクロを使用する事で得られます。SAssignNewマクロを使用して得られたSTextBlockウィジェットのアドレスを保持するポインター変数は、他の関数からもアクセス出来、もちろんデリゲート関数内でも使用できます。
以下に手順を示します。
1.まず、ヘッダーファイルで、変数を宣言します。
今回は、STextBlockのポインターを保持するための変数なので、上記のように宣言されています。
2.イベント中にアクセスしたいウィジェットを作成する時に、SAssignNewマクロを使用してそのウィジェットのアドレスを作成した変数に保存します。
今回の場合はイベント中に、アクセスしたいウィジェットはSTextBlockなので上記のようになります。
3.イベント中に実行するデリゲート関数内からその変数を使用して、ボタン上のSTextBlockを呼び出します。
<おまけ>
FInputModeUIOnlyはクラスなのか、ストラクチャーなのでしょうか?
ストラクチャーである理由は以下に示すものがあります。
1.FInputModeUIOnlyのAPIで以下のように述べています。
そのUIのみにユーザーのインプットに反応する事を許可するインプットのモードをセットするために使用されるデータストラクチャー
2.Fで始まる名前はstructureである。
3.実際のコードでStructと定義している。
クラスである理由は以下に示すものがあります。
- コンストラクターがある。
- メンバー関数がある。
どちらが正しいのでしょうか?