UE4の勉強記録

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

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する バグ出しの続き

f:id:kazuhironagai77:20210307231748p:plain

<前文>

センシティブな同人アニメ絵とアメリカからの圧力による規制について

アメリカに10年住んでいた関係でアメリカの本当の政治事情に詳しい私が予言しますが、これから同人絵のようなセンシティブなアニメ絵に対してアメリカ政府が日本政府に規制するように圧力をかけて来る事はないです。

これは日本のアニメ、特に深夜アニメはポル〇であると断定し、積極的に規制するよう日本政府に圧力をかけていたアメリカの宗教右派がトランプが失脚した事により権力を失ったからです。

アメリカの宗教右派が、日本の同人アニメの絵を規制しないといけない表向きの理由は、彼らの宗教が欲情する事自体を罪にしているからです。しかしその本当の理由は、宗教とは関係なく、有色人種の男性がちょっとでも白人女性に欲情したら使用人として使えないからです。彼らは元々、農場のオーナーなんです。つまりいっぱいいる小作人や使用人を管理する事が彼らにとっての仕事なんです。昔、中国の後宮で働く男性は、宦官にならなければならなかったですが、それと同じ事をやると世論の批判を浴びるので、心理的、精神的に追い込む事で実質、宦官にして使用人にする訳です。そのために欲情する事自体を罪にしている訳です。

アメリカの宗教右派の連中は、日本人全体を彼らの使用人みたく思っていますから、同人アニメの絵の存在そのものを叩く訳です。

これから4年から8年の間、アメリカの政治を司る左派の連中は、元々都会で働くサラリーマンですので、そんな事を考える人は全くいません。むしろ、宗教右派に対して「日本人を使用人にするよりお前のボスにした方が経営上手く行くんじゃないの。」と言う位の連中です。

だから左派の連中がアメリカの政治の中枢にいる限り、日本の同人アニメの絵を叩く事は考えられません。

ただし左派にも僅かにですが同人絵のようなセンシティブなアニメ絵を規制すべきと考える人はいて、彼らが何かのアクションを取る可能性はあります。しかし絶対数が少ないので大した問題にはならないと思われます。

更にアメリカでも、アニメで育った世代が成人となりつつあります。

彼らの中で同人アニメの絵で生計をたてる人も必ず出て来ます。そうなったら規制どころではないです。マリファナですら自分たちに利益をもたらすなら合法化する国が、同人アニメの絵が自分達の利益になった時、180°今までの意見を変えてくるっと賛成派になるのは目に見えています。

そういう訳で、これから同人絵のようなセンシティブなアニメ絵に対してアメリカ政府が日本政府に規制するように圧力をかけて来る事はないと思われます。

ただし、気を付けないといけないのは、これからナチ関係の書籍は逆に大変厳しい規制が課せられたり、賛同するような意見を書いた作者に信じられない位の厳罰が課せられたりするようになると思われます。

先月の前文で説明した通りGenocideの思想そのものがない日本で、本気でナチズムを信じている人はいません。しかしUFOやオカルト本の影響で軽い気持ちで賛同するような意見を書く人はいます。もし貴方の周りにいたら絶対止めるべきです。絶対に最悪な方に誤解されるからです。

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

<本文>

1.今週の予定

以下の事をやって行きたいと思います。

  • モンスターの歩く速度の設定がアニメーションとAIで全然違う
  • BGMの変更、町や村、モンスターが居る廃墟で別々のBGMにする。
  • BPの整理。RPGGameModeBPを整理します。
    • そこら中でThirdPersonCharacter変数に値をセットしている。過去、EventBeginPlay関数がBPから呼べなかったので毎回呼び出していた。これを直す。
    • 戦闘システムから呼ばれるEventを整理してまとめる。
  • セリフシステムの整理。確認出来るだけで3種類以上の方法でセリフを管理している。これを統一する。
  • 更なるバグだし。せめて1時間はこのゲームで遊んでバグ出しする。(今週は10分だった。)
  • Levelデザイン。

2.アニメーションとAIにおけるモンスターの歩く速度の設定の検証

Monsterが使用しているAIではMonsterの歩く速度を150に指定しています。

f:id:kazuhironagai77:20210307231912p:plain

一方で、Monsterが使用しているアニメーションでは歩くアニメーションは85でセットされています。

f:id:kazuhironagai77:20210307231932p:plain

実際は歩き始めるモーションを100%使用し始める速度が85で、そこからは歩くモーションと走るモーションのブレンドになります。

移動する速度が345を超えると100%走るモーションに変わります。

実際の速度が150の時の移動のモーションを見ると、そんなに不自然ではありません。

f:id:kazuhironagai77:20210307231952p:plain

ここで検証したい事は、この二つの速度は同じ単位なのか?もし同じ単位ならその単位は何なのかについてです。

もう一つ検証したい事は、モンスターが探索している時、微妙に浮いている気がする事です。

f:id:kazuhironagai77:20210307232013p:plain

この辺も直せたら直したいです。

2.1 UE4の速度の単位

それではこれから調べて行きましょう。

まずAIの方ですが、モンスターの移動スピードはMonsterBPのCharacter MovementのMax Walk Speedが担当しています。

f:id:kazuhironagai77:20210307232042p:plain

Character Movement:WalkingのMax Walk Speedの設定をみると600.0にセットされています。

f:id:kazuhironagai77:20210307232104p:plain

この単位を探してみましょう。

UE4 Answer Hubのこのサイトで質問されていました。

f:id:kazuhironagai77:20210307232148p:plain

質問者はUU/Sが単位でdefaultでは1UU が1cmなので㎝/sが単位ではないかと予測しています。ただしソースは見つかっていないと言っています。

まずここでの議論で、当たり前のように使われているUUの意味が分からないです。Defaultの1UUは1㎝とか言っています。

UUの意味を調べます。

どうやらUnreal Unitsの意味らしいです。ただし参考で出て来た資料がUE3の時代のものでした。

その後「UE4ではUUは1cmに設定された。」とか「World Settingsで値を変更出来る」とかの断片的な情報は出て来ましたが、確認までは出来ませんでした。

特に、World Settingの項目は以下に示した様にVRの項にあって、普通のゲームにも使用されるのか不明です。

f:id:kazuhironagai77:20210307232207p:plain

ただ100 cmは1 mなので完全に違うとも言い切れないのが難しい所です。

もしMax Walk Speedの単位がcm/sだとしたら6 m/sです。秒速は感覚的に分かりづらいですが、少し速すぎないでしょうか?

色々調べてみたら、大体、日本の高校生の100m走の平均は14秒だそうです。そうすると100 m ÷14 sで7.14 m/sになります。ので6 m/sが速すぎると言う事はないですね。

何か、cm/sで合っている気がしてきました。

この質問に対してのコメントで、実際にゲーム内で100m走って速度を測った人がいました。20回測って平均は5.9 m/sになったそうです。6 m/sに大変近い値です。となるとcm/sがMax Walk Speedの単位である可能性は高いですね。

所がこの質問に対する回答で、スピードは実際はSecondに負っているのではなくFrame rateに負っているから、単純にSecondには変換できないと主張する人がいました。

確かにFrameに負うのはその通りですが、前にUE4のDelta Timeの違いの影響を測定したら差がなかったです。

どの週のブログで測定したのかは覚えていないですが、今100m走の秒速などを計算するためにエクセル開いたら、その時のデータと図だけ残っていました。

ので図だけ貼っておきます。

f:id:kazuhironagai77:20210307232228p:plain

これらの結果から推測するとMax Walk Speedの単位がcm/sであるのはかなり正しそうです。

100m走をUE4内でやるのは面白そうですね。

これは試してみましょう。

良く見たら一目盛り1mと書かれていました。

f:id:kazuhironagai77:20210307232247p:plain

これは簡単に100mが作れますね。

走って見ました。

f:id:kazuhironagai77:20210307232306p:plain

100m走るのに16.809秒かかりました。

計算すると5.949 m/sでした。20回測定して平均を出します。

f:id:kazuhironagai77:20210307232322p:plain

ほとんど時間が変わらないので3回で中止しました。

結果は約5.95m/sです。

多分同じ結果になるんでしょうが、Animationの方も確認します。

Animationの方は、Animation BPであるSkelSwordAni BPで以下の様に設定されていました。

f:id:kazuhironagai77:20210307232340p:plain

Get Velocityノードが何の値を取り出しているのか分かれば、Max Walk Speedの単位と同じであるかどうかが判明出来ますね。

取りあえずどんな説明しているのか、カーソルをノードに添えてみたら、思いっきり説明されていました。

f:id:kazuhironagai77:20210307232357p:plain

Cm/s、つまりUnreal Units/secondと書かれていました。

図らずもAIで使用しているMax Walk Speedの単位とAnimationで使用しているGet Velocity関数の単位が同じであるだけでなく、Max Walk Speedの所で推測したUnreal Unitsの単位が㎝である事と、その速度を測るために秒を測っている(frameではなく)と言う事までも正しい事も分かりました。

これだけ解れば十分ですね。テストなら満点です。

次に行きましょう。

2.2 探索時、モンスターが微妙に浮いている気がする。

これは単純にCollusionを担当するCapsule ComponentがMonsterのMeshより下に飛び出ているからでしょう。

確認すると僅かにですが確かに飛び出していました。

f:id:kazuhironagai77:20210307232426p:plain

直します。

直しましたが、モンスターの動きを見るとまだオカシイです。

スクリーンショットではうまく伝わらないんですが滑っている感じがします。

f:id:kazuhironagai77:20210307232443p:plain

理由が分かりました。

歩いている歩幅に対して実際に進んでいる距離が短すぎるんです。今の2倍位は進むべきです。

85が歩く速度に設定されていますが、どうなんでしょうか?

調べて見ると成人だと1分間で80m歩く位が普通だそうです。1.34 m/sでした。となると134 cm/sですから、歩くを85にセットしていたのはかなり遅いかもしれません。

SkelSword1Dでモンスターの歩く速度を130に変更しました。

f:id:kazuhironagai77:20210307232502p:plain

前より滑ってる感じは無くなりました。かなり良くなってはいます。完全に滑っていないとは言えませんが、これだけ改善されたら十分と思います。

f:id:kazuhironagai77:20210307232518p:plain

3.BGMの変更、町や村、モンスターが居る廃墟で別々のBGMにする

これはLevel designが完成してからやります。ので今回はスキップします。

 

4.BPの整理。RPGGameModeBPを整理します。

4.1 昔、BPからEvent Begin Play関数が呼べなかったので、event毎にThirdPersonCharacter変数に値をセットしていた。これを直します。

まずAnimation for Victoryにありました。外します。

f:id:kazuhironagai77:20210307232606p:plain

Game Overにもありました。外します。

f:id:kazuhironagai77:20210307232623p:plain

Moving Camera Positionの所です。外します。

f:id:kazuhironagai77:20210307232641p:plain

Event Report Character Level Upの所です。外します。

f:id:kazuhironagai77:20210307232658p:plain

テストします。

モンスターと戦闘して勝利しました。レベルも上がりました。エラーの表示が出たりゲームが中断したりする事もありませんでした。

念のためにモンスターと戦闘して負けてみました。単にゲームオーバーになっただけでした。

外しても何の問題も起きませんね。

4.2 戦闘システムから呼ばれるEventを整理してまとめる。

以下に示したのが現在RPGGameModeBP内にあるEventです。

黄色に囲って在るEventの全てが戦闘に関係しています。

f:id:kazuhironagai77:20210307232731p:plain

これらを整理していきます。

以下の様に整理しました。

戦闘に関係のないEvent Begin PlayとEvent Tick 関数をまとめました。

f:id:kazuhironagai77:20210307232752p:plain

f:id:kazuhironagai77:20210307232800p:plain

戦闘に関するEventは以下の様に整理します。

  • Decisionに関係するEvent
  • Action に関係するEvent
  • 戦闘後のVictoryGameOverなどに関係するEvent
  • それ以外のEvent

Decisionに関係するEventです。

f:id:kazuhironagai77:20210307232833p:plain

f:id:kazuhironagai77:20210307232841p:plain

Actionに関係するEventです。

f:id:kazuhironagai77:20210307232900p:plain

f:id:kazuhironagai77:20210307232909p:plain

戦闘後のVictory、GameOverなどに関係するEventです。

f:id:kazuhironagai77:20210307232928p:plain

f:id:kazuhironagai77:20210307232942p:plain

それ以外の関数で戦闘に使用するものです。

f:id:kazuhironagai77:20210307233001p:plain

f:id:kazuhironagai77:20210307233011p:plain

全部で12個でした。

あれ、2つ足りません。

Confirmed Button Is Clicked EventはDecisionの中にありました。

f:id:kazuhironagai77:20210307233032p:plain

同様にConfirmed Button Is Clicked 2EventはActionの中にありました。

f:id:kazuhironagai77:20210307233048p:plain

雑と言えば雑ですが一応整理出来ました。

5.対話システムの整理。確認出来るだけで3種類の方法でセリフを管理している。これを整理する。

5.1 対話システムの確認

今まで作成した対話システムの確認をします。

5.1.1 NPCとの対話

まず、NPCとの会話です。

全てのNPCとの会話はStructであるNPC_ConversationBaseを元にして作成しています。

f:id:kazuhironagai77:20210307233128p:plain

NPC_ConversationBaseはTestタイプの変数とAnswer Comment ManagementタイプのArrayで構成されています。

f:id:kazuhironagai77:20210307233150p:plain

Answer Comment ManagementはStructです。

f:id:kazuhironagai77:20210307233206p:plain

中身はInteger型であるJumpToCommnetとText型であるAnswerCommnetで構成されています。

f:id:kazuhironagai77:20210307233223p:plain

実際のNPCにおける使用は以下の様になります。

f:id:kazuhironagai77:20210307233239p:plain

内容にはこれ以上は深入りしませんが、会話を管理するには非常に優れたシステムとなっています。

5.1.2 店主との対話

次に店主との対話システムです。これは教科書に書かれていた方法をそのまま使用しています。

まずNPC_ParentというWidgetを作成してこれに全ての対話を保持させます。

f:id:kazuhironagai77:20210307233303p:plain

Text型のarrayを作成してそれぞれの店主に対応したセリフを保持させます。

f:id:kazuhironagai77:20210307233322p:plain

実際の保持されているセリフです。

f:id:kazuhironagai77:20210307233342p:plain

このWidgetクラスから子クラスを作成してその子クラスにそれぞれの店主との会話を担当するWidgetにします。

その子クラスからNPC_Parentにある対話用のarrayにアクセスさせ実際の会話に使用します。(以下の様な方法でアクセスします。)

f:id:kazuhironagai77:20210307233431p:plain

この方法では「5.1.1 NPCとの対話」でやったやり方のように、答えの選択が複数ある場合には対応できませんが、それ以外ではまあまあなシステムです。

例えば全てのセリフをNPC_Parentで管理しているので全てのセリフをチェックしなければならない時はNPC_Parentを見れば良いからです。

Widgetを継承するやり方は3種類位勉強しましたが、ここで採用されているやり方でPackaging まで出来るのか不明です。ちょっと心配です。

5.1.3 戦闘システムにおける報連相

以下の方法で管理されています。

f:id:kazuhironagai77:20210307233454p:plain

この方法の何が最悪であるかと言うと、セリフの一括管理が出来ない事です。更にそれ故に対話の流れがセリフからだけでは分からなくなります。

しかしこのシステムから提供されるセリフは、ユーザーからは絶対に必要なものです。何故からそれはユーザーに対する戦闘における報告、連絡、そして相談を担当しているからです。

それでこの戦闘システムの報連相の機能は一切なくさないで、実装方法とセリフの管理方法だけ変更したいんです。

5.2 戦闘システムにおける報連相の全容を確認する。

この戦闘システムの報連相の機能は一切無くさないで、実装方法とセリフの管理方法だけ変更するためにはどうすればいいでしょうか?

取りあえず最初にする事は全容を把握する事です。

そこから始めます。

まず前節で示した戦闘システムの一番最初に出て来る報連相を見ると、セリフをCombat Window ウィジェットのComment Text Contentにセットしています。

f:id:kazuhironagai77:20210307233519p:plain

Combat Window ウィジェットのComment Text Contentを調べます。

調べようとしたらCombat Window ウィジェットのBPがゴチャゴチャしていたので整理をします。

f:id:kazuhironagai77:20210307233536p:plain

以下に示したように整理しました。

f:id:kazuhironagai77:20210307233553p:plain

戦闘前、戦闘システムのDecisionの状態で実行されるEvents、戦闘システムのActionで実行されるevent、最後に「読みましたボタン」を押した時に実行されるEventです。

最初のブロックである戦闘前ですが、ここではComment Text Content変数は全く使用されていないのでこのブロックは無視します。

f:id:kazuhironagai77:20210307233611p:plain

次のDecisionブロックですが、Decisionで決定すべき、攻撃、魔法、アイテム、そして逃げるの4つの選択をした後にそれぞれ実行するコードが書かれています。

f:id:kazuhironagai77:20210307233626p:plain

f:id:kazuhironagai77:20210307233633p:plain

f:id:kazuhironagai77:20210307233641p:plain

攻撃を選択した場合、Comment Text Content変数に関しては以下のTextが追加されます。

f:id:kazuhironagai77:20210307233658p:plain

魔法を選択した場合、Comment Text Content変数に関しては以下のTextが追加されます。

段々、分かって来ました。このComment Text Content変数はDecision状態のユーザーと敵のモンスターの選択した内容を報告するためのセリフを管理するためのものだったんです。

f:id:kazuhironagai77:20210307233734p:plain

こんどはItemを選択した時です。

魔法を選択した時と全く同じです。

f:id:kazuhironagai77:20210307233806p:plain

逃げるを選択した時です。

逃げるを選択した時はここで、Comment For Execute Action変数にもセリフを追加しています。

f:id:kazuhironagai77:20210307233824p:plain

ここはDecisionで選択された結果に対しての実行なのでActionですべき事をここでやってしまうのは、論理的にオカシイ気がしますが、この辺は後で検討しましょう。

ここまで来てComment Text Content変数に保持されているTextは何時、Combat UI ウィジェットのコメント欄に反映されるのかと思ったら、

f:id:kazuhironagai77:20210307233843p:plain

直ぐに反映される仕組みになっていました。

f:id:kazuhironagai77:20210307233859p:plain

確認のために戦闘してみます。

f:id:kazuhironagai77:20210307233918p:plain

f:id:kazuhironagai77:20210307233927p:plain

攻撃を選択しました。

f:id:kazuhironagai77:20210307233956p:plain

攻撃の対象にゴブリンを選択しました。

f:id:kazuhironagai77:20210307234013p:plain

f:id:kazuhironagai77:20210307234021p:plain

Decisionの状態はここまでで、残りはAcitonの状態のはずです。

一応確認するために次のコメントも見ておきます。

f:id:kazuhironagai77:20210307234040p:plain

はい。Actionの状態です。

兎に角、Comment Text Content変数に保持されているTextは直ぐにCombat UI ウィジェットのコメント欄に反映されている事は確認が取れました。

5.3 Comment Text Content変数と戦闘システムにおける報連相の管理方法のまとめ

まだ「5.2 戦闘システムにおける報連相の全容を確認する。」の途中ですが、ここまで整理して来て戦闘システムにおける報連相の管理方法についてのアイデアが出ました。のでここでまとめておきます。

今までずっとComment Text Content変数が、戦闘システムのセリフを管理しているにもかかわらず、その内容はその場その場で管理しているので、戦闘システムにおけるセリフの全体像が把握出来ないという問題がありました。

今回、その問題を解決すべく大々的に戦闘システムのセリフの整理、検討を始めたのですが、ここに来てComment Text Content変数が戦闘システムのセリフを管理しているのではなく、実際は、どの変数も戦闘システムにおけるセリフは管理していないという事が分かりました。

実際、戦闘システム内の個々のセリフは以下の方法で、その場その場で追加されているだけです。

f:id:kazuhironagai77:20210307234102p:plain

と言う事は、これらのセリフを一括で管理するData SheetもしくはTextのArrayを作成して、そこからこのAppendにセリフをパスするように変更すればいいんです。

そうすれば、戦闘システムのセリフを一括で管理する変数が出来ます。

それを作成しましょう。

5.4 戦闘システムのセリフを一括で管理するData Sheet

NPCのセリフの管理で使用したNPC Conversation Baseを使用して戦闘システムのセリフを一括で管理するData Sheetを作成しましょう。

f:id:kazuhironagai77:20210307234124p:plain

これならどこでどんなセリフをいうのか一発で分かりますし、そのセリフに対しての解答とその解答を選択した結果に対してのセリフの関係性も一瞬で分かります。

試してみましょう。

以下に示した様に最初の部分だけ作成しました。

f:id:kazuhironagai77:20210307234139p:plain

ここからセリフを読み取って戦闘システムのセリフが作成されるようにします。

RPGGameModeBPのBeginPlay関数に以下のコードを追加します。

f:id:kazuhironagai77:20210307234216p:plain

これでCombat Dialogue変数にCombat Dialogue data tableの全ての名前が保持されます。

次にCombat UIウィジェットのEvent Constructに以下のコードを追加します。

f:id:kazuhironagai77:20210307234234p:plain

これでComment Text Comtent変数にCombat Dialogue data tableの最初のセリフである「戦闘が開始されました。」がセットされたはずです。

テストしてみます。

Comment Text Comtent変数に入っていたDefaultを「戦闘が開始されました。」を「Test」に変更します。

f:id:kazuhironagai77:20210307234250p:plain

f:id:kazuhironagai77:20210307234258p:plain

これでCombat Dialogue data tableからセリフが読み込まれた場合は「戦闘が開始されました。」が表示されCombat Dialogue data tableからセリフが読みこまれなかった場合は「Test」が表示されるはずです。

f:id:kazuhironagai77:20210307234318p:plain

「戦闘が開始されました。」が表示されました。

出来ています。

しかし完璧ではありませんでした。

段落が無くなってしまっています。

f:id:kazuhironagai77:20210307234335p:plain

直します。

Combat UIウィジェットのGet Comment Text Box関数を以下に示した様に変更します。

f:id:kazuhironagai77:20210307234351p:plain

これでComment Text Content変数のtextに{nextline}が使用されていた場合、段落に変換されます。

更にCombat Dialogue data tableの最初のセリフである「戦闘が開始されました。」に{nextline}を追加しました。

f:id:kazuhironagai77:20210307234416p:plain

テストします。

f:id:kazuhironagai77:20210307234434p:plain

今度は行替えされて表示されています。

出来ました。

次は以下のセリフの交換になります。

f:id:kazuhironagai77:20210307234450p:plain

ここで気が付いたんですが、Combat Dialogue data tableのセリフが少し間違えていました。

直します。

Playerの操作するキャラとモンスターではNewRow_0のセリフは前半部は同じですが後半は違いました。ので二つに分けました。

f:id:kazuhironagai77:20210307234507p:plain

それでは最初の部分のセリフである「“は次の行動を考えています。」をCombat Dialogue data tableから読み込む事にします。

以下に示した様に改良しました。

f:id:kazuhironagai77:20210307234531p:plain

テストします。

f:id:kazuhironagai77:20210307234551p:plain

出来ています。

今度は「{表示されているボタン(攻撃、逃げるなど)の中から一つ選択してください。}」を変えます。

f:id:kazuhironagai77:20210307234611p:plain

テストします。

f:id:kazuhironagai77:20210307234628p:plain

出来ていますね。

行替えは出来てなかったのでそこだけ直しました。

整理しましたがまだまだスパゲティです。

f:id:kazuhironagai77:20210307234645p:plain

セリフをCombat Dialogue data tableから読み込む部分は関数にしますか。

四角で囲った部分です。

f:id:kazuhironagai77:20210307234704p:plain

関数名はGet Dialogue From Combat Dialogue DTです。

f:id:kazuhironagai77:20210307234720p:plain

かなり見栄えも良くなりました。

他の部分も直していきます。

以下の様に直しました。

Combat Dialogue data tableのセリフは以下の様に成りました。

f:id:kazuhironagai77:20210307234741p:plain

以下に実装部を示します。

f:id:kazuhironagai77:20210307234809p:plain

f:id:kazuhironagai77:20210307234816p:plain

f:id:kazuhironagai77:20210307234825p:plain

f:id:kazuhironagai77:20210307234833p:plain

f:id:kazuhironagai77:20210307234842p:plain

f:id:kazuhironagai77:20210307234859p:plain

f:id:kazuhironagai77:20210307234910p:plain

f:id:kazuhironagai77:20210307234919p:plain

f:id:kazuhironagai77:20210307234927p:plain

f:id:kazuhironagai77:20210307234935p:plain

f:id:kazuhironagai77:20210307234945p:plain

f:id:kazuhironagai77:20210307234955p:plain

f:id:kazuhironagai77:20210307235004p:plain

全ての要素でテストした結果、今までと同じセリフが表示されました。

5.5 Action状態におけるセリフを一括で管理するData Sheet

Comment For Execute Action はCombat UIウィジェット内に作成されたTextタイプの変数です。

調べて見るとEvent Report Begin Execute ActionでPlayerの操作するキャラの場合のみ、それまでComment For Execute Action変数内に足していたセリフをComment Text Content変数に移す事でセリフをCombat UIのコメント欄に表示します。

f:id:kazuhironagai77:20210307235032p:plain

f:id:kazuhironagai77:20210307235039p:plain

f:id:kazuhironagai77:20210307235047p:plain

この変数が管理しているTextも全てData Tableで一括に管理する事にします。

調べたら以下の7か所でのみ使用していました。

f:id:kazuhironagai77:20210307235108p:plain

うーん。思っているより少ないですね。

理由が分かりました。Acition状態でも結構な割合でComment Text Contentが使用されていました。

f:id:kazuhironagai77:20210307235129p:plain

Comment Text Contentに代入されるセリフは全部、Combat Dialogue data tableに移したと思ったんですが、まだ残っていました。これは後で直します。

以下の様にセリフを整理しました。

f:id:kazuhironagai77:20210307235146p:plain

簡単に解説します。

まず(始め)を開きます。

すると以下のような選択肢があります。

f:id:kazuhironagai77:20210307235207p:plain

ここで攻撃を選択したとします。

攻撃のJumpToCommentは1ですので、indexの1にJumpします。

Indexは0から始まるので番号の2がIndexの1に当たります。

f:id:kazuhironagai77:20210307235222p:plain

2の「を攻撃しました{nextline}」を開いて見ます。

f:id:kazuhironagai77:20210307235240p:plain

Answer Choiceに選択がありません。ここで終わりです。

所でAnswerのスペルが間違っていますね。後で直しておきます。

こんな感じでアドベンチャーゲームブックのように全てのセリフが繋がっています。つまりこのData Tableでセリフを一括管理している訳です。

以下に実際の実装を示します。

f:id:kazuhironagai77:20210307235256p:plain

全部載せる意義を感じないので一個だけにします。

テストします。

攻撃、魔法、道具、逃げるのそれぞれの場合で試しましたが特に問題は確認されませんでした。

5.5 Action状態でComment Text Contentに直接追加されるセリフを管理するData Table

Comment Text Content変数に追加すべき残りのセリフを調べると、

  • モンスターのセリフ:1
  • Action時のセリフ:2
  • 戦闘終了後のセリフ:2
  • MPHPの増減に関してのセリフ:4
  • 逃げるを選択した場合に関してのセリフ:2

となって結構バラバラです。

今までのようにこれらのセリフ全てを一つに繋げる事は出来なそうですが、出来るだけ整理します。

f:id:kazuhironagai77:20210307235330p:plain

やっぱり全部のセリフの関係性を繋げるのは不可能ではないですが、今回はパスします。

上二つのData Tableと同じやり方で実装しました。一個だけ例を載せておきます。

f:id:kazuhironagai77:20210307235348p:plain

