<前文>
前々から言いたかったのですが、フラットデザインは間違っていると思うんです。何がって、その思想がです。無駄を極限まで省けば効率は上がるかもしれませんが、人間は遊びがなければ生きていけませんよ。
しかし最近、YouTubeであるビデオを見た時、フラットデザインが正しい分野もあると思うようになりました。そのビデオは、フーリエ級数かなんかを解説したビデオだったのですが、物理や数学でその辺りを勉強した人にとっては、とてつもなく分かり易くかつ衝撃的な内容なのでしたが、兎に角地味なんです。そのビデオを見ている最中で、かなり内容に集中している時でも、回りのボタンやら次の動画の紹介なんかが、気になって仕方がないんです。YouTubeは、かなりフラットデザインを取り入れていると思いますが、それでも数式の話をした場合、回りの背景が悪目立ちしちゃうんです。こういう所は、フラットデザインで徹底的に無駄を省いて、背景などのわき役が主役の数式より目立たなくする必要があると思いました。そうです。主役が数式などの地味な場合は、わき役はその地味な主役より目立たなくする必要があります。その時にわき役にフラットデザインを採用するのは正しいと思いました。
<本文>
<目的>
3章3節の5、ターン制の戦闘(turn based combat)の6、UIからの意思の決定(UI-driven decision making)を勉強します。
<方法と結果>
<Step.0>
前回まで使用していたCh2をそのまま今回も使用します。
<Step.1>
意思の決定(decision maker)をプレイヤーにどのタイミングで任せるのかを変更します。プレイヤーが最初に作成された時に意思の決定(decision maker)を任せるのではなくCombatUIWidgetクラスに意思の決定を実装して戦闘が開始した時に意思の決定(decision maker)を任せ、戦闘が終わった時に意思の決定(decision maker)を解消します。
GameCharacter.cppのいくつかを変更する必要があります。
最初にCreateCharacterのオーバーロード内から以下のコードの行を削除します。
character->decisionMaker = new TestDecisionMaker();
はい。
削除しました。
<Step.2>
次にBeginDestory関数内で、deleteのラインをif節でラップします。
しました。
教科書には以下のような解説がされていました。
これの理由は、プレイヤーのための意思の決定(decision maker)はUIになるが、そのUIを手動で開放したくない(するとUnrealがクラッシュするから)からです。
その代わりに、UPROPERTYで修飾されたポインターがそれを指していない限りUIは自動でガーベージに集められます。
UPROPERTYで修飾されたポインターがそれを指していない限りUIは自動でガーベージに集められます。
と読めますがUPROPERTYで修飾された場合はガーベージコレクションで自動的に開放されると思っていたのですが違うのでしょうか?
うーん。考察で考えます。
<Step.3>
次にCombatUIWidget.h内でIDecisionMakerのインターフェイスを実装するクラスを作成します。
そしてBeginMakeDecision()とMakeDesicion()をパブリック関数にします。
教科書に書かれている通りにUCombatUIWidget.h に関数と変数を追加しました。
いくつかの関数と変数について教科書で説明してあるのでそれを簡単に紹介します。
最初の関数は現状のキャラクターのために潜在的なターゲットのリストを取ってきます。
2番目の関数はそのキャラクターにターゲットと共に新しいTestCombatActionを与えます。
現状のキャラクターのための一連のアクションを示します。ブループリントで実装するための関数です。
フラッグとCurrentTarget変数です。これは意思の決定(decision maker)がされたというシグナル(MakeDecisionはtrueを返す)を送るのに使用されます。
<Step.4>
これらの4つの関数の実装はCombatUIWidget.cppで行われます。教科書に書かれている通りに実装します。
パスされたtargetでCurrentTarget変数を初期化します。勿論、意思の決定(decision maker)は終わっていないのでfinishedDecisionはfalseです。ShowActionPanel()関数は現状のキャラクターのための一連のアクションを示すブループリントで実装するための関数だそうです。ここからブループリントへ移行するのでしょうか?
MakeDicision()関数は意思の決定(decision maker)が終わっているかどうかを示す関数でしたね。その通りの機能が実装されています。
AttackTarget()関数を実装します。currentTarget変数のcombatAction変数をこの関数内で初期化したAction変数で指定します。
うーん。これって勝手に開放されるのでしょうか?まずcurrentTarget変数自体がUPROPERTY()で指定されていません。UPROPERTY()で指定されていない変数が自然に開放されるのかどうかがまず分かりません。そこは置いておいても普通のC++で考えてみればnewで指定したメモリー領域は勝手に開放されないからどこかで開放しないといけないと思います。
うん。良く分かりませんね。ここも考察で考えてみます。
この関数の実装には特に疑問はありませんね。意思の決定(decision maker)をするキャラクターがプレイヤーなら攻撃する相手は敵なのでenemyPartyが選択されます。それだけですね。
この後に教科書でもそれぞれの関数の実装に対して軽い説明がされていました。ただAttackTarget()関数のnewの取り扱いについては全く触れていませんでした。
<Step.5>
教科書に以下のように解説されていました。
とうとうUIはIDecisionMakerのインターフェイスを実装しました。
それをプレイヤーキャラクターのための意思の決定(decision maker)として割り当てる事が出来ます。
最初にRPGGAMEMode.cppのTestCombat()関数内で、キャラクターを反復(iterate)するループを変えます。
そうする事でUIを意思の決定(decision maker)として割り当てる事が出来ます。
今度はRPGGAMEMode.cppのTestCombat()関数内のループを変更するのでしょうか?
しましたね。
<Step.6>
そして戦闘が終わった時にプレイヤーの意思の決定(decision makers)をnullにセットします。
戦闘が終わった時とあるのでRPGGAMEMode.cppのTick関数の
内に作成するのかなと思いつつサンプルコードで確認してみると教科書に書かれているコードと全く同じコードがif(combatOver)内に書かれていたのでそれをコピーします。
<Step.7>
今やプレイヤーのキャラクターはUIを使用して意思の決定が出来る様になりました。
しかしながらUIは現状では何もしません。
機能を追加するためにブループリント内で作業をする必要があります。
と説明されています。これからはブループリント内での作業をするみたいですね。
最初に、攻撃用ターゲットのオプション用のウィジェットを作成します。
- AttackTargetOptionと名付けます。
- ボタンを追加します。
- そのボタンにテキストブロックを追加します。
- Size to Contentにチェックを入れます。
- そうする事で動的にボタン内のテキストボックスのサイズを変更します。
- そしてそれをキャンバスパネルの左上のコーナーに配置します。
うーん。大体は想像つきますが念のために完成した時の形を確認しときます。
サンプルコードの方を起動させてAttackTargetOptionを開きます。
確認する必要なかったですね。本当にそのままでした。それではやっていきましょう。
Widgetを作成してAttackTargetOptionと名付けました。
そのまま手順に従って作成出来ました。何の問題もなく出来ました。
<Step.8>
- グラフ内で、新しい二つの変数を追加します。CombatUIのレファレンスタイプのTargetUIとGame Character のレファレンスタイプのtargetです。
- ボタンのイベントを作成します。Designer view から作成したボタンを選択してそのDetailパネル内のOnClickedをクリックします。
- そのボタンはAttack Target関数とAttackTarget関数をパスするためのTargetのレファレンス(このボタンが表しているターゲット)を呼ぶためにTargetUIのリファレンスを使用するようになります。
とありました。
ここでも念のためにサンプルコードで確認してみると
AttackTargetOption内にしっかり二つの変数が作成されていました。作って行きます。
targetUIのタイプにCombatUIを選択しようとしたらないですね。
コンパイルし直してみます。
駄目でした。
ひょっとしてと思いCombatUIを開いて見ました。
今度は選択出来ました。小手先のテクニックかもしれませんが新しい技を獲得しました。もしBPクラスが他のBPクラスから変数として選択出来ない時は、その選択したいBPクラスそのものを開いてしまえばいいという技です。
オブジェクトにレファレンスを選択したのですが正しかったのでしょうか?一応確認しときます。
サンプルコードと同じのを選択していました(CombatUIの左脇の印の色が同じ)。
次はGameCharacterクラスからtarget変数を作成します。
作りました。サンプルコードのtargetUI変数は、Tが小文字だったのでこれも直しておきました。
最後にボタンのイベントを作成します。
しました。
<Step.9>
ボタンクリックイベントのためのグラフは単純です。
- 割り当てられたtargetUIのAttack Target関数の実行。
- Targetのレファレンスをパラメーターとしてパス。
とありました。
教科書に図が載っていたのでそのまま作成しました。
<Step.10>
次にcharacter actionのためのパネルをメインのcombat UIに追加します。
これはcanvas panelに攻撃のための子供のボタン一つとターゲットのリストのためのvertical boxを追加したものです。
何となくしか言っている事が理解出来ないのでサンプルコードで確認します。
サンプルコードのcombatUIを開いて見ると私のcombatUIにはないものが付いています。これを作成するみたいですね。同じように作成してみます。
出来ました。
<Step.11>
そしたら、ブループリントグラフ内で、ShowActionPanelイベントを実装します。
- Actionパネルを可能にするSet Visibilityノードに着くまでの実行のルートを決めます。
- ターゲットのリストを隠す別のSet Visibilityノードに着くまでの実行のルートを決めます。
とありました。
教科書に載っている図とサンプルコードの実際のShowActionPanelイベントを利用して上記のように作成しました。
<Step.12>
Attackボタンがクリックされた時のブループリントグラフは大型なので小さく分けて見ていきます。
- 最初にAttackボタンのOnClickイベントを作成します。
- グラフ内で、前に追加されたかもしれない全てのターゲットオプションをクリアするためにボタンがクリックされた時、Clear Childrenノードを使用します。
とありました。
作りました。
これも教科書に図で書かれているので再現するのは楽勝です。
<Step.14>
- CompareIntノードの>ピンから、新しいAttackTargetOptionウィジェットのインスタンスを作成します。
- それをattack targetのリストのvertical boxに追加します。
とありました。
またまた教科書をそのままコピーで作成しました。まずCreateWidgetノードでAttackTargetOptionウィジェットを作成します。それをこのウィジェット内のTargetsにAddChildノードを使用して追加します。と言う事だと思います。
<Step.15>
そして追加したウィジェットのために、
- SelfノードをSetのTargetUI変数に繋げる
- そしてForEachLoopのArray ElementピンをセットのTarget変数にパスする。
とありました。もうこれだけでも大体分かりますが、教科書の図通りに作成します。
最初のセットはAttackTargetOptionのTargetUIで二番目のセットはAttackTargetOptionのTargetでした。これが分からなくて結構てこずりました。
<Step.16>
最後に、ForEachLoopのCompletedピンから、Target optionのリストの可視性をVisibleにセットします。
はい。
しました。
<Step.17>
これらすべてを行った後で、我々はまだあるアクションが選択された時にActionsパネルを隠す必要があります。
Hide Action Panelと言う名の新しい関数をCombatUIに追加します。
この関数は非常に簡単です。Action panelの可視性をHiddenにセットするだけです。
はい。それでは作ります。
出来ました。
<Step.18>
AttackTargetOptionのグラフ内のクリックハンドラー内で、AttackTargetノードの実行ピンをこのHide Action Panel関数に繋げます。
はい。これも図をみれば何をしなければならないかは一目瞭然です。
出来ました。
<Step.19>
最後に、AttackTargetOptionウィジェットの底に配置されたTextBlockをバインドする必要があります。
デザイナービューに行ってこの章の前のTextBlockでしたようにテキスト用にバインドを作成します。
グラフに戻ります。TargetをCharacterNameにリンクします。
CharacterName変数に示されるテキストのフォーマットを調節します。
テキストのreturnノードをそれにリンクします。
このブループリントは現状のターゲットのキャラクターの名前をボタンに表示するはずです。
結構大変そうですが教科書の図やサンプルコードを参考にしてやってみます。
StringからのTextへの変換のためのノードが教科書の図と違うのですが、多分大丈夫でしょう。その他は完全に同じはずです。
<Step.20>
それではテストをしてみます。
表示はしっかりされているみたいです。
Attackボタンを押してみます。
Goblinが表示されました。Goblinをクリックしてみます。
GoblinのHPが10減って20から10に変化しました。KUMOのHPは100から10減って90になりました。
もう一度Goblinをクリックします。
Playerが勝って戦闘が終了したようです。
動作は正しく動いているようです。
<考察>
考察は来週やります。