UE4の勉強記録

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

第9章4節 ゲーム中にUMG要素のシートを隠したり表示したりする

<前文>

f:id:kazuhironagai77:20180617162610p:plain

で勉強しています。今回は、「9章4節 ゲーム中にUMG要素のシートを隠したり表示したりする」を勉強します。

<本文>

<目的>

ゲーム中にUMG要素のシートを隠したり表示したりする方法を学びます。同じ方法で操作しますが、毎回反対の効果をもたらすタイプのコマンドをtoggleといいます。今回の何かを繰り返し表示したり隠したりする事もtoggleを使用して表現するみたいです。だた日本語訳もトグルするとなっていました。そんな日本語って実際に存在しているのでしょうか?存在したとしてもそんな訳があるのかなって思います。だってただ発音を日本語で表記しただけですから。もっとこう専門用語らしい漢字を使用した単語を作成出来なかったのでしょうか。これだったらアルファベットでtoggleと表記した方がまだましかもしれませんよね。前回の serializeの訳が直列にするであったのと対照的ですね。Toggleの訳がトグルでは、???になってしまいます。

<方法>

Step.0

今回も、新しいプロジェクトを作成します。Unreal Engineのversionは、4.19.2です。プロジェクトの名前は、Chapter9_part4_0619としました。

Step.1

f:id:kazuhironagai77:20180624113841p:plain

前回と同じように、クラスウィザードから作成します。GameModeクラスはGameModeBaseクラスと違いますので、Show All ClassesをチェックしてGameModeクラスを選択しなければなりません。

f:id:kazuhironagai77:20180624113906p:plain

ToggleHUDGameModeと名付けます。このモジュール内の全てのクラスは、他のモジュールから使用される予定はないので、Privateを選択します。

f:id:kazuhironagai77:20180624113937p:plain

以下のように出来ました。

f:id:kazuhironagai77:20180624114002p:plain

Step.2

f:id:kazuhironagai77:20180624114032p:plain

以下に示すようにしました。

f:id:kazuhironagai77:20180624114102p:plain

Step.3

f:id:kazuhironagai77:20180624114137p:plain

f:id:kazuhironagai77:20180624114151p:plain

追加しました。

f:id:kazuhironagai77:20180624114215p:plain

Step.4

f:id:kazuhironagai77:20180624114251p:plain

f:id:kazuhironagai77:20180624114306p:plain

追加しました。

f:id:kazuhironagai77:20180624114332p:plain

がエラーが出ています。とりあえず、PackePublish社のsample codeを見てみますと、

f:id:kazuhironagai77:20180624114403p:plain

特に、何もincludeしていません。とりあえず、UE4APIからSVerticalBoxのヘッダーファイルを調べてみると、

f:id:kazuhironagai77:20180624114433p:plain

と書かれていて、流石に私もSlateCoreモジュールがこのモジュールから使用出来るようになっていない事に気が付きました。何か毎回このミスを繰り返してます。

以下に示すように、Build.csを開き、PrivateDependencyModuleNames.AddRange(new string { "Slate", "SlateCore" });をアンコメントします。

f:id:kazuhironagai77:20180624114501p:plain

以下に示すようにそれでもエラーが出ています。

f:id:kazuhironagai77:20180624114542p:plain

試しに、buildしてみましたが、以下に示すように、失敗しました。

f:id:kazuhironagai77:20180624114605p:plain

SVerteicalBoxのヘッダーファイルをincludeしてみます。Includeする場所はgenerated.hファイルの上です。

f:id:kazuhironagai77:20180624114639p:plain

今度は、成功しました。

f:id:kazuhironagai77:20180624114704p:plain

上のイメージでGENERATED_BODY()がエラーを吐いていますが、時間がたつと消えます。

Step.5

f:id:kazuhironagai77:20180624114902p:plain

f:id:kazuhironagai77:20180624114916p:plain

以下に示すように実装しました。

GEngineクラスを使用しているので、Engine.hファイルをincludeしました。