テストもしました。

Indexで3、番号では4の「は勝利しました。{next line}」は「は勝利しました。{nextline}」が正しかったです。

後、Level Upした時のセリフを忘れていました。

それらを直しました。

5.6 Dialogue System 考察

これで一応、戦闘システムの報連相を担当していた数々のセリフが3つのData Tableにまとめられました。その内2つはそれぞれのセリフの前後関係もはっきり分かりData Tableを見るだけで全体像の把握が出来る仕組みになっています。

最後のData tableはセリフ同士の関係を正しく並べる事が出来なかったので全体像までは分かりません。ただし、今回はやりませんが、これも時間をかけて前の二つのData Tableと組み合わせれば前後関係がはっきり分かる様に作成出来るはずです。

このまとめ方はNPCの対話で発明した方法と全く同じです。

NPCとの対話でも店主との対話だけは教科書で紹介された方法で作成しましたが、勿論、店主との対話も他のNPCと同じ方法で作成する事が出来ます。

つまり、全てのセリフはNPCの対話で作成した形式で書く事が出来るのです。

そしてこのNPCの対話で作成した形式は、一括で全てのセリフを管理出来るだけでなく、セリフの前後関係も会話全体の流れもData Tableを見るだけで理解出来る大変優れた方法です。

NPCの対話で作成した形式を、もっと一般化した言い方をするとアドベンチャーゲームブック形式のまとめ方となります。

これについてはかなり深い考察をする必要があるので「アドベンチャーゲームブック形式によるセリフのまとめ方について」来週考察します。

6.更なるバグだし(1時間はこのゲームで遊ぶ)

先週、10分プレイしただけで大量のバグが見つかってゲームを中断せざるえませんでした。

今週は、1時間はプレイしてバグ出しします。

6.1 兎に角、10分間プレイ

武器屋で武器を買ったら値段が金貨0枚でした。

f:id:kazuhironagai77:20210307235431p:plain

朝になるときにPCが音します。

6.2 モンスターを全部倒す。

2体ほどモンスターを倒した後、隣村の武器屋で最強武器と防具を購入。残りのモンスターはその最強武器で倒した。

所要時間:全部のモンスターを倒すのに、10分程度かかった。

f:id:kazuhironagai77:20210307235452p:plain

見つかったバグ:

  • 勝利のポーズの時に剣が頭に刺さる。
  • モンスターが後ろを向いているとプレイヤーの操作するキャラがすぐ後ろに立っていても気が付かない。

6.3 色々な武器を試す

全部の武器のアニメーションを見てみます。

所要時間:20分程度

見つかったバグ:

弓矢の時、片手剣のモーションになっています。

f:id:kazuhironagai77:20210307235525p:plain

弓と剣のような装備が可能でした。正し、見た目はオカシクない。

f:id:kazuhironagai77:20210307235544p:plain

魔法の杖を装備したら名前が枠からはみ出て表示されました。

f:id:kazuhironagai77:20210307235601p:plain

モーションは両手剣、片手剣、弓、杖など何種類かあった方が良いと思いました。

実際に、以下のアニメーションが提供されているのでそれは可能だと思います。

f:id:kazuhironagai77:20210307235617p:plain

後、結構3D酔いします。1時間プレイしたらきついかもしれません。今週はこれで止めます。

6.4 他に気が付いた事

魔法のeffectが一個しかないのは退屈です。10種類は欲しいです。

7.Levelデザイン

今の所、以下の部分しかありません。

f:id:kazuhironagai77:20210307235655p:plain

因みに作成した地図は以下の様になっています。

f:id:kazuhironagai77:20210307235713p:plain

この地図は結構いい出来ですので、実際のマップも同じに作成するのが良いかもしれません。

ただし、現在ABC、CDEはmap1。JKL、LMNはmap2。そしてYZとXYZはmap3に配置してあります。

この設定は変えたくないです。

ところで、環境でマーケットのフリーを検索すると質の高い完成品があります。

f:id:kazuhironagai77:20210307235731p:plain

これをそのまま使用するのも一つの手であると思います。

普通の箱庭的なゲームのマップのサイズってどれくらいなんでしょうか?

うーん。知らないですね。

やっぱりある程度は自分で作成する事でLevel Designの勉強をすべきですね。

色々考えましたが、このアイデアが一番現実的と思います。

上の地図の海の部分で3分割します。それぞれの陸地を別のマップで作成します。

こんな感じです。

f:id:kazuhironagai77:20210307235749p:plain

ここに今あるmap1、map2、そしてmap3を対応させます。

これで行きましょう。

8.まとめと感想

段々、ジグソーパズルが埋まるような感じでゲームとして成り立つには何をすべきなのかが分かって来ました。

来週は以下の事についてやります。

  • 対話システムとアドベンチャーゲーム本形式によるセリフの整理方法の考察
    • Data Tableを使用する事で、全てのセリフを一括で管理出来る。
    • 全てのセリフの前後関係がData Tableを見るだけで分かる。
  • Game Modeクラスの整理の続き
  • バグの直し
    • 勝利のポーズの時に剣が頭に刺さる。
    • モンスターが後ろを向いているとプレイヤーの操作するキャラがすぐ後ろに立っていても気が付かない。
    • 戦闘時の武器のモーションの追加
    • 魔法の杖を装備したら名前が枠からはみ出て表示されていたのでそれを直す。
    • 魔法のeffectを増やす。
  • Levelデザイン

以上です。

 

 

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する 戦闘中のanimation の直し

f:id:kazuhironagai77:20210228233202p:plain

<前文>

他人の評価と

この前文では、日本の話題についてはあんまり書かないと決めています。その理由は、結構な頻度でその問題の当事者にあったりするからです。他人事で批判していたら、その当事者に会ったりした時、かなり気不味いです。ので基本的に日本の話題については書きません。今回だけは例外です。のでかなりボヤカして書きます。

ある人が社会的にかなり権威のある賞を取ったんですが、その時にその人が「俺を今まで馬鹿にしていた連中め。ザマーミロ。」と言ったんです。思わず呟いちゃったんでしょうね。これって社会人としてはかなり問題発言ですし、常識から考えても「今まで応援してくれた人達のお陰でここまで来れました。」位に留めておくべきでした。

しかし、それ聞いた時、私は逆に感動しちゃったんです。だってそんな賞取る人でも、完全無欠でここまで来た訳じゃなくて、そういう世間や上司などの第三者からの理不尽な低評価に、歯を食い縛りながら戦って来た事が分かったからです。彼も天才じゃなかったんです。見えない所で不断の努力を行っていたんですね。しかもずっと評価されない状態で。

何かを成すためには、第三者からの評価は絶対に必要です。自分勝手になったり、そこまで行かなくても客観的な事実に目を向けられずに、間違ったまま進んでしまったりしている時に、他人からの助言ではっと我に返る事が出来るからです。

しかし多くの第三者の人は理不尽としか思えない低評価しか付けない人が多いです。

他人の評価をする義務なんで無いです。実際ほとんどの人は他人の行為や作品に対しての評価はしてくれません。そんな中で評価したり、敢えて助言したりしてくれる人の意見は大変貴重で大切ですし、どんなに理不尽な評価でも本当にその人が思っている事ならば聞かねばなりません。

しかし本当にその人そう思っているんでしょうか?世間一般がそう言うから低評価しておいた。とか、この部下を低評価しておくと自分が上司から高評価受けるから。とか、あるいは、あからさまな悪意によって低評価している場合も多い様に思えます。

そうなった時に、その意見は聞かないと言う選択肢は有りと私は思っています。

しかし世の中の所謂、仕事が出来る人の多くは、そういう人の意見もかなり真剣に聞く傾向にあります。そういう人達は自分に対する戒めが大変強いので、第三者の評価を非常に大切にします。更に「悪意が在ったとしても助言は助言。」と考える人もいます。

それは私の経験から言うと大変危険です。

悪意のある助言や無能な人の評価を聞くのは、第三者の評価を全く聞かない以上に危ないんです。破滅する可能性まで在る最悪の選択です。

言っては何ですが、こういう仕事が出来る人って恵まれた環境で育っていて、本当に悪意がある人や本当に無能な人に今まで会った事が無いんです。厳しい言い方をすると悪意がある人の意見と真剣に助言してくれる人の区別が付かないし、無能な人でも雰囲気だけで有能そうと判断してしまったりします。

私が個人的に見聞した悪意ある助言は「取りあえずここは謝って」と「今日は家に帰ってゆっくり休んで」です。もし謝ったら「次は誠意が足りない。」と更なる譲歩を要求して、最終的には土下座か一筆書かされるまで追い込むそうです。家に帰すのは、貴方が不在の間に貴方に対しての何かしらの犯罪行為を行うためだそうです。

無能な助言で多いのが「数字の間違い」です。これは日本ではあまり無かったですが、アメリカで何回も経験しました。特に役所で。アメリカの恐ろしい所は、嘘には非常に厳しいのに間違いにはほとんどペナルティーが付かないんです。しかし間違いでも損害が発生しますから。損害を受ける側は大変です。

そこまで行っても「実際、悪意が在るのか、自分が意固地になっていて正しい助言に対して悪意があると思い込んでいるのか、自分では分からないじゃないか。」と反論されるかもしれません。

そこで妥協案です。「一回だけその人の助言を聞いて見たらと。」

絶対に無いでしょうが、それで劇的に改善したら聞き続けるべきです。もし変化が無いのなら、破滅する可能性がある意見は無視すべきでしょう。劇的に悪くなったら、その人に対して今すぐ最大級の警戒をすべきです。

最後にまとめると、馬鹿にしていたり低評価しかしない連中は、悪意を持っていたり無能な人の場合が多いですので、評価や助言は無視すべきです。

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

<本文>

1.今週の予定

戦闘中のアニメーションにPlayAnimation nodeを使用していますが、アニメーションは全部AnimBPで管理すべきでした。今週はここを直します。

  • モンスターのAnimBPを改良して、戦闘のAnimationAnimBPで表示出来る様にします。
  • 同様の方法で、Playerが操作するキャラのアニメーションもAnimBPで表示します。

アニメーションを直した後に時間がまだある場合は、このゲームをプレイしてバグ出しをします。

もしバグ出しした後でも、それでも時間があるならば、Levelデザインでもやってマップを完成させます。

2.PlayAnimation ノードを外して、戦闘中のアニメーションもAnimBPで管理する。(モンスターの場合)

2.1 モンスターのAnimBP の復習

AnimBPでどのようにアニメーションを管理しているのかすっかり忘れてしまいました。まず復習から始めます。

MonsterBPを開いて見ると

f:id:kazuhironagai77:20210228233541p:plain

使用しているAnim ClassはSkelSwordAni_Cとなっています。

f:id:kazuhironagai77:20210228233612p:plain

これですね。

f:id:kazuhironagai77:20210228233634p:plain

開いて見ると、まず変数としてSpeedとDelayがありました。

f:id:kazuhironagai77:20210228233707p:plain

以下に示した様に、開始して1.6秒経つとdelay変数がTrueになります。

f:id:kazuhironagai77:20210228233733p:plain

Speed変数はモンスターの移動スピードを表しています。

f:id:kazuhironagai77:20210228233754p:plain

そうだ。思い出して来ました。Animation BPにはEventGraphとAnimGraphがあったんです。

f:id:kazuhironagai77:20210228233813p:plain

今度はAnimGraphを見て行きます。

f:id:kazuhironagai77:20210228233921p:plain

思い出して来ました。このDefaultの中で色々な条件とそれに沿ったアニメーションを指定したんです。

それはそうと、このAnimGraphではDefaultとOutput Pose以上の作成出来るんですかね。もっとUE4アニメーションに詳しくなったらモット複雑な形のAnimGraphがあるんでしょうか?

Default stateの中を見ます。

f:id:kazuhironagai77:20210228233940p:plain

Spawn stateとIdle/Run stateがありその間に条件節が一個あります。

Spawn stateを見るとSkeleton_Swordman_Spawnのanimationを表示させています。

f:id:kazuhironagai77:20210228233958p:plain

Skeleton_Swordman_Spawnのanimationは以下のヤツです。

f:id:kazuhironagai77:20210228234016p:plain

f:id:kazuhironagai77:20210228234024p:plain

f:id:kazuhironagai77:20210228234032p:plain

f:id:kazuhironagai77:20210228234041p:plain

このアニメーションに掛かる時間が1.6秒でした。

もう分かりました。delay変数がTrueになるのが、Spawn stateからIdle/Run stateに移動出来るため条件ですね。

一応調べて見ます。

f:id:kazuhironagai77:20210228234111p:plain

f:id:kazuhironagai77:20210228234121p:plain

その通りでした。

最後のIdle/Runを見ます。

f:id:kazuhironagai77:20210228234303p:plain

あれ。思っていたより簡単な構造でした。

そうか。歩くのと走るのは、早歩きみたいな歩くと走るの中間があるから、それをBlendspace PlayerクラスであるSkelSword1Dで再現したんでした。

SkelSword1Dの中身も見てみます。

f:id:kazuhironagai77:20210228234322p:plain

Speedが345以上で完全に走り出します。Speedが85までは歩いています。

85?

AIでモンスターが探索する時の最大速度は幾つでしたっけ。

MyBehaivorTreeを見てみると150にセットされています。

f:id:kazuhironagai77:20210228234339p:plain

f:id:kazuhironagai77:20210228234347p:plain

計算式を見直さないと数値が同じ単位なのか分かりませんが、後で数値の確認をする必要がありますね。モンスターが探索している時は、歩くべきですので。

取りあえず、モンスターのAnimBPがどうなっているのかは分かりました。

2.2 戦闘時におけるモンスターのplayAnimationの使用箇所についての復習

RPGGameModeBP内の以下の部分でモンスターの戦闘中のアニメーションを担当していました。

f:id:kazuhironagai77:20210228234414p:plain

あれSetViewTargetwithBlendノードは何をする関数何でしょうか?

覚えていません。調べます。

f:id:kazuhironagai77:20210228234430p:plain

使用するカメラを切り替えるんでした。

あれ。でもこの設定だと、カメラを切り替えるだけですよね。

実際の戦闘では以下の様に、モンスターを正面から撮影しています。

f:id:kazuhironagai77:20210228234446p:plain

この設定だとモンスターの後ろ姿を撮影するんじゃ…。

分かりました。

元々、monsterBPのカメラはモンスターに対して正面を向いた状態で作成していました。

f:id:kazuhironagai77:20210228234509p:plain

戦闘中のカメラの管理については全く忘れていました。今週はやりませんが、このやり方が正しいのかの検証は必要ですね。

そしてPlayAnimationです。

f:id:kazuhironagai77:20210228234533p:plain

Skeleton_Swordman_Default_Attackアニメーションをplayしています。

f:id:kazuhironagai77:20210228234557p:plain

この時にSkelSwordAniからこのアニメーションをplay出来るようにすればいい訳ですね。

大体やり方が分かって来ました。

もう一か所、戦闘中のモンスターのアニメーションでPlayeAnimationを使用している箇所がありました。

f:id:kazuhironagai77:20210228234620p:plain

ここでは、モンスターが死んだアニメーションを流しています。

f:id:kazuhironagai77:20210228234636p:plain

これもSkelSwordAniからPlay出来る様にします。

2.3 SkelSwordAniにSkeleton_Swordman_Default_AttackアニメーションとSkeleton_Swordman_Dieアニメーションを追加する。

それでは、モンスターが使用するAnimation BPであるSkelSwordAniからSkeleton_Swordman_Default_AttackアニメーションとSkeleton_Swordman_Dieアニメーションが使用出来るようにします。

SkelSwordAni内に新しいBoolean変数、Attackを作成します。

f:id:kazuhironagai77:20210228234707p:plain

このAttack変数がTrueの時に、Skeleton_Swordman_Default_Attackアニメーションを流すようにします。

まずDefault StateでIdle/Run stateにAttack Stateを追加します。

f:id:kazuhironagai77:20210228234723p:plain

Attack StateではSkeleton_Swordman_Default_Attackアニメーションを流します。

f:id:kazuhironagai77:20210228234740p:plain

移行する条件は、Attack変数の値がTrueに成る事です。

f:id:kazuhironagai77:20210228234757p:plain

これでSkelSwordAniからSkeleton_Swordman_Default_Attackアニメーションが使用出来るはずです。

同様な方法で、Skeleton_Swordman_DieアニメーションもSkelSwordAniから使用出来るようにします。

f:id:kazuhironagai77:20210228234819p:plain

f:id:kazuhironagai77:20210228234825p:plain

f:id:kazuhironagai77:20210228234834p:plain

f:id:kazuhironagai77:20210228234841p:plain

これでSkelSwordAniからSkeleton_Swordman_Dieアニメーションも使用出来るはずです。

2.4 戦闘中にSkelSwordAniからSkeleton_Swordman_Default_Attackアニメーションを使用する。

RPGGameModeBPのここの部分を変更します。

f:id:kazuhironagai77:20210228234907p:plain

以下の様に実装しました。

f:id:kazuhironagai77:20210228234924p:plain

テストします。

普通に攻撃しています。

f:id:kazuhironagai77:20210228234956p:plain

出来ました。

整理して見やすいようにしました。

f:id:kazuhironagai77:20210228235015p:plain

2.5 戦闘中にSkelSwordAniからSkeleton_Swordman_Dieアニメーションを使用する。

今度は、Skeleton_Swordman_Dieアニメーションの番です。Skeleton_Swordman_Default_Attackアニメーションの場合と全く同じやり方で行います。

このPlayAnimationノードを外します。

f:id:kazuhironagai77:20210228235041p:plain

以下の様に実装しました。

f:id:kazuhironagai77:20210228235108p:plain

テストします。

モンスターを倒した時にこのアニメーションが流れましたが、その後で、モンスターが復活してしまいました。

f:id:kazuhironagai77:20210228235128p:plain

良く考えたら当たり前でした。直します。

f:id:kazuhironagai77:20210228235149p:plain

もう一回テストします。

今度は死ぬモーションをひたすら繰り返しています。

うーん。

もうちょっと考えたら直せそうですが、ここはPlayAnimationを使用した方が適切な気がしてきました。

この後、このモンスターがSkelSwordAniを使用して動く事はないからです。

やっぱりここは元に戻します。

f:id:kazuhironagai77:20210228235209p:plain

f:id:kazuhironagai77:20210228235216p:plain

確認のためのテストをします。

f:id:kazuhironagai77:20210228235234p:plain

元に戻りました。

3.PlayAnimation ノードを外して戦闘中のアニメーションもAnimBPで管理する。(プレイヤーの操作するキャラの場合)

プレイヤーの操作するキャラは、素手、武器、魔法を使用した時にそれぞれ別のアニメーションを使用する必要がありますし、特に魔法を使用した時にParticle Systemも使用します。モンスターの時よりかなり複雑なはずです。気を付けてやって行きましょう。

3.1 プレイヤーの操作するキャラのAnimBP の復習

プレイヤーの操作するキャラのためのCharacter Class ですが、ThirdPersonCharacterをそのまま使用していました。

f:id:kazuhironagai77:20210228235305p:plain

使用されているAnim ClassはMyThirdPerson_AnimBP_Cでした。

f:id:kazuhironagai77:20210228235332p:plain

MyThirdPerson_AnimBPを見て行きます。

まず変数ですが、以下の3つがありました。

f:id:kazuhironagai77:20210228235417p:plain

多分、SpeedとIsInAirは元々あった変数と思われます。WithWeaponは自分で追加した事を覚えています。

EventGraphを見てみます。

f:id:kazuhironagai77:20210228235449p:plain

見にくい。ちょっと整理します。

f:id:kazuhironagai77:20210228235508p:plain

EventGraphでは二つの変数の値を常にUpdateし続けていました。

自画自賛ですが、関数の下にその関数にパスする値を書く私のやり方、先週、発明したReroute  nodeを併用すると非常に見やすくなります。これはBPの書き方における大発明かもしれません。最初、この書き方を始めた時は、自分自身ではもっとも分かり易い書き方であると信じていましたが、誰もやっている人がいなくて内心かなり弱気でした。しかしこれを見れば、私の書き方が見やすい、理解しやすいのは一目瞭然です。

整理する前の書き方だと、TryGetPawnOwnerノードを中心に見てしまいます。しかしTryGetPawnOwnerノードからコードの流れを見ても何をやっているのか曖昧になってしまいます。あれっと最初からコードの流れを見る羽目になります。

所が私の書き方だと、まずこの部分の実装の目的は二つの変数、IsiInAirとSpeedに値をセットする事であると一発で分かります。その後でそれらにセットするための値はどうやって得ているのかを知りたくなったら、その下の関数らを追えば良いだけです。

今度はAnimGraphを見てみます。

f:id:kazuhironagai77:20210228235526p:plain

勿論、Default Stateを開きます。

f:id:kazuhironagai77:20210228235543p:plain

成程、分かりました。Idle/RunとIdle/RunWithWeaponのStateがあるんですね。

後、Jumpの機能もあったんですね。

走ったりするStateの中を開いて見る必要がない事は既に分かっているのでここまでにします。

3.2 戦闘時におけるモンスターのplayAnimationの使用箇所についての復習

戦闘時に攻撃を選択した時、武器を所持している時としていない時のアニメーションの表示にPlayAnimationを使用しています。

f:id:kazuhironagai77:20210228235608p:plain

武器を装備しているかどうかはRPGGameInstanceBPから調べるべきでしょう。何で敢えてAnimInstanceから調べているんでしょうか?

以下に示した様に、武器を装備している時はplayAnimationノードの後で、MyThirdPersonAnimBPをセットし直して更にMyThirdPersonAnimBPの変数WithWeaponをTrueに指定しています。

f:id:kazuhironagai77:20210228235627p:plain

本当に無駄手間をかけています。こここそ、全てのアニメーションをMyThirdPersonAnimBPで直接管理すべき所です。

次は魔法を選択した時のアニメーションです。魔法はParticle Systemを使用していますので複雑な構造になっていると思っていたのですが、実際の実装を見るとplayer の操作するキャラとは別なActorがParticle Systemを担当していました。

f:id:kazuhironagai77:20210228235648p:plain

これなら簡単に直せそうです。

更に戦闘に負けて死んだときと戦闘に勝利した時のアニメーションもPlayAnimationが使用されていました。

f:id:kazuhironagai77:20210228235732p:plain

f:id:kazuhironagai77:20210228235742p:plain

これらに使用されているアニメーションは両方とも武器なしのものだけでした。武器在りのアニメーションも追加したら見栄えがもっと良くなると思って提供されているアニメーションを調べたら武器なしのアニメーションしかなかったです。

f:id:kazuhironagai77:20210228235803p:plain

3.3 MyThirdPerson_AnimBPに武器なしの場合の攻撃アニメーションと武器ありの場合の攻撃アニメーション、そして魔法使用時のアニメーションを追加する。

以下の様に追加しました。

f:id:kazuhironagai77:20210228235828p:plain

新たに作成した変数は、MagicとAttackです。

f:id:kazuhironagai77:20210228235847p:plain

Attack変数の使用方法は「2.3 SkelSwordAniにSkeleton_Swordman_Default_AttackアニメーションとSkeleton_Swordman_Dieアニメーションを追加する。」での使用方法と全く同じです。

Magicを使用した場合は武器を装備している時としていない時で返るstateが違います。以下の様に設定しています。

武器装備なしの場合

f:id:kazuhironagai77:20210228235905p:plain

武器装備在りの場合

f:id:kazuhironagai77:20210228235923p:plain

多分これで上手く行くそれぞれのStateに帰るはずです。

戦闘に負けて死ぬアニメーションと戦闘に勝って処理のポーズを取るアニメーションはPlayAnimationを使用する予定なので、これらのアニメーションはMyThirdPerson_AnimBPには追加しません。

3.4 戦闘中にMyThirdPerson_AnimBPから攻撃アニメーションを使用する。

以下の様に攻撃アニメーションの実装を変更しました。MyThirdPerson_AnimBPの実装が正しくされていれば武器装備ありと武器装備なしで分けてアニメーションを指定する必要はないはずです。

f:id:kazuhironagai77:20210301000005p:plain

テストします。

武器を装備したらいきなり攻撃モーションを繰り返し始めました。

調べたら以下の部分を逆に設定していました。

f:id:kazuhironagai77:20210301000046p:plain

もう一度テストします。

武器なしの場合です。

f:id:kazuhironagai77:20210301000159p:plain

武器なしのモーションが選択されています。

武器在りの場合です。

f:id:kazuhironagai77:20210301000230p:plain

武器在りのモーションが流れました。

出来てますね。

コードを整理します。

f:id:kazuhironagai77:20210301000300p:plain

たったこれだけになりました。

3.5 戦闘中にMyThirdPerson_AnimBPから魔法アニメーションを使用する。

今度は魔法アニメーションです。以下の様に実装し直しました。

f:id:kazuhironagai77:20210301000343p:plain

テストします。

何回も同じモーションを繰り返しています。

魔法のモーションが約0.7秒だったので0.7秒辺りでMyThirdPerson_AnimBP のMagic変数の値をFalseに戻しました。

f:id:kazuhironagai77:20210301000403p:plain

テストします。

まず武器なしで魔法を使用した時です。

f:id:kazuhironagai77:20210301000427p:plain

その後、攻撃を選択します。

f:id:kazuhironagai77:20210301000442p:plain

素手の攻撃モーションを使用しています。

今度は武器ありで魔法を使用します。

f:id:kazuhironagai77:20210301000500p:plain

その後、攻撃を選択します。

f:id:kazuhironagai77:20210301000514p:plain

武器の攻撃モーションを使用しています。

出来ていました。

3.6 戦闘中のコードを整理する。

RPGGameModeBP内の戦闘中の実装が以下の様にぐちゃぐちゃなので整理します。

f:id:kazuhironagai77:20210301000535p:plain

以下の様に整理しました。

f:id:kazuhironagai77:20210301000553p:plain

もう少し整理出来ますが、この状態でテストします。

武器在りの時です。

魔法を使用します。

f:id:kazuhironagai77:20210301000619p:plain

その後、攻撃します。

f:id:kazuhironagai77:20210301000636p:plain

武器で攻撃しています。出来ています。

今度のテストは武器なしの時です。

魔法を使用します。

f:id:kazuhironagai77:20210301000705p:plain

その後攻撃します。

f:id:kazuhironagai77:20210301000722p:plain

武器なしの攻撃をしています。

出来ています。

もう少しだけ整理します。

色んな所でMyThirdPerson_AnimBPにアクセスしているので変数として作成します。

f:id:kazuhironagai77:20210301000740p:plain

RPGGameModeBP内のEventBeginPlay関数で、以下に示した様に変数MyThirdPersonAnimBPを作成しました。

f:id:kazuhironagai77:20210301000802p:plain

まだまだスパゲッティコードですが、前よりは遥かに見やすく成りました。

f:id:kazuhironagai77:20210301000819p:plain

確認のテストをします。

スクリーンショットは取らなかったですが出来ている事は確認出来ました。

4.兎に角、Playしてバグを見つける

4.1 Playします。

まず、プレイ中の音楽が無いです。

夜になると真っ暗で何も見えません。

f:id:kazuhironagai77:20210301000846p:plain

PCが突然、凄い音出し始めました。

f:id:kazuhironagai77:20210301000903p:plain

地面の無い所にいったらフリーズしました。

戦闘から戻った時にカメラの方向が一定です。

戦闘中に「読みましたボタン」が表示されないバグが発生しました。

f:id:kazuhironagai77:20210301000919p:plain

再現性もあります。

Level10に成れないためのバグかもしれません。

f:id:kazuhironagai77:20210301000936p:plain

「読みましたボタン」が表示されないバグではなく勝利シーンに移行できないバグのようです。

たった十分程度Playしただけでこれだけのバグが出て来ました。

でもそれなりに面白かったです。

