UE4の勉強記録

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

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

f:id:kazuhironagai77:20200531215229p:plain

<前文>

このブログのゲームとは別に、プレイしていると英語の音素が区別出来るようになるゲームを制作しています。分かり易く言い換えると、このゲームで遊んでいると英語のRとLの音の違いが分かるようになると言うわけです。あんまりUE4の性能を生かしてはいませんが、今までのゲームの歴史に存在した事のない独自の価値を持つ全く新しい分野のゲームが出来そうです。私はあまりゲームで遊びませんが、ポケモンgoだけは結構遊びました。流石にもう飽きてしまいましたが、今振り返ると費やした時間と労力は勿体なかったと思います。確かに家族との楽しい思い出は残りましたし、それ自体は大切ですが、その思い出+現実世界で役に立つsomethingがあったら更に楽しかったと思います。

外国語を全くしゃべれない日本人はバイリンガルな人を見ると単純に羨ましがりますが、その人がバイリンガルになるために費やした労力と時間を知らないから単純に羨ましがれるんです。これはハーフの子でも例外じゃないですが、信じられない量の勉強と訓練を積まなかったらバイリンガルには成れません。でも何故か、このバイリンガルになるために費やした労力と時間って難しいゲームをクリアするために費やした労力と時間に似ているんです。私は子供の時からアニメを見たり本を読んだりするのが趣味でしたがゲームには全く興味がありませんでした。ので、これらの趣味とゲームとの違いについて良く考えてました。その時の結論は敗北だったんです。ゲームをやると必ず負けます。最後には勝つように出来ているかも知れませんが絶対に負ける様に成っています。ゲームをクリアするためには、その敗北を乗り越える必要があります。アニメを見たり本を読んだりする場合はこの敗北は全くないんです。誰でも最後まで進めます。

ゲームにおける、敗北―>情報収集or/and練習or/and工夫―>再挑戦―>敗北のサイクルを繰り返して勝利に到達するプロセスはバイリンガルに成るための勉強に非常に似ています。(因みにアニメを見たり本を読んだりはするが全く挑戦しない人と英語の勉強は凄い出来るのに全く英語が喋れない人も何かそっくりな所があります。)だからゲームをプレイする事で外国語を会得するために必要な技術の一部の拾得は可能と思っています。人によってはゲームで遊んでいて勉強になるような都合のいい話があるわけない。と思うかもしれませんが私は可能と思っています。

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

<本文>

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

先週、以下の部分を来週以降考えると述べました。

1. SpawnMonsterCharacter変数の宣言

 RPGGameModeクラスのBeginPlay()関数内で以下に示すように

f:id:kazuhironagai77:20200531215342p:plain

SpawnMonsterCharacter変数を宣言かつ初期化していますが、この時作成されたメモリーがその後、memory leakになるのではないのか?と言う疑問です。

2. PCHがどうとか言うエラー

PCHがどうとか言うエラーが出ていたので今週、原因を解明してこのエラーを直す。

と言っていましたが、このエラーは先週のうちに突然直ったのか、全く出なくなりました。

3. LancherからOnline learningに行ったら以下のエラーが表示されて入れなかった。

f:id:kazuhironagai77:20200531215428p:plain

これも先日、試したら普通に入れましたし、ブラウザー(browser) からなら入れるので暇な時に検証します。

と言うわけで検証しなければならないのは1のSpawnMonsterCharacter変数の宣言だけです。これを直します。

4. SpawnMonsterCharacter変数の宣言の直し

UE4C++にはガーベージコレクション(garbage collection)があるので、どのポインター変数からも指されていないアドレスに保持されているデータは自動的に消去されるはずです。ので、memory leakの心配はしなくて良いと思います。しかし使用しているのに勝手にGCに回収されてしまう可能性はどうなんでしょうか?

以下のように変えました。

f:id:kazuhironagai77:20200531215529p:plain

f:id:kazuhironagai77:20200531215538p:plain

