<前文>
で勉強しています。今回は、「9章4節 ゲーム中にUMG要素のシートを隠したり表示したりする」を勉強します。
<本文>
<目的>
ゲーム中にUMG要素のシートを隠したり表示したりする方法を学びます。同じ方法で操作しますが、毎回反対の効果をもたらすタイプのコマンドをtoggleといいます。今回の何かを繰り返し表示したり隠したりする事もtoggleを使用して表現するみたいです。だた日本語訳もトグルするとなっていました。そんな日本語って実際に存在しているのでしょうか?存在したとしてもそんな訳があるのかなって思います。だってただ発音を日本語で表記しただけですから。もっとこう専門用語らしい漢字を使用した単語を作成出来なかったのでしょうか。これだったらアルファベットでtoggleと表記した方がまだましかもしれませんよね。前回の serializeの訳が直列にするであったのと対照的ですね。Toggleの訳がトグルでは、???になってしまいます。
<方法>
Step.0
今回も、新しいプロジェクトを作成します。Unreal Engineのversionは、4.19.2です。プロジェクトの名前は、Chapter9_part4_0619としました。
Step.1
前回と同じように、クラスウィザードから作成します。GameModeクラスはGameModeBaseクラスと違いますので、Show All ClassesをチェックしてGameModeクラスを選択しなければなりません。
ToggleHUDGameModeと名付けます。このモジュール内の全てのクラスは、他のモジュールから使用される予定はないので、Privateを選択します。
以下のように出来ました。
Step.2
以下に示すようにしました。
Step.3
追加しました。
Step.4
追加しました。
がエラーが出ています。とりあえず、PackePublish社のsample codeを見てみますと、
特に、何もincludeしていません。とりあえず、UE4のAPIからSVerticalBoxのヘッダーファイルを調べてみると、
と書かれていて、流石に私もSlateCoreモジュールがこのモジュールから使用出来るようになっていない事に気が付きました。何か毎回このミスを繰り返してます。
以下に示すように、Build.csを開き、PrivateDependencyModuleNames.AddRange(new string { "Slate", "SlateCore" });をアンコメントします。
以下に示すようにそれでもエラーが出ています。
試しに、buildしてみましたが、以下に示すように、失敗しました。
SVerteicalBoxのヘッダーファイルをincludeしてみます。Includeする場所はgenerated.hファイルの上です。
今度は、成功しました。
上のイメージでGENERATED_BODY()がエラーを吐いていますが、時間がたつと消えます。
Step.5
以下に示すように実装しました。
GEngineクラスを使用しているので、Engine.hファイルをincludeしました。
Step.6
以下に示すようにしました。
Buildも成功しました。
<結果>
Step.7
しました。
Step.8
しました。
Step.9
しました。
Step.10
しました。
出来ました。
<考察>
今回も、How it works…を読んでいきましょう。
トグルについて最初に確認しておきます。目的のところで少し語りましたが、トグルはtoggleの日本語訳です。その定義は、
です。同じ動作をしているにも関わらず、正反対の効果が交互に現れるのがトグルです。今回はそのトグルの使い方を勉強すると言う事でしょう。
Garbageはガーベジと書かれたりガーベジと書かれたりしているみたいです。どっちも実際の発音と全然違うように思います。私はガーベジ(garbage)と書く事にしました。
以下の事が2.では述べられています。
(a) レファレンスをUPROPERTYにしてタイマーに保存
(b) URPOPERTYなのでガーベジとして回収されない。
以下に、ToggleHUDGameModeヘッダーファイルから2.が解説しているコードを示します。
まず、レファレンスは、オブジェクトの変数やメンバー関数が保存されているアドレスです。この場合HUDToogleTimerのデータを保存しているアドレスの事です。このアドレスをUPROPERTYを付ける事で保存します。これは分かります。更に「タイマーに保存します。」の意味もFTimerHandleクラスがタイマーのクラスのようですし、タイマーとして保存と考えれば納得出来ます。FTimerHandleクラスのAPIによれば、FTimerHandleは、
全く同じデリゲートを持つタイマーを区別するために使用される唯一のハンドル
と説明されています。
次の(b)が良く分かりません。URPOPERTYをつけると、ガーベジコレクションが自動的に回収してくれるのではなかったでしたっけ。回収されないとは?
この疑問はEndPlay関数を見れば分かると思います。
ウーン。何かHUDToogleTimerを開放しているみたいです。となるとこの解説で正しいようです。
ここからは、BeginPlay関数の実装部の説明のようです。とりあえず以下にコードを示します。
以下の事が、3.では説明されています。
(a) SNewマクロを使用して新しいVerticalBoxを作成。
(b) 最初のスロットにボタンを配置。
以下に示したコードの部分の説明です。
なぜが、SNewがマクロと紹介されています。一応確認してみますが、クラスでなかったでしたっけ。
思いっきりマクロでした。
以下に示す部分のコードについての解説です。
確かにボタンにContentが付いています。そのContent内にSTextBlockが付いています。SButtonのAPIを見てみると、
Slateのボタンはクリック出来るウィジェットです。そのウィジェットはそのContentとして任意のウィジェットを含む事が出来ます。
SButtonのContentについて説明していました。
以下の事が5.で説明されています。
(a) Contentスロット内にSTextBlockを配置
(b) 我々がボタンを正確に見る事が出来るくらいの時間があればテキストブロックの 内容はどうでもいい。
(a)については、以下に示したコードのSNew(STextBlock)に関する説明です。
このレシピをプレイ中このボタンは5秒毎に点滅するはずです。この点滅中にボタン上のテキストが読めるかどうかは、我々には全然重要な問題ではないよ。と(b)は言っています。
ボタンの作成方法については、Slateの文法そのままで特にここで考察する必要はないと思います。
以下のコードに関する解説です。
6.では以下の事が述べられています。
(a) プレイヤーのビューポートにルートウィジェットを追加
(b) ルートウィジェットはプレイヤーから見れるようになる
まず、(a)の前にウィジェットヒラレルキー(Widget hierarchy)とは何でしょうか?多分、VerticalBox→ボタン→テキストのウィジェットの関係を指しています。でもこのウィジェットの初期化はすでに終わっているはずと思ったらhaving initializedと書いていました。つまり初期化が終わったので、(a)を行うと言う意味だったのです。(a)は、上記のコードのAddViewportWidgetForPlayerが行っています。その結果、(b)が起きるとあります。
では、AddViewportWidgetForPlayer関数を見てみましょう。
あれ、これって9章2節で勉強しましたね。
今、9章2節を読み直してみると、訳わからんといいながら結構、使い方に関しては適切な考察をしています。前回、何が訳わからなかったというと、remarkの説明とhow it works…の解説が自分の中でよくかみ合わなかったのでした。今回は、AddViewportWidgetForPlayer関数の目的については、もうはっきり分かっています。我々のウィジェットをプレイヤー画面に表示する事です。そのためには、ウィジェットを表示するplayercontrollerと表示するウェジェットをパスする必要があります。のでその二つは必ずパラメーターとしてパスしなければなりません。更に、競合する2Dのイメージも表示されている場合、優先順を決定するためにZ-orderの指定をする必要もあるでしょう。最近のゲームはマルチプレイの場合も多いので、それぞれのプレイヤーで表示する内容が全く違う事はよくある事でしょうからこれらの事を指定する必要があります。それをAddViewportWidgetForPlayer関数のremarkは説明しています。それだけです。たった2週間前、良く分からなかったものが今は普通に分かるようになりました。勉強を続ける事は偉大ですね。
ただし、今も分からない事があります。
GEngine->GameViewport->AddViewportWidgetForPlayer
の関係です。今回はここを少しだけ調べて見たいと思います。
まず、GEngineですが、APIによると
UEngineクラスのインスタンスのポインターみたいです。このポインターが指すUEngineクラスのインスタンスをGlobal engineと呼んでるのでしょうか。それが一つのプロジェクトに一つだけあるみたいで(ない場合のあると説明されている)、そこにはエンジンに関するいろいろな事が決定されているようです。APIではない場合もあるので必ずチェックしろとあるのでチェック方法を調べて見ます。
If(GEngine)
でいいみたいです。試してみます。
普通にビルドしました。特に問題はないようです。
次に、GameViewportについて調べて見ます。UEngineのAPIによると、UGgameViewportClientクラスのインスタンスで、UEngineクラスの変数の一つとあります。
現在のゲームインスタンスにおけるviewportを表している。0である可能性があるのでチェックしてから使います。
GameViewportはずっとメンバー関数と思っていました。変数だったのですね。GameViewportのタイプであるUGameViewportClientのAPIを調べて見ると、
ゲームのviewportはハイレベルの抽象的なインターフェイスで特定のプラットフォームのレンダリング、オーディオ、そしてインプットのサブシステムに使用されます。
GameViewportClientはGameviewport用のエンジンのインターフェイスです。
それぞれのゲームのインスタンスに対して一対一で対応するGameViewportClientが作られます。
エンジンのインスタンスが一つなのに、複数のゲームのインスタンス(そして複数のGameViewportClient)がある(今までで)唯一の例は、一つ以上のPIEウィンドウを実行している時です。
担当:インプットのイベントをグローバルのinteraction listに伝える。
とありました。UIの表示以外に、オーディオやインプットの伝達なども担当しているクラスのようです。
Gameviewportも0である可能性があるのでチェックしてから使用しろとあります。やってみます。
一応、ビルトもしてみます。
特に問題なく動きますが、実際ここまでする必要はあるのでしょうか?
かなりの意訳になってしまっていますが、以下の事が7.では述べられています。
(b) ウィジェットの可視性をトグルするためにタイマーをセット
これだけです。これからは、以下のコードについての解説です。
8.では以下の部分のコードについて解説されています。
GetWorld関数は、GameModeクラスからも呼べるのですね。WorldクラスとGameModeクラスの関係が知りたいですのですこし調べてみます。
まず、AGameModeクラスのAPIをみると、
AGameModeクラスはAActorクラスのサブクラスなので、GetWorld関数が使えるのは間違いないようです。何故か、GameModeクラスの方が、Worldクラスより上のような気がしてました。
World Settings でGameModeのオーバーライドが出来ると言う事は、一つのWorld内に少なくとも一つのGameModeが存在していると言う事みたいです。よってGameModeクラスからWorldクラスを呼ぶ事が出来るようです。
GetTimerManager関数のAPIをみてみます。
このWorldのためにTimerManagerのインスタンスを返す
とあります。ここで、GetTimeManagerはUWorldクラスのメンバー関数なので、FTimerManagerクラスのインスタンスが変数としてUWorldにあるかと思ったのですが、見つからなかったです。そういえばGameModeクラスのインスタンスを保持する変数もUWorldにあるかと思って調べたのですがこれも見つかりませんでした。
ここからは、SetTimer関数についてです。FTimerManager関数のAPIをみると、
より、このSetTimerのAPIがこのレシピに使用されたSetTimer関数と考えられます。
Remarkには、
全ての一般的なデリゲートを受け取るバージョン
とありました。
このAPIの説明だけではこのSetTimer関数の機能は分かりませんが、SetTimerと言っている以上Timerをセットするはずです。そのセットされるタイマーが、ToggleHUDGameModeヘッダーファイルで宣言された奴です。
以下に示します。
このインスタンスは宣言はされましたが、初期化はされていません。ここでSetTimer関数が何かしらの値をHUDToggleTimerに保存する事で9.で解説されているように、我々が新しいタイマーを作成するのでしょうか?
SetTimer関数でラムダ関数を使う必要があると言っています。Hud関数とは、HUD関数の事です。
はい。
はい。
CreateLamdba関数の部分だけ下に抜き出してみました。
CreateLamdba関数の正確な書き方はまだ完全にはわかりませんが、if節の内容でウィジェットの可視性のオンオフをトグルしているのは分かります。
SetVisibility関数についてですが、以下に示すように、SVerticalBox クラスの関数でもなく、SWidgetクラス関数でもなくSPanelクラスの関数でした。
一方でGetVisibility関数は、
SWidgetクラスの関数でした。GetVisiblity関数が取得するEVisiblityタイプの変数は、SWidgetクラスにある、Visibilityです。
ウィジェットは見えるか
隠れているもしくは崩れている
大体意味は分かったのですが、何か物足りないので、自分で少し試してみました。
Visibilityの代わりに、Swidgetクラスにある変数EnabledStateとその変数のためのgetter, setterであるIsEnabled()関数と、SetEnabled()関数をGetVisibilityの代わりに使用しました。
普通にビルドして、更に、エディターからゲーム画面を実行しても普通にボタンが表示され、5秒毎にオン、オフでトグルしました。
Thisポインターを使う必要があると言っています。Thisポインターがあれば、我々がここで作成したウィジェットのプロパティの状態を変化出来ると言っています。
基本的には、
ここの[this]の説明です。CreateLambdaの後に。[ ]オペレーターを使用する事で、その[ ]内の変数に、CreateLamda関数からアクセス出来るようになるそうです。試しにthisを抜いてみました。
Thisポインターがエラーになっていますね。[ ]内で使用する変数(この場合this)を指定しないと、Lambda内で使用出来ない事が確認出来ました。で、閃いたのですが、変数をパス出来るならthisの代わりにwidgetでもいいんじゃないでしょうか。試してみます。
駄目でした。エラーの理由が「メンバーwidgetは変数ではありません。」とでています。WidgetはAToggleHUDGameModeクラスの変数ではあると思うのですが。???な結果です。
それで、どんな変数なら[ ]内でエラーにならないのか、googleしてみました。
Thisが一番多いみたいです。その他に、&を使用している例もあります。&は一般的に考えられる変数ではないので、やはり内に好き勝手な変数は入れられないみたいです。
もう少し詳細な内容がみたいので、CreateLambda[FullFilename]のコードを見てみます。以下に示すコードはここから引用しました。
このコードを見る限り、FullFilenameはただの変数ですね。ウーン。何かここだけ見ると普通のC++と同じ使い方のように見えますね。以下にcppreference.comからのLambdaにおける[ ]内の変数の使い方についての解説を示します。ここから引用しています。
上記のイメージを見ると、&、=などの意味が説明しています。どうもwidgetが使えなかったのは、widgetが変数でなくメンバーであるからみたいです。
ここによると、
Widgetはメンバー変数にあたるみたいですね。多分、これが駄目な理由なのでしょう。
以下の部分の説明をしています。それぞれのメンバー関数については既に調べていますし、使い方は一般的なC++です。
17.も18.も既に勉強した箇所の説明です。これ以上、ここで考察する必要はないでしょう。
以下に示す、SetTimerメンバー関数のパラメーターのFloat InRateの部分の説明です。
これが、2.の(b)で述べていた「URPOPERTYなのでガーベジとして回収されない。」の本当の意味なのでしょうか?HUDToggleTimerがガーベジとして回収されたら実行中にクラッシュするのでしょうか。多分そういう事なのでしょう。
以下にEndPlayメンバー関数の実装部を示しておきます。
以下にClearTimer関数のAPI からremarkを示します。
前にセットされたタイマーをクリアーします。
SetTimer()関数をa<=0.fの割合で呼ぶのと同じ
タイマーハンドルを二度と使えないように無効にする
はい。
はい。
はい。
はい、確認しました。
はい。
<まとめ>
今回は、「9章4節 ゲーム中にUMG要素のシートを隠したり表示したりする」を勉強しました。今回勉強した中で最も重要な事の2つの内の1つ、CreateLamdba関数の使い方です。
Lambda関数は最初の[ ]で関数の実装部で使用する変数(今回、我々が試した限りではメンバー変数であるWidgetは変数として使用できませんでした。)を指定する必要があります。ラムダ関数の実装は{ }内で行います。また、トグルの使用の方法も、特別なドクル用の関数があるのではなく、シンプルにif節とboolでこのLambda関数内で作成しています。
今回勉強した中で最も重要な事の2つの内のもう一つは、Timerの使い方です。今回のレシピでは、FTimerHandleクラスを使用しました。使用方法について簡単に述べると、まずBeginPlay関数内でFTimerManagerクラスのsetTimerメンバー関数を使用してFTimerHandleクラスのインスタンスの初期化*を行います。この時使用されるFTimerHandleクラスのインスタンスは、以下に示すように、
UWorldクラスのメンバー関数であるGetTimerManagerが取得してくれます。
FTimerHandleクラスは、EndPlay関数で開放する必要があります。以下に示す方法で開放します。
*SetTimerメンバー関数の実際のレシピでの使用は、GetWorld()->GetTimerManager().SetTimer(HUDToggleTimer,…であり、このように、FTimerHandleクラスのインスタンスを、関数のパラメーターとしてパスする事で、そのパラメーターに値を入れる方法を初期化と呼んでいいのか私は知りません。ここでは便宜的にそう呼びました。
<おまけ>
今回は、特にありません。