4.2 バグのまとめ

以下のバグが見つかりました。

  • プレイ中の音楽が無い。
  • 夜になると真っ暗で何も見えない。
  • PCが突然、凄い音出し始めました。(ずっとではない。)
  • 地面の無い所にいったらフリーズした。
  • 戦闘から戻った時にカメラの方向が一定。
  • 戦闘から戻れないバグ発生。

これらのバグを直します。

5. バグの直し

5.1 プレイ中の音楽の追加

以下の方法で、Map1にBGMを追加しました。

f:id:kazuhironagai77:20210301001021p:plain

BGMは場所によって変化させる予定なので音の編集が出来るSpawnSound2Dノードを使用します。

使用するCueは以下のデータです

f:id:kazuhironagai77:20210301001042p:plain

Mega Game Music Collectionから使用しました。

f:id:kazuhironagai77:20210301001119p:plain

BGMをループさせるため、Cueを開いてLoopingにチェックします。

f:id:kazuhironagai77:20210301001143p:plain

モンスターが発生する危険な地域や、町や城でBGMは変更しますが、それは後で作成します。

5.2 夜になると真っ暗で何も見えない。

もう単純にPoint LightをThird Person Characterに追加しました。

f:id:kazuhironagai77:20210301001207p:plain

Lightが無い状態です。

f:id:kazuhironagai77:20210301001224p:plain

Lightがある状態です。

f:id:kazuhironagai77:20210301001239p:plain

かなり見やすく成っていますが、回りを見渡すまでは明るくはないです。今回はこれ位にしておきます。

5.3 PCが突然、凄い音出し始めました(ずっとではない)。

再現したくて何回もゲームをPlayしたがその後、一回もそんな事態にはならなかったです。

F5でShaderの負担をチェックしたが特に特別に重そうな所は見つからなかったです。

f:id:kazuhironagai77:20210301001302p:plain

f:id:kazuhironagai77:20210301001310p:plain

原因が不明なのでこのバグの修復はパスします。

5.4 地面の無い所にいったらフリーズした。

落ちたら死ねば良いだけですが、どうやって実装しましょうか?

思い付きました。

この方法はベストではないでしょうが、これでも出来るはずです。

MyThirdPerson_AnimBPに二つの変数、IsInAirとSpeedがあります。

f:id:kazuhironagai77:20210301001337p:plain

更に、ThirdPersonCharacterのMax Walk Speedを見ると600になっています。

f:id:kazuhironagai77:20210301001356p:plain

この二つから推測すると、IsInAirがtrueでSpeedが600以上だった場合、プレイヤーが操作するキャラが墜落していると考えて良いのではないでしょうか?

RPGGameModeBPのTick関数に以下のコードを追加しました。

f:id:kazuhironagai77:20210301001417p:plain

これでPlayerが操作するキャラが墜落した時が分かるはずです。

墜落した場合は墜落死を知らせる新しいmapを開きます。

f:id:kazuhironagai77:20210301001438p:plain

そのためのWidgetも作成しました。

f:id:kazuhironagai77:20210301001456p:plain

テストします。

f:id:kazuhironagai77:20210301001513p:plain

落ちます。

一瞬でGame Overに成りました。

f:id:kazuhironagai77:20210301001536p:plain

もっと発展させる必要はありますが、最低限の機能は出来ました。

5.5 戦闘から戻った時にカメラの方向が一定

これが何で成るのかが分からないです。

考えられるのはRootCompomentとして指定されているCapsuleComponentにScaleしか指定出来ないため指定出来ないのかもしれません。

f:id:kazuhironagai77:20210301002120p:plain

f:id:kazuhironagai77:20210301002128p:plain

色々試したのですが、結局原因は不明でした。

カメラとCameraBoomの関係をもう一度勉強しないと理解出来なそうです。

色々調べたら、PlayerStartの向きとカメラの向きが同じになっているみたいです。

それならPlayerStart を消してPlayerが操作するキャラを動的に作成すれば向きも自然になると思いその方法を調べました。

以下の方法でPlayerが操作するキャラを動的に作成出来るそうです。

f:id:kazuhironagai77:20210301002153p:plain

この方法を試してみます。

以下の方法でMap1、LevelBP内に実装しました。

f:id:kazuhironagai77:20210301002217p:plain

テストします。

f:id:kazuhironagai77:20210301002233p:plain

後ろ向きで戻って来ました。

ただし。大量のエラー報告と一緒にですが…

f:id:kazuhironagai77:20210301002256p:plain

直していきます。

最初のエラーの原因はRPGGameModeBPのMyThirdPersonAnimBPがnullだからでした。その原因はRPGGameModeBPのEventBeginPlayの時にはまだPlayerが操作するキャラを作成していないからです。

Playerが操作するキャラはRPGGameModeBPで作成する事にします。

f:id:kazuhironagai77:20210301002319p:plain

更に戦闘時の場合はPlayerが操作するキャラの生成される場所が違うのでそれに対応した別のSpawnPlayerCharacterBattleFiledも作成します。

f:id:kazuhironagai77:20210301002343p:plain

Levelによってそれぞれのイベントを呼び出します。

f:id:kazuhironagai77:20210301002419p:plain

テストします。

素手の戦闘では上手く行きましたが、武器を持った場合、元に戻った時、以下の様なエラーが発生しました。

f:id:kazuhironagai77:20210301002439p:plain

原因はRPGGameModeBPでPlayerが操作するキャラを作成して更に、Map1でも作成してたからと思われます。

Map1の作成は外します。

f:id:kazuhironagai77:20210301002459p:plain

今度は大丈夫でした。

f:id:kazuhironagai77:20210301002524p:plain

以下のWarningが出ています。何なんでしょうか?

f:id:kazuhironagai77:20210301002649p:plain

分かりませんね。

ThirdPersonCharacterを(0,0,0)の位置にSpawnせよという命令はしていないはずですが。

5.6 戦闘から戻れないバグ発生

レベルが10になった時、以下のif節に入ってエラーになっていました。

f:id:kazuhironagai77:20210301002714p:plain

色々検討した結果、このIf節はrowがNullの全ての場合に対応しているので、消す訳にはいかないので以下のような変更にしました。

f:id:kazuhironagai77:20210301002733p:plain

テストします。
Levelが10に上がってゲームが途中で止まる事も無くなりました。

f:id:kazuhironagai77:20210301002753p:plain

ここは直ったんですが、以下のコードがLevel10になった時にゲームを停止させてしまう事に気が付きました。

f:id:kazuhironagai77:20210301002811p:plain

取りあえず試してみます。

やっぱり停止しました。

f:id:kazuhironagai77:20210301002833p:plain

以下のコードを追加してそもそもLevelが10になった時はLevelUpの検査を受けないようにしました。

f:id:kazuhironagai77:20210301002851p:plain

テストします。

普通に戦闘が終了しました。

直りました。

6.バグ出しもう一回

「5.5 戦闘から戻った時にカメラの方向が一定」でPlayerが操作するキャラをLevel上に配置したPlayerStartから静的に生成するのではなく、動的にBPから生成する事にしました。

この大改造により、バグが大量に生成されていると思われます。

現状把握しているだけでも、以下のWarningは出続けていますし。

f:id:kazuhironagai77:20210301002921p:plain

もう一回バグ出しをやって行きます。

6.1 スタート画面でエラー、更に墜落死画面でもエラー

直します。

以下の様にしました。

f:id:kazuhironagai77:20210301003003p:plain

ここで動的にPlayerの操作するキャラを作成しなければ勝手にPlayerStartの位置にThirdPersonCharacterが作成されるはずです。

試してみます。

出来ました。

出来ましたが、またエラーが大量に発生しました。

f:id:kazuhironagai77:20210301003022p:plain

直します。

ここでエラーが発生しています。

f:id:kazuhironagai77:20210301003040p:plain

成程。このやり方だとMyThirdPersonAnimがnullになってしまうんですね。

直します。

以下の様に直しました。

f:id:kazuhironagai77:20210301003058p:plain

まずEventを止めました。基本的にEventは電話と同じ機能をProgrammingで実装したものです。全ての作業を中止しても対応しなければならない、突然発生する事態(つまり電話の対応)のための機能です。

Playerのキャラを作成する事は突然発生しません。毎回、Levelを開く時にする必要があります。のでEventは使用しません。

代わりに関数、SpawnPlayerCharを作成しました。

f:id:kazuhironagai77:20210301003118p:plain

キャラの生成する位置と角度を指定するとキャラを動的にSpawnしてくれます。

それぞれのLevelや戦闘が発生した場所で、キャラの生成する位置と角度は変わりますので、そこに対応出来る様にします。

f:id:kazuhironagai77:20210301003138p:plain

この部分はもっとマシな書き方があると思います。後で、少し考えます。今回はこれで行きます。

最後に、エラーが出た変数に値をパスします。これでエラー出ないはずです。

f:id:kazuhironagai77:20210301003202p:plain

テストします。

まずスタート画面です。

エラーの表示はありません。出来ています。

f:id:kazuhironagai77:20210301003228p:plain

出来ていますが、何か画面が異常に明るいです。

そうだ。ThirdPersonCharacterにPointLightを追加した事を忘れていました。ここではそのPointLightは切っておくべきですね。

「新しく始める」をクリックしました。

Map1が作成されました。

f:id:kazuhironagai77:20210301003250p:plain

特にエラーの発生はありません。落下してみます。

ゲームオーバーになりました。

f:id:kazuhironagai77:20210301003313p:plain

エラーの表示もないのでバグは直りました。

6.2 スタート画面の修正

スタート画面でPlayerの操作するキャラが明るすぎるのでそれを修正します。

以下の様に直しました。

f:id:kazuhironagai77:20210301003353p:plain

テストします。

f:id:kazuhironagai77:20210301003410p:plain

暗くなっています。

Map1に入ると今度はPlayerの操作するキャラは明るくなっています。

出来ています。

あれ?

Monsterが動いていません。

f:id:kazuhironagai77:20210301003430p:plain

新たなバグの発見です。

6.3 モンスターを動かす

何で動いてないんでしょうか?

AIが機能していないんでしょうか?

Behavior TreeのDebug機能で調べて見ると、Blackboard Decorator ノードであるIsRandomPlace?がFalseを返している事が分かりました。

f:id:kazuhironagai77:20210301003454p:plain

IsRandomPlaceノードの設定を見ると、以下に示した様に、Blackboardの変数、RandomPlaceの値をチェックしています。

f:id:kazuhironagai77:20210301003512p:plain

この値に問題があるみたいです。

BlackboardのRandomPlaceの値は、ServiceクラスであるFindRandomPlace内で指定されています。

FindRandomPlaceを見るとGetRandomReachablePointInRadiusがFalseを返してる事が分かります。

f:id:kazuhironagai77:20210301003531p:plain

更に、GetActorLocationはあるActorの位置情報は返してる事も分かりました。

と言う事は、GetRandomReachablePointInRadiusが適切な場所が見つけられないからモンスターは動いていないと言う事になります。

確認のために他のモンスターの場合も確認しましたが、以下に示した様に全く同じ問題が発生していました。

f:id:kazuhironagai77:20210301003558p:plain

何で、GetRandomReachablePointInRadiuが機能しないのでしょうか?

一番考えられるのは、NavMeshBoundVolumeが機能していない事です。

確認します。

NavMeshBoundVolumeは効いていませんでした。

取りあえず、左側のNavを動かしたらそれだけは効くようになりました。

f:id:kazuhironagai77:20210301003617p:plain

これでテストしてみます。

以下に示すように凄い勢いで追いかけて来ました。

f:id:kazuhironagai77:20210301003634p:plain

直りました。

6.4 以下のWarningについて

今週最後は、以下のWarningについて調べます。

f:id:kazuhironagai77:20210301003654p:plain

Output Logを読み直したらすぐ分かったのですが、これはPlayerStartを消した事から起きるみたいです。

f:id:kazuhironagai77:20210301003710p:plain

となると、直せませんね。もしPlayerStartを追加したら、戦闘マップから戻って来た時に、playerの操作するキャラの向きが一定になってしまいます。

Packagingの時とかに問題にならないと良いですね。

7.来週以降にやらないといけない事

今週中にバグ出しを終えたかったんですが、無理でした。以下に来週以降やらないといけない事をまとめておきます。

  • モンスターの歩く速度の設定がアニメーションとAIで全然違う
  • BGMの変更、町や村、モンスターが居る廃墟で別々のBGMにする。
  • BPの整理。RPGGameModeBPを整理します。
    • そこら中でThirdPersonCharacter変数に値をセットしている。過去、EventBeginPlay関数がBPから呼べなかったので毎回呼び出していた。これを直す。
    • 戦闘システムから呼ばれるEventを整理してまとめる。
  • セリフシステムの整理。確認出来るだけで3種類以上の方法でセリフを管理している。これを統一する。
  • 更なるバグだし。せめて1時間はこのゲームで遊んでバグ出しする。(今週は10分だった。)
  • Levelデザイン。

これらの事をやっていきます。

8.まとめと感想

今週はここまでです。戦闘マップから戻って来た時に、向きが変わってしまう問題は一応解決できました。Levelが10になるとゲームが止まってしまうバグは予想外でした。まだまだ、先は長いですが、ジグソーバズルが埋まって行く時に感じるような手ごたえを感じれるようになって来ました。

 

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する Saveの改良 Part 2

f:id:kazuhironagai77:20210221234157p:plain

<前文>

Genocideそして「進撃の巨人 Final SeasonPart 2

先週、GenocideとMassacreは日本語に訳すとどちらも虐殺になりますが、英語でGenocideはある民族を完全に消滅する事で単なる虐殺であるMassacreとは全く違う日本には存在しない概念であり、更に世界ではGenocideは身近であると話しました。

そして「進撃の巨人 Final Season」の隠れたテーマがGenocideであるが故に日本ではあまりヒットしないにも関わらず日本以外の世界中で大ヒットしているとの話を今週はします。

ここからは「進撃の巨人 Final Season」のGenocideを軸としての私の個人的な解説をしますが、詳細は間違っているかもしれません。忙しいのであんまりアニメを見る時間がないのでその辺はご了承下さい。

進撃の巨人 Final Season」では世界中の民族が主人公の民族をGenocideしようとしています。所が数年の間だけですが、主人公の民族は強力な兵器を所持していてそれを使用すれば逆に世界の主人公以外の民族を滅ぼす事が出来ます。

ここで3つの選択が出来ます。

  1. 今すぐ戦争を仕掛けて、世界を滅ばして自分たちだけ助かる
  2. 世界と話し合って、お互いにGenocideしないよう約束して共存共栄する。
  3. 自分たちだけ助かるために自分たち以外の全ての民族を滅ぼすのは罪が重すぎるので、自分たちが絶滅するのを受け入れる。

1の選択は最も現実的ですが、最もモラルに反しています。簡便に検証しても以下の道徳に反しています。

  • 自分たちの民族がGenocideされるのを避けるために、他の民族はGenocideしても構わないと言う矛盾。
  • 更に現状では、奴隷みたいな立場ですが、一応服従を誓えば、主人公の民族もギリギリGenocideされないで生きていける選択もあります。それを捨てて自分たちの民族がGenocideされる前に自分たち以外の全ての民族をGenocideしてしまおうという自分勝手な選択。

2の選択は最も理想的ですが、現実に達成可能なのか?常に疑問が付きまといます。例えば、

  • 数年経った後で、世界の軍事力が主人公達の民族より強大になってから約束を反故にされる可能性。
  • 主人公達の民族が持つ強大な軍事力を放棄する事を条件に、お互いにGenocideしないよう約束するが、放棄した途端に攻めて来られる可能性。

3の選択は人間としての道義を守り、かつ実現可能でもありますが、主人公達の民族にとっては悲惨過ぎる結末しか待っていません。

平均以上の常識と知性がある人なら、全ての人にとって受け入れられる選択は2しかないと直ぐに分かります。そして2を選択しつつ最悪3になったらそれも受け入れるしかない位の結論に到達するはずです。

所が、このアニメの主人公、何のためらいもなく1を選択します。

この主人公は世界を弱肉強食、勝者のみが自由を手に入れる事が出来る残酷なものと見なしているからです。主人公にとってはGenocideするかされるか以外の選択は存在しないんです。そしてその戦いに勝ったものだけが自由を手に入れられると心の底から思っています。

この主人公の選択を骨董無形で自分勝手と断ずるのは簡単ですが、世界の視聴者はこの主人公の決断を自分たちの環境に置き換えるとGenocideが身近に在るが故に真剣に受け止めざる得なくなるのです。

その結果「進撃の巨人 Final Season」は世界で大ヒットをし続けるのです。

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

<本文>

1.今週の予定

今週はセーブ機能を完成させます。

  • Game Instance BPクラス内のセーブに必要な変数の値をsave出来る様にする。
  • UE4C++でのGame Instanceクラス内の変数の値をsave出来る様にする。
  • 神官のSave機能を実装する

これらをやって行きます。

2.Game Instance BPクラス内のセーブに必要な変数の値をsave出来る様にする。

2.1 先週の調査の復習

先週の調査でRPGGameInstanceBP内の変数でSave/Loadする時に必要な変数は以下の3つでした。

f:id:kazuhironagai77:20210221234349p:plain

ただしMonster Spawn Dataはやっぱり無くても良い事になったので結局はMap変数とItem Spawn Data 変数の値だけです。

これらの変数の値がセーブ出来るようにします。

2.2 Map変数の値がセーブされるようにする

Map変数は以下に示した様にEnumクラスから作成されています。

f:id:kazuhironagai77:20210221234414p:plain

まずこの変数をSave Gameクラスから派生して作成したクラスに追加してみます。

あれ、SaveGameクラスから派生したBPクラスが見つかりません。

うーん。

調べたら前回は以下の方法でSave機能を作成していました。

ポーズ画面でセーブボタンを押すと、

f:id:kazuhironagai77:20210221234431p:plain

GameInstanceの関数、SaveMyGameを呼び出します。

f:id:kazuhironagai77:20210221234448p:plain

SaveMyGameはUE4C++のRPGGameInstanceで作成されています。

f:id:kazuhironagai77:20210221234504p:plain

実装部を見ると、SaveGameクラスから作成したMySaveGameクラスが使用されています。

f:id:kazuhironagai77:20210221234522p:plain

MySaveGameクラスは以下のように作成しています。

f:id:kazuhironagai77:20210221234543p:plain

f:id:kazuhironagai77:20210221234551p:plain

うーん。このやり方だとBPにある変数はSave出来ないですね。

この関数は取りあえず維持したまま、MySaveGameクラスからBPを作成してそこに変数を追加していきます。

f:id:kazuhironagai77:20210221234611p:plain

Enum、MapクラスからMap変数を作成します。

f:id:kazuhironagai77:20210221234824p:plain

Enumの名前をMapにしてしまったために、Map(dictionary)と紛らわしくなってしまいました。LevelNameとかにすれば良かったです。

後、Pauseメニューからはsaveが出来る仕様にはしない事にしたので、この機能は後で消します。代わりに魔法ボタンを追加します。

先週作成した、神官の会話UIのセーブボタンに

f:id:kazuhironagai77:20210221234855p:plain

以下の実装を追加します。これでSlotにsaveされるはずです。

f:id:kazuhironagai77:20210221234914p:plain

Slot名は前に作成したSave機能と間違えないために新しい名前のMySavedGameにします。

Load機能は以下のスタート画面のロードするボタンをクリックした時に実行するようにします。

f:id:kazuhironagai77:20210221234954p:plain

現状は以下の様になっています。

f:id:kazuhironagai77:20210221235010p:plain

以下の様に変更しました。

f:id:kazuhironagai77:20210221235027p:plain

何で、GameGold変数の値がBPから変更出来るのか?

UPROPERTYでBlueprintReadWriteに指定されていました。

f:id:kazuhironagai77:20210221235043p:plain

後、EditAnywhereに指定されていますが、このコードを書いたとき、今一EditAnywhereの意味が分かってなかったのでしょうね。

以下に示した公式のこのサイト

f:id:kazuhironagai77:20210221235059p:plain

EditAnywhere、EditDefaultOnly、そしてEditInstanceOnlyの違いが説明されていますが、

f:id:kazuhironagai77:20210221235115p:plain

f:id:kazuhironagai77:20210221235122p:plain

ここで大切なのは、ArchTypesとInstanceの違いです。ArchTypesとは簡単に言えばクラスの事で、EditDefaultOnlyにした場合、その変数はそのクラスで一個の値しか持てません。EditInstanceOnlyはその逆で、その変数の値はそれぞれのInsntanceによってバラバラの値になります。EditAnywhereは、ClassからでもそれぞれのInstanceでも値の指定が出来ます。

と言う事です。

Game Instanceは一個しか作成されないのにそのクラスの変数がEditAnywhereを持つ必要はないはずです。EditDefaultOnlyに変更しました。

f:id:kazuhironagai77:20210221235242p:plain

テストします。

Saveは普通に出来たみたいです。

f:id:kazuhironagai77:20210221235256p:plain

Loadします。

エラーになってゲームが強制終了になってしまいました。

f:id:kazuhironagai77:20210221235348p:plain

デバックして調べて見ると、Loadの実装の最後のOpenLevelでエラーになっていました。

f:id:kazuhironagai77:20210221235445p:plain

何で?とLevelNameにパスされている値を見たら、以下の様に成っていました。

f:id:kazuhironagai77:20210221235502p:plain

理屈は良く分からないのですが。LoadしたGameInstanceの変数から参照したらmap1と表示されたのでこっちを使用します。

f:id:kazuhironagai77:20210221235517p:plain

テストします。

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

調べたらMapからStringに変換した時はmap1が表示されますが、MapからNameに変換した時は、Map::NewSameEnumerator1が表示されました。

f:id:kazuhironagai77:20210221235609p:plain

じゃ、一端Stringに変換してNameに直してみます。

f:id:kazuhironagai77:20210221235626p:plain

テストします。

今度は出来ました。

f:id:kazuhironagai77:20210221235643p:plain

所持金も金貨95枚になっているので、Saveした時に神官に支払った金貨5枚が減った状態できちんとセーブされていました。

f:id:kazuhironagai77:20210221235659p:plain

やっと出来ました。

2.3 Item Spawn Data 変数の値がセーブされるようにする

ItemSpawnDataはクラスItemSpawnDataのarrayですね。

f:id:kazuhironagai77:20210221235802p:plain

まずItemSpawnDataクラスを調べます。

調べたらItemSpawnDataはStructでした。

f:id:kazuhironagai77:20210221235823p:plain

これはShallow copyになりそうですね。

取りあえず、MySaveGameBPにItemSpawnDataクラスのarrayを作成します。

f:id:kazuhironagai77:20210221235843p:plain

ここにRPGGameInstanceBPの値をセットします。

ここまでやってあれなんですがArrayの前にStructのそれぞれの値がSaveGameクラスでどのように保持されるのか知りませんでした。こっちから調べます。

2.3.1 StructのデータとSaveGameクラス

StructのデータがどのようにSaveGameクラスに保持されるのかを調べます。

先程作成した、ItemSpawnDataのarrayを変数にします。

f:id:kazuhironagai77:20210221235912p:plain

以下の方法でRPGGameInstanceBPの最初の要素の値をMySaveGameBPのItemSpawnDataにセットしました。

f:id:kazuhironagai77:20210222000005p:plain

勿論、Get はCopyを使用しています。

以下の方法で、Slotから読み込んだMySaveGameBPのItemSpawnDataの値を見てみます。

f:id:kazuhironagai77:20210222000022p:plain

普通に全部正しい値でセーブされていました。

f:id:kazuhironagai77:20210222000039p:plain

おお。

これGetをreferenceにした場合はどうなんでしょうか?

f:id:kazuhironagai77:20210222000100p:plain

GetがReferenceでも出来ていますね。

ここまでやって気が付いたんですが、Saveに必要な情報ってItemをSpawnするかどうかだけでした。Boolean変数のArrayを作成すれば良いだけでした。

もう一回やり直します。

MySaveGameBPの変数をItemSpawnBoolとしてBoolタイプのArrayにします。

f:id:kazuhironagai77:20210222000125p:plain

以下の方法でRPGGameInstanceBPのItemSpawnDataの要素、Spawnの値をMySaveGameBPのItemSpawnBoolに渡します。

f:id:kazuhironagai77:20210222000222p:plain

前にやったのと同じ方法で、Slotセーブしたデータから作成したMySaveGameBPのItemSpawnBoolの値を調べます。

f:id:kazuhironagai77:20210222000239p:plain

MySaveGameBPのItemSpawnBoolの値はslotに正しくセーブされていました。

f:id:kazuhironagai77:20210222000301p:plain

もう一度確認します。

こんどはアイテムを一個だけ回収した後で、セーブしました。

f:id:kazuhironagai77:20210222000316p:plain

出来ていますね。

ではLoad時にこのArrayの値を元にRPGGameInstanceBPのItemSpawnDataの要素、Spawnの値を変更するようにします。

スパゲッティコードになりそうなので関数で実装しました。

f:id:kazuhironagai77:20210222000332p:plain

中身は以下の様に成っています。

f:id:kazuhironagai77:20210222000351p:plain

Arrayの関数をしっかり勉強したら、この方法より簡単な実装方法がありそうなんですが、とりあえずこれで行きます。一応これでもArrayの要素の一部の値の変更が可能なはずです。

テストします。

マップ内に配置されているアイテムの内、盾だけを回収しました。

f:id:kazuhironagai77:20210222000407p:plain

Saveしてloadします。

盾だけ無くなっていました。

f:id:kazuhironagai77:20210222000448p:plain

出来ました。

3.UE4C++RPGGameInstanceクラス内の変数の値をsave出来る様にする。

UE4C++のRPGGameInstanceクラス内の変数でsaveする必要がある変数をSave/Load出来るようにします。

以下の変数がSaveする必要がある変数です。

f:id:kazuhironagai77:20210222000543p:plain

上に行くほど、Save/Loadの作成が難しくなっているので下から作成していきます。

3.1 IsArmorEquippedの値をSave/Load出来る様にする。

この変数は、BlueprintReadWriteで指定されているし、単なるBoolean変数なので今までの知識で簡単にSave/Load出来るはずです。

MySaveGameBPに新しい変数、IsArmorEquippedを作成します。

f:id:kazuhironagai77:20210222000628p:plain

最初はBPのMySaveGameBPでなくUE4C++のMySaveGameに作成した方が良いかと思いましたが特にUE4C++側で作成しなければならないメリットはないのでBP側で作成します。

以下の方法で実装しました。特に新しい事はしていません。

f:id:kazuhironagai77:20210222000650p:plain

f:id:kazuhironagai77:20210222000657p:plain

ここでテストしようと思ったのですが、以下に示した通り、RPGGameInstanceのArmorEquipped変数の値もセットされていないとエラーになってしまうのでこれをセットしてからテストします。

f:id:kazuhironagai77:20210222000715p:plain

3.2 ArmorEquippedの値をSave/Load出来る様にする。

そういうわけでArmorEquippedの値を次にやります。

f:id:kazuhironagai77:20210222000747p:plain

BlueprintReadWriteですし、やった事ないのはFStringだけです。

以下に示した様に、今までと全く同じやり方でやりました。

f:id:kazuhironagai77:20210222000821p:plain

f:id:kazuhironagai77:20210222000828p:plain

これでテスト出来るはずです。

木の盾を装備した状態でSaveしました。

Loadします。

以下に示した様に木の盾を装備して登場しました。

f:id:kazuhironagai77:20210222000851p:plain

出来ました。

3.3 IsWeaponEquippedとWeaponEquippedをSave/Load出来るようにする。

3.1と3.2でした事と全く同じ事をIsWeaponEquippedとWeaponEquippedにします。

f:id:kazuhironagai77:20210222000929p:plain

f:id:kazuhironagai77:20210222000936p:plain

