前回はLevel間で情報をやり取りするだけのためにC++コードを書きました。
しかし、Twitterでも指摘されたように、セーブデータを使うことでBlueprintのみでの実装も可能です。
もちろんこの方法は考えなかったわけではないのですが、個人的には小さなデータを受け渡しするだけのためにセーブデータを利用するというのは好きになれません。
この辺は古くからのコンシューマゲーム開発者だからかもしれません。
PS1、PS2、DC辺りまでは内部ストレージを持っているハードウェアは稀でした。
たいていはメモリーカードのような小容量でアクセスの遅いものを利用していました。
そのため、そこに一時データを書き込んでLevelのロード後に再び読み出すというのはナンセンスでした。
現在のハードはほとんどが内部ストレージを持っていて、UE4が対象としているハードについてはほぼ間違いなく内部ストレージが十分存在している状態です。
コンシューマ機の場合は規約によって一時データの保存ができないかもしれませんが、最近のハードなら規約に引っかからない可能性もありますね。
とにかく、PCをターゲットとするなら問題ないはずです。
なので今回は、前回やったことと同じことをセーブデータを利用してBlueprintのみで実装してみます。
まず、SaveGameProjectをBlankプロジェクトとして作成します。
椅子とテーブルが表示されたらBox Brushを置いて、前回と同様に椅子の上に置きましょう。
Trigger Volumeにコンバートしたらコピーして、もう1つの椅子の上に置きます。
2つのVolumeでBeginOverlapイベントを作成、Open LevelでStarterMapに移動するようにします。
この状態でゲームを始めれば、どちらの椅子に接触してもStarterMapに普通に移動するようになります。
ここまでは前回とあまり変わりません。
次は早速セーブデータを作成します。
[New Class Blueprint]を選択し、新しいBPを作成します。
親クラスの選択ではStandard ClassesではなくCustom Classesを開き、Search窓にSaveGameと入力します。
SaveGameクラスが見つかるので、これを選択してBPを作成します。
名前はMySaveGameとしましょう。
SaveGameクラスはかなり特殊なクラスで、ActorとしてLevelに配置することもできなければ、イベントも持っていません。
しかし、このクラスを保存する命令が存在し、これを保存すると持っている変数などの情報がそのまま保存されます。
なので、まずはMySaveGameに変数を追加します。
MySaveGameのEventGraphを開いたら、MyBlueprintタブにあるVariableボタンを押して2つの変数を作成します。
1つはString型の"Message"変数、もう1つはint型の"Difficulty"変数です。
各変数の横に目のアイコンが存在しますが、この目が開いている時はDefaultsでデフォルト値の設定が可能になります。とりあえず開けておきましょう。
あとはLevelBPでMySaveGameを作成し、必要なパラメータを設定して保存するだけです。
ただ、保存するデータが違うだけの同じ処理をいくつも作成するのは嫌ですよね。
そこで、データを保存するまでの一連の流れを関数化してしまいましょう。
関数というのは定型的な処理を様々な場所で利用できるように処理の流れをパック化したものと考えてもらえればいいでしょう。
まあ、プログラマなら説明不要ですが、それ以外の人にはいまいちピンと来ないかもしれません。
関数には引数と呼ばれる、外部から渡すことが可能な変数が存在します。これによって処理を変えたりすることが可能です。
今回は、Messageの文字列と難易度の整数値を引数とした、MySaveGameを保存するための関数を作成します。
LevelBPを開き、MyBlueprintタブのFunctionボタンを押して関数を1つ作成します。
関数名はSaveLevelDataとでもしておきましょう。
引数はDetailsタブのInputsで追加できます。MessageとDifficultyを追加しましょう。
関数は以下のグラフのように作成します。
簡単に解説しましょう。
SaveGameを作成するには"Create Save Game Object"関数を利用します。
この関数を作成する際、"Save Game Class"にMySaveGameを指定します。
この関数で作成されるオブジェクトはSaveGameクラスですが、その実体はMySaveGameクラスです。
そこでSaveGameクラスをMySaveGameクラスにキャストします。
作成したオブジェクトがMySaveGameクラスなので必ず成功するはずです。
キャストに成功したらSet命令で引数のMessageとDifficultyを設定します。
最後に"Save Game to Slot"関数で指定名(今回はLevelTmpとします)のスロットにデータを保存します。
セーブデータの保存はたったこれだけで済みます。
この関数をBeginOverlapイベントとOpen Levelの間に挟み、片方のMessageを"GetoutHere"、もう片方のメッセージを"ComeIn"にします。Difficultyは使わないので適当な値でかまいません。
さて、これでMinimal_Defaultレベルは完了です。次にStarterMapを変更しましょう。
StarterMapのLevelBPを開いたら、前回と同じような処理を組みます。
違いはMessageを受け取る部分です。
"Load Game from Slot"は指定名のスロットからSaveGame(もしくはSaveGameを継承しているクラス)を読み込みます。
スロット名はMinimal_Default Levelでセーブデータを保存したスロット名と同じにします(つまり、LevelTmp)。
この戻り値もSaveGameクラスなので、MySaveGameクラスにキャストします。
キャストしたら実体からMessageを取得すればOKです。
セーブデータが見つからない場合はキャストに失敗するので、この場合はライトが緑になるようにしましょう。
ここまでできたらStarterMapのままでまず実行してみます。
ライトが緑になったら成功です。まだセーブデータが存在していないのでMessageが存在しないという状態です。
Minimal_Defaultに戻ってゲームを開始すると、動作は前回と同じようになります。
つまり、片方の椅子に接触するとライトが赤くなって5秒後に元のLevelに戻る、もう片方の椅子は普通にLevelを移動するだけです。
前回の挙動と少し違う点として、一度セーブデータができてしまうとStarterMapを直接起動した際にライトカラーは緑色になりません。
セーブデータを削除すれば緑にすることはできます。
セーブデータの保存場所はプロジェクトフォルダ内の /Saved/SaveGames フォルダに指定スロット名で存在しています。
これを消せばStarterMap単体起動時にまたライトが緑色になるでしょう。
さて、前回と比べるとかなり簡単にできたと思います。
大きなタイトルを作成する場合にはあまりお勧めできる手法ではないのですが、小さなゲームであればこちらの方がいいのではないでしょうか。