【Unity+WebGL】セーブ・ロード機能の実装

2022年6月15日

Unity + WebGLでSQLサーバにセーブデータを保存する機能を実装しました.

経緯

わけあって,Unityでブラウザゲームを制作することになったのですが, 依頼主からセーブ・ロード機能が欲しいと言われました.

とりあえず調べてみると,PlayerPrefsあたりが使えるみたいですが, ローカル保存ではなくサーバ保存にしたいとのこと.

さらに調べてみると,ゲームアツマールに投稿すればAPIを使ってサーバに セーブできるらしいのですが,ニコニコアカウント必須なのはNGで, ゲーム自体も依頼主のホームページに置きたいそうなので却下.

結局,SQLサーバに保存する機能を自作することにしました.

概要

とはいえ,実装する構造自体はシンプルです.

  • Unityからセーブデータをサーバに送る処理
  • 送られたセーブデータをデータベースに登録する処理
  • セーブデータをデータベースから読み込んでUnityに送る処理
  • Unityで受けっとたセーブデータをゲームに反映する処理

こんな感じでしょうか?

とりあえず,サーバ側でセーブ・ロードするAPIを書いて, UnityでPostすれば良さそうです.

セーブ・ロードAPI

まずは,データベースとの接続処理を書きます.

<?php
//MySQL接続
function connectDB(){

    //ユーザ名・DBアドレス
    $dsn = 'mysql:dbname=XXXXXXX; host=XXX.XXX.XXX.XXX; charset=utf8';
    $username = 'XXXX';
    $password = 'XXXX';

    try {
        $pdo = new PDO($dsn, $username, $password);
    } catch (PDOException $e) {
        exit('' . $e->getMessage());
    }

    return $pdo;
}
?>

次に,セーブ用の処理をPHPで書いていきます.

<?php
require_once('connect.php'); //connect.phpを使ってデータベースに接続する
$pdo = connectDB();
$table = "XXX"; //DBのテーブル名

//POST受け取り
$id = $_POST["id"]; //ユーザのid
$no = $_POST["no"]; //セーブファイルの番号
$json = $_POST["json"]; //json形式のセーブデータ

if($no != ""){
    try {
        $stmt = $pdo->prepare("INSERT INTO $table (id, no, json) VALUES (
            :id,
            :no,
            :json
            ) 
            ON DUPLICATE KEY UPDATE json = :json");
        $stmt->bindValue(':id', $id, PDO::PARAM_STR);
        $stmt->bindValue(':no', $no, PDO::PARAM_STR);
        $stmt->bindValue(':time', $json, PDO::PARAM_STR);
        $stmt->execute();
    } catch (PDOException $e) {
        var_dump($e->getMessage());
    }
}
$pdo = null;    //DB切断
$res = "NG";
if($stmt) $res = "OK";
echo $res;  //結果を表示
?>

ここでは,ユーザIDとセーブファイルの番号をデータベースの主キーとして, セーブデータはjson形式で保存します.データベースでは以下のようなテーブルを 用意しておきます.

idnojson
ユーザIDセーブファイル番号セーブデータ

この3つのデータはPostで受け取る様にしておきます.

同様に,ロード用の処理も書きます.

<?php
require_once('connect.php'); //connect.phpを使ってデータベースに接続する
$pdo = connectDB();
$table = "XXX"; //DBのテーブル名
//POST受け取り
$id = $_POST["id"]; //要求されてくるユーザのid
try {
    $stmt = $pdo->prepare("SELECT * FROM $table WHERE `id` = :id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $log = $stmt;
    $stmt->execute();

        //ここの処理は適宜変更する(!一度連想配列にしてからjsonにした方がきれい!)
        $res = '{"id":"';
        $res2 = '","data":[';
        foreach ($stmt as $row) { //今回はただカラムを指定し、出力された文字列を結合して出力
        $getid = $row['id'];
        $res2 = $res2. '{"no":"';
        $res2 = $res2. $row['no'];
        $res2 = $res2. '","json":"';
        $res2 = $res2. $row['json'];
        $res2 = $res2. '"},';
        }
        if($id != $getid) $id = "NoSuchIDError!";
        $res = $res. $id;
        $res = $res. $res2;
        $res = rtrim($res,',');
        $res = $res. ']}';

} catch (PDOException $e) {
    $res = '{"id":"SQLError!","data":[]}';
}
$pdo = null;    //DB切断
echo $res;  //unity に結果を返す
?>

こちらは,ユーザIDをPostで受け取るとすべてのセーブデータをjson形式で返しています.

Unity側の処理

あとは,UnityにてPost処理を書きます.ここではwwwを使っていますが,できれば新しい方の UnityWebRequestsを使いましょう.

Dictionary<string, string> dic = new Dictionary<string, string>();
//Postするデータ
dic.Add("id", id);  
dic.Add("no", no);
dic.Add("json", json);

private IEnumerator Post(string uri, Dictionary<string, string> post) {
    WWWForm form = new WWWForm();
    foreach (KeyValuePair<string, string> post_arg in post) {
        form.AddField(post_arg.Key, post_arg.Value);
    }
    WWW www = new WWW(uri, form);

    yield return StartCoroutine(CheckTimeOut(www, 3f)); //3sでタイムアウト;

    if (www.error != null) {
        Debug.Log("Post Error: " + www.error); //そもそも接続ができていないとき
    } else if (www.isDone) { //接続に成功した時
        //送られてきたデータ(www.text)をテキスト表示
        Debug.Log(www.text);
        /*以下で適宜セーブのチェックやロード処理を書く
        *
        *
        *
        */
    }
}

このPostはこんな感じで

StartCoroutine(Post(URI, dic));

使いたいところでコルーチンを開始させます.

所感

思ったより簡単に実装することは出来ましたが,普通にめんどくさいので 特別な理由がなければ,PlayerPrefsかゲームアツマールAPIを使いましょう.