UE4の勉強記録

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

「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の改良は来週やる事にします。

 

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する Bug直しの続きとSaveの改善

f:id:kazuhironagai77:20210117212957p:plain

<前文>

長母音と短母音について

英語の発音記号についてなんですが、ネットとかで英語の発音を教えているほとんどのアメリカ人は大学で言語学の学位を取った人じゃないんです。だから話半分に聞いておくべきなんです。そう言う考え方もあるよね。ぐらいで。

その例として、今回ここで述べておきたいのは、long i とshort iの音の長さは同じと言う説です。

あるサイトでleaf とliveのそれぞれのlong i とshort i の音の長さを測定したんですが全く同じ長さだった。とありました。実際はこのサイトだけでなく結構な数の英語の発音を指導するアメリカ人がlong i とshort i の音の長さは同じと主張しています。

ただそのサイトで提供されていたleaf とliveの音を聞くとliveのiの音の方が短く聞こえたので、本当に二つの単語のiの音の長さが同じなのか自分で測定してみました。

f:id:kazuhironagai77:20210117213034p:plain

そしたら、0.235秒でLiveはVの音が聞こえるますが、LeafのFの音はまだ聞こえないんです。

つまり、私が測定した限りではshort iの音はlong iの音より短かったんです。

本当にこのサイトの主は自分で音を測定したんですかね?

査読付きの論文にうそのデータを載せたら大変です。しかしその辺にたむろしているオッチャンが、運動部の練習している高校生に「水取らないと、危ないぞ。」と言っても、それって「水分を取るべきって言うべきで、生理的食塩水なら運動中に摂取すべきですが、単なる水道水を運動中に摂取したら…」と言い返す人はあんまりいないです。

英語の発音についても同じ事が言えます。ネットで英語の発音を指導するアメリカ人のほとんどは大学で専門に勉強した人ではなく単なるその辺のオッチャンと同じレベルの人です。その人が述べる指導方法で上達する事が出来ないのは自明ですが、それをわざわざ指摘する人は野暮なだけです。

所がですが、一端ネットに載ってしますと、その人が凄腕のコーチなのか、その辺のオッチャンなのか、区別が付きにくくなってしまうんです。その辺のオッチャンがどこかの有名大学の教授の様に見えたり、その逆もあったりします。

だからネットで言われている意見については自分でチェックするのは非常に大切と思います。

アメリカ議会の占拠と殺人

今回の大統領選挙で、不正が行われていると信じてアメリカ議会の占拠をした人達はとうとう殺人まで犯してしまいました。個人的には、この事件の本当の犯人は、偏った意見を述べるinfluencerを一人でも見ると、同じような意見を持つ人ばかり表示するネットのAIだと思います。反対意見が全く表示されない状態で、同じような意見を色々な有名人が述べたら、一般の人は直ぐに信じてしまいます。

しかしほとんどのアメリカ人は単純に、右が間違いで左が正しい、そして問題は解決したと安心しています。

今回の事件で、右寄りの意見を言っていた人は暫く肩身の狭い思いをすると思いますし、実際反省すべきですが、アメリカのリベラルが同じ間違いをこれから犯さないとは私には思えません。今回の問題を引き起こした原因であるネットのAIについて誰も言及しないからです。

私は近い将来、今度は以下の分野でリベラルが同じような問題を起こす可能性があると思っています。

  • 私は、地球温暖化はかなり怪しいと思っています。これから地球は寒くなると思っています。これは単なる個人的な見解ですが、地球温暖化ビジネスに未来のトランプが潜んでいたら。今回以上の事件を将来、リベラルが起こす可能性は非常に高いと思われます。
  • 福祉の充実こそが左派に求めらてれている政策です。しかし実際にそれを実行するリベラルは民主党で浮いた存在になるでしょう。適当な政策でお茶を濁すはずです。個人的な注目は大学の学費です。これが下がるかどうかで民主党の福祉に対する本気度が分かると思います。
  • 無意味なポリコレの強制。これは日本人には馴染みの問題過ぎて、私がここで解説する必要はないですが、一応述べておきます。絵を無くしても問題の解決にはつながりません。

これから犯罪をそそのかす、このようなデマに騙されないためには、前述した発音の問題の様に、一々チェックするのが最適ですが、実際は忙しすぎるのと専門分野以外の話は正しく理解する事すら出来ないので不可能です。

これに対する秘策を実は思いついちゃったんです。

名付けてHxH式操作系念能力者識別法です。

これについて解説すると長くなりすぎて勉強時間が取れなくなってしまうのでこの話は来週に回します。

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

<本文>

1. 今週の予定

先週、量が多すぎるので中止した2つのバグを直します。

  • 徘徊するモンスター用に別なAIを作成
  • PlayAnimationノードの使用は中止。アニメーションはAnimBPで一括して管理する

更に、今まで適当にしていたsave機能を完成させたいと思います。

1.1 (!!)の表示について

先週、作成した後でInstance editableなBoolean 変数を一個作成すれば、わざわざRPGGameModebaseBPに新しい変数を作成する必要もないと思ったのですが、面倒だったので試さなかったです。

試してみます。

f:id:kazuhironagai77:20210117213150p:plain

f:id:kazuhironagai77:20210117213159p:plain

f:id:kazuhironagai77:20210117213207p:plain

f:id:kazuhironagai77:20210117213216p:plain

これで、RPGGameModebaseBPに作成した新しい変数を使用しないで、(!!)の表示が出来るはずです。

テストします。

f:id:kazuhironagai77:20210117213234p:plain

出来ました。

RPGGameModebaseBPのItemNameToPickup変数はいらないので消します。

因みに、拾ったitemを消去する関数内でもRPGGameModebaseBPのItemNameToPickup変数は使用していましたが、以下のように実装を変更する事で、RPGGameModebaseBPのItemNameToPickup変数を使用しないでも収得したアイテムが2度以上表示される事は無くなりました。

f:id:kazuhironagai77:20210117213255p:plain

一応テストした結果も記録として残しておきます。

f:id:kazuhironagai77:20210117213329p:plain

2. 徘徊するモンスター用に別なAIを作成

徘徊するモンスター用のAIを作成します。

2.1 AIの復習

