UE4の勉強記録

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

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する 戦闘システムを完成する4

f:id:kazuhironagai77:20201206213546p:plain

<前文>

コンテクストってどんな意味?

良く和訳されたビジネス書にコンテクストって出てくるじゃないですか。コンテンツじゃないですよ。コンテクスト。Googleで調べると「前後関係」とか「文脈」とか説明されています。しかし和訳された実際の文章でコンテクストを「前後関係」とか「文脈」で置き換えてもよく意味が分からないですよね。

コンテクストって勿論、contextのカタカナ読みです。そしてcontextを日本語に訳せば「前後関係」とか「文脈」になります。なりますが実際の文を読むと意味が分かりません。日本語と英語のある違いからcontextは日本語に訳すと全く理解出来なくなる珍しい単語なんです。

そのある違いとは、英語には一つの単語に沢山の意味がある場合が多いですが、日本語には極少数の例外を除いて一つの単語には一つの意味しかない事です。

分かり易い例を上げると、tipと言う単語です。レストランで使用する場合はチップと言う意味で、ウェイトレスらに渡す「お金」を意味します。しかし本にtipと書かれている場合は「豆知識」と言う意味です。お金と豆知識は全く意味が違います。だからアメリカ人にtipってどういう意味?と前提条件の無い状態で質問すると「それは状況によってです。」としか答えません。だって意味が全く違うんだから。

この「状況によって」と言う意味こそがcontextなんです。

  • Implementってどういう意味?」「このコンテクスト(context)では、コードを書くって意味。」
  • 「ジミーが a computerは好きだけとcomputersは嫌い。」って言ってたけど。どう意味が違うの?」「それはコンテクスト(context)が分からないと答えようがないよ。」
  • fineって「罰金」と「調子が良い」と言う意味は知っているけど?」「このコンテクスト(context)では「非常に細かい(粒子)」と言う意味。」

みたいに基本的にコンテクスト(context)は、単語の意味を説明するのに使用される言葉なんです。

はい。

日本語にそんな全く違う意味を複数持つ単語はないですね。同音な単語は結構ありますが、漢字で書いても同じで意味は違う単語って思い付きますか?

私は「お盆」しか思いつきませんでした。

だから、日本語には基本的にコンテクスト(context)に対応する概念がないんです。無いからcontextを含む分を日本語に訳すと???な文になってしまうんです。

コンテクストってどんな意味?part 2

コンテクストの良い例文がないか調べていたら、コンテクストの意味として「空気を読む」と説明しているサイトがありました。これはかなりの超訳ですが、ある意味的を射ていてかなり感心しました。例えば、今、流行りの自粛ですが、本来の意味では強制的なニュアンスはありませんが、今回の場合は半ば強制です。

  • 「自粛ってどういう意味?」「このコンテクストでは絶対やってはいけないって事。」
  • 「押すなよって言ってるけど」「このコンテクストでは押せって事よ。」

うーん。完璧ですね。

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

<本文>

今週は以下に示した先週のバグの直しをして、

  • Hierarchical Instanced Static Meshの勉強のやり直し
  • Pause画面を開く時は、このSet Game Pausedをオンにすべきなのか?
  • スタート画面のキャラが動かせます。
  • 半分くらいのNPCは会話が可能な状態でも(!!)マークが表示されていません。
  • 現在使用していない機能を実装した箇所は消す。

その後で先週の作業の続きを行っていきます。

1.先週のバグの直し

1.1 Hierarchical Instanced Static Meshの勉強のやり直し

先週の大量の骸骨の表示ですが、

f:id:kazuhironagai77:20201206213739p:plain

ActorBPクラスから派生したクラス、MyBackgroundSkullsクラス内にHierarchical Instanced Static MeshをComponentとして追加する事で作成しています。

f:id:kazuhironagai77:20201206213800p:plain

f:id:kazuhironagai77:20201206213809p:plain

Hierarchical Instanced Static MeshではStatic meshにSkeltonHeadを指定しています。

