RPGツクールMVでオンラインセーブ〜Firebaseを添えて〜

この記事は [RPGツクールMV Advent Calendar 2017](https://adventar.org/calendars/2288) 9日目の記事です。

どうも、ツクール界隈ではじじいのアイコンで「しんぞ」と名乗っている者です。
ツクールMVで、「Twitterなどのアカウントでログインしてセーブデータをクラウド上で管理する」ことを実験してみました。
成功しましたが、それなりに設定に手間がかかる上に慣れないとわかりづらい作業が必要なので、真似する方はめんどくさい思いをするかもしれないことを、あらかじめおことわりしておきます。

#はじめに
##なぜオンラインセーブ？

RPGツクールMVの仕様では、ブラウザでプレイした場合、セーブデータはローカルストレージに保存されます。ローカルストレージとはCookieのようなもので、各ブラウザが独自に管理していて、消去されることもあるデータです。このローカルストレージのみに依存してセーブを管理した場合、下記のような問題点があります。

- **ブラウザのキャッシュをクリアしたらセーブデータも消えた！**
- ブラウザが変わったらニューゲームからやり直し

これを解決するために、セーブデータはクラウド上に保存したい、というのが本稿の目的です。
// つーかね、実機でテストプレイすると頻繁にキャッシュクリアの必要が生じるから、ローカルストレージでは使い物にならんかったのよ

##今あるプラグインは？
RPGツクールMVでオンラインセーブを実現しているプラグインは、国内外に今までもいくつかありました。ですが、「自分のセーブデータ」と「他人のセーブデータ」を区別する方法が簡易的なものだったりして、個人的には満足できなかったのです、ごめんなさい。

じゃあおまいは完璧なログイン処理が書けるのか？　と自問したところで1年ほど立ち止まっていたのですが、**「Twitterでログイン」使えばいいじゃん！**と気づいて、自前プラグインの製作に踏み切ったわけです。
ついでに、もう一つの願望だった「オートセーブ」も実装してみました。

##Firebase使ってみた
このプラグインでは、FirebaseというWebサービスを使っています。下記の記事を全面的に参考にさせていただきました。

[RPGツクールMVでオンラインゲームを作ろう！ @krmbn0576さん](https://qiita.com/krmbn0576/items/ef942219ea72541a580a)

FirebaseはBaaSと呼ばれるサービスの一種で、データベース管理やファイルの蓄積、ユーザー認証などのバックエンド（裏方）でよく使う機能の詰め合わせが用意されています。
無料枠もあり、個人で作ったゲームをレンサバに置いて遊んでもらうくらいの規模であれば、無料枠のサービス内容で充分そうです。私も最初はMySQL + PHPでゴリゴリ書いていたのですが、個人レベルでデータベースを自由に作れるサーバーを用意するのは敷居が高いですよね。良いサービスを知りました。

##プラグイン作ってみた
というわけで、下記にアップロードしてあります。自分用にざっくり作ったものなので、細かい設定が直打ちになっていて変えられなかったりと不備があるかもしれませんが、よろしければ参考にしてください。そのまま使ってもいいし改造してもいいよ。ただ、機能要望に応える余裕はなさそうだよ。

http://shinnoji.main.jp/jimen75th/game/js/plugins/SNZ_FirebaseSave.js

ただし、**これをインストールするだけでは動きません。**後述の手順を参考に、ご自身でFirebaseや各ソーシャルアカウントの設定を行ってください。
[2019.11.16追記]**ブラウザ用に書き出してどこかのサーバーにアップロードすることを想定して作っています。**テストプレイやローカル実行では機能しません。アプリとして書き出すのも未検証です。ごめんね。

#どういう機能のプラグイン？
セーブスロットは一つです。理由は「複数セーブってあんまり使わないよね」っていう私見と、私の技術力の限界です。興味と自信のある方は、複数セーブスロットに対応するよう改造してみてください（丸投げ）。

##ロード（ゲーム開始）の流れ
ゲームを起動すると「Press Start」の1項目のみが表示されます。
（この部分、トリアコンタンさんの「[ニューゲームオンリープラグイン](https://triacontane.blogspot.jp/2015/11/blog-post.html)」を改造させていただきました。ありがとうございます！）

何かボタンを押すと、ゲーム開始の処理に入ります。言葉では説明しづらいのでフローチャートを書いてみました。

![ゲーム開始時のフロー](https://qiita-image-store.s3.amazonaws.com/0/27911/47975dbc-dd28-cb08-b7c5-e288f99585cd.png)


##セーブの流れ

###メニューから「セーブ」を選んだ時
「上書きセーブするか否か」の選択肢が表示されます。
ただし、ゲーム開始時に「ローカルセーブ」を選んでいた場合は、RPGツクール本来のセーブファイル一覧が表示されます。
###プラグインコマンド「FirebaseSave」
上書きセーブが実行されます。また、画面左下に画像「saving.png」が表示され、セーブ中であることを示します。なお、ツクールのコマンドで「セーブ不可」設定をONにしている間は、実行しても何も行われません。
このプラグインコマンドを要所要所で実行すればオートセーブのできあがり！　主なマップに透明のイベントを置いて「自動実行で"FirebaseSave"した後イベントの一時消去」させるとちょうどいいです。途中でセーブすると詰む箇所では「セーブ不可」設定を忘れずに。


#プラグインの概要
本プラグインは「ブラウザゲー」向けです。
ゲーム一式をレンタルサーバーやGitHubなどにアップロードして、ブラウザ上で遊んでもらうことを想定して作っています。そのため、オフラインで遊んでいる時のセーブはどうするか、そもそもエラーが出ることはないのか、ということは特に考慮していません。アプリなどに書き出して配布したい方は、オフライン用の処理を加える必要があると思いますので、改造してみてください（丸投げ）。

ブラウザでURLを開いてゲームを起動。セーブの時にはバックグラウンドでFirebaseと通信が行われ、セーブデータはFirebase上で管理されます。

ちなみに、Firebaseにもファイルをアップロードできる領域はあるのですが、従量課金制で無料枠の制限が厳しめです。そのため、音声や画像を多く扱うゲームには向かなさそうです。ちょっと重いゲームの場合、2回デプロイしたら月間の上限に達してしまった、なんてこともありました。

#プラグインの使い方
まねしてみたい方向けに、自分がやった時の手順を記しておきます。ただし、後日思い出して書いてるので、抜けなどがあるかもしれません。参考までに、ざっくり眺めてみてください。

外部Webサービスを利用しているプラグインなので、それぞれのサービスに登録作業が必要になります。

- Firebaseへの登録
- Twitterのアプリケーション登録
- Facebookのアプリケーション登録

そして最後に、これらの登録結果をプラグインに反映させる作業をします。以下、順を追って簡単にですが説明していきます。

##Firebaseへの登録
まずは[Firebaseのサイト](https://firebase.google.com/)から登録作業を行いましょう。登録にはgoogleアカウントが必要になります。
登録したら「[コンソール（管理画面）](https://console.firebase.google.com/)」に移動。ここで「プロジェクト」をゲームごとに作成して、その中でデータベースやストレージを扱うことになります。

今回使うのは

- Authentication（認証）
- Database（データベース）
- Storage（ファイル保存）

の3つです。

###Authenticationの設定
「ログイン方法」タブで、ユーザーがゲームにログインするときに使いたいソーシャルアカウントをそれぞれ「有効」に設定します。
Googleアカウントでのログインはそのまま使えますが、TwitterとFacebookでは、それぞれの公式サイトで後述する「アプリケーション登録」作業が必要になります。
![スクリーンショット 2017-11-09 13.47.13.png](https://qiita-image-store.s3.amazonaws.com/0/27911/cdc669d2-bc4d-715b-7031-9c9ec17f8b69.png)

###Databaseの設定
他人のデータを参照することができないようにするため、下記のように「ルール」を変更します。
「ルール」タブの記入欄で下記のように記載してください。

```json:「ルール」
{
  "rules": {
    ".read": false,
    ".write": false,
    "user": {
      "$userId": {
        ".read": "auth.uid == $userId",
        ".write": "auth.uid == $userId",
      }
    }
  }
}
```

###Storageの設定
こちらもルールを変更します。
「ルール」タブの記入欄で下記のように記載してください。

```
service firebase.storage {
  match /b/{bucket}/o {
    match /user/{userId}/{allPaths=**} {
      allow read, write: if request.auth.uid == userId;
    }
  }
}
```
###ゲーム本体のあるドメインから使えるようにする設定
Firebase console > Authentication > Sign-in method にて「承認済みドメイン」にゲーム本体のあるドメインを追加してください。

###これも必要かも
「このままではCORSエラーになる」という指摘があって気づいたのですが、もしかしたら「よそのドメインからStorageを取得できるようにする」設定も必要かもしれません。自分の場合、たまたまいろいろ試してうまくいって、その手順を忘れていたのかもしれません…

CORS設定にはgsutilというコマンドラインツールを使うそうです。
https://firebase.google.com/docs/storage/web/download-files?hl=ja#cors_configuration


##各ソーシャルアカウントの設定
次に、ソーシャルアカウントでのログインを可能にするために、各SNSで「アプリケーション」としてあなたのゲームを登録します。
こんな画面、見たことありませんか？

![スクリーンショット 2017-11-09 13.58.32.png](https://qiita-image-store.s3.amazonaws.com/0/27911/2c19a833-ac0a-85cb-ae95-1eba82e646cb.png)
![スクリーンショット 2017-11-09 14.18.42.png](https://qiita-image-store.s3.amazonaws.com/0/27911/9586250d-3691-80d2-accc-350270087f6e.png)

この「連携アプリ」を登録する作業が必要になるわけです。それにしてもひどいタイトルですね我ながら。

###Googleの設定
FirebaseはGoogleが提供しているサービスなので、Googleアカウント向けの設定はすでに行われています。
コンソールの「Authentication」でGoogleを選択して、右上の「有効にする」スイッチをONにすれば完了です。
![スクリーンショット 2017-11-09 14.45.25.png](https://qiita-image-store.s3.amazonaws.com/0/27911/dd4f79ab-4ca8-2250-a847-290610fa280f.png)
（下の2項目は空欄のままでOK）

###Twitterのアプリケーション登録
Twitterアカウントでログインした上で、[Twitter Application](https://apps.twitter.com/)を開いて、アプリケーション登録作業を行います。
登録手順の説明は省略させてもらいますごめんなさい。日本語で説明してくださっているサイトが結構あるはずです。
最終的に必要なのは、「コンシューマーキー（APIキー）」と「コンシューマーシークレット（APIシークレット）」という、言ってみればIDとパスワードにあたるものです。
注意が必要な点として、「ゲームを実行するURL」と「Firebaseの認証(OAuth)を実行するURL」をそれぞれアプリケーションの管理画面で設定する必要があります。
ゲームを実行するURLは「Settings」タブの「Website」に。
認証を実行するURLは同じ画面の「Callback URL」に。

![スクリーンショット 2017-11-09 15.10.30.png](https://qiita-image-store.s3.amazonaws.com/0/27911/7e2c2775-a8ba-ee53-53f2-c7fb887fe0f9.png)

「Callback URL」に何を書けばいいかは、Firebaseの「Authentication」でTwitterの項目をクリックすると出てきます。
いったんFirebaseのコンソールに戻って、この項目を確認してください。下の画像で、薄いグレーになっているところです。
![スクリーンショット 2017-11-09 15.13.00.png](https://qiita-image-store.s3.amazonaws.com/0/27911/2c2d7e8f-614b-470e-3c61-c74bd14aa06a.png)

最終的にこんな感じで、Twitterアプリの「APIキー」と「APIシークレット」が発行されます。

![スクリーンショット 2017-11-09 15.00.55.png](https://qiita-image-store.s3.amazonaws.com/0/27911/5fe72d39-9ecc-dabb-fa2e-13942a1d02f8.png)

（黒塗りで伏せてありますが、英数字の文字列が入っています）

先ほど開いたFirebaseの「Authentication」設定画面に戻って、先ほどまで空欄だった2項目にこの情報を記入してください。
![スクリーンショット 2017-11-09 15.08.17.png](https://qiita-image-store.s3.amazonaws.com/0/27911/c8ef8c50-3413-a902-67e8-949350a6415b.png)



###Facebookのアプリケーション登録
Facebookアカウントでログインした上で、[開発者向けFacebook](https://developers.facebook.com/)を開いて、アプリケーション登録作業を行います。
こちらも登録手順の説明は省略させてもらいますごめんなさい。Twitterより若干ややこしい手順ですが、これも解説してくださってるサイトがたくさんあります。
こちらも、「ゲームを実行するURL」と「Firebaseの認証(OAuth)を実行するURL」をそれぞれアプリケーションの管理画面で設定する必要があります。

ゲームを実行するURLはメニューの「設定 > ベーシック」から、「アプリドメイン」「ウェブサイト」の2箇所に。「アプリドメイン」はその名の通りドメイン名だけ入力してください。

![スクリーンショット 2017-11-09 14.39.17.png](https://qiita-image-store.s3.amazonaws.com/0/27911/5110cc47-3bc5-62a2-2959-d7f64e13a135.png)

Firebaseの認証を実行するURLは、メニューの「Facebookログイン > 設定」にある「有効なOAuthリダイレクトURI」に。

![スクリーンショット 2017-11-09 14.14.27.png](https://qiita-image-store.s3.amazonaws.com/0/27911/1f5925d3-7806-7eea-30ee-8824409432db.png)
（右下の青くなっているところ）

「OAuthリダイレクトURI」に何を書けばいいかは、Firebaseの「Authentication」でfacebookの項目をクリックすると出てきます。
いったんFirebaseのコンソールに戻って、この項目を確認してください。下の画像で、薄いグレーになっているところです。
![スクリーンショット 2017-11-09 14.42.14.png](https://qiita-image-store.s3.amazonaws.com/0/27911/ab5746b4-1b78-8cb3-cc95-3de8c8c2b262.png)

最終的にこんな感じで、Facebookアプリの「アプリID」と「シークレット」が発行されます。
![スクリーンショット 2017-11-09 13.55.34.png](https://qiita-image-store.s3.amazonaws.com/0/27911/ec750d6f-4c96-a9eb-3ebf-859c31bf9d51.png)

先ほど開いたFirebaseの「Authentication」設定画面に戻って、先ほどまで空欄だった2項目にこの情報を記入してください。
![スクリーンショット 2017-11-09 14.17.42.png](https://qiita-image-store.s3.amazonaws.com/0/27911/7916b982-1391-51bd-2bce-ef0b198b4dd6.png)

##プラグインの設定

いや、まだ設定項目はあるんです…これで最後だからご辛抱くださいお願いします。
最後に、今まで設定した「Firebaseによるデータ管理」「ソーシャルアカウントによる認証」をRPGツクールで使えるように、プラグインの側で設定を行います。

いったんFirebaseコンソールのトップページに戻って「ウェブアプリにFirebaseを追加」のボタンをクリックします。わかりづらいですが丸いのはボタンです。Google先生のおしゃれ感覚にも困ったもんですね。

これの一番右です。
![スクリーンショット 2017-11-09 15.26.15.png](https://qiita-image-store.s3.amazonaws.com/0/27911/bdfe26c3-b533-0ea2-9b68-bc3f45ce9155.png)

スクリプトがずらずらと出てきますが、このプラグインの場合、2箇所に分けて記述する必要があります。
index.htmlのヘッダー内に下記のスクリプトを読み込む記述を追加してください。
pixi.jsを読み込んでいる行の下あたりが良いでしょう。

```html:index.html
<script src="https://www.gstatic.com/firebasejs/4.6.0/firebase.js"></script>
```
さらに、プラグイン SNZ_FirebaseSave.js のパラメーター「Firebase所定のタグのうち"config"で囲まれた部分の情報」に、赤枠で囲った部分の情報をそれぞれコピペしてください。
<img width="789" alt="スクリーンショット 2017-12-05 21.29.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/27911/534017d6-6356-81a4-6bf5-607332cb2714.png">
（これは私のゲーム用の設定なので、このまま写さないでね）

お疲れさまでした。ここまでできたら、試しにゲームをデプロイして、サーバーにアップロードして実行してみましょう。
なお、ブラウザゲーム用に設定しているので、ローカルからオンラインセーブをすることはできません。（Firebase用に用意されたJavascriptの仕様で、ローカルからFirebaseの呼び出しが実行できません）テストプレイの時はローカルセーブで動くように設定してあります。

#どういう仕組み？
以下は興味がありましたらご覧ください。

##ソーシャルアカウントで認証

パスワードをいちいち覚える必要のない便利な認証機能として、最近広く使われていますね。管理者の側から見ても、パスワード忘れなどのサポート対応の必要がなく、個人情報を扱わないため比較的気楽に実装できる利点があります。（とはいえ、ソーシャルアカウントのアカウント名は管理する必要がありますが）

各SNSの仕様に合わせて自前で認証スクリプトを書こうとすると結構大変です（[Twitter認証をするためだけのPHPライブラリ](https://twitteroauth.com/)があるほど）。Firebaseを使うと、簡単な記述で対応できます。

ちなみに、今回は採用しませんでしたが、Firebaseには「メールアドレスとパスワードを登録してログイン」する機能もあります。この機能を使いたい場合は「パスワードを忘れた場合のフロー」「本人確認のためメールを送信」など必要な処理が若干増えるようで、また、メールアドレスはゲーム配信者が管理することになるため扱いに注意が必要になるでしょう。

##セーブデータが保存される形式
###Firebaseの「リアルタイムデータベース」にセーブした時刻
Firebaseには「リアルタイムデータベース」という一種のデータベースがあります。MySQLなどの一般的なDBとは別物で、JSON形式でデータを保存しています。
このリアルタイムデータベースは、あまり大きな容量のデータを格納するのには向いていません。従量課金でがんがん費用が発生してしまいます。
そのため、リアルタイムデータベースにセーブデータ本体を保存するのは避け、セーブコマンドを実行した時点のタイムスタンプだけをユーザーごとに記録しています。

###Firebaseの「ストレージ」にセーブデータ本体
Firebaseにはファイルを保存できる領域があります。ユーザーごとにディレクトリを分け、他人のディレクトリは読み書きできないようにする、ということも簡単にできます。
これを利用して、各ユーザー専用のディレクトリに、セーブファイル（圧縮済みJSONをBlob化したもの）を保存します。
ファイル名は「セーブコマンドを実行した時点のタイムスタンプ」とし、前述のデータベースでタイムスタンプを管理しています。
なお、万が一のファイル破損に備え、新しいものから順に5つまでバックアップを保存しています。（一応、ストレージの肥大化で使用料金が発生するのを防ぐため、6つめより古いファイルは順次削除する処理を入れているのですが、消しきれてないみたいです。今後折を見てブラッシュアップしておきます）

#いやーBaaSって本当に素晴らしいものですねー
趣味でちょこっとプログラミングする人の敷居をぐっと下げてくれるサービスが近頃は充実しているんですね。ありがたいことです。
ではまたどこかで。