UE4の勉強記録

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

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

f:id:kazuhironagai77:20200329185235p:plain

<前文>

今週は、書きたい事がいっぱいあってどれを書くべきなのか悩んでいたんですが、全部ちょびっとずつ書く事にしました。

1.NARUTOの作者の次の作品が打ち切りになった事

NARUTOの作者の次の作品がジャンプで打ち切りになったそうです。どんなに過去にヒット作を作った漫画家でも続けてヒット作品を描けないのは、ジャンプ連載の宿命めいたものを感じますが、その辺を私が議論しても今までに誰かが述べている事の繰り返しになってしまいますので、バイリンガルである事を生かして、この漫画がアニメ化された場合アメリカでヒットしたかについて考察します。

大方の予想に反すると思いますが、私はすると思いました。

といっても私はその作品を読んではいません。ネットでその作品に対する感想を見ただけです。のでその程度の考察と思って聞いて下さい。

知り合いのアメリカ人がNetflixみたいなサービスをする会社でプログラマーをしていたんですが、その会社、衛星から画像を送るのに独自の技術を持っていて、技術者の立場から見れば、大変前途有望な会社でした。彼が日本に来た時に、その後の話を聞いたら、既にその会社倒産したと言ってました。理由を聞いたらDisneyのライセンスが高くて払えなくなったと言っていました。ここで、私、Disneyのスポーツのライセンスと言っていたような気がしたのですが、Disneyとスポーツだったのかもしれません。兎に角、放送で流すライセンスが高すぎるのが原因の倒産だったと言ってました。

日本のアニメはライセンスがDisneyに比べると無料みたいに安いらしいんです。それてDisney以上の集客力がある。こんな美味しいコンテンツないでしょう。ただし、絶対アメリカでやっていけないタブーがあるんです。例えば犬を虐めるとか、日本から見たら何でもないような事なんですが。それがあるアニメは絶対アメリカで売れません。この作品の内容を聞いた限りでは、そういうものはなさそうです。

ので、NARUTOみたいな大ヒットは無理でも、そこそこは売れると思いました。

2. 最近、流行っているウィルスについて

家族が先週、熱が出てついにC-何とかに罹ってしまったと思ったら風邪だったみたいです。咳が全く出なくて、熱も一日で下がりました。でも結構その時は怖かったです。今、世界中でパニックに成っていますが、こういう時こそ、明日のために今日、種をまく。の精神を忘れてはいけないと思います。聖書に書かれている終末論的な世界の到達を期待して現在の努力を辞めてしますのは愚かだと思います。ある説によれば、湿度と温度がある一定以上に上がれば、感染が収束する可能性があるらしいです。8月になったら全部、正常運転に戻る可能性もあるのに、今、明日のための努力を投げ出すのは愚かだと思います。特に学生の方が、学校に行かなくていいから自宅でずっと遊んでいるのは愚かな行為だと思います。

3. Light L とDark Lについて

今、作成しているゲームでついでに英語の音素が学べる機能を付けようと思っています。そのために英語の発音について調べていたのですが、Light L とDark Lについて疑問に思った事があるので書いておきます。理系である私でもLight L とDark Lと言う二つのLの音がある事は知っていましたが、単純にLightなどの単語の最初についているLの音はLight L、tellなどの単語の最後のLの音はDark Lと思っていました。でも今回調べて、確かに専門家の人もそう言っているのですが、アメリカ英語の場合、全部、Lの音はdark Lのような気がしました。勿論、地方によって音は大分変りますが、一般的なアメリカ人で、特に男性の場合、単語の最初にLがある場合、私にはmに近い音に聞こえます。日本語で言えばムの音です。今回、色々調べたら、Lightと言っている人達に、何回聞いても日本語でラとしか聞こえない人達が一定数いました。日本に帰って来て既に数年経ち、ついに英語の聞き取り能力が劣化してしまったのか。とショックを受けましたが、後で、日本語でラとしか聞こえない発音をした人達は全員イギリス人と知りました。

