phina.jsとFirebaseで簡単にオンラインゲームを作ってみる

この記事は、[phina.js Advent Calendar 2016](http://qiita.com/advent-calendar/2016/phinajs) １２月１５日の記事です。
昨日→[phina.jsで加速度センサーを使ったミニゲームを作る by simiraaaaさん](http://qiita.com/simiraaaa/items/7e26d40cf3207a8386a9)

ネタ的に[tamochu3141](http://qiita.com/tamochu3141)さんの記事[phina.jsでの多人数参加型ゲームの作製](http://qiita.com/tamochu3141/items/8094aec3348dc9a6ae9d)と被り気味ですが、気にせずいきます。

#Firebaseとは
[こちら](http://qiita.com/kohashi/items/43ea22f61ade45972881)の記事で詳しく説明されておりますのでご参照ください。

#手軽なオンライン対戦環境が欲しい…欲しくない？
大昔、[こんなゲーム](http://9leap.net/games/3040)作って「やっぱりオンラインで対戦してみたいよね」と思って、さくらにサーバー借りてnode.jsでサーバ側スクリプト書いたりしてみましたが、簡単な事をするのにも結構大変で難儀しました。（現在はサーバーを止めている為、オンライン対戦は動作しません）

最近になって、FirebaseというBaaSがあるという事を知ったので、手軽にオンライン対戦環境を構築出来ないか試してみる事にしました。

Firebaseとの同期の為に、以下の様なアクセサリを作成しました。

```JavaScript:phina.Firebase.js
phina.define("phina.Firebase.Receiver", {
    superClass: "phina.accessory.Accessory",

    _stop: false,
    _val: null,

    init: function(firebaseObject) {
        this.superInit();
        this.firebase = firebaseObject;

        this._val = {};

        //firebaseと情報を同期
        this.firebase.on('value', function(snap) {
            if (this._stop) return;
            var v = snap.val();
            this._val.$safe(v);
        }.bind(this));
        this.firebase.on('child_changed', function(snap) {
            if (this._stop) return;
            this._val[snap.key()] = snap.val();
        }.bind(this));
    },

    start: function() {
        this._stop = true;
        return this;
    },

    stop: function() {
        this._stop = false;
        return this;
    },

    key: function() {
        return this.firebase.key();
    },

    val: function() {
        return this._val;
    },
});

phina.define("phina.Firebase.Sender", {
    superClass: "phina.accessory.Accessory",

    _stop: false,
    _data: null,

    init: function(firebaseObject) {
        this.superInit();
        this.firebase = firebaseObject;
        this._data = {};
    },

    start: function() {
        this._stop = true;
        return this;
    },

    stop: function() {
        this._stop = false;
        return this;
    },

    update: function() {
        if (this._stop) return;
        this.firebase.update(this._data);
    },

    setSendData: function(data) {
        this._data = data || {};
        return this;
    },

    remove: function() {
        this.target.detach(this);
        this.target = null;
        this.firebase.remove();
        return this;
    },

    key: function() {
        return this.firebase.key();
    },

    val: function() {
        return this._data;
    },
});
```

Firebaseのデータベース内のオブジェクトに対するセンダーとレシーバーです。それぞれ、オブジェクトの内容の更新と取得を行います。
作ってみたら、アクセサリにする意味はありませんでしたが、アタッチしたオブジェクトが自動更新される様にするとか良いかもしれません。

##使用方法
最初に、[Firebase](https://console.firebase.google.com/?hl=ja)側の設定を行います。
詳しい設定方法割愛しますが、新規プロジェクトを作成して、ＵＲＬ等が取得出来ればオッケーです（手抜き）


###FirebaseAPIをロード
```html
<script src="https://cdn.firebase.com/js/client/2.0.4/firebase.js"></script>
```

###firebaseのオブジェクト取得

```JavaScript:Firebaseオブジェクトの取得
var firebase = new Firebase("firebeseのURL");
//下位オブジェクトの取得
var objects = firebase.child("objects");
```
phina.jsのクラスと違い、`new`が必要なので注意してください。
下位オブジェクトはFirebase側で用意する必要は無く、アプリケーション側からオブジェクトを取得or追加すると自動的に作成されます。

###Firebaseへのオブジェクトの追加

```JavaScript:Firebaseへオブジェクトの追加
this.player = this.objects.push({
    type: "player",
    name: "test",
    age: 0,
    x: SC_W*0.5,
    y: SC_H*0.5,
});
```

```JavaScript:クライアント側オブジェクト追加
objects.on("child_added", function(snap) {
    //セッションキー
    var key = snap.key();
    //内容
    var val = snap.val();

    //クライアント側オブジェクト追加処理
    //...
});
```
Firebaseでオブジェクトが追加されると`child_added`イベントが発火します。
コールバック関数で追加されたオブジェクトの内容が引数として渡されますので、内容に応じてクライアント側で追加処理をします。


###Firebaseオブジェクトの操作
```JavaScript:Firebase側からオブジェクトを取得する場合
//アクセサリの定義
this.firebase = phina.Firebase.Receiver(firebase).attachTo(this);
//オブジェクトの取得
var data = this.firebase.val();
```


```JavaScript:Firebaseへオブジェクトを送信する場合
//アクセサリの定義
this.firebase = phina.Firebase.Sender(firebase).attachTo(this);
//Firebaseへオブジェクトを送信
this.firebase.setSendData({
    x: this.x,
    y: this.y,
});
```
Firebaseとの情報の同期は自動で行われます。

```JavaScript:自動更新の停止と再開
//自動更新停止
this.firebase.stop();

//自動更新開始
this.firebase.start();
```
自動更新の操作はセンダー、レシーバー共通です。


##実際の動作サンプル
と言う事で、簡単ですがこんなのを作ってみました。
[Firebaseでのオンライン対戦サンプル]
(https://minimo.github.io/phina.js_advent_2016_3/)
カーソルキー左右で移動。上でジャンプ。
スペースキーで弾を発射します。
他に接続しているユーザーがいると、自分の画面にも表示されます。
当り判定はありません（対戦とはいったい）


#良い点
同期させたいデータがJSONで一括取得出来るのは非常に楽で、あんまり難しい事を考える＆覚える必要がありません。
特に、サーバーサイドの作り込みが不要なのは大きいです。

#悪い点
サンプルを実行すれば一目瞭然なのですが、かなりラグがあるので激しい動きのあるアクションゲーム等には不向きと思われます。
常に全データ同期するので、規模が大きくなってくるとデータ転送量的にヤバそうではあります。

#おしまい
甚だ簡単ではありますが、オンラインゲームを作るのはサーバー側の知識も必要で相当にハードルが高いけど、使用するサービスによってはかなり楽に出来るよってお話でした。
いやー、技術の進歩って素晴らしい。

サンプルのソースはこちら[[GitHub](https://github.com/minimo/phina.js_advent_2016_3)]