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形式で保存します.データベースでは以下のようなテーブルを 用意しておきます.
id | no | json |
---|---|---|
ユーザ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を使いましょう.