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の箇所でも直そうと思います。