もう一通りの基本は理解しているので細かい点の復習を兼ねてこのサイトUE4_AIの勉強をします。前に途中までこのサイトでUE4_AIの勉強をしたので選びました。

Part 2 AI Perceptionまで見て思ったんですが、これそのまま作成したら十分です。

今のAIはプレイヤーが操作するキャラがNavの領地に入ると、モンスターはどうやってプレイヤーの操作するキャラを見つけたのか分からないけど猛烈に追いかけて来ます。それでモンスターに視覚のようなモノを与えたかったのですが、このチュートリアルはそのものズバリです。そのまま作成して使用する事にします。

前回、勉強した時UE4のAIの全体像が知りたかったので、正直駄目なtutorialとは思いました。2020-12-06のブログで述べましたが「UE4_AIそのものは単なるif節である。」との認識こそが、当時の私が知りたかった事でした。その次のステップとして「AIがコントロールするキャラの動きをもっと自然にしたい。」となった時にこのtutorialは非常に役に立ちます。

前回の勉強をまとめたブログを参考にしつつ、もう一回このtutorialを勉強します。

2.1.1 The Behaviour Tree

この回のTutorialは、AIController、BehaviorTree、BlackboardなどのUE4_AIを構成するクラスの使用方法を説明しています。Behavior TreeについてはTaskの作成についてかなり細かく説明してます。

ただし、私がUE4_AIの基本を説明するために最も大切であると考える「UE4_AIそのものは単なるif節である。」の認識を得る事はこのTutorialからは不可能です。そういう意味では初心者にとってはあまり良くないです。

前回勉強した時のブログを読むと「この回のtutorialに載っている事は全部知っている事である。」とまとめられていました。

今、読み直すと「全部知っている」は完全な嘘で、例えばTaskクラスのEvent関数である以下に示したものの違いは今でも分かっていません。

f:id:kazuhironagai77:20210117213423p:plain

やっぱり人間は本当は良く分かっていない時には「全部分かった」の様な過剰な自信をみせる発言をしてしまうんですね。

一寸だけ、これらのEvent関数の違いを調べて見ましょう。

このサイトにAIとGenericの違いの説明がありました。

f:id:kazuhironagai77:20210117213442p:plain

この説明によるとAIControllerを使用した場合は、AIのEventを選択する必要があると書かれています。

Abort、ExecuteとTick関数の違いについては、見つかりません。

このサイト

f:id:kazuhironagai77:20210117213502p:plain

簡単なBehavior Treeを作成してテストしたら理解出来るとありましたので、試してみます。

以下の様なBehavior Treeを作成しました。

f:id:kazuhironagai77:20210117213520p:plain

Task、PrintStringを以下の様に作成しました。

f:id:kazuhironagai77:20210117213538p:plain

これでテストします。

f:id:kazuhironagai77:20210117213557p:plain

普通にPrintされました。

次にAbort AIを接続します。

f:id:kazuhironagai77:20210117213615p:plain

これでテストします。

f:id:kazuhironagai77:20210117213637p:plain

あれ、何も表示されません。

この方法じゃ単純過ぎてAbortは発動しないのかと思って停止したら、その瞬間に表示されました。

f:id:kazuhironagai77:20210117213655p:plain

AIをAbortした時とは、そのAIが開放された時、つまり一般的に言えばゲームを終了した時に発動するTaskのようです。

そうするとTickはそのAIを使用している間、ずっと発動していると言う事になります。

Tickも確認してみましょう。

f:id:kazuhironagai77:20210117213713p:plain

テストします。

f:id:kazuhironagai77:20210117213730p:plain

その通りでした。

一般的なTaskを作成する分にはExecute AIだけ使用すれば良いみたいです。

2.1.2 AI Perception

前のブログを読んでみたらAIPerceptionについて結構勉強していました。はっきり言って何も覚えていません。動画を見るとAIPerceptionを使用してAIが操作するキャラに視覚を追加しています。ここで知りたい事は、どうやってAIが操作するキャラに視覚を追加出来るのかです。

それをまず勉強します。その後で、前のブログに書かれているAIPerceptionについての勉強を読んでみます。

AI Perceptionを使用する前に、Behavior Treeを以下に示した様に変化させました。

f:id:kazuhironagai77:20210117213809p:plain

Blackboardに作成したBoolean変数であるCanSeePlayerがfalseなら左側、trueならば右側のSequenceが実行されるだけです。

f:id:kazuhironagai77:20210117213826p:plain

このCanSeePlayer変数の値をAIPerceptionを使用して変えます。

まずAIPerceptionですがAIControllerクラス内に作成します。

f:id:kazuhironagai77:20210117213845p:plain

f:id:kazuhironagai77:20210117213852p:plain

作成したら、AIPerceptionのSenses Configにelementを一つ追加します。

そしてImplementationの項目をAlSense_Sightにします。

これは視覚を使用すると言う意味だそうです。

f:id:kazuhironagai77:20210117213910p:plain

以下の項目にチェックを入れましたが、単に敵も味方の第三者も同列に扱うと言う意味です。
詳しい説明は後のtutorialに出てくるそうです。

f:id:kazuhironagai77:20210117213928p:plain

今度は見られる方のキャラクター、この場合はThirdPersonCharacterにAIPerceptionStimuliSourceを追加します。

これが在る事でAIPerceptionから知覚される事が可能になります。今回の場合は視覚によって見つかると言う事になります。

f:id:kazuhironagai77:20210117213957p:plain

更に、AIPerceptionStimuliSourceのAI Perception内でAuto Register as SourceをチェックしてRegister as Source for SensesにAlSence_Sightを選択します。

こうする事で、敵から視覚によって認識されるようになります。

f:id:kazuhironagai77:20210117214024p:plain

そして、AIController内で以下ように実装します。

f:id:kazuhironagai77:20210117214039p:plain

On Target Perception Updated(AIPerception)を使用する事や使用方法については特に疑問はないんですが、これだと一端BlackboardのCanSeePlayer変数がtrueになったらその後はずっとtrueのような気がします。調べてみたらそんな事は無かったですが少し不思議です。

まあ、それ以外でそんなに難しい事はありませんね。

  • 見る側にAIPerception
  • 見られる側にAIPerceptionStimuliSourceを追加する事
  • Eventの開始はAIController 内にOn Target Perception Updated(AIPerception)を使用する事