これなら、RPGGameModeBaseクラスのインスタンスが存在している限りはGCに回収されないし、RPGGameModeBaseクラスのインスタンスが存在しなくなったらGCが回収してくれると思います。

テストしています。

f:id:kazuhironagai77:20200531215611p:plain

問題なく動いています。動いていますが正直、今回の直しが正しいのかの良く分かりません。また後で問題が出たりUE4GCや Characterから派生したクラスの作成方法についての理解が深まったりしたら考えます。

2.Combat Engineの自作の続き

一応先週で5月3日のブログの時と同じ状態に戻れたはずなので、CombatEngineクラスのSelectNextCharacter()関数をまず直します。教科書と違い戦闘は常に一対一なのでこんな複雑な関数は要らないです。

1. SelectNextCharacter()関数の改良

先週から考えているのでそれをまず書いていきます。

まず、ヘッダーファイルの変数

f:id:kazuhironagai77:20200531215750p:plain

を消します。使用しません。

コンストラクターの実装も以下のように変更します。

f:id:kazuhironagai77:20200531215809p:plain

うーん。Member Initializer Listsにした方が良い気もしますが、今週は取りあえずこの形のままで行きます。

変更した部分はcombatantOrderが無くなったのでcombatInstanceのアサイン(assign)は直接MainCharacterとEnemyMonsterにしている所です。

Destructorも以下のように変更しました。

f:id:kazuhironagai77:20200531215836p:plain

何度も書いていますが、EnemyMonsterにnullptrをアサイン(assign)する必要はないと思っています。一応、今週は、EnemyMonster云々は残しておきます。

最後にSelectNextCharacter()関数を示します。

f:id:kazuhironagai77:20200531215856p:plain

combatantOrderを無くしたのでtickTargetIndexで味方と敵の順番を決定しています。
残りの関数は前と同じです。
テストします。

f:id:kazuhironagai77:20200531215925p:plain

まだテスト用のコードを書いていませんでした。

2. バグの修正

教科書の1267/3870を見たら以下の様になっていました。

f:id:kazuhironagai77:20200531220009p:plain

テスト結果では味方の行動しか表示されていませんが、教科書の結果は敵のモンスターの行動も報告されています。このバグをまず直します。

GameCharacterクラスのBeginMakeDecision()関数でThis->PlayerNameとなっていました。

f:id:kazuhironagai77:20200531220031p:plain

EmenyMonsterクラスはPlayerName変数はありませんのでなにも返せないのでしょう。これを直します。

f:id:kazuhironagai77:20200531220112p:plain

一番少ない労力で直すにはEnemyMonsterの種族名をPlayerName変数に保存する事だと思いOccupationを消してPlayerNameを追加しました。

f:id:kazuhironagai77:20200531220212p:plain

今度はしっかり敵のモンスターの行動も表示されています。バグは直せたみたいです。

と言おうとしたら、

f:id:kazuhironagai77:20200531220231p:plain

何か良く分からない警告が出て来ました。

何かepicgame社のホームページにデータを送るのか受け取るのかの要求をして失敗しているみたいですが。良く分からないので恐ろしいです。

調べて見たら、EpicGame社が定期的に利用者のデータを集めているらしいのですがそれが失敗している時にこれが表示されるみたいです。

分かったので次に行きます。

3. Actionの作成

3.1 ICombatActionクラスの作成

教科書を見るとこの次にアクションの作成を行っています。他に作成したいクラスも特にないので、アクションを作成します。

ICombatActionクラスを親クラスなしで作成します。

f:id:kazuhironagai77:20200531220350p:plain

後、Ch2をみるとCombatフォルダー→Actionフォルダー→ICombatActionクラスとなっています。フォルダーの作成も一緒に行います。

f:id:kazuhironagai77:20200531220447p:plain

作りました。

Ch2はCombatフォルダー自体をPrivateフォルダーの外側に作成していました。教科書のサンプルがそういう風に作成していました。Ch4_3は既にPrivateフォルダー内にCombatフォルダーを作成していまっているのでそれをそのまま使用する事にします。