テストします。

剣を装備した状態でSaveし、その後、Loadしてゲームを開始しました。

剣を装備した状態から始まりました。

f:id:kazuhironagai77:20210222001049p:plain

出来ています。

3.4 ItemsをSave/Load出来るようにする。

WeaponよりもItemの方が値段が安いのでテストしやすいと思い、Itemsを先にやる事にしました。

f:id:kazuhironagai77:20210222001119p:plain

まずBlueprintReadOnlyで指定していますが、Arrayなんです。Loadする時Add関数を使用すればBPからでもこの変数に要素の追加が出来たはずです。となると敢えてBlueprintReadWriteに直す必要もない事になります。

UE4C++のRPGGameInstanceはそのままでやって行きます。

以下の方法でMySaveGameBPのItems変数にRPGGameInstanceのItem変数の値を移します。

f:id:kazuhironagai77:20210222001137p:plain

Load時には、以下の方法でMySaveGameBPの値をRPGGameInstanceに移します。

f:id:kazuhironagai77:20210222001156p:plain

見やすいように関数にしました。

f:id:kazuhironagai77:20210222001212p:plain

テストします。

回復薬、イーサー、ダークイーサーを2個ずつ買いSaveします。

f:id:kazuhironagai77:20210222001235p:plain

一端ゲームを終了してloadします。

道具袋を開いて見ると

f:id:kazuhironagai77:20210222001311p:plain

買ったitemはきちんとSaveされていました。

出来てますね。

3.5 WeaponsをSave/Load出来るようにする。

Itemsと同じやり方でやります。

f:id:kazuhironagai77:20210222001336p:plain

f:id:kazuhironagai77:20210222001344p:plain

f:id:kazuhironagai77:20210222001351p:plain

テストします。

武器屋は隣町にしか作っていませんでした。ので配置している武器を拾ってセーブしました。

f:id:kazuhironagai77:20210222001614p:plain

Loadして装備品を開くと所持している武器が表示されていました。

f:id:kazuhironagai77:20210222001631p:plain

出来ました。

3.6 MyYourHeroをSave/Load出来るようにする

最も大変な変数の番が来ました。

GameCharacterクラスである MyYourHero変数をSave/Load出来る様にします。

まずこの変数はObjectなので単純にSave/Loadしたらshallow copyに成ってしまうのかどうかが分かりません。

次に、BlueprintReadOnlyに指定しているので、BP側から書き込みが出来ません。

f:id:kazuhironagai77:20210222001653p:plain

この辺の問題を解決しつつMyYourHero変数をSave/Load出来る様にします。

3.6.1 MyYourHero変数とBlueprintReadOnlyについての調査

今、考えてみると、Levelが上がった時や、戦闘でダメ―ジを貰った時にMyYourHero変数の中の変数であるLevelやHPの値はBPで変更しているはずです。どうやっているのか調べて見ます。

因みにMyYourHero変数のタイプであるGameCharacterクラスを見ると

f:id:kazuhironagai77:20210222001724p:plain

となっていて、BP側から値を変更出来るのかもしれません。

調べて見たら、全部UE4C++側で操作していました。

f:id:kazuhironagai77:20210222001856p:plain

折角ここまでMyYourHero変数はUE4C++側で操作していたので、LoadもUE4C++側でやりたいですね。その方向でがんばってみます。

3.6.2 MyYourHero変数のSave

GameCharacterクラスをMySaveGameBP内に作成してSave出来るのかをテストする所から始めます。

f:id:kazuhironagai77:20210222001936p:plain

以下の方法でRPGGameInstanceのMyYourHero変数をMySaveGameBPのMyYourHero変数にセットします。

f:id:kazuhironagai77:20210222001953p:plain

Load時にMySaveGameBPのMyYourHeroオブジェクトがその変数であるPlayer Nameの値を保持しているのか以下の方法で確認します。

f:id:kazuhironagai77:20210222002023p:plain

テストします。

思いっきりエラーになりました。

f:id:kazuhironagai77:20210222002040p:plain

やっぱりSaveGameクラスは Objectの値は保持してくれないみたいですね。

もしくは、普通のC++のようにGameCharacterクラスにSet()関数をOverrideして書く必要があるのかもしれません。

それともArrayでやったように一個、一個の変数に値をSetすれば出来るのかもしれません。

簡単に出来る方法から試してみます。

3.6.3 MyYourHero Objectの変数の値を一個ずつsetする。

以下の方法で試してみます。

f:id:kazuhironagai77:20210222002114p:plain

駄目でした。

f:id:kazuhironagai77:20210222002132p:plain

3.6.4 MyYourHero Objectの全ての変数をMySaveGameBP内に作成する

このやり方なら絶対Save出来るのは分かっています。

一応試してみます。

f:id:kazuhironagai77:20210222002158p:plain

f:id:kazuhironagai77:20210222002207p:plain

f:id:kazuhironagai77:20210222002216p:plain

テストします。

f:id:kazuhironagai77:20210222002237p:plain

出来ています。

このやり方でsaveするとGameCharacterの内、以下の変数をコピーする必要があります。

ぱっと見た限りでは全部出来そうです。

f:id:kazuhironagai77:20210222002253p:plain

このやり方でやってみます。

よく考えたらPlayerNameはセーブする必要なかったです。Occupationに変更してしまいます。

MHPをsaveします。

この変数、UE4C++側はInt32をタイプに使用しているのですが、BPにはIntegerとInteger64しかありません。Integerをそのまま使用してみます。

f:id:kazuhironagai77:20210222002717p:plain

f:id:kazuhironagai77:20210222002725p:plain

f:id:kazuhironagai77:20210222002732p:plain

これでテストします。

f:id:kazuhironagai77:20210222002750p:plain

出来ていました。

残りのMMP、ATK、DEF、LUCK、XP、LV、そしてMagicsを作成します。

テストします。

f:id:kazuhironagai77:20210222002814p:plain

f:id:kazuhironagai77:20210222002821p:plain

XPとLVは0と1なので出来ています。

こんどは魔法を覚えた状態でSaveしました。

f:id:kazuhironagai77:20210222002838p:plain

魔法もsaveされています。

3.6.5 MyYourHero Objectの変数の値をLoadする。

RPGGameInstanceのMyYourHero のBlueprintReadOnlyの設定は今更変えたくないので、RPGGameInstanceにあるLoad関数を改良して値をパスしようと思ったのですが、MyYourHero変数のクラスであるGameCharacterのそれぞれの変数が、BlueprintReadWriteに設定されています。

それなのにRPGGameInstanceのMyYourHeroがBlueprintReadOnlyである意味はないと思われるので、RPGGameInstanceのMyYourHero の設定をBlueprintReadWriteに変更します。

そしてBP内でLoad時のRPGGameInstanceのMyYourHeroの値を変更します。

f:id:kazuhironagai77:20210222002912p:plain

以下の状態でsaveします。

やっている事は今までと全く同じです。

f:id:kazuhironagai77:20210222002930p:plain

f:id:kazuhironagai77:20210222002936p:plain

f:id:kazuhironagai77:20210222002945p:plain

テストします。

f:id:kazuhironagai77:20210222003009p:plain

f:id:kazuhironagai77:20210222003017p:plain

結果です。

f:id:kazuhironagai77:20210222003033p:plain

f:id:kazuhironagai77:20210222003041p:plain

Loadする時にHPとMPの値をMHPとMMPと同じ値にする必要がありました。

直します。

f:id:kazuhironagai77:20210222003059p:plain

もう一度テストします。

f:id:kazuhironagai77:20210222003117p:plain

出来ました。

正し、2つバグが出ました。

一つ目のバグはトラップに侵入すると発生するモンスターを倒してみたら出ました。

f:id:kazuhironagai77:20210222003134p:plain

二つ目のバグはLevelが上がって魔法を覚えたら同じ魔法を二つ覚えていました。

f:id:kazuhironagai77:20210222003153p:plain

これらのバグを直します。

4.神官のSave機能を実装する

この機能は既に3で作成したのでスキップします。

5.バグの直し

「3.6.5 MyYourHero Objectの変数の値をLoadする。」で発生したバグを直します。

5.1 トラップに侵入すると発生するモンスターを倒すと発生するエラー

これはもうDestroyActorが単純に要らないです。

と思ったら戦闘しないで逃げた時には必要でした。

以下の様に直しました。

f:id:kazuhironagai77:20210222003302p:plain

f:id:kazuhironagai77:20210222003302p:plain

テストします。

f:id:kazuhironagai77:20210222003319p:plain

モンスターの縄張りから出ます。

f:id:kazuhironagai77:20210222003336p:plain

一瞬でモンスターが消えました。これは素晴らしいですが、アニメーションがあった方が更に素晴らしいですね。

今度は戦闘をしてみます。

勝ちました。

元のマップに戻って来てもエラーは出ていません。正し、魔法を使用した後のモーションが素手の攻撃になっていました。前にこのバグは直したはずですが?

このバグも直します。

5.2 同じ魔法を二つ覚えるバグ

これは魔法を追加する時にAddUniqueを使用すれば直るはずです。

直しました。

f:id:kazuhironagai77:20210222003412p:plain

テストします。

f:id:kazuhironagai77:20210222003440p:plain

直っていました。

5.3 魔法を使用した後のモーションが素手の攻撃になるバグ

オカシイですね。このバグは前に直しておいたはずですが。

もう一度確認します。

直っていません。

Blogを確認したのですが、どこでこのバグを直したのか分かりません。

取りあえず以下の方法で直しました。

f:id:kazuhironagai77:20210222003514p:plain

戦闘中のアニメーションをplay animationノードで実装しているため、animationが終わった後でMy Third Person_AnimBPをセットし直す必要があります。

f:id:kazuhironagai77:20210222003542p:plain

この時にMy Third Person_AnimBPの設定が初期化されてしまうため、もう一度、My Third Person_AnimBPの変数with Weaponを設定し直す必要があります。

これがバグの原因でした。

このPlayAnimationを使用するのは、あまりよろしくないです。全てのアニメーションはMy Third Person_AnimBPで管理すべきです。これも後で直します。

5.4 ポーズ画面のセーブボタン

ポーズ画面にまだセーブボタンが残っていました。

f:id:kazuhironagai77:20210222003607p:plain

魔法でも追加しようと思いましたが、更に複雑にしても管理出来ないのでオプションにします。

f:id:kazuhironagai77:20210222003623p:plain

オプションの内容は後で考えます。

5.5 罠モンスターが消える時のアニメーション

一瞬で消えるのは変なのでアニメーションを追加しました。

f:id:kazuhironagai77:20210222003649p:plain

本当はモンスターが発生するアニメーションを逆再生したのを追加したかったのですが、PlayAnimationにその機能がないのでモンスターが倒された時のアニメーションを追加しました。

テストします。

f:id:kazuhironagai77:20210222003729p:plain

縄張りから出ると見事に倒れました。

出来てます。

6.Save/LoadBPの実装部の整理

Save/Loadの実装をBPで作成しましたが、結構ぐちゃぐちゃしています。整理します。更にRPGGameInstanceクラスのSave関数のような必要のない関数や変数を消します。

6.1 BP内のSave/Loadの実装を整理します

どちらが見やすいでしょうか?

f:id:kazuhironagai77:20210222003801p:plain

f:id:kazuhironagai77:20210222003808p:plain

見た目の綺麗さは同じ位だが、Reroute node を使用している方が同じクラスの変数に値をセットしている事が理解しやすい。

Reroute nodeを使用した形に書き直してみます。

f:id:kazuhironagai77:20210222003826p:plain

この部分だと見やすさはそんなに変わらないですが、

f:id:kazuhironagai77:20210222003841p:plain

この辺は段違いに見やすいです。

正し、Arrayの要素に値をパスする所は見にくくなっています。関数を作成して見やすくします。

f:id:kazuhironagai77:20210222003940p:plain

関数にしたら見た目はすっきりしたのですが、Errorが出てしまいました。

f:id:kazuhironagai77:20210222004012p:plain

f:id:kazuhironagai77:20210222004020p:plain

だそうです。

f:id:kazuhironagai77:20210222004037p:plain

もうこの二つのarrayをBlueprintReadOnlyにして置く理由が全くないのでBlueprintReadWriteに変更します。

f:id:kazuhironagai77:20210222004056p:plain

はい。直りました。

f:id:kazuhironagai77:20210222004317p:plain

次はMyYourHeroオブジェクトのそれぞれの変数の値をSaveGameクラスの変数にセットしている所ですが、コレも一つの関数にします。

f:id:kazuhironagai77:20210222004340p:plain

しました。

f:id:kazuhironagai77:20210222004356p:plain

大分すっきりしました。

f:id:kazuhironagai77:20210222004413p:plain

このノードらのしている事はGameInstanceの値をSaveGameに移しているだけなので一個の関数にまとめます。

実際のsaveのノードは以下の3つになりました。

f:id:kazuhironagai77:20210222004430p:plain

非常に見やすいです。コメントも付けておきます。

f:id:kazuhironagai77:20210222004447p:plain

Loadの実装も同様に整理します。

関数Load Gameに全てまとめました。

f:id:kazuhironagai77:20210222004504p:plain

Load Gameの中身は、

f:id:kazuhironagai77:20210222004520p:plain

f:id:kazuhironagai77:20210222004527p:plain

f:id:kazuhironagai77:20210222004543p:plain

となっています。SetMyYourHeroノードの中身は

f:id:kazuhironagai77:20210222004607p:plain

となっています。

綺麗且つ読みやすいような整理は出来ましたが、ひょっとすると何処かに手違いがあって正しくsave/load出来ないかもしれません。確認します。

f:id:kazuhironagai77:20210222004626p:plain

f:id:kazuhironagai77:20210222004633p:plain

f:id:kazuhironagai77:20210222004641p:plain

この状態でSaveします。ここには表示されませんが、魔法は炎(小)、炎(大)が使用出来ます。

Loadします。

f:id:kazuhironagai77:20210222004717p:plain

HP102、MP22、経験値100、レベル3とセーブした時と同じになっています。所持している金貨は70枚から65枚に減っていますが、セーブした時に神官に金貨5枚払っているのでこれで正しいです。

f:id:kazuhironagai77:20210222004734p:plain

AP52、DP32、LK2全て合っています。所持している道具は、回復薬、イーサー、ダークイーサーがそれぞれ一つずつでこれも合っています。

f:id:kazuhironagai77:20210222004807p:plain

装備されている武器、盾は短剣(小)と木の盾(小)で合っています。

戦闘をして使用出来る魔法の確認をします。

f:id:kazuhironagai77:20210222004826p:plain

はい。出来ています。

6.2 RPGGameInstanceクラスのSave/Load関数を消去します

以下の関数を消します。

f:id:kazuhironagai77:20210222004857p:plain

f:id:kazuhironagai77:20210222004906p:plain

ただ完全に消去してしまうと、後でUE4C++側でsave/loadしたい時にどうやるのか分からなくなってしまうので、Comment outするだけにします。

f:id:kazuhironagai77:20210222004923p:plain

f:id:kazuhironagai77:20210222004930p:plain

Buildします。

出来ました。

f:id:kazuhironagai77:20210222004946p:plain

これらの関数を使用してる箇所はないはずなのでこれで大丈夫なはずです。

7.ObjectSaveGameクラスに保持させられないのかの検証

まず全てBPで実験してみます。

新たにProjectを作成するのも勿体ないので、先々週、AIの勉強に使用したProjectで検証します。

以下の方法で試します。

まずActorクラスからTestActorを作成し変数HPを追加します。

f:id:kazuhironagai77:20210222005013p:plain

f:id:kazuhironagai77:20210222005019p:plain

このTestActorのinstanceを一個Level上に配置します。

f:id:kazuhironagai77:20210222005049p:plain

配置したTestActorのHPの値がI をクリックするたびに1だけ減るようにセットします。

f:id:kazuhironagai77:20210222005105p:plain

Save/LoadそしてHPの値の表示様にWidgetを一個作成します。

f:id:kazuhironagai77:20210222005136p:plain

配置しているTestActorをParameterとしてパスする設定にします。

f:id:kazuhironagai77:20210222005202p:plain

HP、Saveボタン、Loadボタンを作成します。

f:id:kazuhironagai77:20210222005223p:plain

HPの値、Saveボタン、Loadボタンの機能を実装します。

f:id:kazuhironagai77:20210222005244p:plain

f:id:kazuhironagai77:20210222005252p:plain

f:id:kazuhironagai77:20210222005259p:plain

ここでSave/LoadするのはTestActorのInstanceそのものです。

このやり方でHPの値がSave/Load出来るのなら、少なくともBP内で完結すればObjectのSave/Loadは出来ると言う事になります。

Widgetを表示させます。

f:id:kazuhironagai77:20210222005323p:plain

GameModeBaseBPクラスからWidgetを作成しようと思ったのですが、これだけのために新しいGameModeBaseBPクラスを作成するのも面倒なのでLevelBP内に作成しました。

これでテストします。

配置しているTestActorのHPを100から91に下げました。

f:id:kazuhironagai77:20210222005346p:plain

Saveします。

f:id:kazuhironagai77:20210222005405p:plain

SaveしたTestActorのHPの値が91である事は画面左上にプリントされた値からも確認出来ます。

一端ゲームを終了してLoadします。

f:id:kazuhironagai77:20210222005421p:plain

Loadしましたが、前のHPの値はSaveされていませんでした。

出来ませんでした。

つまりSave/Load出来るのはその変数が保持している値のみのようです。その変数が保持している値が、アドレスの場合は、そのアドレスの値をセーブすると思われます。

となるとUE4C++でcopy constructorをoverrideして作成すればObjectでもSave/Load出来るのでしょうか?

そんな気はしますが、それを今試す必要はあまり感じないので、今回のSaveGameの検証はここまでで終了にします。

今回の検証だけではSaveGameクラスでObjectはsave/load出来ないと断言は出来ませんが、SaveGameクラスを使用する時、その変数の値が正しくSave/Loadされているかの確認は必ずすべきである。位は言えると思います。

8.まとめと感想

今週はこれで終わりです。一応Save機能も完成しました。来週は戦闘中に使用しているPlayAnimationの箇所でも直そうと思います。

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

f:id:kazuhironagai77:20210214215522p:plain

<前文>

Genocide そして進撃の巨人 Final Season

日本で有名なYouTuberの方が海外に移住するらしいですが、逆に英語圏で有名なYouTuberが日本に移住したりもしていて「Internetで世界は一つの村に成る。」との予言は正しかったんだなと思いました。

それはともかく、日本に移住した英語圏の人達の最初の難関は、市役所への転入の書類の提出だそうです。これに保険証の申請やら何やらで、全部自分でしなければならない場合は、ほとんど不可能らしいです。そしてこれは日本人の間でも有名ですが、外国人が日本でアパートを借りるのは相当大変みたいですね。アメリカ人の間では、一軒家を買ってしまう方が遥かに簡単と、一軒家購入の進めみたいなのまであります。

そんな大変な思いをして日本に移住したのに、2年も経つと自分の期待していた日本と現実の日本の違いに絶望するらしいです。そんなに日本の暮らしって悪いのかと話しを聞くと「2年も日本に住んでいたのに日本人の友達、一人も出来なかった。」とか「日本で暮らした時の最高の思い出は、スタミナ太郎の食い放題。」とか、それは日本人でも絶望するでしょう。みたいな悲惨な経験を語って来ます。

でもこれ日本人が海外に行っても同じ事です。特に、親戚もいない、その国の言語もしゃべれない。ましてや風習や宗教も知らない国に行ったら、相当悲惨な目に合うのは間違いないでしょう。

それでも私は、日本人は海外に一時でも暮らしてみるべきだと非常に強く思っています。

その理由は、幾つかありますが、そのうちの一つは世界の常識と日本の常識があまりに乖離している事が実感出来るからです。

その例の一つがGenocide です。

世界にはGenocideが普通に存在しています。Genocideは日本語に訳すと虐殺で、英語のMassacreと同じ意味に成ってしまいます。しかし、この二つの単語の意味は、全く違います。Massacreは沢山の人を殺す事です。それは酷い事ですが、Genocideに比べるとその悪意は、たかが知れるレベルで、人類史の前では取るに足らない軽犯罪です。

しかしGenocideは全く違います。Genocideとは、ある民族全体を絶滅させる事です。海外に出ると直ぐに分かるのですが、日本民族を絶滅させたいと思っている人々は沢山います。とてつもない悪意をもって日本民族を絶滅させたいと思っている人達がですよ。実際にいるんです。それも一つの思想として根付いているんです。こういう人達に実際に出会うだけでも、海外に住む価値があると思うんです。

アメリカに長く暮らしていた私は、一部のアメリカ人が日本民族に対してGenocideをほのめかす時、冗談で言ってない事に直ぐにピンと来ます。

彼らにとって日本民族が消滅してほしい理由は大きく二つあります。

勿論、全部のアメリカ人がそう思っている訳ではないですが、概念としてのGenocideは普遍的に存在していています。何かあると必ずその邪魔な民族、全滅出来ないかと考えるのは日本人以外にとっては、普通の事なんです。

最近、Capitol Riotを含む国内テロリズムの原因として、〇ox News のAnchorを含む保守派のコメンテーターらが超高額の訴訟を起こされています。その事自体はザマァーですが、その起訴の内容は、昨年の大統領選挙の投票結果に対して嘘を広めた事です。

所が、彼らが嘘をついていたのは昔からなのです。みんな彼らが嘘を言っているのは知っていたんです。その彼らの嘘を元に、メキシコとの国境に壁を立てたり、中国からの輸入品にとてつもない関税を付けたり、日本にアメリカ国内では使用が禁止されている発癌性の農薬を使用した果物を輸出したりした時は、今、彼らに対して激怒しているアメリカ人も一緒になってゲラゲラ笑っていたんですよ。

日本を含めて、これらの国はその生存をアメリカとの貿易に依存しています。それらの国に対して嘘を元に貿易戦争を仕掛けるのは、その国の民族に対して、暗にGenocideを実行しているのと同じ事です。

しかし他民族がGenocideされる分には、ほとんどのアメリカ人にとっては笑い事で済んでしまう事なんです。今、アメリカ人が彼らに対して激怒しているのは、自分たちが彼らの嘘のせいでコロナによって死ぬ可能性が出て来たからです。

こういう事を肌で感じれる様になる経験をする事は全ての日本人にとって大切だと思います。

そして進撃の巨人Final seasonです。

進撃の巨人Final seasonはまさしくGenocideがテーマの作品なんです。だからGenocideの思想がない日本ではそんなにヒットしていないですが、それが普遍的に存在している海外では、あんなに受けているです。

今週は、これについて語ろうと思ったのですが前文が既に長くなりすぎてしまったので、残りは来週にします。

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

<本文>

1.今週の予定

やっとですが、今週からSave機能をもう一度作り直します。予定としては以下の順番でやろうと思っています。

  1. Blogなどで今まで作成したSave機能の復習をする。
  2. Saveしなければならないデータの確認。
  3. データ以外のSaveに必要な要素の確認 (ゲーム内)
    • セーブ出来る場所の決定
    • Loadした後に倒したモンスターは復活するのか?
  4. データ以外のSaveに必要な要素の確認 Part 2 (ゲーム外)
    • セーブデータの保存場所
    • セーブデータの暗号化、
    • Serverに保存する場合の方法について
  5. Save機能のTutorialを復習する。
  6. 実際の作成

2.今まで作成したSave機能の復習

以下のブログでSaveについて書いてありました。

それ以外のブログはSave機能について勉強するといってやってませんでした。Save機能を作成する前に、Saveするデータが揃う必要があったので、実際はあんまり勉強しなかったんです。思い出しました。

2.1 2019-09-22のブログの復習

本文と関係ないですけど、前文でこの統計学の本読んだら、今までよく意味が分からなかった統計学の意味が分かった。と書いていますが、今考えるとこの本読んでも、統計学の本質は理解していませんでした。

今は理解しています。

統計学は確率の裏返しです。

これだけ分かれば後は計算方法だけ理解すれば良いんです。

UE4のSave機能の作成部分ですが、教科書(「Unreal Engine 4.xを使用してRPGを作成する」)の10章を勉強していて、その中でSave機能の作成方法を勉強したようです。

具体的には、

Save機能の作成方法

  1. SaveGameクラスから派生したクラスを作成。
  2. そのクラスにsaveしたいデータが保持出来る変数を作成。
  3. Saveボタンを押したらそのクラスのオブジェクトを作成してその変数に値を追加。
  4. SaveGameToSlotノードを使用してそのオブジェクトをSlotに保存。

を行っています。更に

Load機能の作成方法

  1. DoesSaveGameExitノードでSlot内にSaveしたGameDataがあるのか調べる。
  2. 在る場合はそのデータを、LoadFromSlotノードでゲーム内にロード。
  3. ロードしたデータをGameInstanceなどの変数にセット。

を行っていました。

やり方自体に特にコメントはありません。正攻法だと思います。

一個だけちょっとおかしいかもと思う箇所が以下の部分ですが、

f:id:kazuhironagai77:20210214215756p:plain

Slotのobjectを保持する必要はあんまりない気がします。もし保持したいならその後のNewSameGameにCastしたobjectを保持した方が良い気がします。

2.2 2019-10-20のブログの復習

この回のブログでは、2019-09-22でSaveの作成方法を初めて勉強したので、その時に学習したSaveGameBPノードから一般的なUE4のSaveの作成方法を調査しています。

更に

  • Loadのやり方が分からない。
  • 複数のSaveDataの保存方法が分からない。

と書かれていて、その解答を見つけるためにSaveGameBPノードの調査をすると書かれていました。

Loadのやり方は、2019-09-22で既に解説されていましたが、この時は良く分かっていなかったのでしょうね。

複数のSaveDataの作成は単にSaveGameBPのobjectを沢山作成してそれぞれSlotにSaveすれば良いと思います。ただ今の私の考えでは複数のSave Dataを作成出来るとゲームが面白く無くなるので、Saveは一回しか出来ない様にするつもりです。

以下の二つのサイトで勉強していました。

これらのサイトは今週、もう一度勉強し直します。

2.3 2020-07-26のブログの復習

ここでは、ほぼ現状のSave機能の作成内容について説明しています。流石に半年前のブログだと今読んでもかなり勉強になる内容が書かれていて読んでて唸ってしまいました。

大切だと思う事だけここに抜き出します。

  • SaveGameクラスにGameInstanceの変数を作成してGameInstanceそのものをSlotに保存しようとしたら、load出来なかった。Shallow Copyになっているみたい。
  • GameInstanceクラスにある変数を一つ一つSaveGameクラスの変数として作成して保存しようとしたら、GameInstanceクラスにある変数は全て、BlueprintReadOnlyで指定されていてLoad時に値を代入する事が出来なかった。
  • UEC++からSaveGameクラスの派生クラスを作成してそこから、GameInstanceクラスにある変数の値をSaveGameクラスの変数に保存したり、SaveGameクラスの変数の値をGameInstanceクラスにある変数に代入したり出来るようにした。

SaveGameに作成する変数の値が、Shallow copyに成っていないかの確認が必要である事。そしてGameInstanceクラスにある変数は全てBlueprintReadOnlyで指定したためにUE4C++側にSave, Load機能を作成する必要がある事。この二つは今回、新たにSave機能を作成する時にも気を付けなければならない要素です。

一つだけ問題だと思うのがSaveGameクラスの変数の値をGameInstanceクラスにある変数に代入するための関数ですが、

f:id:kazuhironagai77:20210214215838p:plain

このやり方だと、mySlotと言う名前のSlotが存在しない時はErrorになる気がします。実際のコードを改良する時、この個所はしっかり考えて直します。

2.4 ブログの復習のまとめ

大体、今までやった内容は理解出来ました。GameInstanceクラスにある変数を全てBlueprintReadOnlyで指定したためにUE4C++側でSave、Loadの処理をしなければならない以外は正攻法でSave、Load機能を作成していると思います。