そんな訳で私から見て、ラに聞こえるLこそLight Lで、ムに近い音で聞こえるLはDark Lなのではとちょっと思いました。これは、結構興味深い点なのでこれからも調査を継続していきます。

4. YouTubeと中国拳法

YouTubeは世界中の人が投稿出来るので、日本人からすると空想の世界とほぼ同じと思っているような所から投稿されている事があります。マンガでしか見たことない中国拳法を実際に練習している動画がYouTubeに投稿されているのをみて驚きました。ただ、やっぱりと言うかMMAのような格闘技と試合をするとコテンパンに負けてしまう場合が多いみたいです。それでも特徴のある動作は文化的にも貴重ですし3Dモーションとして保存してほしいです。ただその場合の著作権は誰のものになるのでしょう?Epic Game社もFortniteのダンスのモーションで裁判までいったそうです。

<本文>

1.先週の疑問に対する回答

インターフェイスとその派生クラスの使用方法について

先週、インターフェイスからの派生クラスを使用するのに、ヘッダーファイルでは、インターフェイスをインクルードした上で、Forward Declarationをしていました。これが必要な事なのか、正直知りません。調べましたがネットで書かれてませんでした。

ので自分でコードを書いて試してみます。

まず、Interfaceを作成します。名前はbaseInterfaceとします。

f:id:kazuhironagai77:20200329185409p:plain

その派生クラスを作成します。名前はTestBaseInterfaceとしました。

f:id:kazuhironagai77:20200329185439p:plain

f:id:kazuhironagai77:20200329185446p:plain

CppファイルにbaseInterfaceクラスの仮想関数であるgetInterface()の実装を追加しました。

このクラスを直接、main()関数から呼び出してしまっては、教科書と同じ条件にならないので、教科書と同じ条件にするためだけにクラスをもう一つ作成します。

このクラスからインターフェイスを作成します。名前は特に思いつかなかったのでexampleクラスとしました。

f:id:kazuhironagai77:20200329185509p:plain

f:id:kazuhironagai77:20200329185516p:plain

コードを見ると教科書の例と同じようにベースとなるインターフェイスをヘッダーファイルで宣言し、そのインスタンスの初期化は派生クラスを用いてcppファイル内で行っています。この場合教科書ではヘッダーファイル内でベースクラスをインクルードするだけでなくforward declarationをしています。上記のヘッダーファイルを見ると分かるように、baseInterfaceのForward declarationをコメントアウト(comment out)しています。

このクラスをmain()関数から呼び出してテストしてみます。

f:id:kazuhironagai77:20200329185534p:plain

これで、エラーになって、コメントアウト(comment out)したforward declarationをアンコメント(uncomment)したら、正常に動いたら、予想が正しい事になります。

f:id:kazuhironagai77:20200329185554p:plain

普通に動いちゃいました。

うーん。これが原因ではないようですね。

もう一度、コードを見直して見ます。

f:id:kazuhironagai77:20200329185613p:plain

やっぱり、IDecisionMaker.hをインクルードしていて、更にForward declarationもしています。

あ。

DecisionMakerクラスを見てみます。

f:id:kazuhironagai77:20200329185632p:plain

こっちはGameCharacterクラスをインクルードしていた。更にこっちでもGameCharacterクラスをforward declarationしていました。

なんだ。単なるforward declarationだった。

あれ?

でもそうすると、何でヘッダーをインクルードしているんでしょうか?

Circular dependencyを避けるためには、ヘッダーをインクルードしたままでも良かったんでしたっけ?

いけないですよね。

うーん。分からん。

一つだけ、分かった事は、今回の実験は正確に教科書のコードを再現はしてなく、正確に教科書のコードを再現するためには、exampleクラスとbaseInterfaceインターフェイスクラスをcircular dependencyにしなければなりません。

しかしそれを今ここで試すのはちょっと思慮不足な気がします。もう少し考える必要があります。のでこの問題は来週、もう一度チャレンジします。

2.戦闘時のUIUMGで作成する