f:id:kazuhironagai77:20180624114949p:plain

Step.6

f:id:kazuhironagai77:20180624115027p:plain

f:id:kazuhironagai77:20180624115042p:plain

以下に示すようにしました。

f:id:kazuhironagai77:20180624115121p:plain

Buildも成功しました。

f:id:kazuhironagai77:20180624115149p:plain

<結果>

Step.7

f:id:kazuhironagai77:20180624115229p:plain

しました。

Step.8

f:id:kazuhironagai77:20180624115327p:plain

しました。

Step.9

f:id:kazuhironagai77:20180624115413p:plain

しました。

f:id:kazuhironagai77:20180624115446p:plain

Step.10

f:id:kazuhironagai77:20180624115516p:plain

しました。

f:id:kazuhironagai77:20180624115541p:plain

f:id:kazuhironagai77:20180624115557p:plain

出来ました。

<考察>

今回も、How it works…を読んでいきましょう。

f:id:kazuhironagai77:20180624121634p:plain

トグルについて最初に確認しておきます。目的のところで少し語りましたが、トグルはtoggleの日本語訳です。その定義は、

f:id:kazuhironagai77:20180624121659p:plain

です。同じ動作をしているにも関わらず、正反対の効果が交互に現れるのがトグルです。今回はそのトグルの使い方を勉強すると言う事でしょう。

f:id:kazuhironagai77:20180624121729p:plain

Garbageはガーベジと書かれたりガーベジと書かれたりしているみたいです。どっちも実際の発音と全然違うように思います。私はガーベジ(garbage)と書く事にしました。

以下の事が2.では述べられています。

     (a) レファレンスをUPROPERTYにしてタイマーに保存
     (b) URPOPERTYなのでガーベジとして回収されない。

以下に、ToggleHUDGameModeヘッダーファイルから2.が解説しているコードを示します。

f:id:kazuhironagai77:20180624121817p:plain

まず、レファレンスは、オブジェクトの変数やメンバー関数が保存されているアドレスです。この場合HUDToogleTimerのデータを保存しているアドレスの事です。このアドレスをUPROPERTYを付ける事で保存します。これは分かります。更に「タイマーに保存します。」の意味もFTimerHandleクラスがタイマーのクラスのようですし、タイマーとして保存と考えれば納得出来ます。FTimerHandleクラスのAPIによれば、FTimerHandleは、

f:id:kazuhironagai77:20180624121844p:plain

全く同じデリゲートを持つタイマーを区別するために使用される唯一のハンドル

と説明されています。

次の(b)が良く分かりません。URPOPERTYをつけると、ガーベジコレクションが自動的に回収してくれるのではなかったでしたっけ。回収されないとは?

この疑問はEndPlay関数を見れば分かると思います。

f:id:kazuhironagai77:20180624121943p:plain

ウーン。何かHUDToogleTimerを開放しているみたいです。となるとこの解説で正しいようです。

f:id:kazuhironagai77:20180624122010p:plain

ここからは、BeginPlay関数の実装部の説明のようです。とりあえず以下にコードを示します。

f:id:kazuhironagai77:20180624122041p:plain

以下の事が、3.では説明されています。

     (a) SNewマクロを使用して新しいVerticalBoxを作成。
     (b) 最初のスロットにボタンを配置。

以下に示したコードの部分の説明です。

f:id:kazuhironagai77:20180624122131p:plain

なぜが、SNewがマクロと紹介されています。一応確認してみますが、クラスでなかったでしたっけ。

f:id:kazuhironagai77:20180624122313p:plain

思いっきりマクロでした。

f:id:kazuhironagai77:20180624122416p:plain

以下に示す部分のコードについての解説です。

f:id:kazuhironagai77:20180624122441p:plain

確かにボタンにContentが付いています。そのContent内にSTextBlockが付いています。SButtonのAPIを見てみると、

f:id:kazuhironagai77:20180624122556p:plain