f:id:kazuhironagai77:20201206213830p:plain

以下の方法で大量の骸骨を円周上に配置しています。

f:id:kazuhironagai77:20201206213850p:plain

以下の変数で発生する骸骨の数、骸骨同士の距離、円周のサイズ、骸骨の位置のばらつきをコントロールしています。

f:id:kazuhironagai77:20201206213909p:plain

骸骨の自転は、以下の方法で実装しました。

f:id:kazuhironagai77:20201206213927p:plain

骸骨の公転は、Hierarchical Instanced Static MeshをレベルBPから呼び出し回転を追加する事で実現しました。

f:id:kazuhironagai77:20201206213946p:plain

本来ならばUE4におけるActorクラスにComponentされたstatic meshの回転そのものを厳密に数学として調べたいです。

しかし今回はあくまでデザインが主なので、まず1000個の骸骨がHierarchical Instanced Static Meshを使用して実現する事でCPUに負担になっていない事を確認します。

やっぱり、前に見たこのtutorialが一番分かり易い説明していました。このvideo 内でFor Loopの説明を丁寧に始めたので、こんな初心者向けのTutorialを全部見る必要はない。と途中までしか見ないで作成を始めてしまったのですがやっぱり良く見るべきでした。

まず、このtutorialでは、AddInstance()関数をEvent BeginPlayで呼ばずに、Construction Scriptで呼んでいました。

f:id:kazuhironagai77:20201206214013p:plain

Construction Scriptで呼ぶ事で、viewport内でHierarchical Instanced Static Meshの全体像が確認出来ます。

f:id:kazuhironagai77:20201206214036p:plain

更に、それぞれの変数の目を開く事で

f:id:kazuhironagai77:20201206214056p:plain

Viewportのeditorから値を変更出来る様にしていました。

f:id:kazuhironagai77:20201206214117p:plain

この方法でやると、どんな風に骸骨が配置されるのかを確認しながら上記の値を変更出来ます。

Instanceの作成方法は、tutorialと全く同じだったのでactorを一個ずつ作成した時のような負担はcpuにはかかってないと思われます。

このビデオにはHierarchical Instanced Static Meshを使用した場合の優位性を測定する方法が紹介されていました。が今回はここまでにしておきます。

まあ、少しずつ勉強する事にします。

1.2 Pause画面を開く時はSet Game Pausedをオンにすべきなのか?

現状どうなっているのかの確認から始めます。

f:id:kazuhironagai77:20201206214146p:plain

Pause画面を開いたら、幾らマウスを動かしてもカメラは移動しません。Keyboardからの入力も受け付けません。

f:id:kazuhironagai77:20201206214208p:plain

更に、モンスターも追いかけて来ません。現状で理想の状態が出来ています。

コードを確認したら既にSet Game Pausedが使用されていました。

f:id:kazuhironagai77:20201206214239p:plain

更に、戻るボタンで、Set Game Pausedが解除されていました。

f:id:kazuhironagai77:20201206214301p:plain

これはPause画面に限らず、宿屋の会話でも何でもこれと同じにすべきですね。

確認します。

しました。全部なっていました。

1.3 スタート画面のキャラが動かせます。

Set Game Pausedを使用すれば動かなくなるはずです。

f:id:kazuhironagai77:20201206214339p:plain

カメラも動かなくなりました。

Set Input Mode UI Onlyに変更しました。

f:id:kazuhironagai77:20201206214402p:plain

今度は、カメラは動きますが、キャラはInputから操作出来ません。これで良いですね。

1.4 半分くらいのNPCは会話が可能な状態でも(!!)マークが表示されていません

今、確認したら、表示はされているのですが暗くて見えない時があるみたいです。

f:id:kazuhironagai77:20201206214453p:plain

Point lightを追加してみました。

f:id:kazuhironagai77:20201206214522p:plain

何となく、みんな光っています。

f:id:kazuhironagai77:20201206214602p:plain

しかしそのお陰で夜は滅茶苦茶見やすいです。

