【マインクラフトの通信仕様】

はじめに

世に出回っているようなブロードキャストタイプのサーバー(いわゆるエコーサーバー)を利用するとサーバーやネットワークに負荷をかけるとともにマインクラフトのようなサブスクライブイベントを採用しているようなクライアント相手では無限ループになる可能性もあるので対応できません。その為今回はコマンドタイプのサーバーを採用しています。

▶フレームワークのご紹介のページで実行しているデモ環境は、Websocketサーバーの最小構成モデルとする目的で作った環境なのでチャット機能しか実装していませんが、マインクラフト専用のコンテンツを実装するための環境を▶マインクラフトの環境のページでご紹介していますので、今後の追加実装はそちらがメインになります。

Websocketサーバーを介す事で独自のゲーム内のイベントを取得できたり、サーバーサイドからコマンドを発行する事もできますので今後の追加実装の応用の幅が拡がります。
このページでは、そのWebsocketサーバーと連携できる統合版マインクラフトの今後の拡張を見据えて特有の構造やメカニズムを押さえておくためにまとめています。

ファイル構成

マインクラフト専用コンテンツのプロジェクト環境(contents-project)はデモ環境(demo-project)を引き継いで構築しています。
Websocketサーバーを使っている事には変わりはないので、基本的な構造はデモ版のWebsocketサーバーや▶Websocket開発環境のページでご紹介している内容とほぼ同じです。
そのためサーバーサイドではWebsocketサーバーを構成するクラスファイルを継承して以下の構成で実装しています。
マインクラフト版のサーバーでは上記の色がついている部分のファイルを直接使ってマインクラフト独自の処理を実装しています。

マインクラフトへの送信データ

送信データには2種類のJSONデータがあります。マインクラフト向けのJSONデータは少々複雑なため以下のようにUNITパラメータクラスでメソッド化しています。
サブスクライブデータの生成
/**
 * マインクラフトへ送信するサブスクライブデータを取得
 * 
 * @param string $p_eve サブスクライブするイベント名
 * @return array 送信データ
 */
public function getSubscribeData(string $p_eve): array
{
    // UUIDの取得
    $uuidv4 = $this->getUuidv4();

    // サブスクライブエントリデータ
    $w_ret =
    [
        "header" =>
        [
            "version" => 1,                     // プロトコルのバージョンを指定。現時点では1で問題ない
            "requestId" => $uuidv4,             // UUIDv4を指定
            "messageType" => "commandRequest",  // "commandRequest" を指定
            "messagePurpose" => "subscribe",    // "subscribe" を指定
        ],
        "body" =>
        [
            "eventName" => $p_eve               // イベント名を指定。
        ]
    ];

    return $w_ret;
}
                    

サブスクライブデータというのは、マインクラフト側で発生したイベントをWebsocketサーバー側へ配信してもらうための予約データのようなものです。
$p_eve引数でイベントの種類を指定するのですが、例えばデモ環境ではPlayerMessageのみ使用していますのでプレイヤーがメッセージを送信した時のみイベントデータが配信されます。

※デモ環境での使用は限定的ですが▶マインクラフトの環境のコンテンツページでは、実際に使用している受信イベントのデータ形式を掲載していますので、そちらも合わせてご覧ください。
コマンドデータの生成
/**
 * マインクラフトへ送信するコマンドデータを取得
 * 
 * @param string $p_cmd コマンド文字列
 * @param string $p_typ 処理タイプ文字列('response'コマンドで利用)
 * @return array 送信データ
 */
protected function getCommandData(string $p_cmd, string $p_typ = null): array
{
    // UUIDの取得
    $uuidv4 = $this->getUuidv4();

    // サブスクライブエントリデータ
    $w_ret =
    [
        "header" =>
        [
            "version" => 1,
            "requestId" => $uuidv4,                 // UUIDv4を生成して指定
            "messageType" => "commandRequest",      // commandRequestを指定
            "messagePurpose" => "commandRequest",   // commandRequestを指定
        ],
        "body" =>
        [
            "origin" =>
            [
                "type" => "player"
            ],
            "version" => 1,
            "commandLine" => $p_cmd,                // マイクラで実行したいコマンドを指定
        ]
    ];

    // 待ち受けるレスポンス情報を設定
    $this->setAwaitResponse($uuidv4, $p_typ);

    return $w_ret;
}
                    

こちらはマインクラフトのコマンドを実行するためのデータです。実際のコマンド文字列は$p_cmd引数で指定します。
デモ環境ではtitleコマンド(チャットメッセージ用)とmsgコマンド(ウィスパー)のみ使用しています。

コマンド部の解釈

デモ環境の場合、前項のサブスクライブ登録によりマインクラフトから送信されるチャットデータはブラウザ側で解釈できるコマンドに変換する必要があります。
▶アーキテクチャページの図を引用すると以下のコマンドディスパッチャーの部分でコマンド変換を行っています。
変換を行っているコマンドの種類は以下の通り。
■マインクラフトからの通常のチャットデータ
messageコマンドへ変換
■マインクラフトからのプライベートチャットデータ
<コメント>#<ユーザー名>の形式で送信されたデータをprivateコマンドへ変換
■マインクラフトからの退室要求
$exitの文字列が送信されたデータをexitコマンドへ変換
※コマンドの詳細については▶デモのコマンド仕様のページを参照。

マインクラフト用のコマンド

デモ環境のコマンド部で実装しているマインクラフト特有のものは以下の2種類です。
■entrance-waiting
マインクラフトからの再接続では動作が不安定な事もあり、openingハンドシェイク後にアライブチェックを行うようにしています。
通信が不通の状態でアライブチェックを行うとマインクラフト側で強制切断されるので、この動作はフェイルセーフの役割を担っています。
このアライブチェックの間の待機状態を作るのがこのコマンドです。
■response
マインクラフトへのコマンド送信後には必ずレスポンスが返ってきます。
現状はエラー処理のレスポンス受信後に切断シーケンスを走らせるのが目的ですが、マインクラフトではコマンド送信時に必ずuuidを付与する事になっているので、このuuidでコマンドとレスポンスの紐づけを行っています。

おわりに

現状のマインクラフトの切断処理については色々と問題があります。
entrance-waitingコマンドはそれを賄うためのものですが▶切断フレームの検証⇒マインクラフトの場合のページで詳しくまとめていますので一度ご覧ください。

また、マインクラフトからの接続の判定は当初User-Agentで行う予定でしたが、openingハンドシェイクには載っていない事が判明しましたので、現状はUser-Agentの有り無しで判定するようにしています。

サブスクライブデータの登録は「デモ環境」「マインクラフトの環境」共に以下の設定ファイルで登録可能です。
setting/minecraft.php
return [

    /**
     * @var array 設定するサブスクライブタイプ(複数指定可)
     */
    'subscribe_types' =>
    [
        'PlayerMessage'
    ]
];