UE4の勉強記録

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

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する ターン制戦闘の改良 Part6

f:id:kazuhironagai77:20200405201137p:plain

<前文>

先週の前文をもう少し早く掲載していたら、読者に英語圏と日本語圏の考え方の違いをはっきりと伝えられたのですが、アメリカも日本も突然、方向転換してお互いに歩み寄って差があまりなくなってしまいました。特にアメリカがマスクを推奨し出したのはアメリカに十年住んだ私もびっくりでした。前回も書きましたように、感染者を全部見つけて全部隔離して初めてロックダウン(lock down)の効果は出るので日本のやり方は表面だけ似せているだけですが。

もう時事ネタは書かないつもりです。技術は不変ではないかもしれませんがその技術の元になる理論は不変です。それに比べて時事ネタは刻一刻と内容が変化します。対応する手間が大変すぎます。

統計学について

統計学ほど計算方法は理解出来るけど、どのように応用すればいいのかが不明だった学問はありませんでした。もうすっかり諦めていましたが、先週、論理(ニュートン力学)、確率(量子力学)そして有限のサンプル数を元にして確率を推測するのが統計(統計力学ではない?)と聞いてピンと来ました。確率を有限のサンプル数から推測する学問だったのね。それ以来、MarinStatsLectures-R Programming & Statisticsをずっと見ています。やっぱり計算方法そのものは今でも覚えていますね。これは、やっと統計学を生きた学問として使用出来そうです。

Dark LとLight L パート2

前に書きましたが、一般にLightなどの最初のLがLight L、tellなどの最後のLがdark Lと言われていますが、理由は前回書いたのでここには書きませんが、アメリカ人の特に男性のLはdark Lしかないんじゃないの?と思っています。これを調べる方法を思いつきました。IPA、International Phonetic Alphabetでアメリカ式の発音とイギリス式の発音を比べてみるんです。

このサイトによると、以下のようなIPAの発音記号が付いていました。

f:id:kazuhironagai77:20200412204049p:plain

ɫこの発音記号は何でしょうか?これこそdark Lを表す発音記号じゃないでしょうか?

このサイトに以下の様に書かれていました。

f:id:kazuhironagai77:20200412204110p:plain

はあ!

我が耳ながらあっ晴れですな。しっかりdark Lと書かれています。やっぱり私の聞こえた通りでした。いや、ここは私が作成している英語発音聞き分け能力育成ゲームの性能が素晴らしいと言うべきかもしれません。

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

<本文>

先週まででcombat engineの復習が終わりました。ここで新しいcombat engineを作成するのか、それとも現状のcombat engineを使用するのかを決定しなければなりません。

更に以下の機能を戦闘に追加します。

f:id:kazuhironagai77:20200412204138p:plain

1.新しいcombat engineを作成する?

教科書の戦闘は基本的に多数対多数になっています。私のゲームでは1対1で良いので直したいです。更に自分でCombat engineをデザインする事でどのように生のC++UE4に組み込むのかの理解も深めたいです。ので教科書のCombat engineを元にして自分オリジナルのcombat engineを作成する事にします。

今日は最初なので、モンスターに捕まったら戦闘が発動するようにします。

f:id:kazuhironagai77:20200412204206p:plain

単にevent triggerをこのモンスターに追加すればいい気がしますが、試してみましょう。

f:id:kazuhironagai77:20200412204223p:plain

Add Componentの項を調べましたがBox Triggerはありませんね。仕方ないので付属のCapsuleComponentを使用します。

f:id:kazuhironagai77:20200412204242p:plain

取りあえず以下のノードでプレイヤーの操作するキャラと衝突した場合に反応するか試してみます。

f:id:kazuhironagai77:20200412204310p:plain

思いっきり反応してます。

f:id:kazuhironagai77:20200412204328p:plain

反応するのは一回でいいので、以下のように改良しました。

f:id:kazuhironagai77:20200412204408p:plain

一回だけ反応しています。

f:id:kazuhironagai77:20200412204428p:plain

今度はここからTestCombat2()関数を呼んでみます。

まずRPGGameModeクラスのTestCombat2() 関数をBlueprintから呼べるようにします。

f:id:kazuhironagai77:20200412204451p:plain

次にモンスターのBP内のコードを以下の様に変更します。

f:id:kazuhironagai77:20200412204508p:plain

テストします。

f:id:kazuhironagai77:20200412204527p:plain

見事に戦闘が始まりました。

2.新しいcombat engineのデザイン色々

1. 戦闘中はレベル(マップ)を変えたい

戦闘になったらレベルを変えたいんです。例えば以下に示すような場所にです。

f:id:kazuhironagai77:20200412204604p:plain

そして戦闘が終わったら元のレベルに戻ってくる。今の設計ではGameInstanceクラスに保持されているデータは以下に示すように、

f:id:kazuhironagai77:20200412204624p:plain