UIフォルダー内にCombatUIWidgetクラスを作成していた事をすっかり忘れていました。

f:id:kazuhironagai77:20200329185709p:plain

このクラスの復習をします。

1. TestCombat()関数からの呼び出し

このクラスの復習を全く忘れてしまった理由に、TestCombat() 関数を実行した時の一連のコードの流れの中に、このクラスを呼び出している部分がなかったからです。でも実際にはどっかで呼び出しているはずなので、そこを復習します。

思いっきり、RPGGameModeクラスで使用されていました。

f:id:kazuhironagai77:20200329185742p:plain

以下にTestCombat() 関数内のあったCombatUIWidgetクラスの使用箇所を示します。

f:id:kazuhironagai77:20200329185801p:plain

ここで使用していたんですね。

思い出しました。どうやってCombatUIWidgetクラスのBP派生クラスである

f:id:kazuhironagai77:20200329185825p:plain

を使用する事をどうやってUE4C++に伝えてるのかが分からなくて、この辺のコードは結構読み込んでいました。

因みに、それはRPGGameModeクラスのBP派生クラスである

f:id:kazuhironagai77:20200329185844p:plain

の変数であるCombatUIClassにCombatUIを指定する事で可能にしています。

f:id:kazuhironagai77:20200329185910p:plain

2. CombatUIWidgetクラスの復習

それでは、CombatUIWidgetクラスの内容を見てみます。

まず、ヘッダーファイルから見てみます。

f:id:kazuhironagai77:20200329185939p:plain

最初のクラスの宣言からですが、UserWidgetクラスだけでなく、DecisionMakerインターフェイスクラスも継承しています。こちらのクラスが実行される事で、攻撃対象の敵を選ぶ事が出来る様になるみたいです。

f:id:kazuhironagai77:20200329190022p:plain

やっと戦闘画面で表示される攻撃対象の敵のボタンの表示を実行しているUE4C++のコードの場所が分かりました。このクラスのメンバー関数内にあるはずです。

次のコードは変数の宣言でした。

f:id:kazuhironagai77:20200329190104p:plain

因みに、このクラスと同じ様にDecisionMakerインターフェイスを継承するTestDecisionMakerクラスは変数を一つも持っていません。しかし以下に示すように、BeginMakeDecision()関数内でローカル(local)な変数を作成して対象のキャラを選んでいます。

f:id:kazuhironagai77:20200329190140p:plain

CombatUIWidgetクラスのGameCharacterクラスから作成したcurrentTarget変数も同様な目的に使用されると考えられます。

更に、コードを読み進めると、今度はPublicなメンバー関数が宣言されています。

f:id:kazuhironagai77:20200329190212p:plain

この二つの関数は、UFUNCTIONマクロ内でBlueprintImplementableEventが指定されていますので、実装はBP内で行う関数ですね。

次の二つの関数は、DecisionMakerインターフェイスの関数です。

f:id:kazuhironagai77:20200329190257p:plain

しかしこれらの関数、DecisionMakerインターフェイスから継承した関数にも関わらず、virtual, overrideが使用されていません。

何故なんでしょう?

UFUNCTIONマクロもありませんね。まあ、これは元であるDecisionMakerインターフェイスが単なるc++インターフェイスなのでない方が理屈に合いますが。

その次の関数は、BPで使用するための関数のようです。実装内容はCPPファイルで確認します。

f:id:kazuhironagai77:20200329190352p:plain

最後の関数です。

f:id:kazuhironagai77:20200329190418p:plain

この関数もBPで実装するための関数ですね。

Cppファイルも見ていきます。上記の関数がどのように実装されているのか確認していきます。

まずBeginMakeDecision()関数の実装です。

f:id:kazuhironagai77:20200329190440p:plain

currentTarget変数にパスされたtargetをセットしてます。更にfinishedDecision変数をfalseにしてます。最後にShowActionPanel()関数を呼び出しています。