f:id:kazuhironagai77:20201206214621p:plain

(!!)マークも見やすいです。

f:id:kazuhironagai77:20201206214648p:plain

町の人なんか特に見やすいです。

f:id:kazuhironagai77:20201206214711p:plain

今回はPoint lightはそのままにしておきます。後で昼間は消えるようにします。

1.5 現在使用していない機能を実装した箇所は消す。

消しました。

正し、Show Mouse Cursor if Notノードを使用して

f:id:kazuhironagai77:20201206214736p:plain

以下の箇所を入れ替えようとしましたが止めました。

f:id:kazuhironagai77:20201206214757p:plain

更に、ワープして別のマップに移動する実装をどうやったのかもう分からなくなっているので、その辺りもいじりませんでした。

f:id:kazuhironagai77:20201206214815p:plain

この辺は戦闘システムが完成した後で直します。

2. 先週の続き

先週と言うか先々週の続きをやっていきます。

2.1 タイプライター効果の追加

f:id:kazuhironagai77:20201206214904p:plain

これは、今回のプロジェクトでは作成しません。このRPGの作成が終わった後、Visual NovelをUE4で作成します。その時にタイプライター効果も作成します。

2.2 攻撃ボタンと逃げるボタンしか表示されていない…

f:id:kazuhironagai77:20201206214927p:plain

2.2.1 コメントの直し

まず簡単なコメントから直します。以下のようにコメントを変えました。

f:id:kazuhironagai77:20201206215005p:plain

うーん。セリフが飛び出してしまいました。

f:id:kazuhironagai77:20201206215022p:plain

直しましたが、まだ文章がおかしいです。

f:id:kazuhironagai77:20201206215041p:plain

最終的にはこうなりました。

f:id:kazuhironagai77:20201206215058p:plain

2.2.2 ボタンの直し

ボタンの隙間も直しました。

道具を持っていなくて魔法も使えない時の選択ボタンの表示です。

f:id:kazuhironagai77:20201206215125p:plain

魔法が使用出来る様になった時の表示です。

f:id:kazuhironagai77:20201206215147p:plain

道具のみが使用出来る様になった時の表示です。

f:id:kazuhironagai77:20201206215203p:plain

魔法並びに道具が使用出来る時の表示です。

f:id:kazuhironagai77:20201206215220p:plain

どの場合もボタン同士の隙間は無くなりました。

2.3 戦闘時のモンスターの行動をAIに担当させる

f:id:kazuhironagai77:20201206215242p:plain

UE4のAIの作り方を完全に忘れてしまいました。

今週はUE4のAIの復習をします。

UE4C++はUE4C++と書く事にしていますが、UE4のAIをUE4AIと書くと何の事なのか分からなくなりそうなので、UE4_AIと書く事にします。

2.3.1 UE4_AIの勉強した内容を確認する

まずは過去のblogを見て今まで何を勉強したのかを確認します。

確認しました。

2019年に初めてUE4のAIの勉強をしたのですが、その時期はデータサイエンス大流行で機械学習とか深層学習以外はAIじゃないみたいな風潮が凄かったので、必死こいて大学のAIの教科書を読んでいました。だってUE4のAIってIf節しかないんですもん。

そしたら結局、AIってif節の塊で機械学習とか深層学習もどうやってif節を作成するかが違うだけって言う事が分かりました。

それで安心してUE4のAIの勉強を始めたんです。

始めたんですが、結局やったのはこのtutorialの最初の2~3個とMathew Wadstein氏のAIのチュートリアルをこれまた2~3個見ただけでした。しかしそれでもモンスターが自動でプレイヤーのキャラを追いかけるAIが完成したのでそれで完成と言う事になりました。

以上です。

2.3.2 UE4_AIの復習

兎に角、UE4のAIの作り方を完全に忘れているのでtutorialでもう一回勉強します。適当に簡単なtutorialないのかなと探していたらこのtutorialが短くて分かり易そうだったのでこれで復習します。

大体思い出しました。流石に2回目の勉強になると理解も速いです。