Slotに保持出来る変数がShallow copyになっていないかの確認が必要な事はすっかり忘れていました。

昔書いたブログを読み直すのは恥ずかしいですが、こうやって記録を付けておくと後で簡単に復習出来るので、大変便利です。ブログを書く事の大切さをもう一回確認出来ました。

3. Saveしなければならないデータの確認

RPGGameInstanceクラス内の全ての変数をSaveすれば良いはずです。もしかしたら漏れがあるかもしれませんが、取りあえずそれからやって見ます。

3.1 RPGGameInstanceBP内の変数

BP内だけで以下の変数が作成されていました。どの変数を何のために使用したのか全く覚えていません。

f:id:kazuhironagai77:20210214215922p:plain

一個ずつ確認していきます。

<Map>

クラス:Map(調べたら自作したEnumクラスでした。)

f:id:kazuhironagai77:20210214220012p:plain

ワープして別のMapに移動するために使用されるはずです。

Loadする時にどのMapから始めるのかを記録しておく必要があるので保存する必要がある変数と思われます。

<Zone>

クラス:Zone(Enumクラス)

f:id:kazuhironagai77:20210214220047p:plain

ワープした時にどの地点に出現するかを指定する関数です。この場所を敢えてSaveする必要なないと思われます。

<warped

クラス:Bool

ワープした時に使用される関数であるのは確かですが、何のために使用するのか全く覚えていません。調べます。

検索したらウィジェットであるw_map, とLevelであるmap1, map2, map3で使用されていました。

f:id:kazuhironagai77:20210214220116p:plain

この感じからするとLevelであるmap1, map2, map3は全く同じ使用をされていると思われます。

まずウィジェットであるw_mapを見てみます。

まずデザインですが、世界地図の上にボタンが散りばめられています。

f:id:kazuhironagai77:20210214220138p:plain

それらのボタンを押すと以下に示した様に関数、ChangeMapが呼ばれます。

f:id:kazuhironagai77:20210214220155p:plain

ChangeMap関数内の実装ですが、その中で、warpedがTrueにセットされました。

f:id:kazuhironagai77:20210214220213p:plain

このノードにChangeMap関数があります。その関数は呼ばれると指定されたMapを新たに開きます。

ので、Map1が開かれたと仮定してMap1のLevel BPを見てみます。

Event Begin Play関数の一番最後で、WarpedをFalseにセットしているだけでした。

f:id:kazuhironagai77:20210214220232p:plain

この変数は、全くSaveする必要はないですね。そればかりがワープ機能にも要らない可能性もある気がしています。後でこの変数が本当に必要か調べます。

<DroppedItem

クラス:Bool

何に使用している関数なのか全く覚えていません。調べます。

検索したら沢山の似た名前の変数が引っかかってしまいました。

検索の最初に出て来たDroppedItemBaseクラスから調べて見ます。

f:id:kazuhironagai77:20210214220254p:plain

DroppedItemBaseクラスを以下に示します。

f:id:kazuhironagai77:20210214220311p:plain

分かりました。マップに配置されているアイテムを表示するためのActorクラスです。

Box内にplayerが操作するキャラが侵入するとTrue、出るとFalseにセットしています。

f:id:kazuhironagai77:20210214220327p:plain

これだけでは何のための変数か分かりませんね。もう少し調べます。

PickUpItemクラスでも使用されています。

f:id:kazuhironagai77:20210214220703p:plain

PickUpItemクラスの方も調べて見ます。

まず、PickUpItemクラスはウィジェットでした。

f:id:kazuhironagai77:20210214220726p:plain

これの拾うボタンと戻るボタンを押した時に呼ばれていますが、マジで意味不明です。

f:id:kazuhironagai77:20210214220744p:plain

これ以外には使用されていませんでした。

ひっとすると昔は何かに使用していたのですが、Itemを拾う機能を改善している内に要らなくなった変数かもしれません。

後でこの変数が本当に必要か調べ要らないようなら削除します。

勿論、Saveする必要はありません。

<Dropped Item Content

クラス:Dropped Item Base

以下のクラスで使用されています。

f:id:kazuhironagai77:20210214220811p:plain

ちょっと不思議な点があります。

まずDroppedItemBaseクラスがDroppedItemBaseクラス内で使用されています。更にSetばかりでGetに使用しているのがThirdPersonCharacterBPのみです。

DroppedItemBaseクラスから見てみます。

Box内にPlayerのキャラが侵入したら自身をセットしています。

f:id:kazuhironagai77:20210214220849p:plain

Box内にPlayerのキャラが去ったら自身のセットを解除しています。

f:id:kazuhironagai77:20210214220910p:plain

何故か全く同じ名前の変数がGameModeBaseBPにもあってそれにも同様の事をしています。

f:id:kazuhironagai77:20210214220933p:plain

次はThirdPersonCharacterBPを見てみます。

f:id:kazuhironagai77:20210214221000p:plain

これはDroppedItemBaseのボックス内にPlayerのキャラが侵入したらPickUpItemウィジェットを作成して表示させるための実装ですね。そのためにDropped Item Contentをパスしています。

分かりました。これはどのアイテムがそのボックス内に存在しているかをPickUpItemウィジェットに伝えるために使用される変数です。

確認のためにPickUpItemウィジェットも見てみます。

まず、Dropped Item ContentはMy Dropped Item と言う変数名でPickUpItemウィジェットにセットされていますので、My Dropped Itemを調べます。

アイテムを「拾う」を選択した場合です。

f:id:kazuhironagai77:20210214221029p:plain

黄色で囲った部分がMy Dropped Itemが使用されている箇所です。緑は間接的に使用されている箇所です。

一個ずつ見て行きます。

My Dropped ItemがWeaponかどうかを聞いています。

f:id:kazuhironagai77:20210214221054p:plain

Playerの保有するWeaponとItemは別なArrayで管理しているので、拾ったItemが本当のItemなのか武器なのかの確認が必要です。それをここで行っています。

しかしItemであった場合に実行すべきコードはまだ実装されていませんでした。後で作成します。

次にMy Dropped Itemの名前が本当に武器名なのか確認しています。

f:id:kazuhironagai77:20210214221113p:plain

無くてもいいかもしれませんが、念のために確認しても良い気もします。

その名前をRPGGameInstanceBPのWeapon変数に追加します。

f:id:kazuhironagai77:20210214221128p:plain

覚えていませんが、このWeapon変数はplayerの操作するキャラが保持する武器を管理する変数のようです。

確認のためにRPGGameInstanceBPの変数を見ましたが載っていません。UE4C++のRPGGameInstanceクラスで作成した様です。

f:id:kazuhironagai77:20210214221145p:plain

ありました。

f:id:kazuhironagai77:20210214221201p:plain

思い出して来ました。

この変数、BlueprintReadOnlyなんですが、Arrayだからか、Add関数を使用するとBP側からでも要素を追加出来たんで、BP内のみで新しい値を追加したんです。

このやり方が正しいのかは分かっていません。後でバグが出るかもしれないのでこのやり方でも正しいのかについても後で調べる事にします。

f:id:kazuhironagai77:20210214221229p:plain

次は以下の関数が呼ばれています。

f:id:kazuhironagai77:20210214221245p:plain

このPickUpItemウィジェット内で自作した関数でした。

簡単に説明するとGameInstance内の変数、ItemSpawnDataはそのレベル上で、何処にどんなItemをSpawnさせるのかを管理しています。

そのItemSpawnDataにこのItemはPlayerがもう獲得したのでこれからはSpawnさせないで下さいとお願いしています。

f:id:kazuhironagai77:20210214221309p:plain

このやり方よりマシな方法は絶対あると思いますが、取りあえずは望んだとおりに動くので、今回はこのままにしておきます。

後にもっとUE4のArrayの関数に詳しくなった時に復習します。

f:id:kazuhironagai77:20210214221344p:plain

My Dropped ItemをDestroy Actor関数で破壊しています。

これは今Level上に表示されているItemがこれ以上表示されないためにしたのでしょうか?多分そうですね。

最後に、My Dropped Itemではありませんが、My Dropped Item にパスされているDropped Item Content(ただしGame Instanceに保持されている方)が開放されています。

f:id:kazuhironagai77:20210214221436p:plain

正直、このやり方で開放するのが正しいのかも分かっていないですが、RPGGameInstanceのDropped Item Contentが何をしているのかは理解出来ました。

まず、この変数はSaveする必要はありません。この変数はItemを拾う時のみに必要になる変数でItemを拾う途中でSaveする事は出来ないからです。

更にGameInstanceにある必要は全くないです。RPGGameModeBaseBPにある全く同じ変数が全部対応していますのでGameInstanceのDropped Item Contentは消して良いです。

GameInstanceのDropped Item Contentは後で、もう一度、要らない事を確認したら消去します。

<Time Spend

この変数は最近作成したので覚えています。一日の内どれくらいの時間が経過したのかを記録する変数です。

一応確認だけします。

f:id:kazuhironagai77:20210214221502p:plain

Set Time Spend 0はEvent Despatcher で、Level BPと他のBPのデータのやり取りをする為に作成したものです。

Inn_Welcomeウィジェットは宿屋の管理をするウィジェットですので泊まった時は必ず朝になる必要があります。

f:id:kazuhironagai77:20210214221528p:plain

この変数をsaveする必要はありません。なぜならLoadから始める場合は、いつでも朝から始まってもオカシクないからです。

<Third Person Character Location

この辺の変数は最近作成したのでまだ覚えています。

この変数は、戦闘で別なマップに移動したプレイヤーの操作するキャラを戦闘後に元の位置に戻すための変数です。

Saveする位置がまだ決まっていませんが多分この変数をsaveする必要ないでしょう。

<Monster Spawn Data

この変数はそのレベル上で倒されたモンスターのデータも管理しています。

Loadで戻った時、倒したモンスターは再生されるのか、どうかを決める必要があります。

再生されないのならSaveする必要があります。

<Monster Name In Fighting

戦闘中に表示されるモンスターの種類を決定するための変数です。

Saveする必要はありません。

<Third Person Character Rotation

この変数も、戦闘で別なマップに移動したプレイヤーの操作するキャラを戦闘後に元の位置に戻すための変数です。

この変数はキャラの向きを保存します。

思い出しました。この変数、Meshの向きだけ変えてカメラの位置はそのまま。というバグが残っていました。

Saveする必要は多分ないでしょうね。

<Item Spawn Data

Level上に配置するItemのデータを保持しています。Playerが既に収得したアイテムについてもこの変数が管理しているので、絶対にsaveする必要があります。

RPG Game Instance BP内の変数まとめ>

Saveする必要のある変数は、

  • Map
  • Monster Spawn Data(仮)
  • Item Spawn Data

の3つだけでした。

3.2 RPGGameInstance内の変数

以下の変数があります。

f:id:kazuhironagai77:20210214221652p:plain

Talk Shop、Talk Weapon Shop、そしてTalk Inn以外の変数は全部Saveする必要があります。

Game CharacterクラスであるMyYourHeroやArrayであるItemsやWeaponsはそのままSaveしてもShallow copyになってしまうんでしょうか?その辺の確認が必要ですね。

4.データ以外のSaveに必要な要素の確認 (ゲーム内)

4 .1 セーブ出来る場所の決定

セーブ出来る場所を作成します。

NPC、神官を作成して神官のいる場所でSave出来るようにします。

NPCの作成方法を忘れてしまったので復習します。

4.1.1 NPCの作成方法

2020-10-11のブログでNPCを作成していました。これを読むとこれ以前に、NPC_Oldmanの原型を作成していますね。2020-08-23のブログで最初のNPCを作成しています。こっちから読んでいきます。

2020-08-23のブログを読むとまず、

f:id:kazuhironagai77:20210214221744p:plain

を作成しています。

今のProjectでは、

f:id:kazuhironagai77:20210214221802p:plain

と名前が違っていますが、多分これですね。

開いて見ると、Box内にplayerが侵入した時、RPGGameModeBaseBPのMyPlaceForEvents変数にNPC_Personの変数Occupationをセットしています。

f:id:kazuhironagai77:20210214221822p:plain

MyPlaceForEvents変数はRPGGameModeBaseクラスの変数で、EPlaceForEventsから作成されていました。

f:id:kazuhironagai77:20210214221852p:plain

これがEPlaceForEvents です。

f:id:kazuhironagai77:20210214221910p:plain

ここに神官を追加する必要がありますね。

この後で、ThirdPersonCharacterBPのEvent Do Somethingに行くのは覚えているのですが、どうやってここに飛ぶのかが分かりません。

f:id:kazuhironagai77:20210214221934p:plain

分かりました。Eボタンを押すんでした。

f:id:kazuhironagai77:20210214222004p:plain

f:id:kazuhironagai77:20210214222011p:plain

これは何処かに解説しておかないとUserには分からないですね。

NPCとの会話はEを押す。」を後で追加します。

Event Do SomethingではEPlaceForEvnetsのそれぞれの要素によって分岐しています。

ここに神官の場合を作成する必要があります。

f:id:kazuhironagai77:20210214222030p:plain

分岐の先はほとんど同じで作成するwidgetなどが違うだけです。

f:id:kazuhironagai77:20210214222100p:plain

神官用のWidgetの作成も必要ですね。

老人と魔法研究家のWidgetを以下に示します。

f:id:kazuhironagai77:20210214222118p:plain

f:id:kazuhironagai77:20210214222153p:plain

思い出しました。

単に別のNPCをコピーしてちょっとだけ変更して新しいNPCウィジェットを作成したんです。

後はここに表示する会話のシステムを理解すれば良いだけですね。

ここまでNPCの作成方法を復習してアレ何ですけど、神官は普通のNPCから作成するんじゃなくて武器屋、道具屋、宿屋と同じ方法で作成すべきな気がして来ました。

ここまでやっちゃったので普通のNPCの作成方法、最後まで勉強します。

会話のシステムですが、DataTableから読み込んでいます。

f:id:kazuhironagai77:20210214222215p:plain

以下に示した様なDataTableが作成されています。

f:id:kazuhironagai77:20210214222237p:plain

開いて見るとこんな感じです。

f:id:kazuhironagai77:20210214222255p:plain

思い出しました。

このEnumクラスから作成したDataTableらです。

f:id:kazuhironagai77:20210214222310p:plain

NPCのコメントをConversationに書き込み、その解答をAnswer Choiceに書き込みます。答えによって次のNPCの会話が決まるので、どのコメントを読むのか番号(Jump To Comment)で指定します。

最後に、選択するために表示されるNPCに対する返事が書かれたボタンがNPCの分だけ別なクラスで作成しないとエラーになる事を思い出しました。

f:id:kazuhironagai77:20210214222327p:plain

f:id:kazuhironagai77:20210214222336p:plain

メンドクサイですが現状直し方が分からないです。

これでほとんど完全にNPCの作成方法を思い出しました。念のためにブログをもう一回読み直します。

読み直したら、2020-10-18のブログでNPCの作成方法について逐一記録してありました。これに沿って作成すれば何も復習する必要なかったです。

4.1.2 武器屋、道具屋、宿屋の作成方法

ここまでNPCの作成方法について復習してあれですが、神官は武器屋、道具屋、宿屋と同じ方法で作成した方が良い気がしています。

こちらの作成方法についても調べます。

2020-10-04のブログに宿屋の主人の作成方法についての細かい手順が全て書かれていました。これに沿って作成すれば簡単に作れそうです。

関係ないですが、Vtuberについて前文で触れていました。

ある時からRedditでこのVtuberをアンチから擁護しようと主張するコメントは即ブロックされるので不思議に思っていたら、そのSub Reddit、そのVTuberが属している会社が運営していました。もうVtuberを見るのは止めてしまいましたが、この会社の名前を見ると反吐が出ます。表ではこのVtuberを守ると宣言してファンの信頼を獲得して、裏ではアンチと組んでこのVtuberをイジメているなんで卑劣過ぎます。

4.1.3 神官の作成

色々考えましたが、神官は宿屋の主人と同じ方法で作成する事にしました。

NPC_ItemShopOwnerをduplicateしてNPC_Priestと名付けました。

f:id:kazuhironagai77:20210214222413p:plain

NPC_Priest を開いたらRPGGameInstanceクラスのboolean変数であるTalkInnをTrueにセットしています。

f:id:kazuhironagai77:20210214222437p:plain

このTalkInnに変わる変数をRPGGameInstanceクラスに作成する必要があります。作ります。

この関数が何故必要なのか今一分からないのですが、今回は宿屋と全く同じ作成方法で神官を作る事にします。これらのNPCの作成方法の最適化は後で考えます。

作りました。名前はTalkPriestにしました。

f:id:kazuhironagai77:20210214222504p:plain

NPC_PriestのTalkInnをTalkPriestに変更します。

f:id:kazuhironagai77:20210214222531p:plain

f:id:kazuhironagai77:20210214222541p:plain

今度はMyPlaceForEventsにPE_Priestを追加します。

f:id:kazuhironagai77:20210214222601p:plain

Buildします。

UE4 editorを一回閉じて開きます。

My Place for EventsにPEPriestを選択します。

f:id:kazuhironagai77:20210214222617p:plain

ThirdPersonCharacterBP内にPE_Priestの選択先を実装します。

f:id:kazuhironagai77:20210214222633p:plain

実装内容はPE_Innと全く同じです。

f:id:kazuhironagai77:20210214222648p:plain

Priest用のWelcomeウィジェットが無いので作成します。

Inn_Welcomeを元にPriest_Welcomeを作成します。

f:id:kazuhironagai77:20210214222705p:plain

以下の様にデザインしました。

f:id:kazuhironagai77:20210214222719p:plain

Inn_Welcomeの場合会話ボタンを押すとInn_Talkウィジェットが開かれます。

f:id:kazuhironagai77:20210214222746p:plain

同様のwidgetをPriest用に作成します。Priest_Talkと名付けます。

f:id:kazuhironagai77:20210214222813p:plain

更にNPC_ParentウィジェットにNPCPriestDialogを追加しセリフを書きます。

f:id:kazuhironagai77:20210214222831p:plain

セリフの内容はInnとほとんど同じです。後で、神官にふさわしいコメントに改善します。一応意味は通っているはずです。

f:id:kazuhironagai77:20210214222848p:plain

これらのセリフがPriest_WelcomeとPriest_Talkのコメント欄に表示されるように設定を変更します。

Priest_Welcomeのコメント欄に表示するテキストは以下の様な設定になっています。

f:id:kazuhironagai77:20210214222906p:plain

参照するテキストはNPCPriestDialogに変更しました。

Dialog Numberは

f:id:kazuhironagai77:20210214222937p:plain

にセットされています。

更にセーブボタンを押した時はDialog Numberが2または3にセットされています。

f:id:kazuhironagai77:20210214222958p:plain

ThirdPersonCharacterBPのCreate widgetの設定をPriest Welcomeに変更します。

f:id:kazuhironagai77:20210214223015p:plain

会話ボタンを押した時に開く、WidgetをPriest_Talkに変えます。

f:id:kazuhironagai77:20210214223032p:plain

Priest_Talkのコメント表示の参照にするテキストをNPCPriestDialogに変更します。

f:id:kazuhironagai77:20210214223049p:plain

これで一回テストしてみます。

f:id:kazuhironagai77:20210214223108p:plain

普通に表示されています。

f:id:kazuhironagai77:20210214223123p:plain

「出る」を押したらカーソルが消えてしまいました。バグですね。

以下の様に変更しました。

f:id:kazuhironagai77:20210214223138p:plain

今度は「出る」を押してもカーソルが表示されました。

Innでは泊まるを選択した場合、Sleepingというwidgetが作成されます。

f:id:kazuhironagai77:20210214223159p:plain

こんなヤツです。

f:id:kazuhironagai77:20210214223227p:plain

Saveでも似たようなWidgetを作成します。

f:id:kazuhironagai77:20210214223252p:plain

このwidgetを呼ぶ事にします。

f:id:kazuhironagai77:20210214223307p:plain

Saveをテストします。

セーブボタンを押します。

Prayウィジェットのアニメーションが流れます。

f:id:kazuhironagai77:20210214223323p:plain

f:id:kazuhironagai77:20210214223331p:plain

f:id:kazuhironagai77:20210214223338p:plain

アニメーションが終わったら元の画面に戻りました。

コメントも正しく表示されています。

f:id:kazuhironagai77:20210214223355p:plain

これで神官NPCは完成とします。

4.2 Loadした後に倒したモンスターは復活するのか?

ストーリーに関係するボスキャラが復活したらおかしいですが、雑魚のモンスターは朝になると復活したりしてもおかしくはないです。

今、配置しているモンスターらは雑魚モンスターと仮定すれば、復活しても良くなりMonster Spawn Dataをセーブする必要は無くなります。

4.3 Saveデータの数

前は、少なくとも3つ位のセーブデータが保持出来るような機能は必要と思っていました。

しかしゲームの面白さはバランスだと言う事に気がついた後では、沢山セーブ出来る事もバランスが変化する事が分かりました。のでsave出来るデータは一個だけとします。

5.データ以外のSaveに必要な要素の確認 Part 2(ゲーム外)

ここは調査の記録になります。良く分かっていない事ばかりなので間違えているかもしれませんが最初の一歩と言う事です。

今、データのセーブにおいて私が知りたいのはUE4では、セーブしたデータをどんな形でどこに保存しているのかです。

その理由は、保存しているデータを直接書き変える事で、キャラのパラメーターや所持するアイテムや武器、使用出来る魔法などを好き勝手に書き変えられてしまうかもしれないと私が恐れているからです。

今回確認したいのはその部分です。

後、それらのデータをサーバーに保存する方法を教えているTutorialがあるかも調べます。

5.1 セーブデータの保存場所

このサイトによると

f:id:kazuhironagai77:20210214223440p:plain

セーブデータとセーブされる場所に関しては以下の様に説明されています。

f:id:kazuhironagai77:20210214223457p:plain

Ch4_3で調べて見ると、

f:id:kazuhironagai77:20210214223514p:plain

ありました。

NodePad++で開いて見ると、ほとんどは意味が分からない滅茶苦茶なalphabetの羅列ですが、幾つかは

f:id:kazuhironagai77:20210214223530p:plain

f:id:kazuhironagai77:20210214223539p:plain

f:id:kazuhironagai77:20210214223557p:plain

f:id:kazuhironagai77:20210214223613p:plain

f:id:kazuhironagai77:20210214223626p:plain

意味が分かる単語があります。

.sav fileを読み込めるeditorを使用するか、もしくは技術のある人が作成した解析ツールを使用すれば、ここに書かれているデータを変更する事は簡単に出来そうです。

一般に販売されているゲームはPackageした後のSave dataの管理はどうやっているんでしょうか?携帯のゲームのガチャなどでレアアイテムが出た事に変えられたら結構大問題だと思います。

SlotにSaved dataの保存を任せるのではなくServer側に保存させてしまえば良いのかもしれませんね。

以下に示した図は単なる想像なのですが、

以下の方法でガチャを行うと、

f:id:kazuhironagai77:20210214223645p:plain

途中で結果を変える事が出来る可能性がありますが、

f:id:kazuhironagai77:20210214223700p:plain

以下のやり方ならば、途中でデータを改ざんしても結果を変更する事は出来ません。

f:id:kazuhironagai77:20210214223717p:plain

これだったらServerに直接攻撃されない限りは結果を改ざんされる事はなさそうです。

どうしても結果の改ざんを防ぎたいならblock chainのやり方を真似るのも手かもしれません。block chainがどうやっているのか具体的には知りませんが。

5.2 セーブデータの暗号化

セーブデータをSlotとしてClientのPCもしくはスマートホン内に保存する場合は、暗号化は絶対必要と思っていたのですが、前節の調査と考察を踏まえて、もう一度考えてみるとそうでもない気がしてきました。

  • まず買い取り型の一人で遊ぶゲームの場合ですが、Cheatしても誰にも迷惑はかけないので、ご自由にどうぞ。とも考えられます。
  • みんなで遊ぶゲームでCheatされるのが問題だったんです。そのタイプのゲームは前節のやり方でやれば、Clientが改ざん出来るデータは全くないのでCheatは防げます。

つまり、Slotにあるセーブデータを暗号化する価値はあんまり無いと思われます。

5.3 Serverに保存する場合の方法について

やり方を教えているTutorialがあると助かる訳でそれがないかを調べます。特にServerをタダで使用出来る方法を教えてくれるヤツがありがたいです。

Multi-play用のServer-Clientの作成方法はやっぱり沢山検索に出て来ますね。

How to Host Your UE4 Dedicated Server on AWS for FREEAWSのサーバーを無料で使用していますね。AWSはある程度までは無料なんでしょうか?GoogleのFire Baseをちょっとだけ勉強した時、来月からお金がかかりますみたいなメールが来たので止めちゃいましたが。無料なサービスならAWS使用したいですね。

しかし今回、私が知りたいのはServerにPlayerのデータを保存する方法だけです。

Multi-play Gameのような複雑な仕組みは、もっとUE4に詳しくなってから勉強します。

そのまま検索しても何も引っかからないので「Serverを使用してUE4でLoginを作成する方法」で検索してみました。Loginする方法とデータをサーバーに保存する方法は多分ほとんど同じでしょう。

Unreal Engine 4 Tutorial: Online Login System

がまずありました。Node.jsとAWSを使用しているみたいです。この辺のやり方はWebの開発者だったら超基本的な事かもしれません。UE4からデータをServerに発信する方法と受け取る方法さえ分かれば、残りはWEB開発の人に全部任せるのも手かもしれません。私はWEBの勉強よりUE4の勉強がしたいですし。

5.4 今回の調査からのSave方法に対しての結論

Slotにセーブでいいです。特別にServerを作成してゲームのdataの管理をする必要はないと思います。Multi-play用のゲームを作成した時にServer関係は勉強します。

6.Save機能のTutorialを復習する

正直、もう復習する必要ない位、UE4のSave方法について理解している自信がありますが、一応勉強します。

「2.2 2019-10-20のブログの復習」で紹介されていた

この二つのサイトを復習します。

全部、見ました。

UE4のSave方法について理解してから見るとHTF do I? Use the SaveGame Object in Unreal Engine 4は、簡便に説明していますが、基本はしっかり説明して、このTutorialを見るだけで学習者はSaveGameノードを使用してGame Dataをセーブする事が出来る作りになっています。

Saving and Loading Your Game

の方は、BPとC++の両方のやり方が説明されていますが、BPに関して言えば説明が十分と言えず、これを見ただけでは、学習者はSaveGameノードを使用してGame Dataをセーブする事は出来ないと思いました。C++に関しては、Asynchronousでセーブする方法や、Save Game To Slot関数が、Game DataをBinaryに変換してセーブしている事などが解説されていて勉強になりました。

7.まとめと感想

流石に集中力が切れて来たので今週はここまでにします。残りは来週以降やっていきます。

 

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する My AIの改良

f:id:kazuhironagai77:20210207213711p:plain

<前文>

面白いゲームは最初から面白い

アメリカのトランプ支持派は徹底抗戦する道を選択したみたいですね。日本の〇ウムの時の様にテロを仕掛ける位はやりそうです。しかもアメリカのトランプ支持派の数は半端じゃないので、万が一の確率かもしれませんが内戦になるかもしれません。

アニメの話とか、ネットで非常にオープンにしていた弁護士の動画を最近、久しぶりに見たのですが、この後に及んでまだトランプを支持していました。多分、彼のいる州は、銃で黒人を縛り付けておかなければ国体(州体?)が保てないのでしょう。そのためには、嘘と分かっていてもQの陰謀論に乗るしかないのかもしれません。