このクラスと同じ様にDecisionMakerインターフェイスを継承するTestDecisionMakerクラスは、先程も見せたように、以下の実装を行っています。

f:id:kazuhironagai77:20200329190503p:plain

TestDecisionMakerクラスのtarget変数は、キャラが攻撃する敵を保持しますが、CombatUIWidgetクラスのcurrentTarget変数は、このBeginMakeDecision()関数内で行動を決定するキャラを保持してます。キャラの行動は、この後のUI画面でプレイヤーが決定するので当たり前と言えば当たり前ですね。

この後、TestDecisionMakerクラスのBeginMakeDecision()関数はTestCombatActionクラスを呼び出しますが、CombatUIWidgetクラスのBeginMakeDecision()関数は全くCombatActionクラスもその派生クラスも使用してません。代わりにShowActionsPanel()関数を呼び出して、その実装をBPに任せていくみたいです。

このShowActionsPanel()関数がCombatActionクラスやその派生クラスと同じ役割を果たしているのかはまだ分かりません。

次は、MakeDecision()関数です。

f:id:kazuhironagai77:20200329190529p:plain

この関数は、プレイヤーが行動の選択を決定した時に、trueを返すのが役割の関数です。のでBP内でプレイヤーの行動の選択が全て終了した時に、finishedDecision変数をtrueに変更するはずです。

以下に示すようにfinishedDecision変数はprotectedです。

f:id:kazuhironagai77:20200329190612p:plain

なのでこのクラスの派生クラスであるBPクラスからならアクセス可能と思われます。

次の関数、AttackTarget()の実装をみます。

f:id:kazuhironagai77:20200329190634p:plain

あれ、この関数でTestCombatActionクラスを使用していますね。しかもこの関数内で、finishedDecision変数をTrueにしています。

となると、この関数にパスされるキャラは攻撃対象のキャラでそのキャラの選択までを上記のShowActionPanel()関数が担当しているようですね。

CombatUIWidgetのcppファイルに実装されている最後の関数、GetCharacterTargetrs()関数の実装を見てみます。

f:id:kazuhironagai77:20200329190703p:plain

特に、気になる部分はなく単に、味方なら敵のモンスターのグループ、敵なら味方のグループを返しているだけですね。

GetCharacterTargetrs()関数とAttackTarget()関数の順番が、ヘッダーファイルとcppファイルで逆になってましたが、まあどうでも良い事なのでこのままにしておきます。

BP内で実装されている関数も見てみます。

AddPlayerCharacterPanel()関数の実装です。

f:id:kazuhironagai77:20200329190729p:plain

TargetはAddPlayerCharacterPanel() 関数のパラメーターです。PlayerPartySatusとは何でしょう?

f:id:kazuhironagai77:20200329190748p:plain

CombatUIのデザインの緑の部分の事でした。これは既に勉強してますね。

Spawn Character Widgetの中身を見てみます。

f:id:kazuhironagai77:20200329190810p:plain

これは、前にCastがどうとかで散々やった関数じゃないですか!段々、全てのコードが一つに繋がって来ましたね。

Spawn Character Widget ()関数はAddPlayerCharacterPanel() 関数から呼ばれた時は、以下に示した部分を作成する関数です。

f:id:kazuhironagai77:20200329190850p:plain

今度はAddEnemyCharacterPanel()関数の実装を見てみます。

みたら、この関数は敵のモンスターのためと言う以外はAddPlayerCharacterPanel() 関数と全く同じ実装なのでスキップします。

最後にShowActionsPanel()関数のBP実装を見ます。

f:id:kazuhironagai77:20200329190913p:plain

結構、凄いBP関数ですね。

まずCharacterActionsが何かから調べます。

以下に示す部分の事でした。

f:id:kazuhironagai77:20200329190933p:plain

うん。と言う事は、この関数が敵の選択を表示する関数なの?

ちょっと期待が高まって来ました。

次にTargetsが何を指しているのかを調べたら以下の部分でした。

f:id:kazuhironagai77:20200329190953p:plain

