UE4の勉強記録

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

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する Combat Engineの自作6

f:id:kazuhironagai77:20200607233259p:plain

<前文>

<英語の音節が区別出来るゲームの作成>

前回も紹介しましたが、プレイしていると英語の音節が区別つけられるようになるゲームをこのプロジェクトとは別に制作しています。最近、どれくらい音節が区別出来るようになるのかテストしたのですが効果が半端じゃなかったです。英語のLとRの区別が全く出来ない大人でも、一時間ぐらいプレイするとネイティブ同士が会話で使用するスピードで発せられる英単語のLとRの音を大体区別出来る様になりました。同様の方法でshort a, short u, short o の3つのアの違いやvとb、 sとth、nとngの違いなども区別出来る様になると思いました。

この結果を観て思ったんですが、自転車に乗れるように成るのと似ているなと思いました。最近は、子供に自転車の乗り方を教えるのに補助輪付きの自転車に乗せるのではなく、ペダルを外した自転車に乗せるそうです。そうすると子供は自由に足が着けるので、足で蹴って前に進みます。その内バランスが取れる様になって足を上げて進むようになります。一時間程度練習すると足を上げてカーブも曲がれるようになるそうです。そうなったら今度はペダル付きの自転車に変えて少し練習するだけでもう自転車に乗れるようになるらしいです。

訓練が適切であれば、適量の訓練と学習で望んだ効果が得られる好例と思いました。

それでは今週の勉強を始めます。

<本文>

1.先週のエラーなどの直し

今週も先週のエラーの直しからやって行こうと思います。

1. ICombatAction.hとIDecisionMaker.hで以下に示すようにGameCharacterクラスをforward declarationをしながらヘッダーファイルもインクルードしている。

f:id:kazuhironagai77:20200607233424p:plain

これ、単なるForward Declarationなはずですから、GameCharacter.h はここでインクルードする必要ないのか、Interfaceなのでこの場合はここでGameChracter.hもインクルードする必要があるのかと言う事です。

2. IDecisionMaker.cppファイルでch4_3.hをインクルードしているのにその派生クラスであるTestDecisionMakerクラスのソースファイルでもch4_3.hをインクルードしている

以下に示すようにIDecisionMaker.cppファイルでch4_3.hをインクルードしています。

f:id:kazuhironagai77:20200607233516p:plain

更にその派生クラスであるTestDecisionMakerクラスのソースファイルでもch4_3.hをインクルードしています。

f:id:kazuhironagai77:20200607233536p:plain

この辺が謎なのはLinkingの辺りを完璧には理解していないからなのかもしれません。

3. Member Initializer Listsの使用

コンストラクターを使用するなら、なるだけmember Initializer Listsは使用すべきで作成したコンストラクターにmember Initializer Listsが使用出来ないか検討します。

4. 1の直し

1.のInterfaceにおけるforward declarationの正しいやり方について調べましたが、見つかりませんでした。ただC++にはinterface自体がないと言う考え方も出来るので普通のクラスと同じかもしれません。その辺を実験すれば正解が分かるかもしれません。ただ、このICombatAction.hとIDecisionMaker.hでforward declarationしているGameCharacterクラス、GameCharacter.hでも以下に示すようにForward Declarationしています。

f:id:kazuhironagai77:20200607233644p:plain

だったら最初からGameCharacter.hをインクルードするだけでいいんじゃない。と言う疑問が先にくるので、そっちを先に試します。

f:id:kazuhironagai77:20200607233703p:plain

ビルドします。

f:id:kazuhironagai77:20200607233720p:plain

普通に出来ましたね。

テストします。

f:id:kazuhironagai77:20200607233743p:plain

これも普通に出来ましたね。

IDecisionMaker.hのForward Declarationも取ります。

f:id:kazuhironagai77:20200607233801p:plain

f:id:kazuhironagai77:20200607233813p:plain