Tutorial自体もかなり分かり易い説明でした。一か所だけ不満だったのは、一寸不自然にenumを使用しているように思えた所だけでした。

他のtutorialも見ましたが「コレしてアレして、ハイ出来ました。」的な説明に終始していて「基本」が理解しにくい作りだったので、以下に私が簡単にまとめてみました。

2.3.3 UE4_AIの私なりのまとめ

兎に角、UE4_AIはif節の塊です。普段、

f:id:kazuhironagai77:20201206215336p:plain

と書いている事を別な方法で書くだけです。

その書き方を以下にまとめます。

2.3.3.1 UE4_AIに必要な4つのクラス

まず、UE4_AIをBPで使用するためには4つのBPクラスが必要です。

<Characterクラス>

Characterクラスから作成しました。

f:id:kazuhironagai77:20201206215416p:plain

AIにコントロールされるキャラクターが必要なのは当然ですね。

<AIControllerクラス>

AIControllerクラスから作成しました。

f:id:kazuhironagai77:20201206215439p:plain

以下に示したようにAIControllerクラスは、Controllerの派生クラスです。Player ControllerクラスのAIバーションです。

f:id:kazuhironagai77:20201206215550p:plain

プレイヤーがコントロールするキャラクターは

ユーザーのinput ―> Player Controller―>  character

によって操作されるように、AIがコントロールするキャラは、

CPU―> AI Controller―>  character

でコントロールされるわけです。

<Behavior Treeクラス>

f:id:kazuhironagai77:20201206215732p:plain

UE4_AIにおけるIf節を書くための特別なクラスです。

書き方もちょっと特殊で、木構造を使用しています。最初見た時は、まるで子供だましと思いましたが、理解が深まると、こっちの書き方の方が、可視化されているため、複雑な分岐も理解しやすい事が分かりました。

<Blackboardクラス>

黒板クラスです。

f:id:kazuhironagai77:20201206215758p:plain

さっき勉強したtutorialの説明によると、Behavior Treeクラスは何故か変数の値を保持する事が出来ないんだそうです。

そのためにBehavior Treeクラスで使用する変数の値は別なクラスで保持する必要があるそうです。それをするのがこの黒板クラスだそうです。

まあ、黒板に値を、ちょろっと書いておくと考えると分かり易いですね。

2.3.3.2 それぞれのクラスの繋げ方

クラスが単独で存在していても、それぞれを繋げないとお互いを認識出来ません。繋げ方を以下に示します。

<Characterクラス とAI Controllerクラス>

Characterクラス とAI Controllerクラスを繋げます。

f:id:kazuhironagai77:20201206215840p:plain

CharacterクラスのBPを開いて、Pawnの所のAI Controller Classに作成したAl Controllerクラスをセットします。この場合はNPC_Controllerをセットしました。

<AI ControllerクラスとBehavior Treeクラス>

Al ControllerクラスのBPを開いて、Run Behavior Tree()関数を呼び出します。そのパラメーターに作成したBehavior Treeクラスをセットします。

f:id:kazuhironagai77:20201206215905p:plain

上記の例では、NPCBTと言う名前のBehavior Treeクラスがセットされています。

<Behavior TreeクラスとBlack Boardクラス>

Behavior TreeクラスのBPを開くと、以下に示したように、BehaviorTree、BlackBoard Assetと表示されている箇所があります。

そこに作成したBlack Boardクラスをセットします。

f:id:kazuhironagai77:20201206215926p:plain

これでUE4_AIに必要な4つのクラスが全部つながりました。

2.3.3.3 Behavior Treeクラス内でif節を作成する。

やっとここまで説明出来ました。ほとんどのtutorialでいきなりBehavior Treeの説明から始まるから読者は混乱するんです。少なくとも私は混乱しました。

このBehavior Treeクラスでやる事はただ一つです。「if節を作成する事」だけです。

そしてその具体的な方法はBehaviorTreeのTutorialを見た方が分かり易いと思いますので、私がメモしておきたい事を以下にまとめます。