得たGoldの数とそれぞれのパーティメンバーのGameCharacterクラスだけです。このGameCharacterクラスの変数は、

f:id:kazuhironagai77:20200412204643p:plain

となっていて所持しているアイテムや武器、更に装備している武具の情報はBP側で全て管理しています。これを直したいです。

更に元のレベルに戻ってくるためには、位置情報などのデータもセーブする必要があります。

ここから直すとなると、最初の設計からやり直すべきかもしれませんね。

. 戦闘UIデザイン案

まず、戦闘が始まると、以下の画面が表示されます。

f:id:kazuhironagai77:20200412204709p:plain

攻撃だけでなく他の選択が出来るようなっています。

カメラの位置を移動するか別なカメラを指定して主人公の顔が見える位置から撮影するようにします。

画面下に吹き出しを作成して主人公の選択についての解説をします。

主人公が攻撃を選択した場合、攻撃対象のUIが表示されます。

f:id:kazuhironagai77:20200412204731p:plain

画面下の吹き出しには、プレイヤーが取るべき選択と取った選択についての簡単な解説が表示されます。

プレイヤーの選択が終了すると、モンスターのターンになります。

f:id:kazuhironagai77:20200412204750p:plain

カメラがモンスターを写します。右上にモンスターの種族とHPが表示されます。画面下には吹き出しが表示されモンスターの選択した内容が簡単に説明されます。

f:id:kazuhironagai77:20200412204809p:plain

これで、戦闘準備がお互いに整いました。戦闘が始まります。

戦闘が開始したら、選択した内容によって以下のアニメーションが表示されるようにします。

f:id:kazuhironagai77:20200412204828p:plain

モンスターの場合は以下のアニメーションが変わりに使用されます。

f:id:kazuhironagai77:20200412204846p:plain

戦闘が終了するまで、上記の画面を繰り返します。

戦闘が終了した場合、プレイヤーが勝利した場合は、

f:id:kazuhironagai77:20200412204905p:plain

上に示した図のように、主人公が得た経験値やゴールドなどが画面下の吹き出しに説明されます。そして元の場所に戻ります。

プレイヤーが敗北した場合はゲームオーバーになります。

3. MyCombatEngineなどの作成

新しいプロジェクトを作成します。名前はch3とします。4.25を使用します。

f:id:kazuhironagai77:20200412204959p:plain

ここに新しいGameStateクラスや私のCombatEngineを作成します。

FCharacterClassInfoクラスから作成し直します。FCharacterClassInfoクラスは味方のキャラクターのパラメーターの種類を指定するためのデータテーブルです。

f:id:kazuhironagai77:20200412205017p:plain

色々考えてみましたが、上記の内容だけでFCharacterClassInfoクラスは良いと思います。

名前はここに保持する必要はありませんし、アイテムや武器の内容は別なデータテーブルで管理します。最大値を指定するパラメーターは必要ないとの結論に達したので全部外しました。特殊能力は必要ないので消しました。代わりに魔法を追加しますがキャラクターのレベルと職業で使用出来る魔法は管理する事にする予定ですのでここに特別な変数を作成する事は止めました。キャラクターの最初のレベルは勿論1なのでLevel変数も必要ないと考えそれも消しました。

FCharacterClassInfoクラスを元にデータテーブルを作成しました。名前はCharacterClassesとします。以下に示すようにデータを一種類だけ追加しました。

f:id:kazuhironagai77:20200412205037p:plain

次にUGameCharacterクラスを作成します。このクラス、元々はゲームに登場するキャラクターのパラメーターを管理するクラスのはずなのに、教科書ではcombat engineの管理もさせられています。このcombat engineの管理をこのクラスに担当させるのか、はたまた別なクラスに担当させるのかはまだ考え中です。取りあえずキャラクターのパラメーターを管理する機能だけ持ったクラスとして作成します。

f:id:kazuhironagai77:20200412205056p:plain

Occupation、つまりキャラクターの職業ですが、Enumで管理すべきでFStringで管理すべきではないでしょう。ただしそのEnum内の職業の種類は、後で追加変更が出来るようにBPで管理できるようにすべきです。となるとこの段階ではFStringのままの方がいいのかもしれません。

保持しているアイテムや武具、そして装備している武器や防具はどのように保存すべきでしょうか?

これらもそれぞれDataTableを作成して保存すれば良いと考えています。教科書ではどのように作成したのかを確認します。

FItemDataクラスを以下のように作成していました。

f:id:kazuhironagai77:20200412205118p:plain

思い出してきました。教科書のサンプルにenumであるItemTypeと変数、UTexture2DとTEnumAsByte<ItemType>クラスからそれぞれToolImage変数とItemType変数を追加したクラスを作成しました。