と言う事ですね。

今度は前に勉強した内容を読んでみます。

一杯書かれていて、沢山勉強していますが、先程説明した

  • 見る側にAIPerception
  • 見られる側にAIPerceptionStimuliSourceを追加する事

が分かっていないみたいです。それで大変混乱しています。

去年、理解出来ていなかった内容が今は理解出来ると言う事は、UE4の勉強が着実に進んでいる証拠でもあります。だからまあ良いでしょう。

2.1.3 Chasing the Player

一端、追いかけ始めたら普通ならずっと追いかけるはずです。しかし今のAIはキャラを見失ったら適当な場所に行ってしまいます。それを直します。

Behavior Treeのdecorator、blackboardの設定であるFlow ControlのObserver abortsの値をBothに変更します。

f:id:kazuhironagai77:20210117214225p:plain

しますがこの意味が良く分かりません。一応説明しているのですが、よく聞き取れません。

このサイトに説明されていました。

f:id:kazuhironagai77:20210117214251p:plain

分かりました。

これは何かを実行している途中で、If節の条件が変わってしまった時の対応について決めているです。

  • 普通のif節だったら、条件をチェックした後にその条件が変わっても無視してそのまま進行します。これと同じなのがNoneです。
  • Selfに設定した場合は、今いるNodeとその下にあるNodeの実行を止めて、隣のnodeを実行します。
  • Lower Priorityは今いるnodeとその下に付いているnodeは実行しますが、今いるnodeより右側にあるnodeは無視します。
  • 最後のBothは全部中止して一つ上のnodeに戻ります。

これで合っているはずです。Bothにセットしていますので、Playerを見かけたら何かをやっている途中でもPlayerを追いかけてくるはずです。

テストします。

正直、良く分かりません。色々条件を変更しましたが、完全に上記の解釈が合っていると確認は出来ませんでした。

Playerが逃げ続ける場合、AIはPlayerに対して真っ直ぐ追いかけるはずですが、実際はジグザグに追いかけています。これを直します。

直し方は単純で、新しいTaskを作成してそれをMove toの代わりに使用します。

f:id:kazuhironagai77:20210117214324p:plain

ジグザグは直りましたが今度はカクカクするようになってしまいました。

解決策はCool down Decoratorを追加する事です。Cool down Decorator はDelay関数と同じ働きをするみたいです。

f:id:kazuhironagai77:20210117214342p:plain

0.2秒にセットしたらカクツキが無くなりました。

最後にカーブを曲がるときにもっとスムーズに曲がれるようにします。

以下の部分の設定を変更しました。

f:id:kazuhironagai77:20210117214358p:plain

この項目はPlayer controllerによってキャラクターが操作されている場合にcheckすべきなんだそうです。だからcheckを外します。

f:id:kazuhironagai77:20210117214416p:plain

こっちの設定は回転する時にスムーズに回転するための設定らしいです。正直良く分かりません。

テストしてもこの部分は実際の違いは正直分かりませんでした。

以上です。

最後に前、勉強した時は何と言っていたのか確認します。

まず、Behavior Treeのdecorator、blackboardの設定であるFlow Controlの設定についてですが、以下の様に述べています。

f:id:kazuhironagai77:20210117214439p:plain

これって一個ずれていますね。と言うか、今回勉強した時は、この説明の部分何言っているのか聞き取れなかったんですが、前回勉強した時は普通に聞き取れていたみたいですね。イギリス訛りなんで結構聞きにくい部分があるんですが、日本に帰って来てからlistening力落ちていますね。結構ショックです。

もう一回、今度は真剣に聞いてみます。

言っていますね。Noneの時に一連の動作を止めて、次のNodeの動作をすると。これはこのサイトの説明と違いますね。このサイトではNodeはなにもしないと言っています。多分サイトの説明の方が合っているんでしょう。Tutorialの説明はLower Priorityの辺りからモゴモゴ言って何を言っているのか分からなくなっていますから。

簡単なテスト方法を思いついたら後で確かめてみます。と思ったら、前回も同じ事言っていました。

その後の解説は今回と全く同じです。最後のAIが回転する所が良くなっていると言っているが全然分からないと今回と全く同じ事言っています。

そして最期にこんなのAIの勉強じゃないと怒っています。

まあ。UE4のAIの基本を知りたい時に、こんな走り方の微調整のやり方を教わっていたら、頭に来るのは分かります。ただ今回は実際のモンスターのためにもっとマシなAIを作成したいと言う目的があるので、モンスターが回転する時の微調整なんかも大切です。のでもうちょっとだけ勉強を続けようと思います。

2.1.4 Patrolling NPC

今度はNPCにパトロールをさせるそうです。正直この機能こそモンスターのAIに追加したかった機能です。しっかり勉強しましょう。

まずactorクラスからBPを作成します。PatrolPathと名付けます。

f:id:kazuhironagai77:20210117214502p:plain

このクラス内にVectorタイプのarray変数であるPathPointを作成します。

この変数はInstance editableです。

f:id:kazuhironagai77:20210117214519p:plain

このクラスのinstanceをマップ内に配置します。

f:id:kazuhironagai77:20210117214536p:plain

配置したinstanceのPathPointに3つの要素を追加します。

f:id:kazuhironagai77:20210117214552p:plain

f:id:kazuhironagai77:20210117214600p:plain

注意点として最初の要素の値は0,0,0である必要があるそうです。

このInstanceで指定した場所にNPCは移動していきます。つまりNPCにパトロールをさせると言う事は、このPatrolPathのinstanceが保持するPathPointの値をどうやってUE4_AIに渡すのかと言う事につきます。

次にNPCのBPを開きPatrolPathタイプの変数、PatrolPathを作成します。

f:id:kazuhironagai77:20210117214639p:plain

この変数はInstance Editableに指定します。

配置したNPCのinstanceのPatrol Pathに先程作成したPatrolPathのInstanceを指定します。

f:id:kazuhironagai77:20210117214704p:plain

これは名前がクラスとinstanceが同じなので混乱しますが、全部Instanceです。

なので、それぞれのNPCに違う場所のパトロールをさせる事が出来るはずです。

次にBehaviorTreeを示します。

f:id:kazuhironagai77:20210117214729p:plain

