UE4の勉強記録

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

「Unreal Engine4.xを使用してRPGを作成する」の3章の3.4を勉強する part 6

<前文>

f:id:kazuhironagai77:20190714135027p:plain

先日、家の前で転んでしまいました。何の事はない段差につまずいてスッテンコロリンです。足の骨を折ったかと最初思ったくらいスゴイ転び方でした。つまずいた瞬間に「このまま行ったら足を折る。」と感じました。次の記憶は服の上からヘソを眺めている瞬間で「あ、俺は今受け身を取っている。」でした。そしてデーンと地面に転がりました。結局、足は痛かったですが骨折からは程遠い軽いケガで済みました。完全に主観ですが転んだ瞬間に受け身を取れたので骨折しないで済んだと思っています。ただあまりにもビックリしたのでちょっとショック状態になっています。

<本文>

<目的>

先週、3章3節の5、ターン制の戦闘(turn based combat)の5、UMGによる戦闘UI(Combat UI with UMG)を勉強しました。今週はその考察を行います。

<考察>

まず、前回に新しく追加したコードをstep事に復習します。前回はC++のコードだけでなく沢山のブループリントも使用しました。この辺についても詳しく検討したいと思います。その後全体の流れを見たいと思います。

<Step.1>

.Build.csに"UMG", "Slate", "SlateCore"を追加しました。これはUnreal Engine 4 Scripting with C++ Cookbookの Chapter 9. User Interfaces で散々勉強した内容なので特に考察する事はないです。

<Step.2>

UMG.h、UMGStyle.h、SObjectWidget.h、IUMGModule.h、UserWIdget.hをインクルードしました。このサイト(Extend UserWidget for UMG Widgets)をみると全く同じヘッダーをインクルードしています。UMGをC++から使用するためにUUserWIdgetを拡張するにはこれらのヘッダーをインクルードしなければならないと言う事でしょうね。

<Step.3>

UserWidgetクラスを拡張してCombatUIWidgetを作成しました。分かりました。Step.1から4まではUserWidgetクラスを拡張したC++のクラスを作成していたのですね。

<Step.4>

CombatUIWidgetを親クラスとするCombatUIと名付けたWidget BlueprintをContent/Blueprints/UIフォルダー内に作成しました。これでWidgetにおけるBPとC++を普通のUEC++クラスとBPの関係のように繋げる事が出来ました。

Step.1からStep.4までのまとめ>

今まで、UE4C++を勉強してきて分かって来た事の一つは、UE4C++ではC++で全部やろうとは考えていないと言う事です。これはUE4のゲーム作成に対する哲学みたいなものと解釈しています。その哲学とはUE4C++ではプログラマーとデザイナーの分業をはっきりさせると言う事です。具体的に言えば、ゲームの基盤はプログラマーC++で作成して、デザインやゲームのバランスはデザイナーがBPを使用して調節する。と言う事です。例えばあるクラスを作成する場合、まずアクタークラスやUObjectクラスから派生したクラスをC++で作成して基本的な変数と関数を持たせます。そのC++クラスを作成するのはプログラマーです。そして作成したC++クラスからBPを更に作成して、そのBPを実際のゲームで使用します。そのBPの調整はデザイナーが担当します。

これはそれぞれの専門家に専門分野を担当してもらう為には大変優れた方法です。のでUMGの設計だってそうしたいはずです。しかし今まではUser InterfaceからWidget Blueprintを選択してBPをいきなり作成してそのBPから直接UMGの設計をしていました。これだとプログラマーがUMGを作成すべきなのかデザイナーがUMGを作成すべきなのかですら不明瞭になります。プログラマーがUMGをBPで作成した場合は関数や変数の実装や最適化は完璧でしょうがデザインやゲームバランスは滅茶苦茶でしょう。デザイナーが担当したら関数や変数の実装や最適化の段階で詰まってしまうはずです。