Slateのボタンはクリック出来るウィジェットです。そのウィジェットはそのContentとして任意のウィジェットを含む事が出来ます。

SButtonのContentについて説明していました。

f:id:kazuhironagai77:20180624122646p:plain

以下の事が5.で説明されています。

     (a) Contentスロット内にSTextBlockを配置
     (b) 我々がボタンを正確に見る事が出来るくらいの時間があればテキストブロックの 内容はどうでもいい。

(a)については、以下に示したコードのSNew(STextBlock)に関する説明です。

f:id:kazuhironagai77:20180624122842p:plain

このレシピをプレイ中このボタンは5秒毎に点滅するはずです。この点滅中にボタン上のテキストが読めるかどうかは、我々には全然重要な問題ではないよ。と(b)は言っています。

ボタンの作成方法については、Slateの文法そのままで特にここで考察する必要はないと思います。

f:id:kazuhironagai77:20180624122918p:plain

以下のコードに関する解説です。

f:id:kazuhironagai77:20180624122942p:plain

6.では以下の事が述べられています。

     (a) プレイヤーのビューポートにルートウィジェットを追加

     (b) ルートウィジェットはプレイヤーから見れるようになる

まず、(a)の前にウィジェットヒラレルキー(Widget hierarchy)とは何でしょうか?多分、VerticalBox→ボタン→テキストのウィジェットの関係を指しています。でもこのウィジェットの初期化はすでに終わっているはずと思ったらhaving initializedと書いていました。つまり初期化が終わったので、(a)を行うと言う意味だったのです。(a)は、上記のコードのAddViewportWidgetForPlayerが行っています。その結果、(b)が起きるとあります。

では、AddViewportWidgetForPlayer関数を見てみましょう。

f:id:kazuhironagai77:20180624123031p:plain

あれ、これって9章2節で勉強しましたね。

f:id:kazuhironagai77:20180624123058p:plain

今、9章2節を読み直してみると、訳わからんといいながら結構、使い方に関しては適切な考察をしています。前回、何が訳わからなかったというと、remarkの説明とhow it works…の解説が自分の中でよくかみ合わなかったのでした。今回は、AddViewportWidgetForPlayer関数の目的については、もうはっきり分かっています。我々のウィジェットをプレイヤー画面に表示する事です。そのためには、ウィジェットを表示するplayercontrollerと表示するウェジェットをパスする必要があります。のでその二つは必ずパラメーターとしてパスしなければなりません。更に、競合する2Dのイメージも表示されている場合、優先順を決定するためにZ-orderの指定をする必要もあるでしょう。最近のゲームはマルチプレイの場合も多いので、それぞれのプレイヤーで表示する内容が全く違う事はよくある事でしょうからこれらの事を指定する必要があります。それをAddViewportWidgetForPlayer関数のremarkは説明しています。それだけです。たった2週間前、良く分からなかったものが今は普通に分かるようになりました。勉強を続ける事は偉大ですね。

ただし、今も分からない事があります。

     GEngine->GameViewport->AddViewportWidgetForPlayer

の関係です。今回はここを少しだけ調べて見たいと思います。

まず、GEngineですが、APIによると

f:id:kazuhironagai77:20180624123245p:plain

UEngineクラスのインスタンスポインターみたいです。このポインターが指すUEngineクラスのインスタンスをGlobal engineと呼んでるのでしょうか。それが一つのプロジェクトに一つだけあるみたいで(ない場合のあると説明されている)、そこにはエンジンに関するいろいろな事が決定されているようです。APIではない場合もあるので必ずチェックしろとあるのでチェック方法を調べて見ます。

     If(GEngine)

でいいみたいです。試してみます。

f:id:kazuhironagai77:20180624123353p:plain

普通にビルドしました。特に問題はないようです。

次に、GameViewportについて調べて見ます。UEngineのAPIによると、UGgameViewportClientクラスのインスタンスで、UEngineクラスの変数の一つとあります。