Blackboardには2つの変数を追加しました。

VectorタイプであるPatrolPathVectorとintタイプであるPatrolPathIndexです。

f:id:kazuhironagai77:20210117214745p:plain

最初のTaskであるFindPathPointで移動する場所のVectorNPCからBlackboardに新しく作成した変数、PatrolPathVectorにパスします。

次のTaskであるMove toでその場所に移動します。

その次のTaskであるIncrementPathNextでPatrolPathIndexの値を1だけ増やします。

FindPathPointの実装です。

f:id:kazuhironagai77:20210117214802p:plain

NPCをどうやってパスするんだと思っていましたが、EventReceiveExecuteAIから直接得る事が出来ました。

そこさえ分かれば後は普通のUE4_AIの作成と同じです。

前回の勉強の記録もチェックしておきます。

3Dwidgetについての解説が書かれていました。

f:id:kazuhironagai77:20210117214820p:plain

これは重要な事でした。

後は、ソフトウェアデザインの観点からComponentとしてPatrolPathタイプの変数を作成しているのが注目と述べていました。これは言われてみればそうです。あの当時はゲームにおける正しいソフトウェアデザインとは何か?について凄い考えていたのでこんな事も考えていたんですね。

2.1.5 Patrolling NPC Continued

NPCがずっとパトロールする様にセットします。

取りあえず、前回の勉強ではどんな事を言っていたのか確認したら

f:id:kazuhironagai77:20210117214842p:plain

とありました。

思い出してきました。この辺はもうやりたくなくて兎に角辞める理由を探していた所に、tutorialで言っている通りに動かないのでこれ幸いとここで中止にしたのでした。

ただ、本当に Tutorial通りにやっても駄目だった場合はどこが駄目だったのか記録する必要はあったと思います。その辺も含めてもう一度やってみます。

f:id:kazuhironagai77:20210117214859p:plain

普通に出来ました。

最後までやったのですが普通に動きました。 Tutorial通りにやっても何処にも問題ありませんでした。

うーん。

これはどう総括すべきなんでしょうか?

. まとめと感想

今週は、UE4のAIの復習をやっているだけで終わってしまいました。しかしUE4のAIも一通り勉強する必要があるので、来週もこのままtutorialの続きをやる事にします。

UE4_AIはどの辺まで理解すれば基本は出来たと言えるのでしょうか?

TaskクラスのEvent Abort AIやDecoratorのObserver abortsの値であるSelf やLower Priorityも理解する必要があるのでしょうか?

この辺の疑問を解消するため、久しぶりに「Unreal Engine 4で極めるゲーム開発」を読んでみました。23章に少しだけですがかなり深堀した解説がありました。その説明は私の理解している機能とちょっと違っていたので「え!そんな意味だったの?」とかなり驚きました。TaskクラスのEvent Abort AIやDecoratorのObserver abortsの機能に関しては、来週も引き続き勉強します。

 

「Unreal Engine 4.xを使用してRPGを作成する」の足りない部分を作成する モンスターを配置する part 2

f:id:kazuhironagai77:20210110213544p:plain

<前文>

自作ゲームを売る

今年の目標ですが、自作したゲームを販売します。

1.良いゲームを作る

個人で作成する以上、AAAのような作品は作れません。しかし買う側からしたら個人が作成したから精度が低いとかは関係ないです。買って得したゲームにしたいです。

それで色々考えています。

  • ストーリーを重視
  • ゲームをプレイした人の意見を取り入れる
  • ゲーム好きな人の意見を取り言える
  • 佳作を目指す。満点は狙わず、及第点を狙う。
  • 一点だけ凄い所を作る

1.1 ストーリーを重視

学生時代、本読むのが大好きだった私は、ストーリーを作成するのが大好きなので、この辺で他のRPGと差を出せると思います。面白いストーリーには理由があります。その辺を丁寧に整理する事で自分のゲームのストーリーを面白く出来ると思います。

1.1.1 次の町にたどり着くたびに、新しい情報を得る

ストーリーは変化です。変化は新しい情報を得る事で生まれます。

1.1.2 Character development

ユーザーが新しい情報を得る時に、ユーザーが操作するキャラもその情報を得ます。その時にそのキャラクターがどのように成長するかが、ストーリーが面白くなるかどうかの分かれ目です。

1.1.3 Character そのものの魅力

キャラが成長する前にそのキャラそのものに魅力がないとユーザーは興味を示しません。魅力的なキャラが登場する必要があります。

最近、鬼滅の刃がヒットしているらしいですが、登場するキャラの魅力が他の漫画に比べて段違いです。

1.1.4 Villain の深堀り

日本のアニメがアメリカで受けている理由の一つが、悪役が単に悪い奴でなく、もう一人の主人公である所です。Villainが何故そう言う行動を取るのかの細かい設定が大切です。

1.2 ゲームをプレイした人の意見を取り入れる

ゲームを買ってプレイしてくれた人のコメントを参考にして改良する。

1.3 ゲーム好きな人の意見を取り言える

これが大切なんですが、どうすれば意見を貰えるのか分かりません。検討中です。

1.4 佳作を目指す。満点は狙わず、及第点を狙う

バグを残して販売、他人の著作物の無断使用、などの最低限のルールを破ってしまわない様に気を付ける事。

1.5 一点だけ凄い所を作る

一点だけAAAのゲームですら出来ない凄い事をやるのだったら、materialをもっとアニメ調に変化する事とかですかね。

2. 情報ハイウェイに乗る

上記に良いゲームを作るには何が必要なのかを考察したのですが、何かが違うと思いました。そこで思いついたのがこれです。このアイデアは元々、テレビとインターネットの違いとは何なのか?を考えていて発見したのですが、かなり深堀出来そうなアイデアです。

まだどのような切り口で分析したら良いのか分かっていませんので、今回は簡単にアイデアだけを説明します。

情報ハイウェイに乗るとは、YouTubeの急上昇に載ったり、世界的なinfluencerに紹介されたりする事を指します。

ゲームを売る為には、この情報ハイウェイに乗る必要があります。しかも良い評価と共に乗る必要があります。悪い評価と共にYouTubeの急上昇に載ったらそれこそ載らない方が良かったとなります。所謂炎上というやつです。

2.1 天気予報

