オレンジブログ

オレンジブログ

GameMakerのちょっとしたTipsとか

【GMS2】SSaveアセットの紹介、解説

はじめに

ゲーム開発において、セーブファイルの管理は非常に重要です。

ですが、セーブファイル作成って結構実装めんどくさいんですよね。管理も大変だしエラーもよくわからなかったりするし...。

そこで、これらの問題を解決するために「SSave」というアセットを紹介しようと思います。
自分も愛用しており、セーブファイルの作成はこのアセットに頼りっきりです。


SSaveとは

SSaveはGMS2向けのシンプルなセーブファイルシステムです。
Itch.ioGameMaker Marketplaceでダウンロード可能です。
恐ろしいことに無料です。

stoozey.itch.io

marketplace.gamemaker.io

こちらは解説のGitHub github.com

SSaveはセーブデータの作成を容易かつ安全に行うための多くの機能を備えています。
以下に、SSaveの主な特徴を挙げます。

  • 型付きの値:
    保存されたデータの誤った型から発生するゲームクラッシュなどの問題を防げます。

  • 複数のセーブファイルタイプ:
    メインのセーブファイルとは別に、設定ファイルなどの異なるタイプのファイルを作成できます。

  • 複数バージョンのセーブファイル:
    複数のセーブスロットを持つことができ、セーブファイルタイプの異なるバージョンを管理可能です。

  • データの自動同期:
    古いセーブデータも新しいデータに自動的に同期されるため、
    セーブファイルに値が必ず存在することを保証できます。

  • ファイルの保護:
    ユーザーによるファイルの改ざんを防ぐため、ファイルのエンコードや暗号化が可能です。
    これらの操作は内部的に処理されるため、保護されたファイルをロードする際に追加作業は不要です。

これを聞くだけでも、かなり有能だ!という印象を受けますね。


SSaveを用いたセーブデータ管理の実装

次はセーブデータの管理と保護をどのように実装するかを、サンプルコードを用いながら解説します。


1. SSaveを継承したクラスの定義

まずSSaveを継承したconstructor構造体を定義し、保存するデータの構造を設定します。

サンプルでは下記のConfig構造体とSaveFile構造体を定義しています

// 設定ファイル用の構造体を定義
// この構造体は "config" という名前でセーブデータを扱う
function ConfigFile() : SSave("config") constructor
{
    // セーブスロットのインデックスを登録
    // 型: 実数 (REAL)、初期値: 0
    // 例: プレイヤーが選択したセーブスロット番号を管理する
    add_value("saveIndex", SSAVE_TYPE.REAL, 0);
}

上記のセーブデータでは使用するセーブデータのスロット番号をセーブしています。
マ◯オ64とかでセーブデータが4つあるようなイメージです。

// ゲームのセーブデータ用の構造体を定義
// この構造体は "save" という名前でセーブデータを扱う
function SaveFile() : SSave("save") constructor
{
    // プレイヤー名を登録
    // 型: 文字列 (STRING)、初期値: 空文字列 ("")
    // 例: プレイヤーが設定した名前を記録
    add_value("name", SSAVE_TYPE.STRING, "");

    // プレイ時間を登録
    // 型: 実数 (REAL)、初期値: 0
    // 例: ゲームプレイの累計時間を記録(秒単位や分単位など用途に応じて)
    add_value("playtime", SSAVE_TYPE.REAL, 0);

    // プレイヤーのX座標を登録
    // 型: 実数 (REAL)、初期値: 512
    // 例: マップ上でのプレイヤーの位置を管理 (X軸)
    add_value("x", SSAVE_TYPE.REAL, 512);

    // プレイヤーのY座標を登録
    // 型: 実数 (REAL)、初期値: 300
    // 例: マップ上でのプレイヤーの位置を管理 (Y軸)
    add_value("y", SSAVE_TYPE.REAL, 300);
}

上記のセーブデータでは主にプレイヤー情報をセーブしています。

SSave機能を使うにはこのように、SSave構造体を継承した構造体を用意することが必要です。