f:id:kazuhironagai77:20180624123430p:plain

現在のゲームインスタンスにおけるviewportを表している。0である可能性があるのでチェックしてから使います。

GameViewportはずっとメンバー関数と思っていました。変数だったのですね。GameViewportのタイプであるUGameViewportClientのAPIを調べて見ると、

f:id:kazuhironagai77:20180624123519p:plain

ゲームのviewportはハイレベルの抽象的なインターフェイスで特定のプラットフォームのレンダリング、オーディオ、そしてインプットのサブシステムに使用されます。

GameViewportClientGameviewport用のエンジンのインターフェイスです。

それぞれのゲームのインスタンスに対して一対一で対応するGameViewportClientが作られます。

エンジンのインスタンスが一つなのに、複数のゲームのインスタンス(そして複数のGameViewportClient)がある(今までで)唯一の例は、一つ以上のPIEウィンドウを実行している時です。

担当:インプットのイベントをグローバルのinteraction listに伝える。

とありました。UIの表示以外に、オーディオやインプットの伝達なども担当しているクラスのようです。

Gameviewportも0である可能性があるのでチェックしてから使用しろとあります。やってみます。

f:id:kazuhironagai77:20180624123624p:plain

一応、ビルトもしてみます。

f:id:kazuhironagai77:20180624123649p:plain

特に問題なく動きますが、実際ここまでする必要はあるのでしょうか?

f:id:kazuhironagai77:20180624123715p:plain

かなりの意訳になってしまっていますが、以下の事が7.では述べられています。

     (b) ウィジェットの可視性をトグルするためにタイマーをセット

これだけです。これからは、以下のコードについての解説です。

f:id:kazuhironagai77:20180624123804p:plain

f:id:kazuhironagai77:20180624123834p:plain

8.では以下の部分のコードについて解説されています。

f:id:kazuhironagai77:20180624123900p:plain

GetWorld関数は、GameModeクラスからも呼べるのですね。WorldクラスとGameModeクラスの関係が知りたいですのですこし調べてみます。

まず、AGameModeクラスのAPIをみると、

f:id:kazuhironagai77:20180624123935p:plain

AGameModeクラスはAActorクラスのサブクラスなので、GetWorld関数が使えるのは間違いないようです。何故か、GameModeクラスの方が、Worldクラスより上のような気がしてました。

f:id:kazuhironagai77:20180624124000p:plain

World Settings でGameModeのオーバーライドが出来ると言う事は、一つのWorld内に少なくとも一つのGameModeが存在していると言う事みたいです。よってGameModeクラスからWorldクラスを呼ぶ事が出来るようです。

GetTimerManager関数のAPIをみてみます。

f:id:kazuhironagai77:20180624124025p:plain

このWorldのためにTimerManagerインスタンスを返す

f:id:kazuhironagai77:20180624124102p:plain

とあります。ここで、GetTimeManagerはUWorldクラスのメンバー関数なので、FTimerManagerクラスのインスタンスが変数としてUWorldにあるかと思ったのですが、見つからなかったです。そういえばGameModeクラスのインスタンスを保持する変数もUWorldにあるかと思って調べたのですがこれも見つかりませんでした。

f:id:kazuhironagai77:20180624124127p:plain

ここからは、SetTimer関数についてです。FTimerManager関数のAPIをみると、

f:id:kazuhironagai77:20180624124150p:plain

より、このSetTimerのAPIがこのレシピに使用されたSetTimer関数と考えられます。

f:id:kazuhironagai77:20180624124213p:plain

Remarkには、

f:id:kazuhironagai77:20180624124242p:plain

全ての一般的なデリゲートを受け取るバージョン

とありました。

このAPIの説明だけではこのSetTimer関数の機能は分かりませんが、SetTimerと言っている以上Timerをセットするはずです。そのセットされるタイマーが、ToggleHUDGameModeヘッダーファイルで宣言された奴です。

以下に示します。