ただしこの教科書では、プレイヤーの操作するキャラが違うマップに移動する事は考慮していませんので、以下に示すようにRPGGameInstaceクラスから派生したMyRPGGameInstanceBPクラス内に変数を作成してプレイヤーが保持しているアイテム、武具、装備している武器、防具の情報を管理しています。

f:id:kazuhironagai77:20200412205138p:plain

この管理は一括でUE4C++のGameInstanceクラスでやりたいんです。と言うかやるべきと考えています。

BPの変数をみると、キャラクターが保持しているアイテムや武具のデータは、Testの配列で作成されています。FStringの配列などを直接、UE4C++のGameInstanceクラス内に作成する事で対応出来そうです。

因みにUEC++のデータテーブルから派生したBPデータテーブルのデータをUEC++、つまりRPGGameInstance内で使用するやり方は教科書に載っていて、以下に示すように2か所で使用されています。

f:id:kazuhironagai77:20200412205157p:plain

f:id:kazuhironagai77:20200412205207p:plain

この辺を上手く使用すれば出来そうですね。

まずCreateGameCharacter()関数の実装部を作成してみました。

f:id:kazuhironagai77:20200412205225p:plain

大分、元のコードから改造しました。

まずFCharacterClassInfo* ClassInfo変数を消しました。この変数、特に使用する個所がないので要らないと判断しました。

更にFCharacterInfoデータテーブルも必要性は薄いと考え無くしましたのでCreateGameCharacter()関数のパラメーターが、FString のNameとFString のPlayerRowNameに成っています。

ビルドを試してみます。

f:id:kazuhironagai77:20200412205243p:plain

CreateObject()関数にUObjectをパスしている所とか、FindRow()関数にFNameをパスしている所など、ちょっと良く分かってない所がありますが一応ビルドは出来ました。

この関数が実際に正しく作用するのか先に試した方が良いと思うので、そのためのコードをこれから実装します。

教科書のコードを見ると、以下に示すようにCreateObject()関数は、GameInstanceクラスのInit()関数内から呼ばれています。

f:id:kazuhironagai77:20200412205307p:plain

そしてこのGameInstanceクラスはGameModeクラスから呼ばれています。

のでGameModeクラスとGameInstanceクラスを作成します。

まず、GameModeクラスから作成します。教科書はGameModeクラスを親クラスとしていますが、GameModeBaseクラスが親クラスでも問題ないと思われます。

f:id:kazuhironagai77:20200412205334p:plain

ので以下のようにGameModeBaseクラスから作成しました。同様にGameInstanceクラスからもRPGGameInstanceクラスを作成しました。

f:id:kazuhironagai77:20200412205400p:plain

まず、RPGGameInstanceクラスから作成します。

以下に示すように教科書のconstructorはFObjectInitializerを使用していますが、

f:id:kazuhironagai77:20200412205418p:plain

これは古いやり方なので外し、以下のように宣言します。

f:id:kazuhironagai77:20200412205440p:plain

実装部は同じように作成しました。

f:id:kazuhironagai77:20200412205458p:plain

次にInit()関数内でGameCharacterクラスの変数の初期化を行います。

f:id:kazuhironagai77:20200412205521p:plain

今回のゲームは、敵も味方も一人で戦うのでTArrrayは外しました。

f:id:kazuhironagai77:20200412205541p:plain

まず、エラーの原因が、

f:id:kazuhironagai77:20200412205558p:plain

なので、Staticを追加します。

f:id:kazuhironagai77:20200412205616p:plain

良く見たら、教科書のサンプルもStaticがありました。

FStringがargumentととして一番簡単にパス出来ると思っていたのですが以下に示すように

f:id:kazuhironagai77:20200412205636p:plain

結構大変な書き方になってしまいました。もっと簡単に書く方法があるかもしれませんが無ければFTextを代わりに使用します。

ビルドしてみます。

f:id:kazuhironagai77:20200412205701p:plain

出来ました。

と思ったら、UE4Editorがクラッシュしていまいました。

うーん。再起動させて今度は、UE4Editor側からコンパイルしましたが普通に出来ました。クラッシュもしません。

テストしてみます。

作成したGameModeとGameInstanceクラスからBPを作成します。

f:id:kazuhironagai77:20200412205727p:plain

それらをセットします。

f:id:kazuhironagai77:20200412205745p:plain

f:id:kazuhironagai77:20200412205752p:plain

更に、レベルBP内に以下のコードを追加します。

f:id:kazuhironagai77:20200412205810p:plain

GameCharacterがきちんと作成されていたら、Eを押したらキャラクターのATKの値が表示されるはずです。

テストしてみます。

f:id:kazuhironagai77:20200412205827p:plain

されました。

もう時間がなくなってしまったので今週はここで終了します。

4. まとめと感想

今週は、Combat engineをどのように作成しなすかについて検討しました。教科書のCombat engineは複数対複数の戦闘のために作成されたものなので最初から作り直す事にしました。しかしCombat engineだけ作り直す事は出来ないので全部作り直します。