<変数を作成する>

Behavior Treeクラスの右上のBlackboardをクリックしてNPCBBを開きます。

f:id:kazuhironagai77:20201206220016p:plain

Blackboardクラス内でNew Keyを押すと変数が作成出来ます。

f:id:kazuhironagai77:20201206220054p:plain

こんだけです。

<Selectorとsequenceについての考察>

Selectorがif節の大本で、その下に付くsequence達がそれぞれのif節、else if節に対応していると考えられます。

If節のconditionはDecoratorノードで指定する事が出来ます。Blackboardクラスの値を使用する時はBlackboard Based Conditionを選択します。

If節のconditionが正しい時に実行されるコードは、serviceに書かれます。更にその後にtaskを追加する事も可能です。

2.3.4 Game mode クラスの変数とUE4_AI

モンスターの戦闘中の行動をUE4_AIで決定するためには、Game mode内の変数の値をBehaviorTreeにパスして、BehaviorTree内でそのモンスターが取るべき選択をAIに決定させ、その決定した選択をまたGame Modeに伝達する必要があります。今までの復習で得た知識からこれが可能かどうか試してみます。

まず、新しいGame mode baseを作成します。

f:id:kazuhironagai77:20201206220138p:plain

Attack、Magic、itemのenumeratorを持つEnumを作成します。その変数をCombat Stateと名付けてMy Game Mode内に作成します。

f:id:kazuhironagai77:20201206220201p:plain

更に、Event DispatchersでAttack、Magic、Itemを作成します。

f:id:kazuhironagai77:20201206220225p:plain

それぞれのEvent Dispatchersにcustom eventをbindします。

f:id:kazuhironagai77:20201206220242p:plain

Behavior Treeは以下のように作成しました。

f:id:kazuhironagai77:20201206220257p:plain

Selector内のsetCombatState内で以下の実装を行います。SetCombatStateはserviceクラスから派生して作成しました。

f:id:kazuhironagai77:20201206220313p:plain

State KeyはsetCombatState内の変数でタイプは、Blackboard keyです。上記のやり方で、State Keyに値をセット出来るらしいです。

そして、BehaviorTreeに戻って、State KeyをCombatStateにセットします。

f:id:kazuhironagai77:20201206220335p:plain

かなり手間ですが、これでGameMode内の変数の値が、Blackboardクラスの変数にセットされたはずです。

ここで特殊なのは、Service クラスからblackboardクラスへの値のパスの仕方ですね。Blackboard keyと言う名前の特別なオブジェクトが間に入っています。パスされた値はcopyされたのか単にreferenceされたのかも分かりません。分かりませんが、多分copyされたんでしょうね。住所を写すだけならこんな手の込んだ事をする必要ないですから。

f:id:kazuhironagai77:20201206220405p:plain

Selectorの説明ですが「左から順に実行していきますが、子供のうちの一つでも成功するとそこで実行を中止する。」と書かれています。この成功すると言う意味が子供のノードのdecoratorの条件にマッチしたと同じ意味ならif節、elseif節と同じ意味になります。

ので、

f:id:kazuhironagai77:20201206220423p:plain

CombatStateの値がAttackなら、Magicなら、そしてItemならの3つの条件を作成しました。

設定の詳細は以下の通りです。

f:id:kazuhironagai77:20201206220438p:plain

Decoratorがマッチした時に実行される内容はServiceノードで実装します。

f:id:kazuhironagai77:20201206220458p:plain

ServiceノードであるAtttack_serviceの内容を以下に示します。

f:id:kazuhironagai77:20201206220520p:plain

テストします。

f:id:kazuhironagai77:20201206220542p:plain

出来ていますね。

試しにMagicに変更してみます。

f:id:kazuhironagai77:20201206220557p:plain

出来ています。

ただし、

f:id:kazuhironagai77:20201206220613p:plain

Attack Eventは一回、発動すれば良いので、その辺の改良をするための勉強が必要です。