2. データの保存と取得方法

次に先ほど定義した構造体を使用してデータを保存し、取得する方法について
SSaveのサンプルを用いて説明します。


データの保存

// セーブデータを保存する処理を定義
Save = function()
{
    // obj_ssave_demo_player インスタンスに対して以下の処理を実行
    with (obj_ssave_demo_player)
    {
        // 設定データを取得
        // ConfigFileからセーブスロット番号 (saveIndex) を取得するために使用
        // static を使うことで処理を効率化し、ConfigFileの再取得を防ぐ
        static config = ssave_get(ConfigFile);
        
        // セーブデータを取得または作成し、以下のデータを更新
        ssave_get(SaveFile, config.get("saveIndex"))
            .set("x", x)                 // プレイヤーの現在のx座標を保存
            .set("y", y)                 // プレイヤーの現在のy座標を保存
            .set("name", name)           // プレイヤーの名前を保存
            .set("playtime", playtime)   // プレイ時間を保存
            .save();                     // 上記の更新内容をセーブデータに保存
    }
}

// プレイヤーオブジェクトを作成
// obj_ssave_demo_player のインスタンスを (0, 0) の位置に生成
// ゲーム内でプレイヤーのデータを管理するためのオブジェクト
instance_create_depth(0, 0, 0, obj_ssave_demo_player);

このコードはプレイヤーデータをセーブする処理を定義し、
セーブデータにプレイヤーの状態(位置や名前、プレイ時間など)を保存します。


Save処理

セーブデータにプレイヤーの現在の状態を登録して保存します:

  • ConfigFile から現在のセーブスロット番号(saveIndex)を取得
  • 指定されたスロットに対応するセーブデータ(SaveFile)を取得し、以下のデータを更新
    • x, y: プレイヤーの座標
    • name: プレイヤー名
    • playtime: プレイ時間
  • .save() メソッドで、更新内容を実際にファイルに書き込み

全体の流れ

  1. プレイヤーオブジェクトを (0, 0) の位置に生成。
  2. Save関数を呼び出すとConfigFile を基にセーブスロットを選び、対応する SaveFile にプレイヤーデータ(位置、名前、プレイ時間など)を保存。

3. データの取得

// プレイヤーデータをロードする処理を定義
// この関数は、セーブデータからプレイヤーの状態を取得して設定します
Load = function()
{
    // 設定データを取得
    // ConfigFileから設定値をロードします。セーブスロットのインデックス(saveIndex)を取得するために使用。
    // static を使うことで処理を効率化し、ConfigFileの再取得を防ぐ
    static config = ssave_get(ConfigFile);

    // セーブデータを取得
    // 設定データ (config) の saveIndex に基づいて、対応するセーブデータを取得します。
    var _save = ssave_get(SaveFile, config.get("saveIndex"));

    // セーブデータからプレイヤーの位置 (x, y) を取得して設定
    x = _save.get("x");
    y = _save.get("y");

    // セーブデータからプレイ時間を取得して設定
    playtime = _save.get("playtime");

    // セーブデータからプレイヤー名を取得
    var _name = _save.get("name");

    // プレイヤー名が空の場合、名前を入力するようプレイヤーに促す
    // デフォルト値として "Geoffrey" を表示
    while (_name == "")
        _name = get_string("Enter name for this save file:", "Geoffrey");

    // 入力または取得した名前をプレイヤー名に設定
    name = _name;
}

// プレイヤー名を初期化(デフォルトは空文字列)
name = "";

// プレイ時間を初期化(デフォルトは0)
playtime = 0;

// プレイヤーデータをロード
Load();

こちらのコードではセーブデータからプレイヤーの状態(位置、名前、プレイ時間など)をロードしています。


Load処理

  • プレイヤーの状態をセーブデータから取得し、現在のプレイヤーオブジェクトに設定します。