具体的な考えは、まだあまり発展していませんが、日本に帰って来てびっくりした事の一つが、みんなが良く天気予報を見る事でした。確かに日本では雨になると絶対に傘が必要になるので明日の天気は結構大切です。情報ハイウェイにおいて天気予報はかなり重要な位置を占めている気がしてます。

2.2 流行りに乗る事と著作権

アメリカで日本のアニメが大ブームになった時、英語圏にいた極少数のアニメの専門家は瞬く間に有名に成りました。ポケモンgoが流行った時も同じでポケモンgoについて解説するYouTuberは一気に有名に成りました。

この流行りに乗る事と情報ハイウェイに乗るはある意味同じです。しかし勝手に流行り物を自分のゲームを売るために利用したら著作権違反で訴えられてしまいます。

著作権を破る事と引用は全く違います。

簡単に言えば、誰々が著作物でこう言っていますが、私はこう思います。というのが引用です。

しかしネットの世界(日本だけでなくアメリカも)ではこれらはゴッチャにされて、地位の高い人が低い人の著作物を無断で利用する時は引用にあたり、逆の場合は著作権違反になる。と言うまるで畜生の世界のルールがそのまま、実践されています。

だからアニメの批評をするのに、そのアニメの絵を載せるのは引用であって著作権違反ではないんですが、現実では出版社の意向に沿わない批判をした場合は直ぐに著作権違反で訴えられ、本当に著作権違反かどうかも検討されない内にネットから削除されます。これは何も日本に限った事じゃなくて、アメリカでも大体同じです。最近でもあるvtuberのイラストがペドに当たるんじゃないか?と批判したYouTubeのchannelが著作権違反で一発で消されました。vtuberのイラストがペドに当たるかどうかを語るにはそのイラストを引用する必要は絶対あると思われますがお構いなしです。

この辺の事情を理解した上で、流行り物に乗りまくって情報ハイウェイに入る事が大切と考えます。

2.3 局所的に有名になる

情報ハイウェイに乗る前に、ある一部の人だけには高評価を得る。つまり局所的に有名に成る事も戦略として大事だと思います。この局所の人達に誰を選ぶのか?それを考えるのは結構難しいです。私としては日本とアメリカにいる日米のハーフやバイリンガルの人達の間で高評価を得るのがやりたいのですが、そんなネットワーク存在していませんし。プログラマーの間で高評価を得るのはかなり難しいですし。

それで思いついたのですが、近所や知り合いに売って高評価を得る。というのは結構有りかと思っています。

色々考えましたが結構方法はありそうです。それでは今週の勉強を始めます。

<本文>

今週はもっと沢山のモンスターを配置して問題を抽出します。

先週のバグについては特に今週やらないといけない部分はないと思われるのバグの直しは無しです。

1.モンスターをもっと配置してバグを見つける

以下の様に並べてみました。

f:id:kazuhironagai77:20210110213858p:plain

単にMonsterSpawnDataを増やしただけです。

f:id:kazuhironagai77:20210110213915p:plain

戦闘してみます。

途中で死ぬかと思ったらレベルが上がって全回復して全部の敵を倒せました。面白く無いです。ゴブリンを倒して得られる経験値を調節します。

f:id:kazuhironagai77:20210110213934p:plain

もう一度戦ってみます。

今度は死にました。

そしたらエラーです。

f:id:kazuhironagai77:20210110213952p:plain

これから直していきます。

戦闘で負けた時に実行するコードを以下の様に変更しました。

f:id:kazuhironagai77:20210110214008p:plain

テストします。

簡単にテスト出来るように主人公のパラメーターをモンスターより弱くします。

f:id:kazuhironagai77:20210110214123p:plain

エラーは出なくなりました。

f:id:kazuhironagai77:20210110214141p:plain

しかし沢山のバグがまた出て来ました。

バグ1

戦闘で主人公が死んでいるのに復活しています。

f:id:kazuhironagai77:20210110214205p:plain

バグ2

「もう一度始める」ボタンを押すと戦闘から始まります。戦闘前の場所から始まるべきです。

f:id:kazuhironagai77:20210110214315p:plain

1.1  バグ1の直し

Set Anim Instance Classを外しました。

f:id:kazuhironagai77:20210110214338p:plain

むごたらしく死んでいます。

f:id:kazuhironagai77:20210110214358p:plain

1.2 バグ2 の直し

Game Overになると以下のwidgetが開かれるのですがどこから開いているのかが分かりません。

f:id:kazuhironagai77:20210110214516p:plain

幾らBPを見てもわからないのでUE4C++を見たら、こっちで実装していました。

f:id:kazuhironagai77:20210110214535p:plain

これを読むと、Widgetの方だけ変更すればバグは直せそうです。

Widget、GameOverScreenのreStartButtonをクリックした時に実行するコードを以下の様に変更しました。

f:id:kazuhironagai77:20210110214556p:plain

テストします。

「もう一度始めるボタン」を押すとMap1の元の位置に戻って来ました。

f:id:kazuhironagai77:20210110214739p:plain

直りました。

1.3 GameOverScreenのデザインの改良

GameOverScreenに、少なくとも「止める」「スタート画面に戻る」ボタンの追加は必要だと思います。

以下の様に作り直しました。

f:id:kazuhironagai77:20210110214905p:plain

UIのみ操作出来る様にSetInputModeUIOnlyを追加しました。

f:id:kazuhironagai77:20210110214932p:plain

SetInputModeUIOnlyを追加しましたが、このやり方が正しいのか正直覚えていません。戦闘画面に入るとcharacterの操作は全く出来ませんが、その時にSetInputModeUIOnlyを使用している形跡が全く無いです。後でもう一度この事は調べます。

「もう一度始めるボタン」の実装は以下の様にしました。

f:id:kazuhironagai77:20210110215002p:plain

特に記述する事はありません。前の実装ではExecute Console Commandを使用していました。

「ゲームの終了ボタン」は以下の様に実装しています。

f:id:kazuhironagai77:20210110215026p:plain

QuitGameを使用するのは初めてです。使用方法に特に気を付ける事は無いはずです。

「スタート画面に戻るボタン」の実装は以下の通りです。

f:id:kazuhironagai77:20210110215105p:plain