しかし、大多数のアメリカ人は逆に、Qのような骨董無形の嘘を信じるからコロナですら駆逐出来ないと思っています。その象徴としてマスクを装着するかしないかで逮捕者まで出ています。元々アメリカ人は、カソリックの本部の嘘に我慢できなくて本国を捨ててまで自分の信仰を貫いた人達の子孫なので、絶対に妥協しません。このQの信望者たちを全て殲滅するためには、そのために南部の州の一つや二つが無くなってもお構いなしで戦い続けるでしょう。

つまりアメリカで第二次南北戦争が起きるのは十分あり得る話なのです。更に、もしテキサス州のような強大な州が南部側について独立宣言したら、どっちが勝つのかすら分からなくなります。

日本だって無関係でいられないと思います。自民党(多数のQの信望者や関係者がいる?)が与党の立場で南部連合を正統なアメリカであると表明したら、北軍の司令官である現大統領が在日米軍を出撃させて東京の国会議事堂を爆撃する事態すら起きるかもしれません。勿論、その時には北海道に傀儡の新日本政府を作っているでしょう。

そこまでは行かなくても、内戦で弱体化したアメリカの僅かな援助を頼りに、世界で最も富める核保有国である中国と渡り合わなければならなくなります。

何か、私の話もQの陰謀論と変わらなくなってしまったのでこの辺で止めます。

ここからは、先週話した、念能力をゲームに追加する件について今週考えた事を述べます。

あれから色々検討したのですが、ゲームのコアの部分がつまらないのに、ボリュームを増やしたからと言って面白くなる事はないとの結論になりました。念能力をゲームに追加するとしても今の時点では、ボリュームを増やしただけになりそうだからです。

そこで今回はそのコアについて考察します。

ゲームの面白さには戦略と戦術があると思います。簡単に言えば戦略は準備をしっかりした方が勝つ、で戦術はその場でうまく立ち回った方が勝つです。

今回は戦術面、つまり戦闘の自由度を増やして、選択の結果が勝利に繋がる様なゲームを考えます。

戦闘中の選択ですが、念能力を追加するまでもなく現時点で、

  • 通常攻撃
  • 魔法攻撃
  • アイテム使用
  • 逃げる

の4択が出来るようになっています。

この4択のどれを選ぶかで戦闘の結果に違いが出なければなりません。

それで以下の様に考えました。

  • 通常攻撃
    • 武器を装備する事で上がる。基準となる攻撃
  • 魔法攻撃
    • 魔法で攻撃する。属性があり、通常攻撃×2~1.8の攻撃力になる
  • アイテム使用
    • HPを回復さえる薬草と、MPを回復させる魔法薬がある。
    • 店で購入出来る薬草は、敵のモンスターが与える1回のダメージより低い回復しか出来ない。つまり戦闘中に使用しても意味がない。
    • 戦闘中に使用しても効果がある高い回復力を持つ特別な薬草は、イベントでしか手に入らない。数に限りがある。
  • 逃げる
    • 30%の確率で戦闘から逃げれる

これならば戦術面で工夫する事で戦闘力が高い敵にも対抗する出来ます。更に絶対勝てない相手には「逃げる」の選択も出来ます。戦術の幅が広がります。

これで行く事にします。

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

<本文>

1.今週の作業

今週は、my AIを作成します。

2.理論

先週のアイデアを以下に示します。

f:id:kazuhironagai77:20210207213951p:plain

何かが足りないと思っていたんですが分かりました。

それは縄張りです。

モンスターなんで、行動形式は単純であるべきなんです。

  • 縄張りに侵入して来たら、追いかけて来て攻撃する。

まずはこれです。これをAIで作成します。

<知覚>

  • 視覚で縄張りの境界を確認します。縄張りの外側に居る時には興味を示しません。境界内に侵入してくるかどうかだけ見ています。

思考>

  • 特に何も考えていません。

<行動>

  • 普段は、縄張りの境界上をパトロールしています。敵が侵入して来たら襲い掛かります。

まずはこれを実装します。そしてこれが完成してから障害物が縄張り内にある場合を考えます。

3.実装

まず、今までのAIがどの様なものだったかを復習します。

3.1 復習

AIControllerの実装です。

f:id:kazuhironagai77:20210207214130p:plain

これだけです。

次はBehavior Treeです。

f:id:kazuhironagai77:20210207214201p:plain

ここに、

  • 視覚から得た情報
  • トロール
  • 侵入者に突撃

などを追加していきます。

3.2 視覚の追加

まずAIControllerに視覚を作成します。

f:id:kazuhironagai77:20210207214237p:plain

f:id:kazuhironagai77:20210207214245p:plain

正しい設定の詳細は覚えていません。後で、Tutorialを見て確認します。

次にAIPerceptionStimuliSourceをThirdPersonCharacterBPに作成します。

f:id:kazuhironagai77:20210207214304p:plain

設定を以下の様にします。

f:id:kazuhironagai77:20210207214322p:plain

こっから先が覚えていません。先々週のブログで確認します。

結構詳しくまとめてありました。

次はAIControllerです。

視覚が使用されているのかの確認のために以下の実装をしました。

f:id:kazuhironagai77:20210207214341p:plain

このポイントはOn Target Perception Update (AIPerception) を使用する事でした。

テストします。

f:id:kazuhironagai77:20210207214438p:plain

効いていますね。

ついでにGamePlay Debuggerも使用してみます。

設定を変更してGamePlay Debuggerが使用出来るようにします。

Project settingを開き以下のように設定を変えます。

f:id:kazuhironagai77:20210207214458p:plain

先週勉強した公式のOnline Learningではこの後にConsoleの箇所を…と言っていましたが、これだけで十分なはずです。
確認します。

f:id:kazuhironagai77:20210207214518p:plain

動いています。

しかも視覚で認識されている事も確認出来ました。

Blackboardに変数を作成して視覚の結果をBehavior Treeに追加しましょう。

f:id:kazuhironagai77:20210207214535p:plain

AIControllerを以下の様に変更します。

f:id:kazuhironagai77:20210207214552p:plain

取りあえずの動作確認用としてBehavior Treeの実装を以下の様に変更しました。

f:id:kazuhironagai77:20210207214609p:plain

Blackboard Based Conditionの設定はいじっていません。

f:id:kazuhironagai77:20210207214636p:plain

あ、名前だけは変える事にします。先週勉強したOnline Learningで疑問文にした方が分かり易いとあったからです。

f:id:kazuhironagai77:20210207214653p:plain

Task、PrintStringTaskは以下の様に実装しました。

f:id:kazuhironagai77:20210207214714p:plain

先週までで学習したTaskの作成で必要な事は、

  • Event Receive Execute AIを使用する事、
  • FinishExecuteを使用する事、
  • FinishExecuteはSuccessで返す事。

です。これらを守って作成しました。

テストします。

f:id:kazuhironagai77:20210207214738p:plain

動いている事は確認出来ました。

今度は、Behavior TreeのDebug機能を使用してみます。

I see youが表示された所で、Pauseボタンを押します。

I see youを表示させたモンスターは目の前の奴なのでDebugの対象をMyAIController_C_2に指定します。

f:id:kazuhironagai77:20210207214803p:plain

以下の様になっていました。

f:id:kazuhironagai77:20210207214820p:plain

Back Intoボタンを押すとPrintStringTaskが呼ばれている事が分かります。

f:id:kazuhironagai77:20210207214836p:plain

視覚が機能している事の確認が出来ました。

3.3 縄張りの作成

色々考えましたが、縄張りを最初から作成するのは難しいので、モンスターの周り、例えば半径150mに近づいたら攻撃してくる事にします。

f:id:kazuhironagai77:20210207214900p:plain

Blackboardに新しく作成した変数です。

f:id:kazuhironagai77:20210207214927p:plain

その変数の値をTrueに変更するためのServiceのIsHeInTerritoryは以下の様に実装しました。

f:id:kazuhironagai77:20210207214944p:plain

Service内の変数の値とBlackboardのIsHeInTerritoryを繋げます。

f:id:kazuhironagai77:20210207215016p:plain

Blackboardの変数、IsHeInTerritoryがTrueかどうかを判別するDecoratorを追加します。

f:id:kazuhironagai77:20210207215146p:plain

設定は以下の通りです。

f:id:kazuhironagai77:20210207215212p:plain

テストします。

f:id:kazuhironagai77:20210207215232p:plain

あれ、追いかけて来ません。

f:id:kazuhironagai77:20210207215247p:plain

視覚で認識はされています。

f:id:kazuhironagai77:20210207215317p:plain

IsHeInTerritoryがFalseを返しています。

色々調べてたら原因が分かりました。

ServiceのEventであるEvent Receive Activation AIが発動していませんでした。

f:id:kazuhironagai77:20210207215346p:plain

ServiceのEventを、試しにEvent Receive Search Start AIに変更したらモンスターが追いかけて来ました。

f:id:kazuhironagai77:20210207215409p:plain

f:id:kazuhironagai77:20210207215430p:plain

良く考えたらServiceの正しい作成方法知りませんでした。

ちょっと勉強します。

3.4 Serviceの正しい作成方法

ググったらこのサイトが詳しく解説していました。

後、UE4のAnswerHubの「BT Services not activating」の質問の解答に以下の解説が載っていました。

今まで気が付かなかったんですが、UE4のAnswerHubってLogInしないと見れなかったんですね。これからはUE4のAnswerHubのスクリーンショットを貼るときは引用元をはっきり書く事にします。

f:id:kazuhironagai77:20210207215501p:plain

UE4Answer Hubの「BT Services not activating」の解答より引用

上記の説明によると、Event Receive Activationはbranchがsuccessを返した時だけ発動するとあります。となると先程の例で呼ばれないのは納得です。

と言うかこれで完全に解決してしまいました。

3.5 トロールの追加

縄張りに侵入してこない限りは、モンスターはその辺を歩き回っています。最初は先々週勉強したTutorialでやったような

f:id:kazuhironagai77:20210207215604p:plain

の様にパトロールする要所を設定する方法にしたかったのですが、この方法を採用すると全てのモンスターに一々、パトロールする要所を設定する必要がある事に気が付きました。それは大変過ぎるので適当な場所に歩いて移動する事にします。

まず適当な場所を見つけるServiceを作成します。

f:id:kazuhironagai77:20210207215627p:plain

作成したServiceを追加して、Blackboardに適切な変数を作成してそれと関連づけます。

f:id:kazuhironagai77:20210207215654p:plain

これで一応、モンスターはその辺を歩き回るはずです。

テストします。

f:id:kazuhironagai77:20210207215718p:plain

近づくと攻撃してくるので遠くから観察しましたが、全てのモンスターはランダムに移動はしています。

ただし2つ問題がありました。

  • 歩かずに走り回っている。
  • どんどん別な場所に移動してしまう。

これらも直していきます。

3.6 モンスターを歩かせる

これは先々週勉強したtutorialにやり方が出ていました。

f:id:kazuhironagai77:20210207215755p:plain

同じserviceを作成します。

f:id:kazuhironagai77:20210207215813p:plain

Behavior Treeに追加します。

f:id:kazuhironagai77:20210207215834p:plain

Max Walk Speedはぶらぶら歩いている時は150、侵入者を見つけた時は600にセットされるようにしました。

テストします。

遠くからしか観察できないのでスクリーンショットは非常に小さいですが、全部のモンスターがモゴモゴ歩いています。

f:id:kazuhironagai77:20210207215852p:plain

成功したと思ったら、歩いている途中のモンスターに近づいても、モンスターは襲って来ません。

f:id:kazuhironagai77:20210207215909p:plain

あれ。ひょっとして。

Does Monster See Player? の Observer abortsの設定をLower Priorityに変更したら

f:id:kazuhironagai77:20210207215950p:plain

f:id:kazuhironagai77:20210207220003p:plain

モンスターは散歩を中止して凄い勢いで追いかけて来ました。

f:id:kazuhironagai77:20210207220037p:plain

Lower Priorityとはそういう意味だったんですね。

やっと意味が分かりました。

3.7 モンスターを元の位置に戻す。

これもTutorialでやりました。以下にその方法を示します。

f:id:kazuhironagai77:20210207220102p:plain

My AI Controllerから上記の方法と全く同じやり方でやろうとしたら、Monster BPのAIはMy AI Controllerであるにもかかわらず、Get Controlled PawnをMonster BPにcastする事が出来ません。

f:id:kazuhironagai77:20210207220142p:plain

仕方がないので以下の方法でMonsterがspawnする最初の位置をBlackboardにセットしました。

f:id:kazuhironagai77:20210207220213p:plain

更にBehavior Treeを以下の様に改良しました。

f:id:kazuhironagai77:20210207220257p:plain

これで3回ほど適当な場所をうろついたら最初にSpawnした位置に戻ってくれるはずです。

テストします。

f:id:kazuhironagai77:20210207220321p:plain

戻っているみたいです。

Behavior Treeをdebugしたらモンスターは最初の位置に移動しています。

f:id:kazuhironagai77:20210207220347p:plain

一応は出来ました。

4.Lower Priorityの機能の確認

前節の「3.6 モンスターを歩かせる」でLower Priorityの機能の意味が分りました。一応確認のテストを行います。

4.1 テストの条件

まずBehavior Treeを以下の様に組みました。

f:id:kazuhironagai77:20210207220427p:plain

TaskのPrintStringとPrintString3は単にPrintStringを実行するだけです。

f:id:kazuhironagai77:20210207220452p:plain

TaskのPrintString1とPrintString2 はPrintStringを実行する前に3秒間停止します。

f:id:kazuhironagai77:20210207220509p:plain

Blackboardの変数ChooseNumberは1にセットされています。

この条件でテストします。

4.2 Noneの場合

Observer abortsをNoneにセットします。

f:id:kazuhironagai77:20210207220529p:plain

f:id:kazuhironagai77:20210207220536p:plain

ChooseNumberが0に変更された場合でも、実行中のノードPrint String 3は停止しないで最後までtaskの内容を実行しています。その後、変更した条件に沿ったtaskを実行しています。

Behavior treeのdebugも見てみます。

f:id:kazuhironagai77:20210207220611p:plain

ChooseNumberの値が0にセットされました。

f:id:kazuhironagai77:20210207220645p:plain

PrintString3のtaskが終了してSuccessを返しています。

f:id:kazuhironagai77:20210207220709p:plain

その後で、Selectorが新しい条件にあったNodeを選択しています。

4.3 Lower Priorityの場合

今度はObserver abortsをLower Priorityにセットします。

f:id:kazuhironagai77:20210207220734p:plain

テストします。

f:id:kazuhironagai77:20210207220822p:plain

ChooseNumberが0に変更されると、Noneの場合と違い、実行中のノードPrint String 3はただちに中止します。その後、変更した条件に沿ったtaskを実行しています。

Behavior treeのdebugも見てみます。

f:id:kazuhironagai77:20210207220854p:plain

ChooseNumberの値が0にセットされました。

PrintString3の作業は直ちに中止され、新しい条件にあるtaskが実行されています。

f:id:kazuhironagai77:20210207220917p:plain

Abortが発動した時もfailureの信号が親ノードに返されると思っていたのですが、何も返していませんね。

4.4 Lower Priorityまとめ

2021-01-24のブログ

f:id:kazuhironagai77:20210207221008p:plain

と書きましたが、その時は、高い優先順位から低い優先順位に変更された場合でテストしています。

その条件ではLower Priorityは発動しません。

Lower Priorityが発動するのは低い優先順位にあるTaskを実行中に、条件が高い優先順位に変更された場合です。

Observer AbortがNoneにセットされている場合は、低い優先順位にあるTaskを終了してから、変更した条件に合う高い優先順位にあるtaskを実行します。

しかしObserver AbortがLower Priorityにセットされている場合は、低い優先順位にあるTaskをabortして、直ちに変更した条件に合う高い優先順位にあるtaskを実行します。

4.5 2021-01-24のブログのLower Priorityに関しての間違いを直すのか?

直しません。

このブログは滑った転んだの過程を記録するために書き始めたからです。間違えた事も立派な記録です。

2週間前は、Lower Priorityの機能を間違えて理解していましたが、今は正しく理解しています。

この正しい理解に至った過程こそが、大切であると私は思っています。

もし2021-01-24のブログを直してしますと、その正しい理解にたどり着いた過程を消す事になってしまいます。だから2021-01-24のブログは間違った事が書かれていてもそのまま残します。

もっと深い理由もあります。

アメリカに居た時に非常にびっくりした事の一つに、アメリカ社会では、間違いは、絶対に罰しないんです。どんなにその間違いで損害が発生しても、間違った場合は罰しないです。

しかし嘘をついた場合は、ものすごく厳しい罰則があります。

その時に学んだんですが、人間は必ず間違えます。間違えない事は不可能です。しかし間違いは、後で直す事が可能なんです。所が嘘は違います。嘘を突かれると、どこで間違えているのか分からなく成ってしまうんです。その結果、後から直すのがほとんど不可能になります。

だから人は、間違いには寛大で、嘘には厳しく在るべきなんです。

しかし日本では、ほとんどの場合、逆で、間違いには非常に厳しいのに、嘘には寛大です。

これはソフト制作を含めたモノづくりにとって最悪な習慣だと思っています。

だから直しません。

4.6 Lower Priority おまけ

因みに「Unreal Engine 4で極めるゲーム開発」のp414に犬がお母ちゃんと遊んでいる時にお父ちゃんが帰ってきたらの例え話でLower Priorityを説明しています。

Lower Priorityの機能を理解してから読めば、全くその通りの説明ですが、二週間前の私は理解出来ませんでした。

理由は簡単で、私が子供の時に飼っていた犬は、飯を用意してくれるお母ちゃんと遊ぶ方が、お父ちゃんと遊ぶより優先順位が高かったからです。仕事を終えたお父ちゃんが家に帰って来てもその犬はお母ちゃんと遊んでいたらお父ちゃんは無視していました。

だからこの部分を読んだとき、私は勝手にお母ちゃんと遊ぶ方が優先順位が高いと思っていました。

5. EQSの作成

Environment Query Systemなんて仰々しい言い方をしているから、非常に恐れていましたが、数値解析なんかで、面をグリッドで表し、それぞれの点に値を与え、更にinterpolationする時に、点の値をどれくらい考慮するかを重みで表します。要は、これと同じ事をやっているだけだと分かりました。なら恐ろしくないです。

なのでEQSもしっかり勉強する事にします。今まではEQSに関しては読んだだけでしたが今回は実際に作成してみます。

先週、公式のサイトのtutorialがかなり良かったので今回も公式のTutorialで勉強します。(と言うかGoogleで検索したら一番上に出て来たのでそのまま選びました。)このサイトです。

f:id:kazuhironagai77:20210207221131p:plain

今回、私が注意して学習する点は以下の事になります。

  • グリッドの作成方法
  • そのグリッドに対してどうやって値をつけるのか?
  • 重みの計算方法
  • 出来た値と重みをどう使用するのか?

これらの点に注目しながら勉強していきます。

5.1 Environment Query System Quick Start

始める前からアレ何ですが、このTutorial、何時作成されたのかが分かりません。

以下の警告が表示されているんですが、4.26ではEQSは標準で装備されていますので、今もってExperimentalではないとは思います。

f:id:kazuhironagai77:20210207221204p:plain

せめてTutorialが作成された日付が分かると、この時はExperimentalだったんだな。とか今もってExperimentalなのか?とか、学習者が推測できるので有り難いです。

このTutorialで以下のsystemに対しての基本的な理解が出来ると紹介されています。

f:id:kazuhironagai77:20210207221242p:plain

5のEQSと6のEQS Contextsが分けられているのはEQSそのものとEQSの使い方の事でしょうか?興味深いです。

さらに7のAI Debugging Toolsでデバックについても勉強するみたいです。

5.1.1 Required Project Setup

わざわざこのTutorialのために新しいProjectを作成するのも勿体ないので、今あるAIの勉強用のProjectをそのまま使用します。Versionは4.24です。Tutorialは4.21を使用していますが、多分大丈夫でしょう。

Editor PreferenceのEQSにチェックを入れます。

f:id:kazuhironagai77:20210207221307p:plain

どうでも良い話ですが、Editor PreferenceとProject Settings…は違ったんですね。

f:id:kazuhironagai77:20210207221324p:plain

最初、同じものと思ってProject SettingsからEQSを探していました。

以下のBPクラスを作成しました。

f:id:kazuhironagai77:20210207221344p:plain

Characterクラス、AIControllerクラス、Behavior Tree、Blackboardはお馴染みですが、これらとは別に、新たに2つのクラスを作成しました。Environment QueryからとEnvQueryContext_BlueprintBaseからです。

この二つのクラスについての簡単な解説がないか、ググって見たんですが見つからなかったです。

ショウガナイので、先に行きます。

5.1.2 Environment Query Context

早速、EnvQueryContext_BlueprintBaseを使用します。

このクラス何に使用するのか全然分かりません。しかし名前から想像するにEnvironment Queryクラスを使用するためのContextを作成するのかもしれません。

Provide Single Actor関数を以下の様にOverrideしました。

f:id:kazuhironagai77:20210207221405p:plain

Provide Single Actor関数が呼ばれたらPlayerのCharacterを返す(この場合、ThirdPersonCharacterBPを返す)。

それだけの機能です。ここからはまだ何も分かりません。

5.1.3 EQS Setup

今度は、Environment Queryから派生して作成したEQS_FindPlayerにBPで実装していきます。

EQS_FindPlayerを開いて見ると結構寂しいです。

f:id:kazuhironagai77:20210207221440p:plain

RootからPoints:Gridを選択しました。これがGridを作成するのでしょうか?

f:id:kazuhironagai77:20210207221510p:plain

説明が、もろにグリッド作成します。と成ってました。

f:id:kazuhironagai77:20210207221533p:plain

ただQuerierの周りって何処なんでしょうか?それは分かりませんね。

半径と言っているのを見ると、Gridは正方形ではなく円形みたいです。

SimpleGridのPropoertiesの詳細を以下に示します。

f:id:kazuhironagai77:20210207221549p:plain

GridHalfSizeは半径を示しているみたいですね。

SpaceBetweenはグリッド内で値を持つ点同士の距離を示しているみたいです。

Projectionは何なんでしょうか?今はまだ分かりません。

それでは説明文を読んでいきます。

説明文によると、今、作成したSimpleGridや他の選択肢は総じてGeneratorと呼ばれるそうです。そう言えば、

f:id:kazuhironagai77:20210207221621p:plain

と書かれていました。そして何をGenerateするのかと言うとItemだそうです。このItemは後でTestに使用されるそうです。

これだけだと謎が更に深まりますね。

そしたらTestの例が説明されていました。

f:id:kazuhironagai77:20210207221658p:plain

この文章から推測すると、

  • Context Yが先程、EnvQueryContext_BlueprintBaseから派生したクラスでOverirideしたProvide Single Actor関数が返す、GameCharacterの事
  • Itemはグリットのそれぞれの点
  • Testはそのそれぞれの点が持つ値の計算方法を指す

と思われます。

Generate Aroundについての簡潔な説明がありました。

f:id:kazuhironagai77:20210207221734p:plain

どこにグリッドを作成するかを決定するそうです。

f:id:kazuhironagai77:20210207221805p:plain

敵のモンスターを中心にグリッドは作成するので、AI Characterは分かるのですが、querierは何なんでしょうか?

f:id:kazuhironagai77:20210207224551p:plain

だそうです。

因みに、他の選択肢は何があるのか見てみたら、以下のクラス(item?)が表示されました。
f:id:kazuhironagai77:20210207221826p:plain

EnvQueryContext_itemはItemつまり、単なるActorの周りに作成、EQC_PlayerContextはPlayerの周りに作成するのでしょうか?

今度は、SimpleGridにtestを追加します。

f:id:kazuhironagai77:20210207221855p:plain

TraceはQuerierからItemが見えるかどうかと言う事でしょうか?Distanceは単にItemとQuerierとの距離を表していますね。

Traceの設定を以下の様に変更しました。

f:id:kazuhironagai77:20210207221910p:plain

Test PurposeのFilter OnlyはQuerierが見えないitemは全部排除すると言う意味だと思います。前見たEQSの動画でそう説明していた気がします。

ContextにEQC_PlayerContextを選択しましたが、ここが意味が分かりません。EQC_PlayerContextは単純にPlayerの操作するキャラを指していると推測していたのですが違うようです。

Bool MatchはTutorialで解説されていました。

f:id:kazuhironagai77:20210207221941p:plain

だそうです。

Distanceの方は以下の様に設定しました。

これって先週見たOnline LearningのEQSと同じ事やっているんでしょうか?

何か見覚えがあります。

f:id:kazuhironagai77:20210207221959p:plain

先週のTutorialの記憶が正しければ、

Test PurposeのScore Onlyは点数をそれぞれのItemに付ける事で、Scoring Factorに-1を入れると、点数が反対になるはずです。

ここではDistance toでEnv_QueryContext_Querierを選択しています。

5.1.4 Blackboard and Behavior Tree Setup

ここはEQSと関係ないのでスキップします。

5.1.5 Behavior Tree: Patrol Setup

新しいtaskの作成中にGetRandomReachablePointInRadius関数を使用していました。

f:id:kazuhironagai77:20210207222036p:plain

あ。

Reachableじゃない時はfalseを返すみたいです。

「3.5 パトロールの追加」で作成したservice、FindRandomPlaceでGetRandomReachablePointInRadius関数を使用しましたが、使用方法が間違っていました。

直します。

f:id:kazuhironagai77:20210207222053p:plain

Branchを追加してGetRandomReachablePointInRadius関数がFalseを返した場合にも対応できるようにしました。

一応、テストもしてみます。

f:id:kazuhironagai77:20210207222117p:plain

普通に動いています。

GetRandomReachablePointInRadius関数の返した値が、Reachableじゃない時はfalseを返すのでは無くて、GetRandomReachablePointInRadius関数の返した値は全てReachableですが、何らかの理由で値が返せない時に、falseが返されるみたいです。

上記の実装の場合、どちらの解釈が正しくても正常に作動するので、GetRandomReachablePointInRadius関数を使用する場合はこれからもこの方法で実装します。

5.1.6 Behavior Tree: In Combat Setup

Run EQSQuery を使用しました。

f:id:kazuhironagai77:20210207222149p:plain

このTaskは先程、Environment Queryから作成したEQS_FindPlayerを呼び出します。

設定方法は以下の通りでした。

f:id:kazuhironagai77:20210207222207p:plain

Blackboard KeyにBlackboardにある変数を選びます。これは他の Taskと全く同じです。

次にQuery Templateに使用したいEnvironment Queryから作成したクラスを選択します。今回は先程作成したEQS_FindPlayerをセットします。

Run ModeにSingle Best Itemを選択します。これは最も成績が良いitemを一個選ぶ設定です。

5.1.7 AI Controller Setup

ここはかなり複雑なコードを実装していますが、EQSとは関係ないのでスキップします。

5.1.8 Final Setup

EQSとは関係ないのでスキップします。

5.1.9 テストします

Run EQS Queryが実行されています。

f:id:kazuhironagai77:20210207222244p:plain

Failureを返しています。

f:id:kazuhironagai77:20210207222328p:plain

何処かにバグがあるって事ですね。

うーん。

次の章が、このTutorialの最後の章ですが、EQSのテスト方法が紹介されています。これを勉強してDebugに使用出来ないか試してみます。

それでもダメな場合は、先週勉強したOnline learningにEQSのdebug方法が紹介されていたはずなので、それを復習して試してみます。

5.1.10 End Result

GamePlay DebuggerでEQSを見る方法が簡単に紹介されていました。

f:id:kazuhironagai77:20210207222359p:plain

それぞれの点の計算はされているみたいです。

EQS Testing Pawnの使用方法について説明されていました。

まずEQS_PlayerContextのProvide Actors Setを以下の様にOverrideします。

f:id:kazuhironagai77:20210207222419p:plain

作成したEQS Testing PawnのEQSのQuery TemplateにEQS_FindPlayerをセットします。

