こんばんは。
ピクセルアニメクリエイターのおかか容疑者でございます。
GDevelopで簡単なゲームを作ろう企画。
前回「GDevelopで簡単なゲームを作ろう・22 ~キャラクターセレクト~ 」では、もう一人のキャラクターを作成して、これまでのキャラクターと選択できる仕組みを作成しました。
いろんなキャラクターを登場させて、ゲームに彩りを加えていってみてください。
ゲーム部分はここまでで完成といたしまして、今回と次回はその他で重要になる部分を少しばかりお話ししてまいりましょう。
では、今回はセーブ機能の実装についてです。
※GDevelopのバージョンが5.4.205になりました。
なんか物騒な話してる。
セーブとロードの概念
まず最初に「セーブ」「ロード」についてカンタンなお話だけ。
ゲームをやっているとごく普通に使っている機能ですが、現在の状態を保存しておくのが「セーブ」。それを読み込んで同じ状態から再開するのが「ロード」ですね。
当たり前に実装されている機能ですが、これをゲーム開発で実装しようとなると、あまり考えたことがなくて逆に悩むのではないでしょうか。
(ワタシは「すごい難しそう……」とだいぶ実装に不安を抱えていました。)
ですが、セーブ機能の実装について調べてみると、案外シンプルなものなのです。
幽霊の 正体見たり 枯れ尾花
PCのメモ帳ってありますよね。
メモ帳には文字やら数字やら好きなように書いてメモができます。
セーブはこれと同じなのですね。
つまり、セーブというのは「メモ帳にパラメーターなどをメモしておくこと」。
そしてロードは「そのメモ帳を見ながら同じようにデータをメモ通りに戻していくこと」。
このように考えることで、「なんでえ!カンタンじゃねえか!」と前向きに取り組むことができるようになりました。
多分本格的なゲーム開発だともっとキッチリいろいろやるのだと思いますが、個人開発レベルであればこんな感じの気持ちで実装していっていいのではないでしょうか。
GDevelopでのセーブ機能について
そんなわけで実際にセーブ機能を作ってみましょう。
の前に。
まずはGDevelopでのセーブ機能について少しお話ししておいた方がよろしいでしょう。
GDevelopでは「ストレージ」というものにデータをセーブすることができます。
コチラ↓に公式の日本語ページがございます。
ストレージ – GDevelop documentation
が。
何をしたらいいの……?
情報が少なすぎてよくわからないので、コチラ↓のファイルシステムについての情報を読んだ方がよいでしょう。
File system – GDevelop documentation
(ファイルシステムのページを日本語翻訳した文章です。)
コチラの内容はさっきとは逆に複雑すぎる気がしますが、重要なのはデータの保存には先の「ストレージ」と「ファイルシステム」という2種類のものがあるよ、という部分。
ざっと読んだ感じ、「ストレージ」の方ですとデータをウェブブラウザが保存してくれる仕組みになるようです。(GDevelopのゲームはどういう形式でもブラウザを通しているらしいです。PC向けに出力した場合は……どうなんだろう……)
対して「ファイルシステム」はテキストエディタが使えるもの。まさに先ほど話した「メモ帳」のような形での保存となりますね。
ワタシの知識的にあまり踏み込んだ話もできませんので、今回はストレージを使ってのデータ保存についてお話ししていきますね。
とりあえずこういう種類のセーブ方法があるよということだけ知っておいていただければ。
データをストレージにセーブする
それでは実際にセーブを実装してみましょう。
先に申しておきますが、今回のセーブ機能はあくまでテストとして見せるだけで、このゲームの実装には組み込まないようにしますね。
企画開始当初はセーブ機能まで語る予定ではなかったので、何をセーブすべきか?というのが定まっておりませんでした。
このゲームでゲーム内の状態を全部保存することにするとなんかすごい大変そうですので、今回はデータの一部だけ保存してみる、というテストをしてみることにします。
というわけで今回のセーブの仕様ですが。
「Playerの情報(選択しているキャラクター、移動速度、ライフ、位置)と、残り時間をセーブする」ということにします。
最初に、今回は敵の出現は不要となりますのでイベントをDキーで無効化しておきましょう。
同時にクリア条件のイベントも無効化しておきます。
そして前準備。
ここは必須ではないのですが、テキストの表示をするための2つのフラグ、「load_flag」と「save_ok_flag」という2つのグローバル変数を追加しておきました。
どちらも真偽値で、初期は「偽」にしておきます。
新しくイベントグループ「データのセーブとロード」を作成します。
今回はコチラにイベントを付けていきますね。
まずは一応、modeが1のときにだけセーブできるという条件だけつけておきます。
セーブのボタンはキーボードのQキーで行うということにします。
ですので条件は「Qキーを押したとき」となりますね。
そしてアクションなのですが、今回は「ストレージ」の「値を保存」というアクションを使います。
まずはグローバル変数「player_select」のセーブをしてみましょう。Playerを0(女性)か1(男性)か判断する変数ですね。
「値を保存」を選ぶと項目が3つ出てきます。
ストレージに関して英語ページ↓ではちゃんと解説がなされております。
Storage – GDevelop documentation
のですが。
「ストレージ名」と「グループ」の使い分けがイマイチよくわからないのですね。
個人的には「ストレージ名」で大枠のフォルダを作成して、そのフォルダの中に「グループ」というメモ帳(テキストエディタ)をいろいろ入れられる、というイメージを持っているのですが。これが正しいのかどうかはよくわかりません。
とりあえず、このイメージに基づいて名前を入れてみております。
「ストレージ名」に「Test」という名前を入れました。今回のデータについてはこの「Test」に全て入れていくイメージです。
そして「グループ」に「player_select」という名前をつけます。変数名と同じ名前にしてわかりやすくしております。
そして「式」。ここに実際に保存したい数値となるものを入れます。
グローバル変数ですので「GlobalVariable(player_select)」と書く……のですが。
最近のGDevelopのアップデートにより、「単に変数名だけ書けばシーン変数・グローバル変数ともに使用できる」という仕様に変わっております。
ですのでそれに倣って変数名の「player_select」だけ書いております。違和感ある。
このようなイベントができました。
これをコピーして、「グループ」と「式」の部分を変えてどんどん項目を増やしていきましょう。
そしてできあがったものがコチラになります(3分クッキング方式)。
合計7つのパラメーターをTestストレージ内に保存するイベントができました。
「speed」の項目以下はPlayerのオブジェクト変数を使用しますので、「式」のほうには「Player」の指定を入れてあげましょう。
加えて、グローバル変数「save_ok_flag」をここで「真」にしておきます。
そしてセーブをしたら一度タイトル画面に戻るようにしたかったので、シーン変更で「title_scene」に移動するようにしております。
では、title_sceneの編集へ移りましょう。
最初にセーブできたということを示すテキスト「save_ok」を新規作成します。
テキストはこのような設定で、とりあえず画面の真ん中あたりに置いてあげましょうか。お好きな場所に置いてあげてください。
このテキストについてのイベントを作っておきます。
基本的には非表示ですが、「save_ok_flag」が真のときだけ表示されるようにします。
シーンをまたいで使用するので、「save_ok_flag」はグローバル変数で作成しておきました。「load_flag」も同じ理屈です。
そしてさらに新規イベントで、ロードをタイトル画面から行えるようにします。
今回はWキーでロードするということにしましょう。
「Wキーを押したら「load_flag」を真にしてゲーム画面に移る」というイベントを作っておきます。
変更する変数が全てグローバル変数ならこの時点で読み込みをしていってもよいのですが、シーン変数に触る場合は該当シーンに移動しないと変更ができません。
ですので実際のデータ読み込みのイベントはゲーム画面のイベントで作成するのがよいですね。
というわけでゲーム画面のイベント編集画面に移ります。
「データをセーブとロード」のイベントグループに新規イベントを作成。
条件は先の「load_flag」が真の時です。
アクションは保存のときと同じ要領です。今回は保存ではないので、「値を読み込む」というアクションを選んでください。
同じように3つの項目が出てきますので、「ストレージ名」「グループ」は先ほどセーブで使用したものを一つずつ選んでいきましょう。
そして一番下ですが、項目名が「変数」となっておりますね。
書いてある通り、ストレージのデータを変数に入れることになるのですが、この変数は「グローバル変数」か「シーン変数」にしか適用されないようです。
オブジェクトの指定ができず、オブジェクト変数に直接データを入れることができないみたいですな。
ですので、窮余の策として、オブジェクト変数に入れる必要のあるデータをいったん受け取るためのシーン変数を5つ作成しました。
保管用として「hokan_xxx」という名前にしております。
値は全て0でよいですね。
あとは全てセーブのときと同じ流れで進めましょう。
7つのパラメーターを該当する変数に読み込み、オブジェクト変数に移す必要があるものを追加で変数代入アクションを入れております。
(つまり、オブジェクト変数にロードしたいものは直接は入れられないので、シーン変数をワンクッション挟んで移しているわけですな。)
そして最後に「load_flag」を偽にするのをお忘れなく。
せっかくですので、セーブのときと同じくテキストによる表示もつけておきましょう。
今回は「load_ok」テキストとして、ロードしたよというテキストを作ってUIレイヤーに置いておきました。
イベントはこんな感じに。
先のロード用のイベントに「load_okテキストを表示」を追加しておきます。
そしてその上に「シーン開始時にload_okテキストを非表示にする」イベントの作成。これで基本的にはこのテキストが消えている状態にできます。
最後に、ゲーム中にこのテキストがずっと表示されているのも邪魔なので、シーンタイマー”kai”を利用してゲーム開始から1秒後に非表示になるイベントを作ってあげました。
(動画はTime:46秒のところからがスタートです。)
Time:45秒の時に、男性キャラクターを画面右下まで動かしてQキーでセーブをしました。
そしてタイトル画面で女性キャラクターにカーソルを合わせましたが、Wキーを押してロードをすることで、男性キャラクターで同じ位置・時間からゲーム再開できています!
……なんか、見返してみるとライフ表示の位置だけがロードのときにズレてますね。
リソースゲージの仕様が何かしらあるのでしょうか。その他一切のことはわかりません!!!
JSONでのセーブ・ロード(他サイトの紹介)
というわけで無事にセーブ・ロードについての解説が完了したのですが。
今回はもうちょっとだけ続くんじゃ。
ワタシが以前作ったGDevelopのゲームは「JSON」を使ってセーブとロードをしておったようです。
なぜ他人事みたいな口調なのかと申しますとぶっちゃけよく覚えていないからですね。あの頃はイヨクMANMAN(Vジャンプ)だったんだなあ……。
で、唐突に出てきたJSONって何ぞや?という話ですが。
要は「いろんなプログラミング言語でやり取りする際に使える橋渡しみたいなデータ形式」だと認識しました。
とりあえずデータ保管するために適した使い方ができるやつだよ。みたいな。
(プログラミング言語はLuaしか触ってないマンですので、このくらいの認識でお許しください。)
ちなみにGDevelopのプロジェクトファイルもこのJSON(名前の末尾が.json)で保存されておりますので、とても広く使われている形式なのだなというのはわかりますね。
じゃあコレ使った方がなんかよさそうじゃん。
と思うのですが、GDevelopはこのJSONでのデータ保存ができません。
ですので、GDevelopでJSONを使うためにいろいろと準備が必要なのですね。
ワタシが独力でこのような真似ができるはずもなく、おそらくコチラの記事を参照してそのまま使っていると思われます。↓
GDevelopでJSONファイルを読込|katakuriko
この場を借りてお礼申し上げます。
ですので、もし「JSONでデータ管理したい!!!」という強い意志をお持ちであれば是非ご覧くださいませ。
暗号化について
では最後にちょっとだけ。「暗号化」についてのお話です。
あくまで情報提供だけですので難しい話はいたしません。例によってワタシも詳しいことはわかっておりません。
今回はGDevelopの「ストレージ」を使ってセーブ機能を作りました。
先のFile system – GDevelop documentationの説明でも書いてあったのですが、ストレージにはユーザーは直接アクセスすることができないようです。
ですので、いわゆる「データの改ざん」についての心配は基本的にしなくてもよいでしょう。
アクセスできないとありますがアクセスできるようです。詳細は下の追記にて。
問題は「ファイルシステム」形式の場合。
冒頭で、「セーブって要はメモ帳に書いてるだけなんやで」というお話をしておりますが、これは裏を返せば「そのメモ帳のデータを自分で書き直したらそのままゲームに影響が出る」という話となります。
なんせ、ロードという行為は「メモ帳の中身をそのままゲーム内に移していくだけ」なのですから。
さすがにコレはアカンでしょ。
というわけで、こういったデータ保存の際にはだいたいデータの「暗号化」がセットとなっているようです。
セーブの際に、何かしらの決まった規則に基づいてデータを簡単には読めないように変換しているのですね。
そしてデータを読み込む際に、その決まった規則を適用してやるとまた読めるようになると。
このあたりは本当に専門的な話になってくるのでワタシはそこまで学習したくないていませんが、幸いにして以前使っていた「LOVE2D」というゲームエンジンには、ライブラリ(追加機能)として「データの暗号化」という機能がございました。
それを使って一応暗号化をしておったわけですね。
GDevelopを使うだけであれば特に意識しなくともよいかと思いますが、他のゲームエンジンなどでセーブ機能を作る際は、どのようにデータの暗号化をするのか?いろいろと調べておくとよいでしょう。
個人製作のゲームであれこれデータをいじる人がいるのかは疑問ですが、意図していない限りはあまりよろしい事ではないはずですからね。
重要追記:やっぱり安全じゃないじゃん!
というわけで、無事にセーブ機能が完成しました!よかったよかった!と記事を投稿したわけですが。
後日パンダコ先生から連絡がございまして。
どうやら「ストレージのデータはデベロッパー(開発者)モードで見て変更する事ができるらしい」です。
デベロッパーモードってこういうのですね。
ブラウザ(GoogleChromeなど)を開いてF12キーを押すと出てくるやつです。
「ソース」というタブにフォルダめいたものがいろいろと出てきますので、その中を調べていくと多分見られるんじゃないでしょうか(ワタシも自分のゲームで探してみたのですがイマイチよくわかりませんでした)。
ほうほう。デベロッパーモードでデータが見られるのね。
なるほどね………
………ダメじゃん!!!
実は今回のようなお話はすでにパンダコ先生が記事にしておられました。しかも相当前に(2021年11月とあります)。
【GDevelop】セーブデータは暗号化しよう!
というわけで、やっぱり何かしらの対策はしておくに越したことはないだろう。という結論となりました。うおォン。
UnicodeConversionで暗号化チャレンジ
そうなると今回の追記は「パンダコ先生のページを読んでね!」で終わり!でいいのかもしれませんが、それだけなのもこのサイトの存在意義が疑われるため、せめてちょっとはよさげな情報を提供しておくべきだろうと。
記事をお読みいただければおわかりですが、このページでは暗号化の拡張機能について
・Compressor 拡張機能
・Unicode (UnicodeConversion) 拡張機能
・Hash 拡張機能
と、3つ紹介がなされております。
このうち、「Compressor 拡張機能」と「Hash 拡張機能」については記事内でご紹介なされているのですが、「Unicode (UnicodeConversion) 拡張機能」については実例が書かれておりません(別のページで使い方についての解説はございます)。
なら、いっちょコイツで暗号化、試してみるか!
今回のお話で使ったデータを全て扱ってもよいのですが、逆にわかりづらくなりそうでもある手間もかかるしので、今回は2つのデータ「player_select」と「limit_time」を暗号化してストレージ保存までしてみたいと思います。
では最初の下準備からまいりましょう。
グローバル変数に、今回使用する2つのデータを保管する変数「hokan_player_select」と「hokan_limit_time」を追加しました。
コチラはテキスト型にしておいてください。
理由としてはシンプルで、今回使用するUnicode拡張機能というものが文字列(テキスト型)にしか対応していないからですね。
そのため今回は数字と文字列との変換工程が必要になってきて少々ややこしくなります。予めご了承ください。
次に拡張機能をインストールしておきましょう。
関数を作るときと同じ要領で、プロジェクトマネージャーの「拡張機能」の+ボタンを押します。
ウインドウの上の方に検索窓がございますので、「unicode」で検索してみましょう。
「10」の書かれたアイコンのものが今回使用するUnicode拡張機能です。コチラを選択して「プロジェクトにインストール」してやりましょう。
インストール完了しますとこのように拡張機能の欄にUnicode拡張機能が追加されます。
この拡張機能はどこで選べるんじゃい、という話なのですが。
コチラは関数型が「式」という分類なのです。関数を作るときに「条件」や「アクション」など選んでおりましたが、あれの3つめにある選択肢ですね。
このタイプのものはイベントの「条件」や「アクション」の欄には表示されません。
先ほども申しましたが、このUnicode拡張機能は「テキスト」に対して使えるものです。
ですので、テキストオブジェクトやテキスト型の変数などの値を変更する場合に、数式ボタンを押してみましょう。
一覧をスクロールしてだいぶ下の方に「Unicode」の項目が見つかるはずです。
それでは実際にグローバル変数「player_select」を暗号化して「hokan_player_select」に入れてみましょうか。
今回は「Qキーを押してセーブしたとき」のイベントのアクション、ここの一番上にこの暗号化のアクションを作っていきます。
hokan_player_selectの値を変更するので、アクションとしては「変数の値を変更」。記号は「=」とします。ここまでは問題ないでしょう。
値の部分ですが、ここで右の数式ボタンを押して「Unicode」の3つめの項目、「Unicode to string」を選択してください。
コチラが「数字を文字列に暗号化しつつ変換する」機能となります。上2つは数字ではなく文字列の変換に関するものとなりますのでご注意。
(詳細は先の解説ページを参照。)
項目を選ぶと新しいウインドウが出ます。
上から「実際に変換したい数値」「何進数にするか」「値を区切るときの記号」という項目となっておるようです。
ですので、上から順に「player_select」「16」「”,”」を入れてやりました。
一番上の項目はこれでないとダメですが、残り2つは決まりの中であれば好きに変えてよいと思います。
そして今回のお話の中でストレージに「値を保存」しましたが、今回はテキストを扱いますのでこれを「テキストを保存」に変えておいてください。
項目の変更はナシでよいです。
というわけで、セーブのイベントに暗号化してテキストで保存するアクションを作成できました。
赤の四角が「player_select」用、青の四角が「limit_time」用となっています。
ストレージにテキストを保存する項目が一見して「値を保存」と何も変わらなくて見ただけでは判断ができませんので、ここを変えることだけは忘れずにやっておいてください。
続いて、ロードのイベントも変更していきましょう。
「load_flagが真のとき」のイベントのアクションに追加をしていきます。
先と同じく、今度はストレージから値ではなく「テキストを読み込み」する必要がございます。
ここで一気に暗号化の解除までできるとよいのですが、ここでは変数の指定しかできません。
まずはいったんテキストを出す必要がございますので、読み込み先を一度「hokan_player_select」にしておいてください。
そして「player_select」に暗号化を解除しつつ値を読み込ませます。
えらい長い値となっておりますが、よくご覧いただければ「さっきのUnicodeの機能を数字に変換しているだけなのだな」ということがおわかりではないかと思います。
書き方としてはまずは数式ボタンを押して「Unicode」、今回は文字列を数字に戻したいので2番目の項目を選びます。
コチラで「hokan_player_select」からデータを戻していきます。ここの2番目・3番目の項目は最初に暗号化したときと同じ値を選びましょう。
ただ、この機能ですとどうも返される値はテキストのままらしいのですね。
「player_select」は数値を扱う変数ですのでこのままだとよろしくない。
ですので、最後に「ToNumber」式を使って、テキストを数値に変換してやる必要がございます。
これで暗号化を解除してロードを行うイベントができました。
2つの変数分のアクションを用意してあげましょう。
では、テストしてみましょうか。
Playerは「1」の男性キャラクター、残り時間「57秒」のところでQキーを押してセーブ。
デバッカーを使って、このときの「hokan_player_select」と「hokan_limit_time」がどうなっているか確認してみます。
んん!
前者のほうはもはや読めませんが、とりあえず何か違う値になっていることが確認できますね。
これなら手を加えようにも難しいでしょう。
そしてロードしてみて再度確認。
2つの変数の値が正常な「1」と「57」に戻っておりますね!
もちろんロード時のゲーム内容もこの状態でロードできております。
ただ、なぜかPlayerAもこの位置に来てしまいました。
読み込み順の問題かな?と思われるので、セーブロードのイベントを一番上に置いたり、いっそのこと別のイベントで「選んでいないPlayerを画面外に置いておくイベント」を作るなりするのがよいかもしれません。
JSONもデフォルトで使えるじゃん?
あと、この検証作業をしている中でテキストの変換なども見ていたのですが。
普通にデフォルトの機能でJSONに変換できるのでは??
というか、JSONに関してのお話はパンダコ先生もいろいろと書いておられました。
【GDevelop】セーブデータを JSON 化するとメリットしか無い件
データをまとめるのにかなり効率がよくなるぽいので、ツリー化した項目のセーブなどを考えておられたら是非とも使ってみましょう。
まとめ
・グループ分けをしながらデータをセーブしていこう!・GDevelopのストレージなら基本的にデータにアクセスされない!
されるかもしれないので対策もしてみよう!
今回はセーブとロードの実装についてお話しいたしました。
セーブするデータの数が少なかったので今回はさほど実装が難しくなかったのですが、いざアクションゲームやシミュレーションゲームなんかで「途中セーブ」を実装しようとすると、キャラクター一人一人の状態などを記録する必要がございます。保存するデータが非常に大量になりますね。
こういうのを処理していくのは大変だろうなぁ……と開発者の心配をしてしまうようになります。
本格的に実装するのであればセーブ機能自体を関数にして使い回せるようにしたり、ループ処理なども使って効率よく保存する手法が必要となってくるのでしょう。
ワタシもそういうものは未だ手を付けたことがございませんので、必要であれば他の情報も探してみてくださいね。
次記事→GDevelopで簡単なゲームを作ろう・24 ~ゲームを公開しよう~
さて…そろそろお時間です。
またのご面会、心よりお待ちしております。
この記事がお気に召したなら、
◎ギャラリーからワタシの作品を見ていただいたり、
☆Misskeyアカウント(@daidaimyou)
☆Caraアカウント
☆X(旧Twitter)アカウント(低浮上)(@daidaimyou)
をフォローいただけますと脱獄の励みになります。よろしくお願いいたします。
ドット絵(一枚絵・アニメーション)制作のお仕事も承っております。
お仕事依頼ページよりご連絡くださいませ。(現在受付停止中)