libGDX入門 その05 ゲームデータの保存
はじめに
注意
今回「ゲームデータの保存」というタイトルにしてしまいましたが、 扱えるのはごく小さなデータのみです1。 また、データはテキストとして保存されるので、 ユーザーに改竄されて困るようなものは暗号化するなどの処置が必要です。
さらに、ゲーム中の複雑なデータを扱う場合は、 データをシリアライズしたりして保存する必要があるかと思います。 これらについては、今回は扱いません。
インスタンスの取得方法
下記で取得できます(公式からの引用)。
Preferences prefs = Gdx.app.getPreferences("My Preferences");
文字列の部分が名前になります。名前を変えればひとつのアプリで複数のインスタンスを扱えるようです。 個人的にこの名前は、javaのパッケージ名が良いかと思います。 (ただしそれがユニークであるという保証がある場合のみ)
というのものPreferenceというのは、対象の端末全アプリの中でユニークである方が安全だからです。 今回の例でいうと下記の名前になります。
Preferences prefs = Gdx.app.getPreferences("com.zarudama.fishcatch");
この辺の考察は下記が詳しいので、一読をおすすめします。
値の書込みと読込み
libGDXの Preferences
は、Javaの標準クラスの
java.util.prefs.Preferences
や java.util.Map
クラスに少し似ています。
公式から引用します。
prefs.putString("name", "Donald Duck");
String name = prefs.getString("name", "No name stored");
prefs.putBoolean("soundOn", true);
prefs.putInteger("highscore", 10);
下記のようなメソッドの仕様になっています。
Preferences#put型名 (キー文字列, 型に応じた値);
保存できるデータ型は、int、long、float、boolean、Stringになります。
getterは第2引数にデフォルト値を指定できます。 これは値が存在しなかった場合に返却する値です。
String name = prefs.getString("name", "No name stored");
この場合、 name
というキーの値が存在しないときは、 No name stored
が返却されます。
データを保存するタイミング
Preferences
のインスタンスに値をセットしただけでは、それはメモリ上の操作に過ぎませんので、
これをストレージに書きだす必要があります2。 Preferences#fluash()
メソッドを呼びだしてください。
prefs.flush();
データを保存するタイミングですが、基本的にはデータを変更する都度 flush()
するのが良いと思います。
都度アクセスするのは少々冗長ですが確実です。
しかしアプリによっては、データ量が多い場合やタイミングが不特定の場合、
保存するタイミングが頻発するのは望ましくないなどの状況があると思います。
そんな時は、 ApplicationLisnter#pause()
に保存するのが良いと思います。
理由は pause()
はアプリが消滅する寸前および、フォーカースが外れたときに必ず呼ばれるメソッドだからです。
これまた下記が詳しいです。
ライフサイクルの図をもう一度掲載します。
保存される場所
公式からのそのまま転用ですが、下記に保存されます。
OS | 場所 |
Windows | %UserProfile%/.prefs/My Preferences |
Linux and OS X | ~/.prefs/My Preferences |
Androidでは、SharedPreferences を使用するとのこですが、アプリを削除すると保存データも同時に削除されるようです。 Androidの保存場所は機種依存が大きいため一概にどこと言えません。下記を参考にしてください。
iOSの場合はどこにどんな状態で保存されるかわかりません(公式にも載っておらず、私もMacを持っていないため) 。
参考までにUbuntuの場合は、下記のようなXMLデータが保存されます。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="bgmOn">true</entry>
<entry key="seOn">false</entry>
<entry key="hiscore">9</entry>
</properties>
実際の実装例
前回実装したショボゲーに、下記のような感じで実装してみました。
左下にハイスコア表示、右側にそれぞれ「BGM」「SE」のオンオフボタンを配置してあります。
これらは下記の Settings
クラスを通して操作します。
package com.zarudama.fishcatch;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
public class Settings {
private static final String NAME = "com.zarudama.fishcatch";
private static final String KEY_SE_ON = "seOn";
private static final String KEY_BGM_ON = "bgmOn";
private static final String KEY_HISCORE = "hiscore";
private static final boolean DEFAULT_SE_ON = true;
private static final boolean DEFAULT_BGM_ON = true;
private static final int DEFAULT_HISCORE = 0;
private Preferences mPrefs;
public Settings() {
mPrefs = Gdx.app.getPreferences(NAME);
}
public boolean seOn() {
return mPrefs.getBoolean(KEY_SE_ON, DEFAULT_SE_ON);
}
public boolean bgmOn() {
return mPrefs.getBoolean(KEY_BGM_ON, DEFAULT_BGM_ON);
}
public int hiscore() {
return mPrefs.getInteger(KEY_HISCORE, DEFAULT_HISCORE);
}
public void toggleSeOn() {
mPrefs.putBoolean(KEY_SE_ON, !seOn());
mPrefs.flush();
}
public void toggleBgmOn() {
mPrefs.putBoolean(KEY_BGM_ON, !bgmOn());
mPrefs.flush();
}
public void hiscore(final int aScore) {
mPrefs.putInteger(KEY_HISCORE, aScore);
mPrefs.flush();
}
}
Settings
クラスは、 FishcatchGame
クラスを通してアクセスします。
ただし自分はドットが二つ以上つづく文があまり好きではないので
(メソッドチェーンとか流れるようなインターフェイスというやつ3)、
外部からアクセスするときは Settings
クラスへアクセスするのではなく、
専用のメソッドを FishcatchGame
クラスに設けています。
public class FishcatchGame extends Game {
:
private Settings settings;
public boolean seOn() {
return settings.seOn();
}
public boolean bgmOn() {
return settings.bgmOn();
}
public int hiscore() {
return settings.hiscore();
}
public void toggleSeOn() {
settings.toggleSeOn();
}
public void toggleBgmOn() {
settings.toggleBgmOn();
}
public void hiscore(int score) {
settings.hiscore(score);
}
:
}
ちなみに現在の自分コーディングスタイルは原則下記の影響を受けています。
「オブジェクト指向エクササイズ」を解説したもので、 オブジェクト指向言語を普段使い全ての人に読んで欲しい内容です。 このlibGDXシリーズではほぼ無視してコーディングしてますが…。
ソースコード
今回実装したサンプルコードは、下記にあります。
おわりに
今回までの内容で最低限必要な機能は網羅できたのではないかなと思います。
次回からは、 Scene2d
、 広告の実装、 Googleの「Google Play ゲーム」あたりを扱っていきたいなと思います。
参考書籍
注釈
libGDXの実装によっては、非同期で書き出している可能性もありますが、意識しないほうがいいと思います。