f:id:kazuhironagai77:20210207222434p:plain

Level上に配置します。

f:id:kazuhironagai77:20210207222451p:plain

Traceが効いているのが分かります。更にDistanceの値がBP_Enemyに近い程高く、遠い程低くなっています。

5.1.11 EQS Testing Pawnいろいろ

折角なんで、EQS Testing Pawnで遊んでみます。

ThirdPersonCharacterBPにEQS Testing Pawnを重ねて置いて見ました。更に障害物は全てどかしました。

f:id:kazuhironagai77:20210207222514p:plain

ThirdPersonCharacterBPに最も近い点が1.00で最も遠い点が0.00と表示されています。

今度はThirdPersonCharacterBPの前に障害物を置いて見ます。

f:id:kazuhironagai77:20210207222536p:plain

障害物の後ろにある点ではTraceが0にセットされました。

EQS Testing Pawn を使用すればEQS_FindPlayerがどんな機能を持っているのかは確認出来ますね。

5.1.12 EQS Quick Start 勉強まとめ(仮)

バグがまだ直っていませんが、忘れないうちにまとめ(仮)を書いておきます。

  • EnvQueryContext_BlueprintBaseがどうやって呼ばれてどのように使用されているのかが分からないです。
  • 色々なTestの設定で、EnvQueryContext_itemEnvQueryContext_QuerierそしてEQC_PlayerContextから選択しましたが、それぞれが何を指しているのか不明。
  • GamePlay DebuggerEQSで、Divideが選択出来ない。

以上が不明な点です。

これらは後でもう一度検討します。

5.2 EQSのDebugの復習

Creating the First EQS QueryからEQS Gameplay Debuggerまでをサラッと見直しました。それだけでも新たに分かった事が結構あったので、まずそれらをまとめます。

<疑問1> GamePlay DebuggerEQSで、Divideが選択出来ない。

Project Setting->GamePlay Debugger->Add Onsを見たらDivideの設定がNum/になっています。

f:id:kazuhironagai77:20210207222645p:plain

試してみます。

Num/を押すとそれぞれのItemの値が表示されました。

f:id:kazuhironagai77:20210207222703p:plain

もう一度押すとそれぞれのItemの値は消えました。

f:id:kazuhironagai77:20210207222809p:plain

<疑問2> EnvQueryContext_BlueprintBaseがどうやって呼ばれてどのように使用されているのかが分からないです。

これは分かってしまえば笑い話ですが、EQC_PlayerContextとしてEnvironment QueryのTestから呼ばれていました。

<疑問3> EnvQueryContext_itemEnvQueryContext_Querierについて

これは実際にテストして確認してみます。

まず、Environment Queryから新しいクラスを派生します。Distanceと名付けます。

f:id:kazuhironagai77:20210207222856p:plain

Distanceの中身は以下に示した様に、Generator にSimpleGridを選択し、TestにDistanceを選択しただけです。

f:id:kazuhironagai77:20210207222918p:plain

Test、DistanceのDistance to をEnvQueryContext_Querierに指定します。

f:id:kazuhironagai77:20210207222933p:plain

EQS_TestPawnから以下のクラスを作成します。名前はMyEQS_TestPawnとしました。

f:id:kazuhironagai77:20210207222951p:plain

EQSのQuery Templateに先程作成したDistanceをセットします。

f:id:kazuhironagai77:20210207223006p:plain

MyEQS_TestPawnをLevel上に配置します。

f:id:kazuhironagai77:20210207223023p:plain

MyEQS_TestPawnから近いItemほど数値が低く、遠いItemほど数値が高くなっています。と言う事は、EnvQueryContext_QuerierはMyEQS_TestPawnを指しています。

つまり実際の場合は、EnvQueryContext_Querierは、そのEQS Queryを呼び出したAIをセットしているCharacterを指していると考えられます。

では、EnvQueryContext_Itemの場合はどうでしょうか?

f:id:kazuhironagai77:20210207223039p:plain

これは全部、0になってしまいました。多分Itemを指定しないといけないのだと思います。後で検討します。

では、EnvQueryContext_BlueprintBaseから自作したEQC_PlayerContextの場合はどうでしょうか?

EQC_PlayerContextでは以下の二つの関数をOverrideしました。

f:id:kazuhironagai77:20210207223055p:plain

f:id:kazuhironagai77:20210207223103p:plain

f:id:kazuhironagai77:20210207223110p:plain

どちらの関数が呼ばれるのかは不明ですが、(多分Overrideしているので両方呼ばれる。)どちらにしてもThirdPersonCharacterBPに対してのDistanceが計算されるはずです。

f:id:kazuhironagai77:20210207223129p:plain

その通りでした。

ここで注意しないといけないのは、Gridとその中のItemはEnvironment Queryを呼び出したAIをセットしているCharacterを中心に作成されている事です。EnvQueryContext_BlueprintBaseはあくまでも計算するための起点の指定です。

*追記

Gridの発生場所は、Simple GridのGenerate Aroundで指定します。「5.1.3 EQS Setup」を読み直していたら自分でそう書いていました。

f:id:kazuhironagai77:20210207223207p:plain

<>その他の役立ちそうな情報

Step to Debug Drawの数値はどのTestまでを実行するか指定出来ます。

f:id:kazuhironagai77:20210207223241p:plain

Querying Modeは色々な結果を表示します。一番条件にあったitem一個だけの表示なども出来ます。

f:id:kazuhironagai77:20210207223318p:plain

5.3 EQSのバグの直し

これだけEQSの知識があれば先程のバグも直せるでしょう。やってみます。

「5.1.10 End Result」で作成したEQS Testing PawnのStep to Debug Drawを1にセットしてみます。

f:id:kazuhironagai77:20210207223342p:plain

以下に示した様に、EQS_FindPlayerの最初のtestであるTranceのContextはEQC_PlayerContextなのでThirdPersonCharacterBPから見て見えるGrid内のitemが選択されます。

f:id:kazuhironagai77:20210207223401p:plain

f:id:kazuhironagai77:20210207223409p:plain

されています。

次にStep to Debug Drawを2にセットしてDistanceを表示させました。

f:id:kazuhironagai77:20210207223433p:plain

Distance toがEnvQueryContext_QuerierにセットされているのでEmenyに一番近い場所が選択されています。

f:id:kazuhironagai77:20210207223449p:plain

これってPlayerの操作するキャラを追いかける目的ならば、EQC_PlayerContextにしないといけませんよね。

確認します。

サイトスクリーンショット

f:id:kazuhironagai77:20210207223506p:plain

EnvQueryContext_Querierにセットされていますが、その下の説明文に

f:id:kazuhironagai77:20210207223521p:plain

となっています。なので

f:id:kazuhironagai77:20210207223549p:plain

に直しました。

f:id:kazuhironagai77:20210207223609p:plain

今度は、Playerの操作するキャラから見えて、更に最も近いItemが選ばれています。

これでテストしてみます。

f:id:kazuhironagai77:20210207223626p:plain

今度はSuccessで返っています。

ただ、この設定だと、EQS_FindPlayerは最初のtestであるTranceが全部outな場合はfailureを返しても良い気もします。つまりFailureで返したとしてもバグとは言えない場合もあると思います。

のでこれ以上は追及するのは止めます。

6.まとめと感想

やっとUE4のAIの勉強が終わりました。大体の基礎は理解したと思います。EQSも大体は理解しました。また個人的な意見ですが、EQSは知覚、思考、行動の分類だと思考に入り、TaskではなくServiceで作成出来るべきだったのではないのかと思いました。

やっとですが、来週からはSaveの機能を改善します。

 

 

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する AIの復習とSave機能

f:id:kazuhironagai77:20210131232752p:plain

<前文>

HxH式念能力分類法とRPG戦闘システム

ほとんどの英単語は、一つの単語に名詞と動詞の両方の意味があります。Cookもそうです。Cookの名詞の意味はコックつまり料理人です。ではcookの動詞としての意味は知っていますか?

9割の日本人が「え?料理するでしょう」って答えると思います。確かに、今日調べた時点ですが、結構な英和辞典でcook の動詞の訳は「料理する」って書かれています。

これって実は100%間違いなんです。

Cookの意味は「食材に火を通して食べれる状態にする事」です。この火を通す事がcookの根本的な意味で、例えば「寿司を料理する」とか「サラダを料理する」の訳としてcookは使用出来ません。

良く「日本人って10年英語勉強しても全然しゃべれるようにならないよね。」と言われますが、在米10年の私から言わせればそれは当たり前なんです。だって勉強に使用している教科書そのものが間違っているから。今回のcook に関して言えば英和辞典そのものが間違っています。

所が、私がこの話を英語がしゃべれない日本人にすると大抵の人は烈火の如く怒ります。「偉い学者先生が言っているんだから絶対正しい。」と盲信しているのか、それに騙されている信者から利益を巻き上げて生計を立てているからなのかは知りませんが、英和辞典が間違っている訳ないと。激怒します。

そこで気が付いたんですが、この「偉い学者先生が言っているんだから絶対正しい。」と盲信しているのや、それに騙されている信者から利益を巻き上げるって、先週話したHxH式念能力分類法の操作系の能力そのものだったんです。

HxH式念能力分類法は科学的な根拠は全く無いですが、何故か現実の現象に良くあてはまる深い部分があるんです。

それでHxH式念能力分類法を、何かしらの方法で現実的に役に立つ事を証明したいなと思ったら、盲点ですが、ゲーム内でシミュレーションするのが一番簡単と気が付きました。

RPGの製作で属性について勉強したんですが、ゲーム内で属性を簡単にするために、水が火に攻撃する時は攻撃力が2倍になる事にしました。そして主人公を火属性にして色々工夫して水属性の敵を倒す。そこまでは良かったんですが、攻撃力を2倍にしたら、戦闘中にどんな選択をしても絶対、火は水に負けてしまうんです。そして煮詰まってしまったんです。結果が100%分かっているなら戦闘する必要はないです。攻撃力を2倍以下にすると今度は、水属性の敵が火属性の主人公を攻撃する理由が弱くなります。敵は100%勝てると考えて戦闘を挑んでくるですから。

これにHxH式念能力を追加してゲーム内でシミュレーションすると、HxH式念能力分類法が現実的に役に立つ事を証明出来るだけてなくゲームの戦闘システムが煮詰まっている状態からも抜け出せる事に気が付いたんです。

以下に簡単に説明します。

<強化系>

戦闘力そのものを1.5倍から3倍まで強化します。敵が2倍の攻撃力で攻めて来ても、戦闘力そのものを2倍以上に挙げれれば返り討ちに出来る訳です。

<変化系>

属性を変化します。例えば主人公が火属性から水属性の弱点である雷属性に変化するとかです。これも属性が上手く合えば、返り討ちに出来ます。

<具現化系>

敵の苦手な属性を持つ魔物を召喚したり、戦闘力を強化する武器を現実化したりする事で、対応します。

<操作系>

敵を眠らしたり、混乱させたりするか、武器を操る事で戦闘力を挙げる事で対応します。敵を混乱する武器を装備したら、かなり凄くなりそうです。

<放出系>

ここまでは上手く行っていたのですが、放出系で困ってしまいました。魔法を放出するのは当たり前で、敵の攻撃が届かない遠距離から攻撃出来るようにするためには距離という新しいパラメーターを追加する必要があり、そうなると最初から考え直す必要が出て来ます。更に距離を敵が詰める事が出来ないのなら、戦闘自体を避ける事が簡単になってしまい別なゲームになってしまう気がします。

それで現実に戻って考え直してみました。

そしたらすぐ答えが出ました。泣き叫ぶんです。子供が襲われたら、泣き叫んで助けを呼びます。つまり情報の放出を行うんです。味方なら直ぐに救助に駆けつけますし、最悪でも敵の情報を放出する事で敵の弱点をはっきりさせる事が出来ます。

<特質系>

それ以外の全ての対応策です。

戦闘中に死にそう(HPが10%以下)になったら、これらの念能力が目覚めるです。あるいはどこかの町で念能力を学んでも良いです。

これは上手く行きそうですね。もう少し練る必要はありますがこのアイデアは活けそうです。

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

<本文>

1.今週の予定

以下の事をやります。先週AIのtutorialをやりましたが、公式のOnlineコースにAIがあるのを見つけたので一応、それも勉強しておきます。

  • AIの勉強
  • モンスターのAIの作成
  • Save機能を直す

2.AIの勉強

公式のOnline learning にAIの教材があるのを見つけたので一応勉強する事にしました。

f:id:kazuhironagai77:20210131232936p:plain

はっきり言ってしまうと私は公式のTutorialは好きではありません。理由は

  • 5分の説明で済む事を1時間かけてじゃべったりしている。
  • 綺麗ごとが多い。
  • 学習者のために作成していない。

のが多いからです。

公式のOnline learningはそれに比べるとかなり出来が良い。とは聞いていたので、一応、飯を食べながら軽く読んでいました。そしたら結構、為になる事が書かれているので今週改めて勉強する事にしました。

2.1 Introduction

以下の事を勉強するらしいです。

f:id:kazuhironagai77:20210131233023p:plain

AIとは簡単に言えば、知覚―>思考―>行動である。って最初に読んだAIの教科書に書いてあったです。すっかり忘れていました。これは大事です。

注目すべき点はEQSとGameplay Debuggerを初心者向けのこの教材で教えています。

Gameplay Debuggerは日本語のキーボードの設定のせいで、勉強を避けがちですが、絶対知っておくべき機能なのかもしれません。

EQSの方はどうなのでしょうか?先週、勉強したtutorialでは、まだ試験的な機能と言っていましたが、そのTutorialは2年前に作成されたものです。今は絶対知っておくべき事なのでしょうか?

その辺も勉強して行くに連れておのずと判明するでしょう。

2.2 Downloading Project Files from Box

これから勉強するための教材がDownload出来るのかと思ったら出来ません。

仕方がないので、次の章の勉強を開始したら、この章で紹介されていたように教材のリンク先が動画の下にありました。

f:id:kazuhironagai77:20210131233049p:plain

この辺はいつも公式のOnline learning で勉強している人達には当たり前かもしれませんが、初めての人には結構分かりにくいです。

2.3 Creating the Project and Environment

Introductionでこの教材で勉強するためには、BPの使用方法を一通り理解しておく必要があります。と言って、初心者向けの教材といっても全くUE4を触った事のない初心者用の教材じゃないよ。と忠告しておきながらBakeされた影を新しくするためにはどうすればいいかの様な本筋と全く関係ない部分、しかも超初心者以外知らない人はいない事を延々と語っています。

というかこのレベルの人が対象の教材なのかもしれません。

EQSとGameplay Debuggerをどの程度教えているのか以外は特に重要ではないかもしれません。

2.4 AI Theory

知覚―>思考―>行動について説明しています。それだけです。特に深堀してる部分もありません。AIが知覚―>思考―>行動で構成されている事を知らない人には重要だと思います。

2.5 Creating the AI Character

まずCharacterBPとAIControllerBPについて説明しています。決して駄目な説明ではないですが「Unreal Enigne4で極めるゲーム開発」で紹介されていた家庭用ゲーム機のコントローラーの先にキャラクターが繋がったイメージを使用した説明の方が100倍位分かり易いです。

これに限らないですが、ポンチ絵を使用した方が直観的に理解出来る事は多いです。

2.6 Quiz1

AIのフレームワークは知覚―>思考―>行動であるが、UE4のAIはそれに沿って作成されてるか?の質問にYESと答えたら×でした。一応沿って作成されていると思うんですが、明確には沿っていないと言う事みたいです。

2.7 Navigation Theory

Baking the NavigationでNavigation MeshをBakeする方法について説明しています。これって初めて知ったんですが…。

f:id:kazuhironagai77:20210131233150p:plain

ここでアクターの位置をちょっとでも動かした時もヤレと書かれています。

そうだったのか。

理論の所は良くまとまっています。一般的なAIにおけるnavigation systemとUE4で採用された方法についての解説は一読の価値アリです。

私が重要と思う所を簡単に以下にまとめました。

  • Navigationとは現在地から目的地にどの様に移動するかを決定する事です。
  • そのためには障害物の把握と、移動可能なルートの捜索が必要になります。
  • UE4ではNavigation Meshを使用してそれらを可能にします。
  • Navigation Meshは地形などの情報をAIが利用しやすいように予め計算しておくため、すべてのAIが迅速に動く事を可能にしています。その一方で動的な変化に対応する事は出来ません。

そうだったのか。これならBakeする必要も納得です。

2.8 Using the NavMesh Bounds Volume

普段はBakeは要らないって。言ってました。

うん。

NavMesh Bounds Volumeの使用方法についての解説と実演なので特に記録したい事は無いです。

2.9 NavMesh Agents

NavMesh Agentsについて学びました。初めて知った機能です。簡単に説明するとAIが操作するキャラのサイズに合わせて、NavMesh Bounds Volumeの領域が変わる機能です。

  • 複数の大きさの違うキャラの場合の設定方法
  • 空を飛んだり泳いだりする場合

等の解説がありました。

流石にこの説明だけでは、実装は出来ないかもしれませんが最初の情報源としては十分です。

かなり勉強になりました。

2.10 Navigating the Mesh

ここで、今まで作成したNavMesh Bounds Volume上でAIを動かします。

CharacterBP内で全ての実装を終わらせていますが、このやり方、返って難しくしている気もします。

後、AIControllerを呼び出していましたがいつ作成したのか覚えていません。Creating the AI Characterの所で作成していたんでしょうか?見直すのも面倒なので先に進みます。

2.11 Nav Mesh and the Gameplay Debugger

マジですか。もうGamePlay Debuggerについて説明しています。

最初にアメリカ以外では、’でDebug出来ないので設定を変える必要について説明しています。おお、流石公式のサイトと思ったんですが、Console云々のパートが何を言っているのか良く分かりません。先週、参考にした日本語のサイトは確かConsole云々のパートは使用しなかったと思うんですが、この方法でも出来る様になるんでしょうか?

因みに、このTutorialの製作者はイギリス人っと言ってました。イギリスのキーボードはアメリカのと違うんでしょうか?

GamePlay Debuggerを開始する時に、絶対AIの方を向いてやるように。と言っています。

これがGamePlay DebuggerにAIが表示されたりされなかったりする原因でしょうか?

試してみます。

最初の一回はそうみたいです。一回AIに合わせるとその後、そのAIを見なくてもそのAIの情報が表示されます。

マジか。

これだけ有効な情報が得られるとなると、これから公式のOnlineコースのチェックは欠かせなくなりますね。

後、細かい事ですが、表示される情報が速すぎて読めなかったんですが、Pauseすれば良い事も分かりました。

全体的に言えばかなり有用な情報を得る事が出来ました。

2.12 Quiz 2

Using the NavMesh Bounds Volumeで実演した内容に関しての質問が一つあったんですが、良く聞いてなかったので正解が分からなかったです。

2.13 AI Perception Theory

ここは理論パートだから一般的なAIの知覚について語るのかと思ったら、完全にUE4のPerception systemについてでした。正直、一般的なAI Perception Theoryの説明が聞きたかったです。

AI Perception Component と AI Perception Stimuli Sourceについての解説、そしてTarget Perception Updated eventについての説明がちょっとだけあります。

確かに初めてUE4のAIを勉強する人たちには大変有用な情報であるし、簡潔にUE4のPerception systemを説明しています。が先週、先々週とUE4_AIについて勉強していた私にはほとんど自明な内容でした。

実際の戦場でAI付きのドローンに戦車を自動攻撃する時は、敵の戦車はAI Perception Stimuli Sourceを付けてはくれないですよね。

最新の機械学習や深層学習を使えば、簡単に敵の戦車を認識出来るのでしょうか?それとも現在でも敵を認識するのは難しいのでしょうか?その辺は興味深い話題です。

2.14 Setting up AI Perception

AI Perception Component と AI Perception Stimuli Sourceの実際の実装方法について実演しました。Lose sight radiusについての解説が今一良く分からなかったんですが、

f:id:kazuhironagai77:20210131233324p:plain

Editorの説明文を読んだら分かりました。

2.15 AI Perception and the Gameplay Debugger

まず、Anti-aliasing が効いているとDebugの細い線が見えないから切れと説明しています。

先週作成したAIで確認して見ましたが、絶対見えないと言う事はないと思います。

f:id:kazuhironagai77:20210131233350p:plain

個人的にはやんなくても良いかなと思います。

Tutorialの例ではSightが1、Ageは0と表示されています。

f:id:kazuhironagai77:20210131233408p:plain

自分のでも確認しましたが、一応表示されています。

f:id:kazuhironagai77:20210131233428p:plain

それよりもPlay中にtabキーを押す事で、自在にカメラの位置を変えているんですがこのやり方が分かりません。Spectator cameraと言っているんですがそれで検索しても出て来ません。

うーん。

こっちのやり方を教えてほしいです。

その後でinner とouter の違いについて実演を踏まえて解説していましたが、この辺も自分で確認したいのですが、カメラの位置の変え方が分かりません。

分からないので先に進みます。

2.16 Using the AI Perception Events

Target Perception Updated eventのSuccessを返した場合についての簡単な実装を学びました。このSuccessを返す場合の条件でさっきのInnerとouterに居る場合を調べたかったんですが、play中にカメラの位置が変えられないので上手く試せませんでした。

2.17 Quiz 3

Stimulusの要素にStimulus Locationを知らなかった事とActorの情報もStimulusに入っていると思っていた事の二つを間違えました。

f:id:kazuhironagai77:20210131233532p:plain

確認するのが面倒だったので適当に答えたら間違っていました。

満点は中々取れないですね。

2.17 Behavior Tree Theory

Behavior Treeについての説明でした。Selectorとsequenceの違いについての簡単な説明をしていました。

Behavior Tree自体は一般的なAIにも使用されているのか、それともUE4独自の理論なのかの説明みたいなのが欲しかったです。

自分で調べて見ました。

f:id:kazuhironagai77:20210131233557p:plain

載ってませんでした。

f:id:kazuhironagai77:20210131233613p:plain

5章の4節がまるまるBehavior Treeについての解説でした。少しだけ読んだらゲームのAI作成ではBehavior Treeは一般的な方法みたいでした。あんまり脱線するとtutorialが終わらなくなるのでこれ以上の調査は中止します。

2.18 Building the Initial Behavior Tree

AIController内からBehavior Treeを使用する時のEventにPossessを使用していました。

f:id:kazuhironagai77:20210131233658p:plain

f:id:kazuhironagai77:20210131233714p:plain

いつ、Eventが発動するのか今一分かりません。

f:id:kazuhironagai77:20210131233740p:plain

Taskの作成の説明で、FinishExecuteノードを忘れると、このtaskは何時まで経っても終了した事にはならないとありました。うー。恐ろしい。

あれ、その後、Behavior TreeからBlackBoardの変数と関連づけたりする所の説明が全くないみたいですが?

2.19 Chasing the Player

長すぎます。

見てるだけでは完全な理解は無理でした。

f:id:kazuhironagai77:20210131233808p:plain

Observer abortsの説明は普通で、特にバグがあるとかは言っていませんでした。

うーん。やっぱり普通に動くんですかね。

2.20 Testing the Behavior Tree

Behavior treeのdebugのやり方が説明されていました。

f:id:kazuhironagai77:20210131233846p:plain

実際に試してみないと分かりませんが、先週やったようなPrintStringを使用したTaskをつけて動作を追うより分かり易いかもしれません。

その後で、GamePlay debugを使用して現在どのBehavior Treeのノードが使用されているとか、Blackboardの変数の値などを見る事が出来る事が説明されていました。

f:id:kazuhironagai77:20210131233920p:plain

この辺も実際に自分で試してみないとどの辺が問題なのか分かりません。

2.21 Quiz 4

始めて満点取れましたけど、taskにPrint stringを使用しないでDebugを使用する事が正解なヤツとか、Observer abortsでBothを選択するのが正解なのがありました。

これらの答えが正しいのは分かりますが、実際の運用でこの通りに出来るかはまた別問題で、先週のObserver abortsでPriorityが効いていないのかどうかのチェックをこれらの機能を使用してもう一度調べるとなると、どうやって良いのか今一分かりません。

2.22 Creating the First EQS Query

なるほど、EQSが何故必要なのか分かりました。

今のPerception systemではNPCがキャラを見つけて追いかける事は出来ますが、もっと複雑な事、例えばNPCがキャラを見つけたら追いかけますが、その途中にある障害物にキャラから見つからない様に隠れる。のような事は出来ません。

それを可能にするのがEQSだそうです。

今、4.24を使用していますが、その時点でもEQSはまだ試験的導入に位置付けられています。

f:id:kazuhironagai77:20210131234005p:plain

4.26のプロジェクトでも確認しましたが、こっちはExperimentalの箇所にはEQSはありませんでした。

f:id:kazuhironagai77:20210131234046p:plain

既に標準仕様になっています。

このtutorialのサンプルでEQSをtaskのように使用していました。

f:id:kazuhironagai77:20210131234108p:plain

あれ、EQSってそういう風に使用するだったのか?と驚きです。とうか先週みたEQSのtutorialの時は何で気が付かなかったんでしょうか?

2.22 Making a Decision Based on Hunger

最初に全然関係ないんですが、2.15 AI Perception and the Gameplay Debuggerでゲーム中に俯瞰してrangeを見る方法が分からないと嘆いていましたが分かりました。

f:id:kazuhironagai77:20210131234144p:plain

Tabキーを押すだけでは駄目で、GamePlay debugを開始してから押す必要があります。一回tabキーを押すとカメラがplayerのキャラから外れて自由に動くようになります。WASD操作で普通に動きます。

Behavior treeのdebugのやり方が分かり易く紹介されています。

簡単にまとめておきます。

まず、Behavior Treeを開いた状態でゲームを開始します。

f:id:kazuhironagai77:20210131234217p:plain

ゲームが開始した瞬間にBehavior Treeのpauseボタンを押します。

f:id:kazuhironagai77:20210131234245p:plain

DebugしたいAIのキャラが選択されている事を確認します。

f:id:kazuhironagai77:20210131234313p:plain

以下に示した様に、現在発動しているtaskまでの起動のルートが示されています。

f:id:kazuhironagai77:20210131234348p:plain

現在の一歩前のステップが見たいとします。

Back:Introをクリックします。

f:id:kazuhironagai77:20210131234415p:plain

以下に示した図が表示されました。

f:id:kazuhironagai77:20210131234436p:plain

この赤い線で表示されているのはfailureが返されてるのを表しているみたいですね。となると緑の線はsuccessを表しているのでしょうね。

先週やったtutorialのBehavior Treeで実際に試してみます。

ゲーム開始直後に、Pauseしました。

f:id:kazuhironagai77:20210131234454p:plain

説明されていた通りにDebugの対象をチェックします。

f:id:kazuhironagai77:20210131234511p:plain

NPC_ALC_0とあります。

UE4 editorで確認してみるとNPC_AIのID NameがNPC_ALC_0とあります。

f:id:kazuhironagai77:20210131234536p:plain

NPCのキャラクターが選択されていると思ったのですが、実際はAIControllerの方が選択されていました。

Back:Introをクリックします。

f:id:kazuhironagai77:20210131234553p:plain

おお、一歩前のFailureされたステップが赤い線で表示されています。

敵に見つかってから、PauseにしてBehavior Treeに戻って来ました。

敵に見つかった瞬間のBehavior Treeの挙動をDebugしたいと思い、ひたすらBack:Introをクリック(16回)したら、以下に示した様に見れました。

f:id:kazuhironagai77:20210131234612p:plain

これなら、Behavior Treeの挙動を逐一追う事が出来ます。

Behavior TreeのDebug 使える様になりました。

2.23 Creating the Second EQS Query

EQS Queryを使用してPlayerに最も近い場所かつPlayerから見つからない場所を探すそうです。