こっちも普通に出来ました。

これでも解決と言えば解決なのですが、肝腎のinterfaceにおけるforward declarationのやり方が不明のままになってしまうので、逆で試してみます。

f:id:kazuhironagai77:20200607233833p:plain

GameCharacter,hではICombatActionとIDecisionMakerのforward declarationを外して、ICombatAction.hとIDecisionMaker.h をインクルードします。

更に、IDecisionMaker.h ファイルとICombatActionファイルではUGameCharacterクラスをForward Declarationします。

f:id:kazuhironagai77:20200607234022p:plain

f:id:kazuhironagai77:20200607234029p:plain

正し、GameCharacter.hクラスをインクルードするのは、以下に示すようにICombatAction.cppとIDecisionMaker.cppファイル内です。

f:id:kazuhironagai77:20200607234046p:plain

f:id:kazuhironagai77:20200607234053p:plain

これで試してみます。

ビルドします。

f:id:kazuhironagai77:20200607234117p:plain

エラーになりました。

これをみるとInterfaceのcppファイルにインクルードしたヘッダーはその派生クラスからは呼ばれていないみたいですね。

でもなんでTestDecisionMaker.cppのみがエラーを出していて、同じ様にセットしたTestCombatAction.cppはエラーを出さないんでしょうね。

単に順番の問題かもしれませんが、一応確認します。

f:id:kazuhironagai77:20200607234135p:plain

IDecisionMaker.hのみGameCharacter.hをインクルードしてビルドします。

f:id:kazuhironagai77:20200607234203p:plain

うん。今度はTestCombatAction.cppがエラーを出しました。

こっちも直します。

これで直ったはずなのでビルドします。

f:id:kazuhironagai77:20200607234234p:plain

あれ、エラーになってしまいました。

f:id:kazuhironagai77:20200607234250p:plain

この二つの変数を認識していないみたいです。

f:id:kazuhironagai77:20200607234311p:plain

IDecisionMaker.hとICombatAction.hはインクルードしているので認識しないのはおかしいと思いますが、何回やってもエラーになります。

なので

f:id:kazuhironagai77:20200607234344p:plain

こっちをForward Declarationに変更しました。

f:id:kazuhironagai77:20200607234406p:plain

ここまで戻ったらビルド出来ました。

f:id:kazuhironagai77:20200607234423p:plain

普通に動いています。

うーん。このプロジェクトは既に複雑に入り組んでしまってこの中でC++のinterfaceにおけるforward declarationの追及は難しいですね。

普通のC++で試してみました。

f:id:kazuhironagai77:20200607234457p:plain

f:id:kazuhironagai77:20200607234505p:plain

やっぱりinterfaceのcppファイルでインクルードしたヘッダーファイルはそのinterfaceから派生したクラスからは認識されないですね。

これって普通のクラスでもそうでしったけ。

試します。

f:id:kazuhironagai77:20200607234529p:plain

f:id:kazuhironagai77:20200607234537p:plain

普通のクラスでも親クラスのcppファイルにインクルードしたクラスは子クラスにはインクルードされていませんでした。

マジですか!

知らなかった。もうちょっとLinkingについての勉強が必要ですね。

となると、Interfaceもしくは親クラスでforward declarationされたクラスはそのinterfaceもしくはその親クラスを継承したクラス内でインクルードしないといけなくなりますね。結構面倒くさいので忘れる可能性大ですね。となると

f:id:kazuhironagai77:20200607234556p:plain

ヘッダーファイルでforward declarationされたクラスのヘッダーをインクルードするのは、継承したクラスでインクルードを忘れないためにも必要な事なのかもしれませんね。

やっぱりForward declarationしたクラスのヘッダーは同じクラス内でインクルードする事にします。

5. 2.の直し

これはもう4.で勉強した通りです。親クラスのソースファイルでインクルードしたヘッダーは子クラスには継承されません。子クラスのcppファイルでもう一度インクルードするか、親クラスのヘッダーファイルでインクルードするしかないです。