前回作成した時に「Combat Engineを理解するには、戦闘部分のみを専門に行う特別な技術を持った零細企業のイメージを持つと良い。」とまとめました。その時ICombatActionクラスとIDecisionMakerクラスはこの零細企業に勤める凄腕の二人の職人と例えました。

でも、何が凄腕の職人だったんでしたっけ。思い出せませんね。後で確認しましょう。

f:id:kazuhironagai77:20200531220532p:plain

f:id:kazuhironagai77:20200531220546p:plain

これはクラスと言うかインターフェイス(Interface)ですね。

あ、思い出しました。ICombatActionクラスとIDecisionMakerクラスを機械に見立てて、TestCombatActionクラスとTestDecisionMakerクラスの二つをその機械を扱う凄腕の職人と見立てたんでした。更にこの零細企業だけが戦闘システムが開始と実行に分かれていてそれぞれを別に作成しなければならない事に気が付いているので、小さいながら大きな会社からも仕事の依頼が来る特別な技術を持った零細企業として成り立っているとしたんでした。

このストーリーを考えた時は、単に暇つぶし以上の価値はないと思っていたんですが、今思い出してみるとかなりCombat Engineの作成においてかなり重要なポイントを含んだ含蓄のある逸話に仕上がっていますね。

それはともかくとして、ソースファイル内でproject名.hをインクルードしています。Ch2のproject名.hは以下に示すように

f:id:kazuhironagai77:20200531220623p:plain

Engine.hをインクルードしています。(それ以外のヘッダーは後でインクルードするはずなので今は無視します。)

一応、Engine.hをch4_3 のch4_3.hファイルに追加しておきます。

f:id:kazuhironagai77:20200531220643p:plain

3.2 TestCombatActionクラスの作成

教科書には生のC++クラスを作成してそれを改造して作成せよ。と書かれているのでそうします。

f:id:kazuhironagai77:20200531220715p:plain

f:id:kazuhironagai77:20200531220722p:plain

いきなり最終形態を作っても良かったのですが復習を兼ねて教科書通りに作成しました。

4. GameCharacterクラスからActionを使用する。

GameCharacterクラスは大手ゲーム会社の社員でゲームに登場するキャラクターの特性を担当している人だったのですが、部長から下請けの零細企業にゲームの根幹にあたる戦闘システムを握られてしまうとやばいと言われ、それを防ぐために、全然関係のない戦闘システムの管理も任されてしまったちょっと押しの弱い人です。と言う設定で考えるとGameCharacterクラスの役割が非常に理解しやすいと前に言いました。このストーリー、本当に分かり易いです。

ICombatActionクラスとTestCombatActionクラスを作成した瞬間からもうGameCharacterクラスが干渉してくるなんて実際の企業ドラマを見ているかのようです。更に本来、GameCharacterクラスはCombatEngineクラスのみと干渉すべきで、ICombatActionクラスとTestCombatActionクラスはCombatEngineクラスを通して干渉すべきなのに、そんな原則をガン無視して直接、ICombatActionクラスとTestCombatActionクラスに干渉する処も現実世界の会社を見ているかのようで面白いです。

このクラスの擬人化ことがオブジェクト指向高級言語中の高級言語としているような気がします。もうオブジェクト指向については議論し尽くした感がありますが、クラスの擬人化と言う観点からは議論はほとんどされていません。この事はもっと注目すべき点と思って来ました。

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

f:id:kazuhironagai77:20200531220934p:plain

まず、GameCharacter.hファイルにICombatActionクラスから作成したポインター変数、combatActionを宣言します。

ICombatActionクラスをGameCharacterクラスから使用するためにはICombatAction.hをインクルードする必要がありますが、ICombatAction.hで既にGameCharacter.hをインクルードしていますので、それをするとCircular dependencyに成ってエラーになります。ので

f:id:kazuhironagai77:20200531221006p:plain

とForward Declarationする事で回避します。

教科書は同時にICombatAction.hもインクルードしていて、一体全体この部分のコードが何をしているのか奇奇怪怪だったのですが、今年の3月あたりのブログで散々議論した結果、単なるForward Declarationであるとの結論が出ました。

