ざる魂

真似ぶ魂、学ぶの本質。知られざる我が魂

libGDX入門 その05 ゲームデータの保存

はじめに

libGDX を勉強するついでに解説記事を書く シリーズ 5回目です。

前回 は、以下のことを学びました。

  • Screenインタフェイスを使った画面遷移

今回は、前回作ったゲームに以下の機能を追加します。

  • BGMのON/OFF機能
  • SEのON/OFF機能
  • HiScoreの表示

これらの情報は、ゲームを再開したときも保持していて欲しいですね。

そこで今回は、これら3つの情報を com.badlogic.gdx.Preferences というクラスを利用して 保存していきます。

公式情報は下記にあります。

注意

今回「ゲームデータの保存」というタイトルにしてしまいましたが、 扱えるのはごく小さなデータのみです1。 また、データはテキストとして保存されるので、 ユーザーに改竄されて困るようなものは暗号化するなどの処置が必要です。

さらに、ゲーム中の複雑なデータを扱う場合は、 データをシリアライズしたりして保存する必要があるかと思います。 これらについては、今回は扱いません。

インスタンスの取得方法

下記で取得できます(公式からの引用)。

Preferences prefs = Gdx.app.getPreferences("My Preferences");

文字列の部分が名前になります。名前を変えればひとつのアプリで複数のインスタンスを扱えるようです。 個人的にこの名前は、javaのパッケージ名が良いかと思います。 (ただしそれがユニークであるという保証がある場合のみ)

というのものPreferenceというのは、対象の端末全アプリの中でユニークである方が安全だからです。 今回の例でいうと下記の名前になります。

Preferences prefs = Gdx.app.getPreferences("com.zarudama.fishcatch");

この辺の考察は下記が詳しいので、一読をおすすめします。

値の書込みと読込み

libGDXの Preferences は、Javaの標準クラスの java.util.prefs.Preferencesjava.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 のインスタンスに値をセットしただけでは、それはメモリ上の操作に過ぎませんので、 これをストレージに書きだす必要があります2Preferences#fluash() メソッドを呼びだしてください。

prefs.flush();

データを保存するタイミングですが、基本的にはデータを変更する都度 flush() するのが良いと思います。 都度アクセスするのは少々冗長ですが確実です。

しかしアプリによっては、データ量が多い場合やタイミングが不特定の場合、 保存するタイミングが頻発するのは望ましくないなどの状況があると思います。 そんな時は、 ApplicationLisnter#pause() に保存するのが良いと思います。

理由は pause() はアプリが消滅する寸前および、フォーカースが外れたときに必ず呼ばれるメソッドだからです。 これまた下記が詳しいです。

ライフサイクルの図をもう一度掲載します。

/img/libgdx-beginner/4/life-cycle.png
libgdxのライフサイクル

保存される場所

公式からのそのまま転用ですが、下記に保存されます。

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>

実際の実装例

前回実装したショボゲーに、下記のような感じで実装してみました。

/img/libgdx-beginner/5/000.png
改造したタイトル画面

左下にハイスコア表示、右側にそれぞれ「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 ゲーム」あたりを扱っていきたいなと思います。

注釈


2

libGDXの実装によっては、非同期で書き出している可能性もありますが、意識しないほうがいいと思います。