CompilerとLinkerについて軽く勉強し直しましたが、今回の件で関係ある所だけまとめると以下のように成っていました。

f:id:kazuhironagai77:20200607234631p:plain

まず、基本ですが、compilerによって、それぞれのcppファイルを基準に(そうでない場合もあるそうですが、あくまでも簡単にまとめています。)objファイルが作成されます。この時に最初にpreprocessorによってcppファイルの最初に書かれている#include “some.h”が実際のヘッダーファイルに書き変わります。(この時、#defineなども書き変わりますが今回の話には関係ないので無視します。)

上記の話を元に考えると、子クラスのcppファイルがobjファイルに変換される時に含む事が出来るhファイルはあくまでそのcppファイルに書かれているhファイルだけです。親クラスのhファイルにインクルードされているhファイルはここでインクルードされるかもしれませんが、親クラスのcppファイルにインクルードされているhファイルが子クラスにインクルードはされないでしょう。

大体分かりました。

Linkingの勉強が足りなかったと言っていましたが、問題はLinkingの前に既に起きていましたね。まあ、理解出来たので良しとします。

6. 3の直し

今まで作成したクラスのconstructorにMember Initializer Listsを使用します。と思ったのですが、これ以上C++の勉強にリソースを取られると肝腎のcombat engineの作成が、今週は出来なくなる可能性も出てくるので、来週以降やる事にします。

2.Combat Engineの自作の続き

プロジェクトを立ち上げたら以下に示すようなエラーが1秒置きに表示されるようになってしまいました。

f:id:kazuhironagai77:20200607234715p:plain

何かいじり過ぎたんでしょうか?

調べると、このサイトに、同じようなエラーが表示されて困っていると書かれている人達がいました。いましたが原因と解決方法は分からないままでした。

ただ

f:id:kazuhironagai77:20200607234947p:plain

と書かれていて、

f:id:kazuhironagai77:20200607235003p:plain

Enable Transportのチェックを外してみると、

f:id:kazuhironagai77:20200607235026p:plain

見事に止まりました。解決したわけではないですがこれで進めます。

またゲーム制作と関係ない話になってしまいますが、要するにこれってUDPのソケットのアドレス(ポート番号?)を他のソフトが使用しているからUE4エディターが使用出来ないと言う話でしょう。ネットワークは大学の授業でTCPソケットそのものを自作した位までしか勉強していませんし、更にCUIが嫌いな私はその勉強した内容の大半の事は忘れてしまいました。しかしそんな私のレベルでもちょっと勉強すれば解決出来る問題と思いますので、この辺はゲームのサーバーを作成する時にまとめて解決する事にします。

1. UMGを追加する。

ch4_3.Build.csを開いてUMGで使用するモジュールであるUMG、Slate、SlateCoreを追加します。

f:id:kazuhironagai77:20200607235058p:plain

この辺は非常にお馴染みな所なので、簡単に書いて行きます。

次にch4_3.hファイルを以下に示したように変更します。

f:id:kazuhironagai77:20200607235124p:plain

ビルドします。

モリーが足りませんと出て来てしまいました。

一端全部閉じてやり直します。

f:id:kazuhironagai77:20200607235141p:plain

普通にビルド出来ましたね。

特に問題もなさそうですが。タスクマネージャーを開いて見ましたが、

f:id:kazuhironagai77:20200607235159p:plain

うーん。どうなんでしょうか?不自然にメモリーを使用している箇所があったりするんでしょうか?YouTube見ながらやってるのがいけなかったんでしょうか?

それはともかくとして、UUserWidgetクラスを親クラスとした新しいクラスをUE4C++で作成します。

前回、これを作成した時、なぜこのクラスをUE4C++で作成するのかが良く分かってなかったので、その点についてだけは、ちょっとだけ詳しく書きます。