ので今回は

f:id:kazuhironagai77:20200531221046p:plain

はcppファイルの方に書く事にしました。

テストします。

f:id:kazuhironagai77:20200531221111p:plain

うん。これを見る限りは成功しているみたいですね。

5. Decisionの作成

5.1 IDecisionMakerクラスを作成する。

今度は、IDecisionMakerクラスを作成します。

f:id:kazuhironagai77:20200531221207p:plain

f:id:kazuhironagai77:20200531221216p:plain

教科書のままに作成しました。

あれ?GameCharacter.hをインクルードしていながらUGameCharacterクラスをforward declarationしている。

見直して見るとICombatAction.hも同じようにしていました。

f:id:kazuhironagai77:20200531221241p:plain

むむ。本来ならこっちのクラスは普通にインクルードするだけでいいはずです。

ですがinterfaceなので100%の自信はないです。この部分は来週以降考える事にします。

5.2 TestDecisionMakerの作成

これも教科書通りに作成します。

f:id:kazuhironagai77:20200531221312p:plain

f:id:kazuhironagai77:20200531221319p:plain

IDecisionMaker.cppファイルでch4_3.hをインクルードしているのにその派生クラスであるTestDecisionMakerクラスのソースファイルでもch4_3.hをインクルードしているのが不思議なくらいですね。

これも来週検討する事にします。

6. GameCharacterクラスからDecisionMakerを使用する。

もう本当にGameCharacterクラスさんはしつこい。IDecisionMakerクラスとTestDecisionMakerクラスが作成するやいなや、即座にそれらのクラスを管理しに来ます。

まあ仕方がないので、TestDecisionMakerクラスに指令を出すコードをGameCharacterクラスの関数に追加します。

まずGameCharacterクラスのヘッダーファイルにIDecisionMakerクラスから作成したdecisionMaker変数を宣言します。

f:id:kazuhironagai77:20200531221426p:plain

IDecisionMakerクラスをGameCharacter.hファイルから使用するためにIDecisionMakerをforward declarationします。

f:id:kazuhironagai77:20200531221513p:plain

今度は、cppファイル内で、以下の関数の実装を変更します。

f:id:kazuhironagai77:20200531221539p:plain

f:id:kazuhironagai77:20200531221547p:plain

f:id:kazuhironagai77:20200531221555p:plain

更に、

f:id:kazuhironagai77:20200531221632p:plain

を追加します。

最後にGameCharacterクラスのコンストラクターであるCreateYourHero()関数とCreateEnemyMonster()関数内に以下のコードを

f:id:kazuhironagai77:20200531221656p:plain

追加しました。

これで完成のようなのでテストします。

f:id:kazuhironagai77:20200531221715p:plain

正常に動いているようなので成功とします。

7. SelectTarget()関数の作成

SelectTarget()関数はキャラクターが攻撃する敵を選択するための関数です。教科書のサンプルは敵味方が複数である場合を想定して実装されていますので、コードを大幅に変更します。

最初にSelectTarget()関数とisPlayer変数を宣言します。

f:id:kazuhironagai77:20200531221750p:plain

f:id:kazuhironagai77:20200531221803p:plain

ここは教科書と同じです。

コンストラクター内でisPlayer変数にtrue, falseをassignします。

CreateYourHero()関数内で

f:id:kazuhironagai77:20200531221837p:plain

CreateEnemyMonster()関数内で

f:id:kazuhironagai77:20200531221926p:plain

を実装しました。

そしてSelectTarget()関数を以下のように実装します。

f:id:kazuhironagai77:20200531222011p:plain

大分、実装内容が変わると思ったのですが終わって見れば余り変わらなかったですね

8. ダメージを与える

次はTestCombatActionクラスを改良して実際にダメージを与えられるようにします。

うん。前回、どうやって作成したのか全く覚えていません。

教科書を読みながら復習します。