RPGGameInstanceに保持しているデータは全て消す必要があると考えていますが、後でその辺は直します。

テストします。

f:id:kazuhironagai77:20210110215127p:plain

即興で作成した割には中々いい感じです。

この画面でキャラを操作しようとしましたが動きません。SetInputModeUIOnlyを使用するのが正しいやり方なのか一寸覚えていませんが、目的は達せられています。

「もう一度始めるボタン」をクリックします。

f:id:kazuhironagai77:20210110215203p:plain

戦闘が始まる前に戻って来ました。

セーブポイントを作成したら、その場所からやり直すようにしたいです。

「ゲームの終了ボタン」を押したら、ゲームが終了しました。

「スタート画面に戻るボタン」を押したらゲームの最初に戻りました。

f:id:kazuhironagai77:20210110215224p:plain

スタート画面のボタンは新しく始める以外は適当にしか作成していないので、後でこれらも直します。

2. 更なるバグの発見

更にバグを見つけるために配置したモンスターを倒していきます。

2.1  向きの保持

戦闘画面から頻繁に元のマップに戻ってくると酔い易くなっている事に気が付きました。

2.2 戦闘が永遠に終わらないバグ

プレイヤーが操作するキャラがすぐ負けてしまうので、武器と防具を装備させて戦闘に参加したら、お互いの防御力が互いの攻撃力を上回り、お互いにダメ―ジが与えられなくなりました。

f:id:kazuhironagai77:20210110215307p:plain

戦闘が終わりません。

2.3 拾えるアイテムは(!!)表示を追加

戦闘システムのバグではありませんが、NPCとの会話の様に、拾えるアイテムは(!!)で知らせてほしいです。

f:id:kazuhironagai77:20210110215334p:plain

(!!)をアイテムに追加します。

2.4 徘徊するモンスター用の別なAIが必要

プレイヤーの操作するキャラがモンスターが動ける領域に侵入すると、全てのモンスターが、プレイヤーの操作するキャラ目がけて追いかけて来ます。

f:id:kazuhironagai77:20210110215358p:plain

f:id:kazuhironagai77:20210110215410p:plain

非常に単調な動きで、不自然に感じます。

2.5 待ち伏せするモンスターの作成

ある領域に侵入するとモンスターが出現するパターンも面白いと思います。

2.6 武器を装備した状態で戦闘開始したらアニメーションが…

武器を装備した状態で戦闘開始したらアニメーションが最初の戦闘の時だけ武器持ちのアニメーションで、2回目から武器なしのアニメーションになってしまいました。

f:id:kazuhironagai77:20210110215503p:plain

正直、こんなに沢山のバグが見つかってショックです。バグ出しに挑戦して良かったです。

3. バグだし条件の設定

プレイヤーの操作するキャラのパラメーターの値を変えて、8体のモンスターと戦う事で、バグを見つけようと思います。

3.1 魔法使い的なパラメーター

プレイヤーの操作するキャラのパラメーターの値を、魔法中心で戦う事を強いられる値に変更しました。

f:id:kazuhironagai77:20210110215544p:plain

一体倒すと、HPが40、MPが70、そして金貨が110になっていました。

f:id:kazuhironagai77:20210110215605p:plain

因みにポーズ画面を押すとモンスターの追跡も止まっていました。

f:id:kazuhironagai77:20210110215630p:plain

MPは70も残っていますが、HPは40しかありません。道具屋に行って回復薬を買えるだけ買って残りのモンスターと戦う事にします。

アイテムの回復薬を使用した後に、ゴブリンの攻撃を受けたら、アイテム使用時用のコメントが表示されました。

f:id:kazuhironagai77:20210110215649p:plain

一個バグが見つかりました。

一応、8体全部倒せました。しかし持っていた金貨100枚が0になってしまいました。

f:id:kazuhironagai77:20210110215715p:plain

ゲームを終了したらエラーが出ていました。

f:id:kazuhironagai77:20210110215740p:plain

戦闘中に流しているBGMを保持しているはずのBattleBGMに値が無いと言っているみたいです。

f:id:kazuhironagai77:20210110215804p:plain

これは多分ですが、戦闘中、ほったらかしてブログを書いていたら戦闘BGMの演奏が終わったので、勝手に消滅してしまったと思われます。

確認します。

戦闘BGMが終了する前に戦闘が終了した時は、ゲームを終了してもさっきのエラーは出て来ません。

f:id:kazuhironagai77:20210110215839p:plain

今度は戦闘BGMが終了するまでほっときます。

エラーが出ました。

f:id:kazuhironagai77:20210110215904p:plain

エラーの内容は微妙に違いますが、同じ個所です。これだけは今直してしまいます。

元の音源のLoopingにチェックを入れます。

f:id:kazuhironagai77:20210110215922p:plain

これで戦闘中に戦闘BGMが終了する事はないはずです。

テストしてみます。

戦闘中にして、10分位別な作業していましたが、戦闘BGMが続いていました。戦闘終了後にゲームを終了してもエラーは出ませんでした。

直りました。

3.2 初級者的なパラメーター

今度はMMPを20まで下げてみました。

f:id:kazuhironagai77:20210110215948p:plain

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

最後の2体が倒せずに死んでしまいました。

f:id:kazuhironagai77:20210110220017p:plain

ゲームオーバーの時は、専用の音楽がほしいですね。

特にバグは見つかりませんでしたが、モンスターやプレイヤーが操作するキャラのパラメーターの調節は、ゲームの面白さに直結する事が分かりました。

3.3 ギリギリモンスターに勝てない位のパラメーター

やっぱり最初はモンスターにぎりぎり勝てない位が良い気がします。以下のパラメーターで試してみます。

f:id:kazuhironagai77:20210110220047p:plain

一体も倒せず負けました。

f:id:kazuhironagai77:20210110220215p:plain

現在は負けてもペナルティが一切ないので何か作成したいです。

落ちている剣を拾って装備しました。

f:id:kazuhironagai77:20210110220249p:plain

その状態で戦ったらギリギリ勝てました。

一回死んでからアイテムが置いて在る場所に戻ると、既にアイテムを取ったにもかかわらず新しいアイテムが置いて在りました。

f:id:kazuhironagai77:20210110220308p:plain

これも後で直します。

特に新しいバグは見つかりませんでした。