実際に自分で作成しないと詳細は分からないですが、ここまでビデオを見ているだけだったので今更これだけ作成する事は出来ません。

代案としてPlayerに最も近い場所かつPlayerから見つからない場所を探すEQS Queryの作成方法をTutorialの動画を使用して以下に簡単にまとめます。

f:id:kazuhironagai77:20210131234643p:plain

まず、SimpleGrid : generate around Querierを追加します。

このノードはNPCの周りに大量のグリッドを作成するそうです。Playerから見えないグリッド、そしてその中で最もplayerに近いグリッドを選択する事で上記に示した条件の場所を特定するのでしょうか?

その通りでした。以下に示した様に、二個のTestを追加して上記に示した条件の場所を特定しています。

f:id:kazuhironagai77:20210131234750p:plain

最初のtestでPlayerから見えないグリッド、次のtestでその中で最もplayerに近いグリッドを選別しています。二つのtestsの細かい設定については見ているだけでは理解出来ませんでした。後で実際に作成してみます。

その後で、その場所への行き方が存在してるのかをチェックしていました。

f:id:kazuhironagai77:20210131234810p:plain

これは忘れていました。見つけた場所にNPCが到達できなければ意味ないですね。

2.24 Using the EQS Testing Pawn

UE4ではEQSの挙動を確認するための専用のテストがあるそうです。ここでその使用方法を勉強します。

まず、EQS Testing Pawn BPクラスから以下のクラスを作成します。

f:id:kazuhironagai77:20210131234832p:plain

そのInstanceをLevel内に配置します。

f:id:kazuhironagai77:20210131234848p:plain

配置したInstanceの詳細にEQSがありますので、テストしたいEQS Queryをセットします。

f:id:kazuhironagai77:20210131234905p:plain

以下の様な結果が表示されます。

f:id:kazuhironagai77:20210131234931p:plain

うーん。なるほど。分かり易いです。

次に2.23 Creating the Second EQS Queryで作成したEQS Queryのテストを行う方法を説明していますが、

f:id:kazuhironagai77:20210131234959p:plain

内で、以下に示した様に、テストの対象をPlayerのキャラクターから今回、EQS Testing Pawn BPクラスから作成したpawnに変更しています。

f:id:kazuhironagai77:20210131235017p:plain

このEQSC_Playerが何なのか分かりません。

この辺は後でもう一回このTutorialを勉強する時に理解する事にします。

それで既に訳わからなくなってしまったんですが、2つ重要な機能の説明をしていたのでそれだけ記録しておきます。

Querying ModeをSingle Best Itemにセットすると最後に選ばれたグリットがどれなのかを表示します。

f:id:kazuhironagai77:20210131235049p:plain

Step to Debug Drawの数値は、SimpleGrid : generate around Querier内のどのテストまで実行したのかを表しています。

f:id:kazuhironagai77:20210131235108p:plain

f:id:kazuhironagai77:20210131235116p:plain

この数字を1に変えると最初のテストのみを実行した状態を表示します。

2.25 EQS Gameplay Debugger

はい。GamePlay DebuggerをEQSに使用します。

解説によると、2.24 Using the EQS Testing Pawnのテストとの違いは、EQS Gameplay Debuggerはゲーム中の実際のEQSの挙動を表せる事だそうです。

実際の操作に関しては普通のGameplay Debuggerとほぼ同じに見えましたのでここに特に記録はしません。

2.26 Comparing UE4 Implementation to Theory

ここでは、一般のAIの理論である、知覚―>思考―>行動に、UE4_AIのそれぞれの機能がどう対応するかについて解説しています。

ここで述べられている事は前々から自分の中で思っていた事でした。

一つだけ質問を残しておきます。

Taskが行動に対応するのは正しいと思います。

しかしこのTutorialでhungerの値を1から0に戻る機能をTask内で作成しました。これは行動ではありません。

だから理論の立場から考えればこの機能はServiceで作成すべきです。

しかし実際の実装から考えるとTask内でhungerの値を1から0に戻した方が簡単です。

このような場合、理論と実際の便利さのどちらを優先して設計すべきなのでしょうか?

2.27 Next Steps

ここで制作したサンプルを更に向上させるためのヒントが述べられています。

f:id:kazuhironagai77:20210131235159p:plain

内積を求めてその結果を使用すれば方向についても考慮出来ます。と書かれていました。

このTutorialの対象者って内積位軽く分かっている人向けだったのでしょうか?

色々なアイデアが述べられていましたが、正直、今の私のレベルでこのTutorialの作者の真意が分かるとも思えないので何となく聞いておきました。

2.28 Quiz 5

Tutorialを見ていただけで、全く作成しないでテストした割には2問しか間違えませんでした。理論を問う問題が多かったからかもしれません。

2.29 このTutorial についての感想

一応、このTutorialに対しての感想を記録しておこうと思います。

想像していたのより10倍くらい質がありました。

UE4のAIの基礎を学ぶなら、この教材は必ず勉強する必要があると思います。正し全くUE4のAIの知識がない人がこの教材で勉強を始めて全部理解出来るのかは分かりません。

公式のOnline learningは一個を除いて見た事無かったのですが、その一個の教材も良かったですし、この教材も素晴らしかったです。もっと頻繁に利用する事にします。

3. モンスターのAIの作成

2で結構な時間が取られてしまったので出来るところまでやります。

3.1 AIの設定

知覚、思考、行動に分けてそれぞれの機能において必要な機能を考えてみます。

3.1.1 知覚

以下の機能は欲しいです。

  • AI Perception Component AI Perception Stimuli Sourceは必ず使用する。
  • 視覚、聴覚の両方の機能を追加したい。
  • プレイヤーの操作するキャラに反応するだけでなく他のモンスターの動向も知覚出来る。

3.1.2 思考

進撃の巨人を見ていたら、敵が奇襲攻撃を始めた時に直ぐに戦場に向かう巨人と、ある程度の損害を覚悟しても武装して戦場に向かう巨人がいました。これってどちらが正しいとは言えませんが、どちらも最善を尽くしています。こういう人によって違う思考も再現したいです。

イデアの一つに、重要度と言う概念があります。

  • 重要度によって行動を変える知能
  • 知覚から得た情報を元に需要度が変化

3.1.4 行動

  • 敵にまっすぐに向かう。
  • 警報をならす。
  • 武器を取りに行く

などが考えられます。

ちょっとこれでは弱いですね。来週までもう少し考えてみます。

4. まとめと感想

公式のOnline Learningを勉強した所までは調子よかったんですが、その後緊急の用事が発生してしまってそれ以上出来なくなってしまいました。残りは来週やります。

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する Bug直しとSave Part 2

f:id:kazuhironagai77:20210124230308p:plain

<前文>

HxH式操作系念能力者識別方法

U.S. capitolに侵入した人たちは、最低でも10年位の懲役を食らうらしいです。最近の捜査では彼らを手引きした議員か職員が居る事が明らかにされ、捜査関係者やその場にいた議員から、計画的なクーデターであったと、認識され始めています。ネットでも彼らの側に立っていた保守派、トランプ支持派のInfluencer達も急速にトークダウンして今までの自説を引っ込めて、黙るか、平然とU.S. capitolに侵入した奴らとは関係ないと開き直るかしています。

まだ、日本にはこのアメリカの空気の変化が微妙に伝わってはいませんが、日本におけるオ〇ムのサリン殺人事件の様な扱いになって来ている気がします。

トランプ大統領を含めたこの暴動をincitement(扇動?)した人達は、もう完全に自分だけが助かるのが目的になっていて、特にトランプ大統領に関しては、中で暴動を起こした人達が恩赦を求めても完全に無視しています。

可愛そうと言えば甘いと言われてしまうかもしれませんが、保守派の扇動に騙されてU.S. capitolに突撃した人は、多分、国内テロリストとして残りの人生を生きていかなければならなくなりました。飛行機に乗る事すら出来なくなるかもしれません。トランプ大統領とその支持者たちは「今回の大統領選挙が、不正によって本当は選挙に勝っているにも関わらず、負けてしまった。」と一見、誰にでも分かるような嘘をネットで何百回も繰り返し主張し、実際に極僅かですが、真面目なアメリカ人を騙す事に成功しました。

これほど騙され損な事はないですが、先週述べた理由によりこのような事件はこれから沢山起きてくると思われます。

その理由とは「ネットのAIが同じような事を言うサイトやビデオばかり薦めてくる。」事です。それによって「今回の大統領選挙が不正である。」の様な骨董無形な話にも視聴者や読者には真実に映ってしまいます。

私はこれに気が付いたのは、Epic Game社とAppleとの裁判の時です。YouTubeのお勧めに出てくるサイトの全てがEpic Game社が悪いとしか言わないんです。後で自分で検索したら、沢山の人がEpic Game社の味方をしているにも関わらず、お勧めに出て来るビデオは全てEpic Game社が悪いで統一されていたんです。

今回のU.S. capitolにおける暴動の後に、私自身で確認しましたが「今回の大統領選挙が不正によって本当は選挙に勝っているにも関わらず負けてしまった。」と主張している人のビデオを一個見たら、立て続けに似たようなビデオが表示されました。その中には、弁護士などが作成したビデオがありました。一般人の立場なら、そんな人たちが言っているなら正しいのだろうとなっちゃいますよ。

だから今回の事件の主犯は、ネットのAIなんです。

ここを直さない限り、同じような事件は必ず起きます。それまでは、我々に出来る対策は、このような扇動をあおる詐欺に引っかからない様に気を付ける事だけです。具体的に言うと、彼らの主張に対して科学的に正しいのか検証する事です。しかし常識的に考えれば、一般人には、そんな事が出来る時間も、予算も、そして専門的な知識もあるわけないです。

そういう時間もない、予算もない、そして専門的な知識もない状況で最適な判断を下すために、先週、思いついたHxH式操作系念能力者識別方法をここに紹介します。

前に書きましたが、HxHの念能力者のタイプ分類に沿ってプログラマーを分類すると結構当たっています。このHxHの念能力者のタイプの一つが操作系で、このタイプは何かを操作する事に長けているタイプです。この分類方法は、実際にはプログラマーだけでなくあらゆる専門職にも使えて、今回の事件に当てはめると、ネットで扇動していた人達は操作系に当てはまります。

彼らは、色々言っていますが、やっている事は、扇動(操作)する事だけです。このタイプが指示する側にいたら、間違いなく自分は扇動(操作)されていると考えるべきです。この操作系を見つける方法の一つは寄付の支払い方を確認する事です。寄付をする段階になった時に操作系は絶対しないです。騙されている側の人は飯を抜いてでも寄付します。しかし扇動(操作)する側は絶対に寄付しません。

あなたを扇動する操作系が見つかったなら、あなたは操作されている事は確認出来たわけです。そして操作されている間は最適な答えを見つける事は出来ないと自覚しましょう。なぜなら操作されている間は、あなたは操作している人の望み通りに動いているだけだからです。この操作されている状態を外すだけで、最適解を見つける確率が0ではなくなります。

ではどうやって外すかと言う事なんですが、これもHxHの中に答えが既に提示されています。操作系は別の操作系がコントロールしている人は操作出来ないんです。つまり既に誰かに操作されている状態の人を操作する事は出来ません。これってかなり真実でしょう。今回の暴動に参加した人の中に、イスラム教の人や、日本のアニメやゲームが好きな層、更に言えば白人以外の人っていますか?私が見た限りでは一人もいませんでした。例え居ても極少数でしょう。(何故か、ロシア訛りの英語を話す人やPCゲームのファンは居たみたいです。これは本筋から離れるのでここでは述べません。)

先程述べたEpic Game社とAppleとの裁判の例でも、口の悪い言い方をすれば、私自身が既にUE4の信者であったので「Epic Game社が悪い」と言う扇動(操作)に罹らなかったんです。だから、何かの信者(アニメでも、ゲームでも、スポーツでも、そして勿論、宗教も)に前もって成っていればいいんです。

そこで私のお勧めは、プログラミング教の信者になる事です。プログラミングの勉強を一生続けると、死後天国に行けると言う信仰です。一定のレベル以上のプログラマーは休みでも何かしらの勉強をしています。これは普通の人から見ると大変きつそうですが、天国に行くための修行と捉えればきつくても納得です。

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

<本文>

1.今週の作業

今週はUE4_AIについての勉強をします。

  1. TaskクラスのAbort、ExecuteとTick関数とdecoratorクラスのObserver abortsの設定の関係について
  2. 先週やったTutorialの続き
  3. 実際に使用するモンスター様のAIの作成

を行います。

2. TaskクラスのAbort、ExecuteとTick関数とDecoratorクラスのObserver abortsの設定の関係について

先週「Unreal Engine 4で極めるゲーム開発」を読んでいたらTaskクラスのAbort()関数は、Observer abortsでLower Priorityのような設定でAbortされた時に発動すると書かれていました。目から鱗です。これを確認します。

2.1 SequenceノードとSelectorノードの復習

ですが、全てのノードの機能についての確認を最初にやっておきます。まずSequenceノードとSelectorノードについての確認です。

まずはSelectorノードのテストです。

以下のコードを作成しました。

f:id:kazuhironagai77:20210124230449p:plain

実行されるのはどのTaskでしょうか?もしSelectorのChildの一つでもSucceedしたらそこで止まります。

ので、PrintStringのみが実行されるはずです。

テストしてみます。

f:id:kazuhironagai77:20210124230511p:plain

PrintStringのみが実行されました。ここまでは予測通りです。

次にこのゲームを終了します。どのTaskのAbortが実行されるのでしょうか?

実行されているのは、PrintStringノードのみです。だからPrintStringのAbortのみが実行されるはずです。

テストして試してみます。

f:id:kazuhironagai77:20210124230528p:plain

その通りでした。

今度は以下の様にSequenceノードを代わりに使用します。

f:id:kazuhironagai77:20210124230545p:plain

SequenceはFailureするまでChildノードを次々に実行するはずです。ので

  • PrintString
  • PrintString1
  • PrintString2
  • PrintString3

が表示されるはずです。

f:id:kazuhironagai77:20210124230622p:plain

あれ?

PrintString Task内でFinish Executeノードを追加するのを忘れていました。

f:id:kazuhironagai77:20210124230643p:plain

PrintString1、PrintString2、PrintString3にもFinish Executeノードを追加しました。

もう一度テストします。

f:id:kazuhironagai77:20210124230702p:plain

なんと永遠にPrintされています。

Selectorの場合ももう一度試してみます。

f:id:kazuhironagai77:20210124230721p:plain

Selectorも永遠に実行されていました。しかもこの場合、終了した後、Abortが呼ばれません。

Successのチェックを外してみます。

f:id:kazuhironagai77:20210124230738p:plain

最後まで全てのTaskを実行して、その後また最初のtaskに戻って実行しています。

f:id:kazuhironagai77:20210124230755p:plain

この場合も終了した後、Abortは呼ばれません。

うーん。なるほど。Abortは途中で収容した時に呼ばれるのかもしれませんね。

2.2 Abort Eventを呼ぶ

検証のために以下のコードを作成しました。

f:id:kazuhironagai77:20210124230817p:plain

ChooseNumberは0に指定しています。

Finish Executeは全部Trueに変更しました。

テストします。

f:id:kazuhironagai77:20210124230834p:plain

想像していた通りの結果がプリントされています。

今度は、ChooseNumbarを途中で2に指定してみます。

f:id:kazuhironagai77:20210124230925p:plain

PrintString1(Execute)が終了した時にChooseNumberが2に成りました。そしてその後にPrintString2(Execute)とPrintString3(Execute)がプリントされています。Abortはプリントされませんでした。

f:id:kazuhironagai77:20210124230949p:plain

Observer AbortがNoneにセットされているからでしょうか?

Selfに変更してみます。

f:id:kazuhironagai77:20210124231005p:plain

今度は、PrintString(Execute)を実行した後、にChooseNumber is set to 2が実行されています。そしてその後で、直ぐにPrintString2(Execute)が実行されています。

これは、先週紹介したサイト

f:id:kazuhironagai77:20210124231023p:plain

この説明に合っているように見えます。

もう少し厳密に検討するためにWait taskを追加します。

f:id:kazuhironagai77:20210124231041p:plain

これでもう一度テストします。

最初はObserver Abort をNoneにセットしました。

f:id:kazuhironagai77:20210124231056p:plain

今度は分かり易いです。PrintString(Execute)が実行した後、Waitノードが実行されている時に、ChooseNumberが2に変更されました。しかしその次のノードであるPrintString1(Execute)は実行されています。全てのノードの実行が終了した後で、ChooseNumberが2である条件に該当するNodeが実行されています。

これはサイトにある説明通りです。

今度はselfに変更します。

f:id:kazuhironagai77:20210124231245p:plain

今度も前回と同じ様に、PrintString(Execute)が実行した後、Waitノードが実行されている時に、ChooseNumberが2に変更されました。今回は次のノードであるPrintString1(Execute)は実行されず、ChooseNumberが2である条件に該当するNodeが実行されています。

これもサイトの説明通りです。

しかしこの時にAbortは呼ばれていません。何故なんでしょうか?

ピンと来ました。Waitノードを外してPrintString1 taskにDelayノードを追加します。

f:id:kazuhironagai77:20210124231335p:plain

これでテストしてみます。

f:id:kazuhironagai77:20210124231352p:plain

出来ました。今度はAbortが呼ばれています。

PrintString1 taskの実行中にChooseNumberの値が2に変化したので、PrintString1の実行をabortして、ChooseNumberが2である条件に該当するNodeを実行しています。

2.3 Observer Abort の確認、None、Self、Lower PriorityそしてBothの違い

2.3.1 None

上記のSelfと同じ条件でNoneをやってみます。サイトの説明通りならNoneの場合は、abortは呼ばれないはずです。

f:id:kazuhironagai77:20210124231425p:plain

PrintString1 taskの実行中にChooseNumberの値を2に変化しましたが、今回はその変化を無視してPrintString1の実行を続行しています。

予想通りでした。

2.3.2 Lower Priority

f:id:kazuhironagai77:20210124231456p:plain

と書かれています。これだとこのテストだとNoneと同じ結果になるのでは?

と思いテストの条件をちょっとだけ変える事にします。

しかしその前に一応確認のテストをしておきます。

f:id:kazuhironagai77:20210124231624p:plain

うん。同じ結果ですね。

一応、Bothの場合もテストしておきました。

f:id:kazuhironagai77:20210124231644p:plain

こちらはSelfと同じ結果です。

それではテスト条件を以下の様に変更します。

f:id:kazuhironagai77:20210124231707p:plain

PrintString1の隣にPrintString2を追加しました。

まずNoneの場合です。

f:id:kazuhironagai77:20210124231737p:plain

PrintString1を実行している途中で、ChooseNumberの値を2に変化しました。しかし最後までPrintString1を実行し、更にPrintString2を実行しています。その後でChooseNumberの値が2の時の条件に合うSequenceのTaskを実行しています。

今度はselfです。

f:id:kazuhironagai77:20210124231831p:plain

PrintString1を実行している途中で、ChooseNumberの値を2に変化しました。PrintString1の実行を中断し、その後でChooseNumberの値が2の時の条件に合うSequenceのTaskを実行しています。PrintString2は実行していません。

Lower Priorityの場合です。

f:id:kazuhironagai77:20210124231953p:plain

あれ、PrintString2は実行されないと思ったのですが、想像していたのと違う結果ですね。

ちょっと考えます。

分かりました。Lower Priorityの場合、

f:id:kazuhironagai77:20210124232016p:plain

この右側の部分が全て無視されるはずです。

そしてbothの場合は、Self + Lower priorityとなり、全部中止になるはずです。

これを明確に表示するために、以下のコードを作成しました。

f:id:kazuhironagai77:20210124232256p:plain

このコードの特徴はPrintStringとPrintString3のFinish ExecuteをFailureで返す所です。

これで、実際のコードの流れをもっと細かく見れます。

PrintString1を実行している途中で、ChooseNumberの値を2に変化しました

Noneの場合

f:id:kazuhironagai77:20210124232350p:plain

予測通りですがそのまま実行しています。PrintString2(Execute)を実行した後でSelectorにSuccessを送るので、selectorは最初からやり直しています。

Selfの場合

f:id:kazuhironagai77:20210124232456p:plain

予測通りPrintString1(Execute)の実行を中止してabortを発動しています。その隣のtaskであるPrintString2(Execute)は実行されません。Selectorに戻ると、selectorはSuccessの信号を受け取っていないのでそのまま次のnodeを実行しています。

Lower Priority

f:id:kazuhironagai77:20210124232548p:plain

これでもNoneと同じ結果になってしまいました。

Noneとの違いを確認するために、PrintString2(Execute)のFinish Executeの返しをFailureにします。

Noneの場合です。

f:id:kazuhironagai77:20210124232624p:plain

PrintString3、PrintString4が実行されています。

Lower Priorityの場合です。

f:id:kazuhironagai77:20210124232645p:plain

この場合、PrintString1(Execute)のみ実行されて、PrintString2、PrintString3、PrintString4はabortされると思われるのですがされていませんね。Noneと全く同じ形になってしまっています。

何故なのでしょう?

Both

f:id:kazuhironagai77:20210124232746p:plain

これもSelfと全く同じ結果になっていますね。

2.3.3 Observer Abortまとめ

もし私のObserver Abortのそれぞれの要素に対する理解が100%正しいとしたら、Lower Priorityが効いていないと考えると全部辻褄が合います。

まあ今回は、そういう可能性もある位で、留めておいてObserver Abort はNoneとSelfだけ使用するようにします。

3. 先週勉強したTutorialの続き

3.1 Guard AI

特になしです。単にServiceを使用してNPCの歩くスピードを調節しただけ。しかしNPCとの追いかけっこが結構面白くなってきました。

3.2 Guards Searching

特に新しい関数とかは出て来ませんでした。どうやったらAIが本物のGuardみたく動くのかを勉強する感じです。

f:id:kazuhironagai77:20210124233232p:plain

Abortした時は、failureになると思っていたのです、finish abortで返してもそうなんでしょうか?Loopの下のBlackboard Conditionはselfにセットされているので、途中で条件が変わると行動が変化します。

f:id:kazuhironagai77:20210124233257p:plain

流石に、追い駆けっこそのものは滅茶苦茶面白いです。これだけでゲームに成りそうなくらいの完成度です。

何でこんな面白いんでしょうか?

NPCの反応が、生きた人間そっくりだからでしょうか?

不思議ですね。

3.3 Melee Attacks (Animation)

Meleeってどういう意味だったけ。と調べたらグーグルの一番最初に以下の説明が表示されました。

f:id:kazuhironagai77:20210124233332p:plain

もっと、抽象的な概念を想像してたんでびっくりです。

アニメーションの作成方法で今まで知らない方法を学びました。

まずAnimation Montageです。

f:id:kazuhironagai77:20210124233424p:plain

f:id:kazuhironagai77:20210124233433p:plain

もしかしたら一回位は、作成した事位はあるかもしれませんが、ほとんど初めて聞いたクラスです。使用目的や使用方法などは全く分かりません。

次はAnimationBlueprintクラスのAnimGraphで先程作成したslotとDefaultをblendする方法です。

f:id:kazuhironagai77:20210124233451p:plain

これも初めて勉強する方法でした。

正直、完全に理解したとは言えません。Tutorialに書かれてる通りにやったら動きました。と言う感じです。UE4_animationは一度しっかり勉強する必要がありますね。

f:id:kazuhironagai77:20210124233507p:plain

Blendしたanimationをスクリーンショットで撮ったんですが、動作がほとんど終了していました。

3.4 Melee Attacks (Behaviour)

今度は前節で作成した攻撃モーションをNPCが使用します。

以下に示した様に最終的には出来ましたが、結構大変でした。

f:id:kazuhironagai77:20210124233535p:plain

以下に示したtaskのChasePlayerのfinish Executeがfailureで返されていて次のMelee Attackが呼ばれなかったです。

f:id:kazuhironagai77:20210124233557p:plain

このバグに気づかなくて、直すのに30分位かかりました。

新しい関数としてSimple Parallelを習いました。

f:id:kazuhironagai77:20210124233648p:plain

Simple ParallelのFinish Modeには2種類あり、immediateは左に接続したmain taskが終わり次第、終了します。DelayedはBackgroundで実行されている他のTaskが終了するまで待ちます。

f:id:kazuhironagai77:20210124233707p:plain

Get Distance to関数は二つのActorの距離を返します。

f:id:kazuhironagai77:20210124233724p:plain

3.5 Hearing Perception

まず、AIのデバッグをするために ‘を押してくださいとありますが、

f:id:kazuhironagai77:20210124233759p:plain

Shift+7を押しても何も表示されません。

これは日本語キーボードには対応していないのかもしれないと思い日本語でこれについて書いてある記事を探したらここにありました。

その記事を参考にして1を押すとGamePlayデバックが表示されるように設定し直しました。

f:id:kazuhironagai77:20210124233826p:plain

表示されるようになりました。

Perceptionを表示しています。

f:id:kazuhironagai77:20210124233845p:plain

でも何も表示されない時もあります。

f:id:kazuhironagai77:20210124233901p:plain

何も表示されない時は、Debug actorがNoneになっていました。

f:id:kazuhironagai77:20210124233921p:plain

表示される時は、

f:id:kazuhironagai77:20210124233954p:plain

NPC_2になっています。どうやってDebug actorを選択しているのか、今は分かりません。

Hearing Perceptionについて

一応、Tutorial通りに作成しましたが、Hearing Perceptionそのものについては、個別のTutorialで教えてほしかったです。そして今まで作成したSight Perceptionとの結合方法を教えてくれた方が全然分かり易いと思いました。

複数の知覚を設定した時に、どの知覚から信号を受け取ったのか判別する方法は、以下に示しました。

まず、AIPerceptionStimuliSourceのReport Noise EventのTagに名前を追加します。

f:id:kazuhironagai77:20210124234017p:plain

AI_Controller側で刺激を受け取りTagから名前を調べます。

f:id:kazuhironagai77:20210124234105p:plain

3.6 Damage Sensing

このTutorialからコメント欄が文句の嵐になっていてます。多分何か問題があるのでしょう。ここからは軽く見る事にします。

前のtutorialのバグの直しから始まっています。プログラマーなら自分で直せるはずなので直しそのものは重要ではないですが、その後のテストで、

  • NPCがパトロール中にプレイヤーを目撃した場合
  • NPCがパトロール中に何かの音を聞き、プレイヤーを確認した場合
  • NPCがパトロール中に何かの音を聞き、調査に行った途中でプレイヤーを確認した場合

を確認しているのを見て、そう言う事だったのかと思いました。確かに門番がパトロール中に侵入者を目撃したら、追いかけていきます。不審な音が聞こえたらパトロールのルートを外れても確認します。そしてその過程で侵入者を見つけたら追いかけます。

こういう事を真剣に考える事で人間らしく反応するAIが作成できる事が分かりました。

Damageの設定そのものはAction Gameか何かの時に本格的に勉強すると思いますので、今回はUE4にはこんな機能もあるのか位に留めておきます。

3.7 Nav Link Proxy

Nav Link Proxyの使い方の説明で、NPCが高い所から低い所にNav Link Proxyを使用して降りる場合を説明していました。たった5分の説明なのでこれだけでNav Link Proxyを使いこなす事は出来ないと思いますが、大変興味深い機能と思いました。

この次のTutorialでNPCが低い所から高い所に移動する場合について説明するとありましたが、次のtutorialはAI EQSについてでした。

3.8 AI EQS Part 1とAI EQS Part 2

EQSは試験的な部分がまだありますと説明されていたんで、AIの基礎を勉強中の私が特別、積極的にやる必要はないと思い、軽く動画を見るだけにしました。

4.まとめと感想

今週はここまでで時間が無くなってしまいました。モンスターのAIの改良は来週やる事にします。