教科書によると攻撃するキャラとされるキャラを表す変数を作成するそうです。GameCharacterクラスを使用します。更にコンストラクターを作成してそのコンストラクター内で攻撃されるキャラをアサイン(assign)します。次にBeginExecuteAction()関数内で攻撃するキャラにアサイン(assign)します。

うん。普通は攻撃するキャラをコンストラクター内でアサイン(assign)するんじゃね。それ以前に、攻撃するキャラの前に攻撃されるキャラを知る事が可能なの?

ちょっと気になるので調べて見ます。

f:id:kazuhironagai77:20200531222114p:plain

こんな感じで初期化していますね。コードを読む限りでは可能みたいですが、敢えてこんな形にする理由があるのでしょうか?新たな注目点を見つけましたね。

まあいいです。作成していきます。

f:id:kazuhironagai77:20200531222154p:plain

f:id:kazuhironagai77:20200531222203p:plain

f:id:kazuhironagai77:20200531222211p:plain

作成しました。これらは教科書に書かれているのと全く同じです。

更にBeginExecuteAction()関数を以下のように改良しました。

f:id:kazuhironagai77:20200531222235p:plain

こちらは教科書のコードと比較するとかなり変わっています。私のゲームの対戦は一対一だからです。やっている事は教科書と一緒です。

一つだけ心配なのが、既に攻撃されるキャラのHPが0の状態でこのBeginExecuteAction()関数が呼ばれる事があるのでしょうか?

調べて見ます。

まず、以下に改良する前のコードを示します。

f:id:kazuhironagai77:20200531222307p:plain

何と、SelectTarget()を使用しています。この関数は以下に示すように

f:id:kazuhironagai77:20200531222328p:plain

攻撃するキャラが全滅した場合、nullptrを指すように実装されています。と言う事は結果的に私のコードと同じ結果になるはずです。

ので、既に攻撃されるキャラのHPが0の状態でこのBeginExecuteAction()関数が呼ばれる事があろうがなかろうが、結果は同じになるはずです。

つまりこの実装方法でも大丈夫でしょうね。

テストします。

ビルドしてたらメモリーが足りないとか出て来てエディターが止まってしまいました。

その後で、VSからエラーが二つほど表示されたのでそれを直しました。

一つ目のエラーは

f:id:kazuhironagai77:20200531222415p:plain

この部分のコードを自分のプロジェクトに追加するのを忘れていました。

追加しました。

二つ目は、

f:id:kazuhironagai77:20200531222450p:plain

BeginExecuteAction()関数のパラメーターであるcharacterが変数のcharacterと区別がつかない。との事。

面倒くさいのでもうmyCharacterと名前を変更しました。

f:id:kazuhironagai77:20200531222529p:plain

今度はビルドしました。

f:id:kazuhironagai77:20200531222553p:plain

攻撃するキャラが正しく選ばれていませんね。直しましょう。

ここが間違っていました。

f:id:kazuhironagai77:20200531222631p:plain

もしプレイヤーのキャラなら攻撃相手は敵のモンスターになります。逆になってました。

f:id:kazuhironagai77:20200531222653p:plain

今度は正しく動いていますね。

9. UMGを追加する

この辺は本当に覚えていませんね。UMGをUE4C++から追加する方法は前の教科書Unreal engine 4 Scripting with C++ Cookbookでも散々勉強したので覚えていますが。

実際のゲーム制作においてどのようにそれを使用するんでしょうか?

しょうがないのでもう一回教科書を読み直します。

読みました。

ここからUMGとUE4C++のプログラミングの話になるので、内容が大胆に変わってきます。切りがいいので今週はここで止めます。

3.まとめと感想

この教科書はもう一度、読むほどの内容かなと思っていて、早く別の教科書を勉強したいと思っていましたが、今回読み直したら、結構勉強になりました。石の上にも三年は正しい時もありますね。

前回勉強した時は「何でBPで書いているんだよ。」とか言ってましたが、今UMGの章を読んだら「BPで実装した事をUEC++で実行するためには。」とかなり実用的な内容でした。この辺は来週しっかり勉強します。