Skip to content

JavaScriptでMMOGをつくってみよう その7 JSON-RPC風通信

今回は、node.jsとUnity3Dの間でJSON-RPCを用いた通信を行います。

(この連載で紹介している、JavaScriptを用いたMMOG開発は、実験的な手法です。拙著「オンラインゲームを支える技術」という本では実績のある制作技法を紹介しています。そちらもどうぞ!)

前回はminecraftの通信プロトコルをざっと調べました。調査はひととおり終わり、今回からはゲームがプレイできる状態を保ちつつ、コードを書いていきます。

サーバーのソースをgithubに置きました。 https://github.com/kengonakajima/wise9mmo

クライアントはzipファイルで圧縮してここに:
http://junk.wise9.jp/ringo/fpstest1_20110421_0.zip

Unity3Dのソースコードのバージョン管理については、仕事でUnity3Dを使っているいろいろな人に聞いてみても、

「フリー版だとzipで固めてバックアップする以上の方法がないんだよね〜」

ということだったので、あきらめることにします。
ちょっといいスクリプトを書いてどうにかならないかとも思うけど、なかなかいい方法が見つからない。

今回は、JSON-RPCの仕組みをひととおり実装して、Unity3Dとnode.jsのサーバ間で、互いに関数を呼び出せるようにしました。
通常の、HTTPベースの JSON-RPCとの違いは、「コール」と「結果」という組みではなく、「コール」しかない点です。
テキストベースのプロトコルなので、サーバを起動してtelnet コマンドで試すことができます。 その様子がこれ:

端末フォントが小さいので720pにして全画面でみてみてください。

現在、サーバに実装されている関数は、引数をそのまま返すechoと、合計して返すsumしかありません。

その定義はこんなかんじ

// RPC 関数定義
function echo( a, b, c ) {
  sys.puts( "echo: abc:"+a+","+b+","+c);
  this.send( "echo", a,b,c);
}

function sum( a, b, c ) {
  sys.puts( "sum: abc:"+a+","+b+","+c);
  this.send( "sum", a+b+c);
}

// RPC関数を登録
addRPC( "echo", echo );
addRPC( "sum", sum );

引数は3つまでです。 JavaScriptなので、文字列が来た場合には、文字列として連結されるし、引数が足りない場合はundefinedが渡されます。

ひとつ残念なことは、受信はrpcになってるのに、送信は、関数呼び出しの形式にはなっていなくて、共通の送信関数になってることです。 非対称なAPIになっているので、若干気持ちが悪いです。理想的には、

function sum( a, b, c ) {
  sys.puts( "sum: abc:"+a+","+b+","+c);
  this.send_sum( a+b+c);
}

こんな感じで関数呼び出しを変換できればいいのですが。 静的言語を使う場合ソースコードの自動生成を使いますが、JavaScriptでは、Rubyにある method_missingのようなものがあれば動的に生成して対応できます。ただ、書き比べてみると、逆に今回の方式のほうが見やすい気もしてきたので、このままいくことにします。

telnetコマンドのログはこんな感じです。

{"method":"echo","params":[1,2,"hoge"]}     送信
{"method":"echo","params":[1,2,"hoge"]}     受信
{"method":"sum","params":[1,2,"hoge"]}     送信
{"method":"sum","params":["3hoge"]}      受信

sumは動作が違うのがわかります。

 

関数を登録するのは連想配列に入れるだけ。

 

// 関数登録
function addRPC( name, f ) {
  functions[name]=f;
}

送信はJSON.stringifyを呼ぶだけ。受信は改行記号でsplitするだけ。というところは前々回説明しました。全然、トリッキーなところがなく、すなおなサーバーになっています。ファイルも1個しかなく、上から順に追える内容になっています。MySQLにアクセスする部分は次回以降ですが、コメントに書いてあるので参考に。

MMOGのコアの部分はこんなものです。ここからふくらませていくだけです。大して複雑ではないですね。

 

次にUnity3Dの側ですが、こちらはいろいろと制限がありました。まず、Unity3Dでは、JavaScriptの可変長引数の関数が使えません。arguments を使った柔軟な処理ができないので、関数オーバーロードを使って、泥臭いやり方を使っています。

ソケット通信はJavaScript側からできず、C#側からしかできないのですが、JavaScriptの修正だけでゲーム開発ができるように、RPCの登録部分はJavaScript側に実装しています。

 

まず送信。

以下のコードは、画面に表示している”Clear”というボタンを押したときの挙動です。 sendという1個の関数を呼び出せばJSONRPCを呼び出せます。 送信関数の引数のフォーマットは、サーバと同じですね。