3.4 プレイヤーが操作するキャラのパラメーターの調節で分かった事

最初はモンスターが倒せない位で、短剣を装備したらギリギリ一体倒せる位、更に盾を装備する事で、沢山のモンスターと戦えるのが最適な値と感じました。これを達成する為にはもはやゲーム一部を作成するだけでなくゲームそのものの作成が必要と思いました。

回復薬ですが、戦闘後に使用するには有効ですが、戦闘中に回復薬を使用しても意味がないです。これはこれで面白いと思いました。

4. バグの直し

これまでに以下のバグが見つかりました。

  • 戦闘画面から戻って来た時に向きが変わっている。
  • 戦闘が永遠に終わらない時がある。
  • 拾えるアイテムにも(!!)表示を追加
  • 徘徊するモンスター用に別なAIを作成
  • 待ち伏せするモンスターの作成
  • 2回目から武器なしのアニメーションになるバグの直し
  • 戦闘中にアイテムの回復薬を使用した場合、次の攻撃の後にもアイテム使用時用のコメントが表示されるバグ
  • 同じアイテムが何回も取れるバグ

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

4.1 戦闘画面から戻って来た時に前と同じ向きにする

まず、戦闘が始まった場所をどうやって保持しているのかを確認します。

モンスターの領域にプレイヤーが操作するキャラが侵入するとRPGGameModebaseBPにあるdispatcherのCallCombatBeginがcallされます。

f:id:kazuhironagai77:20210110220404p:plain

RPGGameModebaseBP にあるCombat BeginはMap1上で以下のコードにbindされています。

この時にRPGGameInstaceBPにある変数、Third Person Character Locationにプレイヤーが操作するキャラの現在の位置が保持されます。

f:id:kazuhironagai77:20210110220609p:plain

取りあえずここまでRotationも同じように作成します。

RPGGameInstaceBPに、Third Person Character Locationにプレイヤーが操作するキャラの現在の回転を保持するための変数、ThirdPersonCharacterRotationを作成します。

f:id:kazuhironagai77:20210110220636p:plain

Combat Beginの実装部にこの変数に、プレイヤーが操作するキャラの現在の回転値をコピーさせます。

f:id:kazuhironagai77:20210110220655p:plain

これで戦闘画面に移動する前のプレイヤーが操作するキャラの向きは保持されました。

その値は、もう一度Map1が開かれた時に読み込まれていたので、その時に一緒にプレイヤーが操作するキャラの向きが読み込まれるようにしました。

f:id:kazuhironagai77:20210110220715p:plain

テストします。

f:id:kazuhironagai77:20210110220733p:plain

キャラクターの向きは直っていましたが、カメラがキャラクターを向いています。

その結果、カメラを一回転する必要が出て来て、結局酔います。

色々弄りましたが、カメラの位置の直し方が分かりませんでした。今回はここまでとしてカメラの位置の直しは後でやる事にします。

4.2 戦闘が永遠に終わらない時がある。

お互いの防御力が攻撃力より高い場合、ダメージを与える事が出来ないので永遠に戦闘が続きます。

これは「逃げる」ボタンが機能すれば解決します。

逃げるが成功するためにはCharacterのLuckが敵のLuckより上である必要があります。

f:id:kazuhironagai77:20210110220804p:plain

取りあえず、以下の様に設定を変更してLuckの値が同じならば逃げる事が出来る様にします。

f:id:kazuhironagai77:20210110220824p:plain

テストします。

逃げれましたが以下のようにエラーが大量に発生しました。

f:id:kazuhironagai77:20210110220856p:plain

以下の実装部のSetVolumeMultiplierがEmptyと言っています。

f:id:kazuhironagai77:20210110220924p:plain

調べて見たら、DownVictoryMusicを戦闘から逃げた時もtrueにセットしていました。

f:id:kazuhironagai77:20210110220947p:plain

直します。

テストします。

f:id:kazuhironagai77:20210110221014p:plain

今度はエラーが無くなりました。

f:id:kazuhironagai77:20210110221031p:plain

Luckが敵のモンスターより低い場合でも30%の確率で逃げ出せるようにしました。

これで逃げるボタンを押し続ければいつかは戦闘から逃げ出せるように成ったはずです。

テストします。

敵のモンスターのLuckをプレイヤーが操作するキャラよりも高く設定しました。

f:id:kazuhironagai77:20210110221050p:plain

ひたすら、戦闘から逃げたら4回目くらいで逃げれました。

f:id:kazuhironagai77:20210110221110p:plain

4.3 拾えるアイテムにも(!!)表示を追加

まず、NPCにどうやって(!!)を追加したのか確認します。

f:id:kazuhironagai77:20210110221131p:plain

TextRenderを使用しています。

TextRenderのvisibilityは以下の方法でオンオフされています。

f:id:kazuhironagai77:20210110221155p:plain

結構複雑な仕組みをしています。

思い出してきました。最初、Box内に侵入したらTextRenderのvisibilityをオンにセットしていたのですが、この方法だと全部のNPCのtextRenderがOnになってしまったんです。それで上記のような方法を採用したんでした。

ただこの方法だと、まだ一つ一つのinstanceまでは区別出来ないので、以下の方法でやる事にします。

まず、RPGGameModebaseBPにこれから拾うアイテムの名前を保持する変数を作成します。

f:id:kazuhironagai77:20210110221222p:plain

次に、DroppedItemBase内にそれぞれのインスタンスの名前を保持するための変数、ItemNameを作成します。

f:id:kazuhironagai77:20210110221239p:plain

f:id:kazuhironagai77:20210110221248p:plain

このItemNameはInstance Editableにしてそれぞれのinstanceで違う名前が持てるようにします。

f:id:kazuhironagai77:20210110221315p:plain

更に、TextRenderとPointLightをDroppedItemBaseに追加します。

f:id:kazuhironagai77:20210110221337p:plain

プレイヤーが操作するキャラがボックス内に侵入したら、そのボックスが属しているDroppedItemBaseのインスタンスが持つItemNameの値、つまり名前を、RPGGameModebaseBPのItemNameにコピーします。

f:id:kazuhironagai77:20210110221356p:plain

勿論、プレイヤーが操作するキャラがボックス外に出た時は、RPGGameModebaseBPのItemNameにNoneをセットする事で、それまでセットされていた名前を消します。