f:id:kazuhironagai77:20180624124335p:plain

このインスタンスは宣言はされましたが、初期化はされていません。ここでSetTimer関数が何かしらの値をHUDToggleTimerに保存する事で9.で解説されているように、我々が新しいタイマーを作成するのでしょうか?

f:id:kazuhironagai77:20180624124405p:plain

SetTimer関数でラムダ関数を使う必要があると言っています。Hud関数とは、HUD関数の事です。

f:id:kazuhironagai77:20180624124428p:plain

はい。

f:id:kazuhironagai77:20180624124456p:plain

はい。

f:id:kazuhironagai77:20180624124519p:plain

CreateLamdba関数の部分だけ下に抜き出してみました。

f:id:kazuhironagai77:20180624124547p:plain

CreateLamdba関数の正確な書き方はまだ完全にはわかりませんが、if節の内容でウィジェットの可視性のオンオフをトグルしているのは分かります。

SetVisibility関数についてですが、以下に示すように、SVerticalBox クラスの関数でもなく、SWidgetクラス関数でもなくSPanelクラスの関数でした。

f:id:kazuhironagai77:20180624124620p:plain

一方でGetVisibility関数は、

f:id:kazuhironagai77:20180624124645p:plain

SWidgetクラスの関数でした。GetVisiblity関数が取得するEVisiblityタイプの変数は、SWidgetクラスにある、Visibilityです。

f:id:kazuhironagai77:20180624124710p:plain

     ウィジェットは見えるか

     隠れているもしくは崩れている

大体意味は分かったのですが、何か物足りないので、自分で少し試してみました。

f:id:kazuhironagai77:20180624124808p:plain

Visibilityの代わりに、Swidgetクラスにある変数EnabledStateとその変数のためのgetter, setterであるIsEnabled()関数と、SetEnabled()関数をGetVisibilityの代わりに使用しました。

f:id:kazuhironagai77:20180624124829p:plain

普通にビルドして、更に、エディターからゲーム画面を実行しても普通にボタンが表示され、5秒毎にオン、オフでトグルしました。

f:id:kazuhironagai77:20180624124858p:plain

Thisポインターを使う必要があると言っています。Thisポインターがあれば、我々がここで作成したウィジェットのプロパティの状態を変化出来ると言っています。

f:id:kazuhironagai77:20180624124936p:plain

基本的には、

f:id:kazuhironagai77:20180624125012p:plain

ここの[this]の説明です。CreateLambdaの後に。[ ]オペレーターを使用する事で、その[ ]内の変数に、CreateLamda関数からアクセス出来るようになるそうです。試しにthisを抜いてみました。

f:id:kazuhironagai77:20180624125040p:plain

Thisポインターがエラーになっていますね。[ ]内で使用する変数(この場合this)を指定しないと、Lambda内で使用出来ない事が確認出来ました。で、閃いたのですが、変数をパス出来るならthisの代わりにwidgetでもいいんじゃないでしょうか。試してみます。

f:id:kazuhironagai77:20180624125112p:plain

駄目でした。エラーの理由が「メンバーwidgetは変数ではありません。」とでています。WidgetはAToggleHUDGameModeクラスの変数ではあると思うのですが。???な結果です。

それで、どんな変数なら[ ]内でエラーにならないのか、googleしてみました。

f:id:kazuhironagai77:20180624125145p:plain

Thisが一番多いみたいです。その他に、&を使用している例もあります。&は一般的に考えられる変数ではないので、やはり内に好き勝手な変数は入れられないみたいです。

もう少し詳細な内容がみたいので、CreateLambda[FullFilename]のコードを見てみます。以下に示すコードはここから引用しました。

f:id:kazuhironagai77:20180624125213p:plain

このコードを見る限り、FullFilenameはただの変数ですね。ウーン。何かここだけ見ると普通のC++と同じ使い方のように見えますね。以下にcppreference.comからのLambdaにおける[ ]内の変数の使い方についての解説を示します。ここから引用しています。