if( GUI.Button( Rect( 20,40,80,20), "Clear" )) {
  clearCubes();
  send( "sum", 1, 2, "hoge" );
}

send関数自体はこうです

function send( meth, arg0 ){ sendWithParams( meth, [ arg0 ] ); }
function send( meth, arg0, arg1 ){ sendWithParams( meth, [ arg0, arg1 ] );}
function send( meth, arg0, arg1, arg2 ){ sendWithParams( meth, [ arg0, arg1, arg2 ] );}

function sendWithParams(meth, params ){
    var h={};
    h["method"]=meth;
    h["params"]=params;
    protocol.writeSocket( hashToJson(h));
}

なんと泥臭い。引数10個にしたい場合は、関数を10個も作らないといけない。。ですが可変長引数がないので仕方がありません。 配列を引数にしてしまうと、引数に配列を複数渡したい場合とあいまいになってしまいます。

最終的にprotocolという変数を経由してソケットに対してデータを送信していますが、
protocolはグローバル変数で

var protocol;

function Start () {
    pitch = 0;
    dy = 0;
    cubes = new Array();

    protocol = GetComponent( "ProtocolScript");
}

このように1度だけ初期化して外部のスクリプトを読み込んでいます。ProtocolScriptはC#で書かれています。

hashToJsonという関数では、Unityの Hash構造を文字列に変換します。Unity3Dには組み込みのJSON関数群がないので、自作しました。これは色々なことに使えるかもしれません。

function escape(s){
    return s.Replace( '"', "\\\"" ).Replace( "'", "\'" );
}

function toString(x) : String {
    var out;

    var s  = typeof(x).ToString();
   
    switch(s){
    case "System.String":
        out= "\"" + escape(x) + "\"";
        break;
    case "System.Int32":
        out= ""+x;
        break;
    case "System.Int32[]":
        out=arrayToJson(x);
        break;
    case "System.String[]":
        out=arrayToJson(x);
        break;
    case "Boo.Lang.Hash":
        out=hashToJson(x);
        break;
    case "System.Object[]":
        out=objaryToJson(x);
        break;
    default:
        throw "not implemented:"+typeof(x);        
    }
    return out;
}

function hashToJson(h) {
    var out = new Array();
    for( key in h.Keys ){
        out.push( "\""+key+"\":" + toString(h[key]) );
    }
    return "{"+out.Join(",")+"}";
}
function objaryToJson(oa) {
    var out = new Array();
    for( o in oa ) {
        out.push( toString(o));
    }
    return "["+out.Join(",")+"]";
}

function arrayToJson(ary) : String {
    var out = new Array();
    for(var i=0;i<ary.length;i++){
        out.push( toString(ary[i]) );
    }
    return "["+out.Join(",")+"]";
}

次は受信側。JSONは送信より受信して構文解析するほうがだいぶん大変です。なのでUnityの開発者コミュニティの成果物を使いました。

これ:

http://www.unifycommunity.com/wiki/index.php?title=Json

ソケットからきたデータをつっこむとJSONObjectというクラスのインスタンスを返します。このクラスは内部に、参照を利用してJSONObjectのツリー構造を持っています。このツリー構造はJavaScript側でそのまま利用することができます。

これをそのままつかって、

    public JSONObject readJSON() {
        String s = readSocket();
        if( s != "" ){
            try {
                return new JSONObject(s);
            } catch( Exception e ) {
                Debug.Log( "exception!:"+e);
                return null;
            }
        } else {
            return null;
        }
    }

readJSONという関数をC#側で定義しています。これはJSONを1個とりだしてくれます。
JavaScript側ではこうなります。

function doProtocolRecv() {
    var h = protocol.readJSON();
    if( h != null ){
        print( "from server:"+h);
    }

}

function Update () {
    doProtocolRecv();
}

いまは単にprintしてるだけです。Update関数が毎ループ呼び出されるので、その中で受信しています。Update関数は主人公(主人箱?)に割り当てられているスクリプトです。

これで、node.js側と、Unity3D側とで、JSON-RPCを用いたシンプルな通信ができるようになりました。

 
次回は、この通信を使って、キャラクターの座標や向きの情報を送信して、移動ができるようにしていきます。

しかし今週は minecraft 1.5が出たり、portal2も出たりで、時間がいくらあっても足りないですね。。。

このエントリーをはてなブックマークに追加
はてなブックマーク - JavaScriptでMMOGをつくってみよう その7 JSON-RPC風通信
Post to Google Buzz
Share on GREE

Related posts:

  1. JavaScriptで MMOGをつくってみよう その4
  2. [投稿]JavaScriptでBASICっぽい言語をつくる
  3. JavaScriptでMMOGを作ってみよう その5 RPCとORM

Facebook comments:

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*