UIの性能は、UIの性質を考えると分かりますが、デザインの優劣に大きく負います。ので今までのコードのように

  1. UE4C++から命令。
  2. その命令をBPで実行

のような一方的な流れだけでなく

  1. UE4C++でイベントが発生。
  2. そのイベントに対して何をするのかはBPで決定する。

のような流れが発生する場合が考えられます。のでそのための特別なUUserWidgetクラスを親クラスとした新しいクラスをUE4C++で作成する必要があります。その作成方法をここで教えています。それについては教科書でも数行の説明は書かれていましたが、前回読んだときは飛ばして読んでました。

f:id:kazuhironagai77:20200607235520p:plain

教科書に書かれていた説明

この辺を理解した上で新しいクラスを作成していきます。

f:id:kazuhironagai77:20200607235606p:plain

UE4エディターからUserWidgetクラスを親クラスとして選択して作成します。名前はCombatUIWidgetとします。

f:id:kazuhironagai77:20200607235626p:plain

更に、二つの関数を追加しました。このUFUNCTION内のBlueprintImplementableEventがBPからUE4C++に命令出来る様にする魔法のFunction Specifierですね。

ビルドします。

f:id:kazuhironagai77:20200607235643p:plain

普通に成功しました。関数の宣言だけして実装しないでビルドした事がないので、これが普通の事なのか、BlueprintImplementableEventが指定されているか出来る事なのかは分かりません。

次にこのクラスからBPを作成します。

f:id:kazuhironagai77:20200607235739p:plain

名前はCombatUIとします。

f:id:kazuhironagai77:20200607235802p:plain

開いてみるとしっかり先程作成した関数の実装がBP内で出来る様になっています。

f:id:kazuhironagai77:20200607235818p:plain

ここから先は単なるUIWidgetのデザインになってますね。

もうこの辺でオリジナルな作りに変えようかと思ったのですが、教科書を見ると次の節、「UIからの意思の決定」があります。今、内容を変更するとその節の内容はまるまる無視する事になってしまうので、もう少しだけ教科書と同じように作成します。

簡単にやった内容を以下に示します。

以下に示すように2つのHorizontal boxを追加しました。

f:id:kazuhironagai77:20200607235834p:plain

今度は、widget BlueprintからPlayerCharacterCombatPanelを作成しました。

f:id:kazuhironagai77:20200607235859p:plain

f:id:kazuhironagai77:20200607235907p:plain

それぞれのTextに関数をバインドしました。関数の中身は一個だけ以下に示しました。

f:id:kazuhironagai77:20200607235930p:plain

EnemyCharacterCombatPanelも同様に作成しました。

combatUIに以下のコードを追加しました。

f:id:kazuhironagai77:20200607235946p:plain

f:id:kazuhironagai77:20200607235953p:plain

この辺のコードの内容は散々復習した時に勉強した内容なので今回はスキップします

2. 作成したUMGをUE4C++のARPGGameModeBaseから呼び出す

これも、何回も復習した内容ですね。段々、何を勉強しないとオリジナルのCombatEngineにならないのかが分かって来ました。

ExecuteActionのフェーズにそのキャラクターの戦闘用のアニメーションとカメラワークをどうやって呼び出すかを勉強しなければならなかったんです。

それ以外のやりたい事はまあ今の自分の知識でなんとか出来そうですし。

うん。分かりました。

取りあえず、教科書に書かれているCombatEngineの内容を終わらせてしまいます。

ARPGGameModeBase.hに以下の変数を追加します。

f:id:kazuhironagai77:20200608000028p:plain

TestCombat()関数の最後に以下のコードを追加します。

f:id:kazuhironagai77:20200608000044p:plain

戦闘が一対一に変わったので教科書のコードと多少違いますが、それ以外は全く同じです。

Tick()関数の最後で作成した変数を開放します。