その問題を解消してくれる方法が今回のStep.1からStep.4で使用したUserWidgetクラスを拡張したC++を作成しそのC++を親クラスとしたUMGのBPを作成する方法です。と解釈しました。

<Step.5>

Step.4で作成したBP、CombatUIのDesignerインターフェイス内で、二つのHorizontal Box widget、enemyPartyStatusとplayerPartyStatusを作成しました。ここは完全にデザインをどうするかの問題になると考えられデザイナーに任せるべき分野と思われます。

<Step.6>

BaseCharacterCombatPanelと言う名前のWidget Blueprintを作成しました。Game Character クラスから変数を作成しCharacterTargetと名付けました。

先程、あれほど熱くUMGもUE4C++から作成できるので基礎クラスはC++で作成してその派生クラスをBPで作成すべきと述べていたのですが、いきなりここでBPで基礎のUMGクラスを作成していますね。言い訳めいた事を言えばこのクラスはCombatUIのヘルパークラスみたいなもので、まあBPから作成してもいいのかなとも言えなくもないと思います。しかしこのクラスはUE4C++から作ってもいいんじゃないとも思います。

f:id:kazuhironagai77:20190714135108p:plain

理由の一つはデザインを全く担当していない事。

f:id:kazuhironagai77:20190714135127p:plain

二つ目の理由は独自の変数を持たせている事。これはプログラマーが考える範囲の内容と思います。

<Step.7>

新しいWidget Blueprintを作成しました。名前はPlayerCharacterCombatPanelとしました。新しいBlueprintの親にBaseCharacterCombatPanelを指定しました。

うん。これをみてもBaseCharacterCombatPanelをUE4C++で作成する。BaseCharacterCombatPanelからBaseCharacterCombatPanelBPを作成する。PlayerCharacterCombatPanelをWidget Blueprintから作成する。PlayerCharacterCombatPanelの親にBaseCharacterCombatPanelBPを指定する。でもいいと思います。

もし上記の方法が出来るのならプログラマーとデザイナーの分業がしっかりされるので上記の方が良いと思います。

<Step.8>

PlayerCharacterCombatPanelのデザイナーインターフェイスから、三つのテキストウィジェットを追加しました。一番目のラベルはキャラクターの名前、二番目はキャラクターのHP、そして三番目はキャラクターのMPです。スクリーンの左下に配置されるようにしました。さらにCombat widget内のplayerPartyStatusboxのサイズである高さ200ピクセル内に収まるようにしました。

はい。ここは完全にデザイナーの担当部分ですね。分業が前述で述べたようにしっかり出来ていればこの部分はデザイナーが担当しているはずです。

<Step.9>

次にテキストブロックを生成するためのブループリント関数を作成しました。具体的には以下に示す手順で作成しました。

  1. このグリッド内のオープンエリア内を右クリックし、Get Character Targetを探します。探したらそれをクリックします。
  2. このノードのアウトプットピンを引っ張り、Variables|Character Info下にあるGetHPを選択します。
  3. 新しいFormat textノードを作成します。そのテキストをHP: {HP}と設定します。そしてGetHPのアウトプットをFormat Textノードのインプットに接続します。
  4. 最後にFormat TextノードのアウトプットをReturnノードのreturnに接続します。

f:id:kazuhironagai77:20190714135207p:plain

うっ。私の今までの分類によるとこの部分はデザイナーの担当になってしまいます。教科書ではAやってBやってCやって…となっているので誰でも出来ますがこれを最初から作れとなると、デザイナーにそれを要求する事はちょっと酷ですし、得意でない事をさせるのは効率も悪いです。

まあ仮に、デザイナーがBPがバリバリに出来る人であったしてもUE4C++の設計でCharacter Target変数のクラスであるGame CharacterがHPなどのCharacterの特性を担当していて、Character Target変数がそのキャラクターの現在の状態を表している事を理解する事はUE4C++のコードが読めないと出来ないと思います。