f:id:kazuhironagai77:20180624125244p:plain

上記のイメージを見ると、&、=などの意味が説明しています。どうもwidgetが使えなかったのは、widgetが変数でなくメンバーであるからみたいです。

ここによると、

f:id:kazuhironagai77:20180624125415p:plain

Widgetはメンバー変数にあたるみたいですね。多分、これが駄目な理由なのでしょう。

f:id:kazuhironagai77:20180624125444p:plain

以下の部分の説明をしています。それぞれのメンバー関数については既に調べていますし、使い方は一般的なC++です。

f:id:kazuhironagai77:20180624125514p:plain

f:id:kazuhironagai77:20180624125532p:plain

f:id:kazuhironagai77:20180624125551p:plain

17.も18.も既に勉強した箇所の説明です。これ以上、ここで考察する必要はないでしょう。

f:id:kazuhironagai77:20180624125618p:plain

以下に示す、SetTimerメンバー関数のパラメーターのFloat InRateの部分の説明です。

f:id:kazuhironagai77:20180624125642p:plain

f:id:kazuhironagai77:20180624125657p:plain

これが、2.の(b)で述べていた「URPOPERTYなのでガーベジとして回収されない。」の本当の意味なのでしょうか?HUDToggleTimerがガーベジとして回収されたら実行中にクラッシュするのでしょうか。多分そういう事なのでしょう。

以下にEndPlayメンバー関数の実装部を示しておきます。

f:id:kazuhironagai77:20180624125808p:plain

以下にClearTimer関数のAPI からremarkを示します。

f:id:kazuhironagai77:20180624125830p:plain

     前にセットされたタイマーをクリアーします。

     SetTimer()関数をa<=0.fの割合で呼ぶのと同じ

     タイマーハンドルを二度と使えないように無効にする

f:id:kazuhironagai77:20180624125910p:plain

はい。

f:id:kazuhironagai77:20180624125929p:plain

はい。

f:id:kazuhironagai77:20180624125951p:plain

はい。

f:id:kazuhironagai77:20180624130013p:plain

はい、確認しました。

f:id:kazuhironagai77:20180624130037p:plain

はい。

<まとめ>

今回は、「9章4節 ゲーム中にUMG要素のシートを隠したり表示したりする」を勉強しました。今回勉強した中で最も重要な事の2つの内の1つ、CreateLamdba関数の使い方です。

f:id:kazuhironagai77:20180624130109p:plain

Lambda関数は最初の[ ]で関数の実装部で使用する変数(今回、我々が試した限りではメンバー変数であるWidgetは変数として使用できませんでした。)を指定する必要があります。ラムダ関数の実装は{ }内で行います。また、トグルの使用の方法も、特別なドクル用の関数があるのではなく、シンプルにif節とboolでこのLambda関数内で作成しています。

今回勉強した中で最も重要な事の2つの内のもう一つは、Timerの使い方です。今回のレシピでは、FTimerHandleクラスを使用しました。使用方法について簡単に述べると、まずBeginPlay関数内でFTimerManagerクラスのsetTimerメンバー関数を使用してFTimerHandleクラスのインスタンスの初期化を行います。この時使用されるFTimerHandleクラスのインスタンスは、以下に示すように、

f:id:kazuhironagai77:20180624130231p:plain

UWorldクラスのメンバー関数であるGetTimerManagerが取得してくれます。

FTimerHandleクラスは、EndPlay関数で開放する必要があります。以下に示す方法で開放します。

f:id:kazuhironagai77:20180624130256p:plain

SetTimerメンバー関数の実際のレシピでの使用は、GetWorld()->GetTimerManager().SetTimer(HUDToggleTimer,…であり、このように、FTimerHandleクラスのインスタンスを、関数のパラメーターとしてパスする事で、そのパラメーターに値を入れる方法を初期化と呼んでいいのか私は知りません。ここでは便宜的にそう呼びました。

<おまけ>

今回は、特にありません。