もう攻撃相手の選択するための関数で間違いないですね。
次のノードを見てみます。

f:id:kazuhironagai77:20200329191023p:plain

あれ、Attackx2がtrueが聞いています。この変数は、プレイヤーが操作するキャラのレベルが2以上になったtrueになる関数で、敵のモンスターの表示とは関係ないですね。この後のノードもこのブランチでtrueの場合のみに必要なので、敵のモンスターを表示するためのコードではないですね。

その上の方で実装しているOnClicked(AttackButton)内で敵のモンスターへの攻撃ボタンの表示は実装しているみたいです。

f:id:kazuhironagai77:20200329191042p:plain

この関数、それぞれのノードが全然整理されていないので、教科書で復習する時に整理しつつ読み直します。

3. 教科書の復習の続き

3.1 UMGを使用してのCombatUIの作成

まず、この教科書では、UMGをUE4C++から使用するためには、Build.csファイルを開き

f:id:kazuhironagai77:20200329191123p:plain

UMG、Slate、SlateCoreを以下に示す様に追加するように説明してます。

f:id:kazuhironagai77:20200329191140p:plain

UE4C++はここに表示されたモジュールしかアクセス出来ないので、この追加をしなければUMG、Slate、SlateCoreモジュールを使用する事は出来ません。個人的な経験から語れば、知ってしまえばどうって事はないですが知らないと結構地雷となる箇所です。

次に、[project名].hファイルに、つまり私の場合は、Ch2.hファイルに以下のコードを追加するようにと書かれています。

f:id:kazuhironagai77:20200329191201p:plain

この辺は、まあどのファイルにインクルードするかの問題だけと思います。

次に、UserWidgetを親クラスにしてCombatUIWidgetクラスを作成します。

更に以下の関数を追加します。

f:id:kazuhironagai77:20200329191221p:plain

おお、この関数から作成したんですね。

教科書では、ここでこのクラスの作成は一端お休みして、このクラスのBP派生クラスの作成に入ります。

このBPの作成部分は、特に新しい内容はなかったのでスキップします。

またCombatUIWidgetクラスに戻って以下の変数を追加します。

f:id:kazuhironagai77:20200329191240p:plain

この辺の変数が何をするための物かは既に分かっているので、私が知らない新しい情報が教科書にあるかどうかに注目して読んでいきます。

今度はRPGGameModeに戻ってCombatUIWidgetクラスの初期化のコードを追加していきます。

その後、以下のコードを追加します。

この部分のコードは今までキッチリと読んだ事はありませんでした。

f:id:kazuhironagai77:20200329191257p:plain

AddPlayerCharacterPanel() 関数をここで呼び出しているんですね。うーん。

更に、その後でGameCharacterクラスのDecisionMaker変数にCombatUIWidgetクラスから作成されたCombatUIInstanceを指定しています。

これを全てのパーティのメンバーに対して行っています。

なるほど、今まで、decisionMaker変数の中身は漠然と、TestDecisionMakerと思っていましたが、実際はCombatUIWidgetクラスだった訳ですね。

これは、コードを見直して見ると完全な間違いではなく、GameCharacterクラスのCreateGameCharacter()関数で敵のモンスターを生成する場合は、以下のように

f:id:kazuhironagai77:20200329191326p:plain

TestDecisionMakerが使用されています。

つまり、敵のモンスターの時は、GameCharacterクラスのdecisionMaker変数はTestDecisionMakerクラスから作成され、味方のパーティメンバーのためのGameCharacterクラスのdecisionMaker変数はCombatUIWidgetクラスから作成されていると言う事です。

ここに気付かなかったので、今の今までどうやって攻撃対象の敵のモンスターが表示されているのかが分からなかったんですね。

これは小さな気付きですが、とうとうCombatEngineの曖昧だった部分に光を差し込む事に成功しました。

この後で、戦闘が終了した場合、ポインターを外してGCがいらなくなったメモリーを回収出来る様にするためコードをTick()関数に実装します。前にこのポインターを外す実装について色々考察しましたが、それに対しての特に新しい情報は書いてないように思えます。