処理の流れ:

  • ConfigFileの取得:
    • ConfigFile からセーブスロット番号(saveIndex)を取得
  • セーブデータのロード:
    • SaveFile を利用して指定されたスロット番号のデータをロード
  • セーブデータからの値の取得:
    • .get() を使って以下の値を取得:
      • x, y: プレイヤーの位置
      • playtime: プレイ時間
      • name: プレイヤー名
  • 名前の入力処理:
    • セーブデータにプレイヤー名がない場合、get_string を使ってユーザーに名前を入力させます。
    • デフォルト名として "Geoffrey" を使用。

仕様

  • セーブスロット管理:

    • ConfigFile に保存された saveIndex を基に、対応するセーブスロットのデータをロードします。
    • 複数のセーブデータを扱う場合に便利です。
  • .get() の利用:

    • セーブデータから特定の項目を取得し、プレイヤーオブジェクトに適用しています。
    • x, y, playtime は即座に設定されますが、name は空の場合に入力を促す仕様です。

データ保護レベルの設定

SSaveではデータの保護レベルを選択することができます。
これにより、データの改ざんや不正アクセスを防ぎます。


各保護レベルの解説

下記3つのタイプが用意されています。

enum SSAVE_PROTECTION
{
    NONE,     // Save data is stored in plaintext json - good if you don't care about tampering
    ENCODE,       // Save data is stored in base64 encoded json - good if you want *most* players to not know how to tamper
    ENCRYPT,  // Save data is encrypted with a key - good if you want *most* players to be unable to tamper. This is NOT secure enough for sensitive data
}

1. SSAVE_PROTECTION.NONE

  • 内容: 保護なし。データはそのまま保存される
  • 用途: 機密性のないデータに適している
  • メリット: 読み書きが速く、簡単に実装できる、デバッグ等に用いれる
  • デメリット: データ改ざんのリスクが高い

値がそのまま表示されているのでわかりやすい反面、改ざんされる恐れがある


2. SSAVE_PROTECTION.ENCODE

  • 内容: データがエンコードされ、人間に読みにくい形式で保存される
  • 用途: 軽度のデータ保護が必要な場合に適している
  • メリット: データ構造を隠すことで、改ざん防止効果がある
  • デメリット: エンコードの解除が可能であり、完全な保護にはならない

どんなデータを保存しているかがわからない


3. SSAVE_PROTECTION.ENCRYPT

  • 内容: データが暗号化され、復号キーなしでは読めない形式で保存される
  • 用途: 高度なセキュリティが必要なデータに適している
  • メリット: データの保護が非常に強力で、改ざんや不正アクセスを防げる
  • デメリット: 処理が少し重くなる可能性があるが、SSaveが内部処理を行うため、実装は簡単

文字化け状態


設定方法

主に2つの方法で設定できます。
デフォルト値を変更もできますが、内部の処理に手をいれるのであまりオススメしません。


constructorで設定

constructorSSaveを継承する際、第二引数に設定することが出来ます。

// 暗号化を使用する設定
function SaveFile() : SSave("save", SSAVE_PROTECTION.ENCRYPT) constructor
{
    add_value("name", SSAVE_TYPE.STRING, "");
    add_value("x", SSAVE_TYPE.REAL, 0);
    add_value("y", SSAVE_TYPE.REAL, 0);
}

set_protection メソッドを使用して後から設定

初期化後、set_protection関数を呼び出すことで設定ができます。

// 各スロットの保護設定
ssave_get(SaveFile, "1").set_protection(SSAVE_PROTECTION.NONE);
ssave_get(SaveFile, "2").set_protection(SSAVE_PROTECTION.ENCODE);
ssave_get(SaveFile, "3").set_protection(SSAVE_PROTECTION.ENCRYPT);

まとめ

SSaveを利用することでGMS2でのセーブデータ管理が簡単かつ安全に行えます。
定義した構造体を通じてデータの構造を明確にし、内部メソッドを使用してデータの保存と取得を簡単に実現できます。
また、データ保護レベルを設定することでセーブデータの安全性を確保することができます。
これにより、ゲームの信頼性とプレイヤーの安心感を向上させることが可能です。

個人的には非常にオススメのアセットですので、ぜひ使ってみてください!