f:id:kazuhironagai77:20200608000113p:plain

Ch2を見直すとこの部分のコードに対して最初は

f:id:kazuhironagai77:20200608000129p:plain

のif節内に書かれていたのに、途中でif節の外に書かれていてどっちが正しいか分かりません。とコメントに書かれていましたが、結局は戦闘が終了したら戦闘開始時に初期化もしくはassignした変数を開放しているだけなので、どっちでも良い訳です。

f:id:kazuhironagai77:20200608000151p:plain

をセットしてテストします。

f:id:kazuhironagai77:20200608000210p:plain

出来ていますね。

3. プレイヤーが行動を決定する

今までの戦闘は、プレイヤーがキャラの行動を選択する事は出来ませんでした。それをここで改良します。

これです。このUIからUE4C++の行動を決定する方法さえ分かれば、後は自分で勝手に改良出来ます。しっかり復習していきましょう。

教科書には、今ユーザーが操作出来き、かつUE4C++と通じている唯一のクラスであるUCombatUIWidgetにDecisionMakerの役割を与えればそれが可能であると述べています。

うーん。なるほど!そういう訳だったんですね。

f:id:kazuhironagai77:20200608000315p:plain

それがこれだったんですね。

まず、CreateYourHero()関数内でdecisionMakerをTestDecisionMakerクラスを使用して初期化するのを止めます。

f:id:kazuhironagai77:20200608000330p:plain

これは、プレイヤーが操作するキャラのdecisionMakerはUIになるからです。

おお。本当に前作成した時はこの辺読んでいたのかと言うくらい理解していなかったですが、このやり方でプレイヤーが戦闘に対して操作出来る様にしていたのですね。まあ前回は動くのを作成するだけで精一杯でしたのでしかたないですが。

以下に実際のやり方を示します。

f:id:kazuhironagai77:20200608000417p:plain

まず当然ですがIDecisionMakerクラスをCombatUIWidgetに親クラスとして追加します。

当然、IDecisionMakerクラスの2つの関数、

f:id:kazuhironagai77:20200608000437p:plain

を継承します。

教科書のサンプルコードは何故かoverrideが書かれていませんが、ここはしっかりと付け足しておきました。

更に、BPで使用する二つのhelper関数を追加します。

f:id:kazuhironagai77:20200608000508p:plain

GetCharacterTarget()関数の返り値は、少し変えています。

そして最期に、

f:id:kazuhironagai77:20200608000524p:plain

BPで実装する関数を宣言します。

更に以下に示す変数を宣言します。

f:id:kazuhironagai77:20200608000539p:plain

実装内容は以下の通りです。

f:id:kazuhironagai77:20200608000554p:plain

成程。BPで実装するShowActionPanel()関数をここで呼ぶ事によりBeginMakeDecision()の実装をBPというか、UI内で可能にしているんですね。更に、以下に示したhelper関数を

f:id:kazuhironagai77:20200608000609p:plain

UI内での選択が終了した時点で呼ぶ事で、finishedDecision = trueとなってMakeDecisionのフェーズが終了した事をUE4C++に告げる事が可能になると言うわけですね。

うーん。分かりました。

これと同じ事をICombatActionクラスのBeginExecuteAction()でやって、BP内で流すアニメーションをセットすれば戦闘シーンのアニメーションも作成出来そうですね。又、敵のモンスターのAIとBeginMakeDecision()の実装を関連付ける事も出来そうですね。

段々オリジナルなcombat engineの作成方法も見えて来ました。

それはそうと教科書の内容を終わらせてしまいましょう。

f:id:kazuhironagai77:20200608000646p:plain

RPGGameInstanceクラスの変数MyYourHeroはGameCharacterクラスから作成されていますので、GameCharacterクラスの変数であるdecisionMakerをassignする必要があります。それをRPGGameModeBaseクラスのCombatEngine()関数内で行っています。

