Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

WebSocketでカンタンなゲームを作った

火をつけて消えないように薪を足し続けるだけのゲームを作りました。サイトに接続している人なら誰でも薪を足したり、火をおこせます。

http://ws.pandanoir.net/

WebSocket通信をどう使っているか

WebSocketは一度確立したコネクションを切断しないでそのまま繋ぎ続ける技術で、クライアント―サーバー間の双方向通信が可能になります。

たとえばクライアントAが火を起こすと、次のような処理をします。

  1. クライアントAが「着火した」とサーバーに送る
  2. サーバーが全クライアントに「着火した」と送る
  3. 全クライアントの情報が更新される

薪が足されたときも同様です。

火が消えたときはサーバーが全クライアントに「火が消えた」と送るだけです。各クライアントが消えたかは関係ありません。

使用した技術

WebSocketとVue、Vuexを組み合わせて作りました。

今回は「残り時間」を表示しなければならないので、Reactive Time with Vue.js - Cushionを参考にして現在時刻を更新しています。

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import fire from './modules/fire.js';

Vue.use(Vuex);

const store = new Vuex.Store({
    modules: { fire },
});

store.dispatch('fire/start'); // 時刻の更新を開始する

export default store;
// store/modules/fire.js
import socket from '../socket';
const state = {
    existsFire: false,
    extinguishTime: 0,
    startTime: Infinity,
    maxRecord: 0,
    now: +new Date(), // 現在時刻を数値で格納
};

const getters = {
    restTime({extinguishTime, now}) {
        return Math.max( Math.ceil((extinguishTime - now) / 1000), 0 );
    },
    remainTime({now, startTime}) {
        return Math.max( Math.floor((now - startTime) / 1000), 0 );
    },
    /* ... */
};

const actions = {
    start({commit}) {
        // これがdispatchされると時刻の更新が始まる
        const fps = 32;
        setInterval(() => commit('updateTime'), 1000 / fps); // 1秒間に時刻を32回更新するように設定
    },
    /* ... */
};

// mutations
const mutations = {
    updateTime(state) {
        // 上のsetIntervalに設定されているように、この関数が1秒に32回呼び出される
        state.now = +new Date();
    },
    /* ... */
};

export default {
    namespaced: true,
    state,
    getters,
    actions,
    mutations
};

また、Socket.ioの部分は、イベントを受け取るとstoreにアクションを発行するよう設定しています。

// store/socket.js
import io from 'socket.io-client';
import store from './';

const socket = io();

socket.on('info', (data) => store.commit('fire/getServerState', {payload: data}));
socket.on('fire_extinguished', (data) => store.commit('fire/fireExtinguished', {payload: data}));
socket.on('fire', (data) => store.commit('fire/litFire', {payload: data}));
socket.on('add_fuel', (data) => store.commit('fire/addFuel', {payload: data}));

export default socket;

ほかはただ表示するためのコンポーネントを作っただけで技術的に面白い部分はないので、割愛します。