2.3.5 ServiceクラスとTaskクラス

ServiceクラスとTaskクラスの違いがまだ良く分かりません。

f:id:kazuhironagai77:20201206220635p:plain

結局どっちもIf節のconditionが合致した時に実行される内容です。 TaskクラスにはServiceクラスにしか出来ない何かがあるのでしょうか?

UE4_AIの勉強は、今週はここまでにします。

2.4 戦闘時のキャラのアニメーション表示時でもUIは表示させる

以下に示したように、ユーザーが操作するキャラのアニメーションではUIは消えています。

f:id:kazuhironagai77:20201206220703p:plain

一方で、モンスターのアニメーションでは

f:id:kazuhironagai77:20201206220718p:plain

UIが表示されたままです。

UIが表示されたままの方が、見栄えが良いのでユーザーが操作するキャラのアニメーション中もUIが見える様に変更します。

f:id:kazuhironagai77:20201206220734p:plain

当然ですが、アニメーションが終わった後にUIを再表示させる必要もないので、その部分のノードも外しました。

f:id:kazuhironagai77:20201206220754p:plain

試してみます。

f:id:kazuhironagai77:20201206220808p:plain

はい、ユーザーが操作するキャラのアニメーション中もUIが見える様になりました。

2.5 勝利のアニメーション時のカメラ位置を調整する。

f:id:kazuhironagai77:20201206220830p:plain

カメラの位置を変えるコードを追加しました。

f:id:kazuhironagai77:20201206220905p:plain

当然ですが、アニメーション終了後に、カメラの位置を戻すコードも追加しました。

f:id:kazuhironagai77:20201206221129p:plain

テストします。

出来ています。

f:id:kazuhironagai77:20201206221147p:plain

このスクリーンショットだとしょぼいですが、実際のアニメーションはかなりの迫力があります。

戦闘を速く終わらせるために今回は武器を持たせたのですが、

f:id:kazuhironagai77:20201206221204p:plain

武器を持っても戦闘中のアニメーションが素手の時と一緒でした。後で直します。

2.6 モンスターの死亡アニメーションの追加

f:id:kazuhironagai77:20201206221228p:plain

追加します。

Skelton_Swordman_Dieと言うアニメーションがありますのでこれを追加します。

f:id:kazuhironagai77:20201206221246p:plain

追加しました。

f:id:kazuhironagai77:20201206221304p:plain

テストします。

f:id:kazuhironagai77:20201206221319p:plain

素晴らしい死に方です。

ですが、カーソルが消えてしまいました。

2.7 戦闘終了後のカーソルの表示

直します。

色々な個所にShow Mouse Cursor If Not ()関数を追加してみたのですが、戦闘が終了するとカーソルが消えてしまいます。

これはUE4C++のRPGGameModeBaseクラスで何かカーソルの設定をしたかも。と思い調べると、

やっぱりUE4C++のRPGGameModeBaseクラスのTick()関数内のif(FightIsOverIsRead){}内で以下のように設定されていました。コメントアウトしました。

f:id:kazuhironagai77:20201206221354p:plain
そしたらずっとカーソルが表示されるようになってしまいました。

BPにCall出来る新しい関数を、このRPGGameModeBaseクラス内に作成して、if(FightIsOverIsRead){}内に実装して、その関数をBattleFieldレベルBP内で呼び出して、Show Mouse Cursor If Not ()関数をその後に実装すれば…。

面倒なので、今回はLevelBPのTick()関数に直接、Show Mouse Cursor If Not ()関数を付けちゃいました。

f:id:kazuhironagai77:20201206221415p:plain

UE4C++はCompileする時間が面倒です。ちょっと直して試しにBuildする事が出来ません。「BPでいいや。」となってしまいます。

テストします。

出来ました。

カーソルはスクリーンショットが取れないのでイメージ図はないです。

3. まとめと感想

今週はこんだけです。AIの残りは後で考えます。先々週にチェックした戦闘システムのバグを直しました。細かい部分はまだ直していないのでそれは来週以降になります。