最後にcombatUIを指定する方法について説明されています。

CombatUIWidgetクラスに対して大体の理解が出来ました。

3.2 UIで操作するDecisionMaker

まずPlayerの場合はTestDecesionMakerクラスを使用しないようにコードを変更しています。この辺の内容は丁度、3.2で勉強した内容です。

豆知識ですがUIwidgetをUE4C++からマニュアル的に消去するとUE4がクラッシュするそうです。UIwidgetの消去はGCに任せなければならないそうです。UPROPERTYで装飾された変数が参照していないUIwidgetはいつかGCによって消去されます。

以下に教科書の説明文を示しておきますが最初にこの文を読んだとき、よく意味が分からなかったです。今は完全に自分の理解と教科書の説明が一致しています。私の実力も大分成長しているみたいですね。

f:id:kazuhironagai77:20200329191407p:plain

次に教科書ではDecisionMakerから継承した関数BeginMakeDecision()とMakeDecision()を宣言します。

f:id:kazuhironagai77:20200329191429p:plain

これらの関数をvirtual overrideを使用しないで宣言していますが、その理由は説明されていませんでした。

次に、以下の関数を追加しています。

f:id:kazuhironagai77:20200329191447p:plain

これらの関数は単なるヘルパー関数であると解説されていました。

全部、一々書くのも面倒なので新しい情報が書かれている箇所だけ書きます。

特になかったです。

今度はUIの作成に移ります。

まず、

f:id:kazuhironagai77:20200329191515p:plain

特に目新しい内容は説明していませんが、ボタンのSize To Contentにチェックするとボタンの中のテキストのサイズを動的に調節してくれるそうです。

f:id:kazuhironagai77:20200329191540p:plain

まあ、知っていましたが、一応記録に残しておきます。

次にボタンを押した時のイベントの実装をします。

f:id:kazuhironagai77:20200329191601p:plain

このAttackTargetは

f:id:kazuhironagai77:20200329191618p:plain

です。

このウィジェットが敵の名前が書かれたボタンとして表示されていたんですね。

今度はCombatUIのBPウィジェットにおける攻撃ボタンがクリックされた時のイベントを実装します。

f:id:kazuhironagai77:20200329191642p:plain

上記のコードを作成していきます。

簡単にまとめれば、AttackTargetOptionクラスを作成してそのTargetに敵のモンスターのキャラを保持させています。

次にAttackTargetOptionで戦闘ボタンを押した時の最後に呼ばれるHideActionPanel()関数を実装します。

f:id:kazuhironagai77:20200329191702p:plain

以上です。特に技術的に新しい内容はなかったです。

3.3 ゲームオーバー画面の作成

ゲームオーバーウィジェットの内容も見ていないしRPGGameModeクラスのtick関数のGameOver状態からどのようにこのウィジェットが呼ばれるのかも知りません。それを先に勉強してから教科書のこの部分を読み直さないと、復習の効果が出ないので来週に回します。

3.まとめと感想

今週は、あんまり進まなかったですがCombatEngineの理解出来ていなかった所の構造が判明したのでまあ良しとします。今回理解した箇所はある意味勘違いして分かったと思っていました。こういう思い込みからくる勘違いは中々気づかないものです。でも今週は気が付く事が出来ました。やっぱり、コツコツやる事が大切なんです。

今年の最初に「アイデアは発展させる物で批評するものじゃない。」と言う意見を書きましたが全くその通りでした。このCombatEngineについても同じでした。大切に楽しく毎週勉強すると少しずつですが理解が増していきます。これを最初から完璧に出来ないからとあれも駄目これも駄目と否定ばかりしていたらここまで理解出来るほど勉強を続ける事は出来なかったでしょう。まるで植物を種から育てるようです。種から芽が出たばかりの時に、こんな小さな芽では駄目と否定して踏みつぶしていたら何時まで経っても花や実を得る事は永遠に出来ないです。