f:id:kazuhironagai77:20200608000711p:plain

勿論、戦闘が終了した後は、decisionMakerにセットされたCombatUIInstanceは削除されてしまうので、Tick()関数内の最後でdecisionMaker にnullptrをセットします。

これでUE4C++側のCombatEngineの実装は終わったはずです。実際のBeginMakeDecision()関数の実装はUI側で行うので、それを作成していきましょう。

4. Widget内でdecisionMakerの実装を行う

DecisionMakerの実装を担当するためのUIを作成していくのですが、この部分の教科書の説明は大変分かりにくくて、教科書の説明を読んだだけでは混乱するだけです。のでch2で既に作成したwidgetを参考にしてUIを完成させてから全体の流れを理解したいと思います。

4.1 Attack Target Optionの作成

Ch2と教科書の説明を参考にして以下に示したように作成しました。

f:id:kazuhironagai77:20200608000748p:plain

f:id:kazuhironagai77:20200608000755p:plain

f:id:kazuhironagai77:20200608000802p:plain

Ch2ではAttack Targetの後にhide active Panelノードがありましたが、教科書の図では付いていないので今は付けないでおきます。

f:id:kazuhironagai77:20200608000818p:plain

4.2 CombatUIの改良

CombatUIにwidgetを追加していきます。

f:id:kazuhironagai77:20200608000839p:plain

を追加しました。

更に、ShowActionPanel関数の実装をここで行います。

f:id:kazuhironagai77:20200608000855p:plain

次にattackedbuttonが押された時の実装を行います。

f:id:kazuhironagai77:20200608000912p:plain

この辺は全部変更する予定なので、内容の解説はスキップします。

これで一応完成です。

テストします。

f:id:kazuhironagai77:20200608000930p:plain

あれ?何も表示されません。

結構なケアレスミスを連発していてCombatUIの変数の名前を直したり、>の記号の向きを直したりしました。

それらを修正しました。

f:id:kazuhironagai77:20200608001002p:plain

攻撃を選択します。

f:id:kazuhironagai77:20200608001018p:plain

攻撃対象であるゴブリンが表示されました。

ゴブリンをクリックします。

f:id:kazuhironagai77:20200608001038p:plain

プレイヤーが操作するKUMOと敵のモンスターであるゴブリンが一回ずつ攻撃し合いました。

f:id:kazuhironagai77:20200608001110p:plain

その結果両者のHPが10ずつ減少しました。

f:id:kazuhironagai77:20200608001126p:plain

また、攻撃ボタンをクリックするとゴブリンが表示されます。

ゴブリンをクリックすると

f:id:kazuhironagai77:20200608001157p:plain

プレイヤーが勝利しました。

うん。直りましたね。

5. GameOver画面の追加

本来ならこれで、自作のcombatEngineの作成に進むのですが、今週はもう時間も無くなっているので、教科書のCombatEngineの章の最後の節「GameOver画面の作成」をやります。

まずWidget BPでGameOverScreenを作成します。

f:id:kazuhironagai77:20200608001232p:plain

f:id:kazuhironagai77:20200608001239p:plain

f:id:kazuhironagai77:20200608001253p:plain

RPGGamemodeBaseのTick()関数のGameOver内でGameOverScreenを表示するためのコードを実装します。

f:id:kazuhironagai77:20200608001322p:plain

f:id:kazuhironagai77:20200608001332p:plain

RPGGameInstanceクラスのPrepreReset()関数は

f:id:kazuhironagai77:20200608001349p:plain

URPGGameInstanceの変数を開放しているだけです。

テストの結果を以下に示します。

f:id:kazuhironagai77:20200608001407p:plain

戦闘に負けるとGameOverScreenが表示されます。ReStartボタンを押すと、

もう一度ゲームが始まります。

3.まとめと感想

やっとCombatEngineの複製の作成が終わりました。来週からこれを改良して自作のCombatEngineを作成します。