f:id:kazuhironagai77:20210110221442p:plain

そして、RPGGameModebaseBPのItemNameにセットされている名前とプレイヤーが操作するキャラが侵入しているboxが属しているDroppedItemBaseのインスタンスが持つItemNameの名前が一致しているか確認します。

f:id:kazuhironagai77:20210110221506p:plain

一致した場合は、設置したPointLightとTextRenderが表示されます。しない場合は表示されません。

これで放置されているアイテムが拾える場合は、そのアイテムの上に(!!)が表示されるはずです。

テストしてみます。

(!!)マークが表示されました。きちんと剣だけ表示されています。隣の盾には(!!)マークは表示されていません。

f:id:kazuhironagai77:20210110221530p:plain

念のために、隣に移動して見ると今度は盾の上に(!!)が表示されました。そして剣の上の(!!)は消滅しています。

f:id:kazuhironagai77:20210110221547p:plain

出来ました。

4.4 徘徊するモンスター用に別なAIを作成

現状では、全てのモンスターは、プレイヤーが操作するキャラが、NavMeshBoundsVolumeが指定した範囲内に侵入した途端、全力でそのキャラを追いかけるだけです。持ち場を設定してその範囲内に、プレイヤーが操作するキャラが侵入した時だけ追いかけるようなAIを作成したいです。

つい最近UE4のAIの勉強をやったのに既にやり方を忘れてしまいました。やる内容が大量にありそうなので、この部分は別に枠をとって後でやる事にします。

4.5 待ち伏せするモンスターの作成

ある場所にプレイヤーが操作するキャラが侵入したらモンスターが突然発生しだすトラップ的な物を作成したいです。

以下の様に作成しました。

f:id:kazuhironagai77:20210110221617p:plain

MonsterTrapの方ですが、Boxを追加しただけのActorです。

f:id:kazuhironagai77:20210110221636p:plain

このBox内にプレイヤーが操作するキャラが侵入したら以下のコードによってモンスターが発生します。

f:id:kazuhironagai77:20210110221719p:plain

突然、モンスターが発生するとユーザーが混乱するかもしれないので、以下のwidgetも追加しました。

f:id:kazuhironagai77:20210110221737p:plain

テストします。

以下の様にモンスターが現れました。結構急に出て来ます。

f:id:kazuhironagai77:20210110221754p:plain

これでOKとします。

4.6 二回目から武器なしのアニメーションになるバグ

これは一番直さなければならないバグです。

原因が分かりました。

PlayAnimationノードを使用しているため、いつも使用しているMyThirdPerson_AnimBPを一端外している様です。そのためMyThirdPerson_AnimBPの変数であるWithWeaponの値が消滅してしまったようです。

f:id:kazuhironagai77:20210110221821p:plain

この事からキャラのアニメーションはAnimBPで一括して管理した方が良くPlayAnimationノードの使用は中止した方が良いと思われます。

ただし、この理論が正しいかの確認をするために、一端、以下に示したコードを戦闘で武器を装備した状態で攻撃をした後に追加しました。

f:id:kazuhironagai77:20210110221839p:plain

これでテストします。

f:id:kazuhironagai77:20210110221856p:plain

出来ています。

理論は正しい様です。

PlayAnimationノードの使用は中止してキャラのアニメーションはAnimBPで一括して管理する。これも結構な量になりそうなので別に枠をとって後でやります。

4.7 戦闘中にアイテムの回復薬を使用した場合、次の攻撃の後にもアイテム使用時用のコメントが表示されるバグ

理由が分かりました。

プレイヤーが操作するキャラがアイテムを選択すると対戦しているモンスターの選択も自動的にアイテムになってしまっています。

以下の方法で直しました。

f:id:kazuhironagai77:20210110221920p:plain

Itemを選択したと判断されてもその後で、プレイヤーが操作するキャラかモンスターを検査します。モンスターの場合はAttackと同じセリフになるようにします。

テストします。

f:id:kazuhironagai77:20210110221937p:plain

プレイヤーが操作するキャラがアイテムを使用した後に、モンスターが攻撃した時も正しいセリフが選択されています。

直りました。

4.8 同じアイテムが何回も取れるバグ

4.8.1 BPを使用してアイテムを動的に生成する

現状、マップ内に直接Instanceを配置したケースしかないので、BPで動的に作成したItemをマップ内に配置出来る様にします。

まず、Dropped Item Baseを作成するためには、最低でも以下のパラメーターが必要です。

f:id:kazuhironagai77:20210110222008p:plain

それで以下のStructureを作成しました。

f:id:kazuhironagai77:20210110222025p:plain

そのStructureをタイプとしたArray、ItemSpawnDataをRPGGameInstanceBPに作成しました。

f:id:kazuhironagai77:20210110222048p:plain

試しに2つほど要素を作成しておきます。

f:id:kazuhironagai77:20210110222111p:plain

今度はmap1内でSpawnItemsと名付けた関数を作成します。この関数はItemをSpawnするための関数です。以下に実装部を示します。

f:id:kazuhironagai77:20210110222134p:plain

Map1のEventBeginPlay関数でこの関数を呼び出します。

f:id:kazuhironagai77:20210110222153p:plain

テストします。

f:id:kazuhironagai77:20210110222211p:plain

Itemは出来ています。

BPを使用してItemを動的に生成する事が出来ました。

4.8.2 収得したアイテムが二度と生成されないようにする。

PickUpItemウィジェットに以下の関数を作成します。

f:id:kazuhironagai77:20210110222234p:plain

f:id:kazuhironagai77:20210110222242p:plain

この関数を「拾う」ボタンが押された時に実行すれば、そのボックス内にあったアイテムは二度と生成されなくなるはずです。

f:id:kazuhironagai77:20210110222301p:plain

テストしてみます。

アイテムを拾った後、戦闘を行い、map1に戻って来ました。拾ったアイテムがある位置に行ってみるとその場所にあったアイテムは生成されていませんでした。

f:id:kazuhironagai77:20210110222332p:plain

一応、完成です。

5.まとめと感想

今週はここまでとします。

今年は、正月返上でやろうとしたのですが、結局休んでしまいました。だから今回は2週間分まとめてブログに載せています。