となるとやっぱりプログラマーがここを担当しなければならなくなるのでしょうか?そうすると分業が曖昧になってしまいます。

ここで妥協案としては、プログラマーの責任としてCharacter Target変数はここで表示する全ての値を保持しています。例えばHPなら

f:id:kazuhironagai77:20190714135231p:plain

とすればHPの値を得る事が出来ます。とプログラマーがメッセ―ジを残すのはどうでしょうか?この伝言さえあれば、BPがバリバリに出来る人ならばPlayerCharacterCombatPanelのデザイナーインターフェイスに作成した三つのテキストウィジェットにそれぞれのキャラクターの状態を示す値を表示する事が出来ると思います。そしてこれが正しいBPの使い方だと思います。値を持つ変数が用意されていてこの変数を値をBPを用いて貴方がデザインしたText内に表示して下さい。と限定された状態でのプログラミングをBPを用いて行うのです。

となるとやっぱりゲーム作成ではデザイナーもBPは使えないといけないのでしょうか?それともテクニカルアーティストみたいな人が間に入って作成するのでしょうか?

うーん。分かりませんね。結構難しい問題なのかもしれません。

<Step.10&11&12>

CombatUIのブループリント内でAddPlayerCharacterPanel関数とAddEnemyCharacterPanel関数を実装しました。

f:id:kazuhironagai77:20190714135253p:plain

その実装を簡単にするために以下に示すBP関数、SpawnCharacterWidgetを作成しました。

f:id:kazuhironagai77:20190714135319p:plain

やっている事は勿論分かりますが、何故これをBPでプログラミングしてC++でしないのかの境界が全く分かりません。ひょっとするとこの部分はゲームバランスに影響があるのかもしれません。デザイナーによる細かい調整が必要な場面が後から出てくるのかもしれません。

<Step.13>

ここからは、今まで作成したUMG widgetが計算通りに動くのかのテストの作成になります。

f:id:kazuhironagai77:20190714135350p:plain

をARPGGameModeクラスに追加しました。

<Step.14>

以下に示すコードをARPGGameModeクラスのTestCombat()関数に追加しました。

f:id:kazuhironagai77:20190714135419p:plain

このコードの詳細な解説は先週やっているのでここでは繰り返しません。

先週疑問だった

f:id:kazuhironagai77:20190714135437p:plain

Player Party Statusは単なるボックスなのにこれを送る意味があまり分かりません。について調べて見ます。

以下に示すようにSpawnCharacterWidget()関数は構成されていてPlayer Party StatusはTarget PanelとしてAdd Child()関数にパスされます。

f:id:kazuhironagai77:20190714135502p:plain

まずAddChild()関数が何をする関数なのか良く知りません。調べます。

f:id:kazuhironagai77:20190714135520p:plain

ここにAdd Child()関数についてのAPIがありました。それによると

f:id:kazuhironagai77:20190714135543p:plain

新しい子供のウィジェットをコンテナ(container)に追加します

あ、もう分かりました。この関数はPlayer Party StatusをBaseCharacterCombatPanelに追加する関数です。例えばAddPlayerCharacterPanel()関数が呼ばれた場合ならば、playerPartyStatusが追加され、playerPartyStatus内にPlayerCharacterCombatPanelウィジェットの3つのテキストボックスが追加されるはずです。

ただもっと沢山の例を見ないと具体的な使い方は分かりませんね。この関数はこれからUMG widgetを使用するにあたり、何回も出てきそうですのでその時にまた勉強します。

<Step.15>

ARPGGameModeクラスのTick()関数に以下のコードを追加しました。

f:id:kazuhironagai77:20190714135623p:plain

先週説明した通りCombatUIInstance変数を開放しただけですね。

Step.16はテストしただけですのでこれで終わりです。

この後、全体の流れも見る予定でしたが、それも既に分かったのでこれで今週はお終いです。