読者です 読者をやめる 読者になる 読者になる

WebSocketでスマホブラウザ→heroku(node.js)→Unity(mac)

イベントでインスタレーションをした時に、来場者が気軽に参加できるようにスマホのブラウザで操作できる仕組みを勉強しとこうかなと。

こんなイメージ。
f:id:primevision:20140327173717j:plain


スマホからのデータを受け取るにはOSCもありますね。
でもアプリじゃないとたぶんダメだし、制御用PCと同じwifiに入ってもらわないといけないし、お客さんがわざわざそこまでやってくれるような規模のものならいいけど、気軽に参加できるものでは無いです。

で、今まで気になってたけど手を出してなかったnode.js+socket.ioをやってみることに。

サーバーはどこが主流なのか分からないけど、とりあえずherokuで。


1.ブラウザのみでsocket.ioをテスト
そのへんで見つけたサンプルを動かす→動いた!


2.Unityでsocket.ioを試す
サーバーサイドそのままで、Unityから接続するテスト。
検索するとsocket.io用のライブラリが出てきたのでそれを使う。→動かない!


3.UnityをWebSocketに
socket.ioのライブラリ使ってても結局はWebSocketで通信すると思うので
それであれば最初からWebSocket限定でいいかということでWebSocketのライブラリを探して使用。→動かない!


4.サーバーサイドもWebSocketに
サーバーサイドもWebSocketに変更→動いた!

socket.ioは勝手にwebsocketとかの通信方法を変えてうまくやってくれるヤツだと思ってたんだけども、WebSocketだけにしたらできた。
スマホのブラウザから制御用PCのUnityにデータを送ることができました。
WebSocketはAndroidだと4.4からっぽいので厳しいけども。。
Androidだけアプリにしちゃうとかかな。そしたらiPhoneもアプリでもいいけど審査を考えるとリスクがある。
ただ、この方法だと現地にいなくてもデータ送れちゃうのでそこは何か考えないといけない。GPSとか、現地にいないと分からない個別のURL(ID)発行とか。


↓ちょっとやってみたのがこれ。


WebSocketでスマホブラウザ→heroku(node.js)→Unity(mac ...

ライゾマさんとかのアレみたいにスマホでオブジェクトをスワイプすると飛んでいって映像に反映されるとかをやってみたかったのでそんな感じに。
四角か丸を選んで飛ばすとUnity上でCubeかSphereが降ってくるというものです。

そういえばNexus5でしか試してないけどiPhoneも大丈夫なはず!


●サーバーサイド
herokuのWebSocketのサンプルをベースに。

var WebSocketServer = require('ws').Server
  , http = require('http')
  , express = require('express')
  , app = express()
  , port = process.env.PORT || 5000;

app.use(express.static(__dirname + '/'));

var server = http.createServer(app);
server.listen(port);

console.log('http server listening on %d', port);

var hostPC;

var wss = new WebSocketServer({server: server});
console.log('websocket server created');
wss.on('connection', function(ws) {
    //定期的にデータ送っとかないと接続が切れてしまう環境があるらしいのでたぶんそのための処理
    var id = setInterval(function() {
        ws.send(JSON.stringify({'msg':'none', 'date':new Date()}), function() {  });
    }, 1000);

    console.log('websocket connection open');
    ws.on('message', function(msg) {
    	//メッセージはJson形式にした
    	var obj = JSON.parse(msg);
    	
    	if (obj.msg == 'setpc') {
    		//制御用PCの設定
    		hostPC = ws;
    	}
    	if (obj.msg == 'fromclient') {
                //スマホブラウザからのメッセージ
                //制御用PCへ送信
    		if (hostPC != null) {
    			hostPC.send(msg);
    		}
    	}
    });

    ws.on('close', function() {
        clearInterval(id);
    });
});


●Unity(制御用PC)
「WebSocket-Sharp」というのを購入。(15ドル)
自分でビルドできれば買わなくてもいいっぽい。

    //接続
    //ポートの指定はしなくても大丈夫だった。80で接続してるはず。
    ws =  new WebSocket("ws://*********/");
    ws.OnOpen += (sender, e) => {
        //接続したら自分を制御用PCとしてサーバーに教える
        SendSetPCMessage();
    };
    ws.OnMessage += (sender, e) => {
        string s = e.Data;
        Debug.Log (s);
        //LitJsonというライブラリを使用
        LitJson.JsonData jsonData =  LitJson.JsonMapper.ToObject(s);
        string msg = (string)jsonData["msg"];
        Debug.Log(msg);
        if (msg == "fromclient") {
            //クライアントからのメッセージ
            //四角か丸かの情報
            int no = (int)jsonData["no"];
            Debug.Log(no);
            //このタイミングでGameObjectはいじれないので、フラグたててOnUpdate内でオブジェクトを生成する
            objID = no;
            b = true;
        }
    };
    ws.Connect();
    //メッセージ送信
    void SendSetPCMessage(){
        string jsonText = "{\"msg\":\"setpc\"}";
        ws.Send(jsonText);
    }


●クライアント
サーバーサイドと同様にherokuのWebSocketのサンプルがベース。
オブジェクトを飛ばすためにcanvasで表示。
勉強ついでにFlash CCのHTML5の機能で作成、herokuのサンプルとくっつけました。

Flashでのソースです。

createjs.Ticker.setFPS(30);
stage.enableMouseOver(30);

var MARGIN = 8;
var width = canvas.width;
var height = canvas.height;

var drag = false;
var fly = false;

var mc = this.mc01;
mc.gotoAndStop(0);
this.addEventListener("tick", function() {
    if (fly) {
        mc.y -= 40;
        if (mc.y < -200) {
            send(mc.currentFrame);
            fly = false;
            mc.y = 200;
        }
    }
    
});
    
var mouseX = 0;
var mouseY = 0;
var downX = 0;
var downY = 0;

if (createjs.Touch.isSupported()) {
    console.log("Touch enable");
    createjs.Touch.enable(stage);
    canvas.addEventListener("touchstart", touchStartHandler, false);
    canvas.addEventListener("touchmove", touchMoveHandler, false);
    canvas.addEventListener("touchend", touchEndHandler, false);
} else {
    canvas.addEventListener("mousedown", onMouseDown, false);
    canvas.addEventListener("mousemove", onMouseMove, false);
    canvas.addEventListener("mouseup", onMouseUp, false);
}

function onDown(xx, yy) {
    downX = xx;
    downY = yy;
    mouseX = xx;
    mouseY = yy;
    drag = true;
}
function onMove(xx, yy) {
    if (!drag) {
        return;
    }
    mouseX = xx;
    mouseY = yy;
}
function onUp() {
    drag = false;
    if (mouseY < downY - 20) {
        fly = true;
    }
}

this.changeBtn.addEventListener("mousedown", function(e){
    if (mc.currentFrame == 0) {
        mc.gotoAndStop(1);
    } else {
        mc.gotoAndStop(0);
    }
});

function onMouseDown(e) {
    stageW =  document.documentElement.clientWidth;
    var xx = 0;
    var yy = 0;
    if(e) {
        xx = e.pageX;
        yy = e.pageY;
    } else {
        xx = event.x + document.body.scrollLeft;
        yy = event.y + document.body.scrollTop;
    }
    xx -= MARGIN;
    yy -= MARGIN;
    onDown(xx, yy);
}
function onMouseMove(e) {
    stageW =  document.documentElement.clientWidth;
    var xx = 0;
    var yy = 0;
    if(e) {
        xx = e.pageX;
        yy = e.pageY;
    } else {
        xx = event.x + document.body.scrollLeft;
        yy = event.y + document.body.scrollTop;
    }
    xx -= MARGIN;
    yy -= MARGIN;
    onMove(xx, yy);
}
function onMouseUp(e) {
    onUp();
}
function touchStartHandler(e) {
    var touch = e.touches[0];
    var target = touch.target;
    var xx = touch.pageX - target.offsetLeft;
    var yy = touch.pageY - target.offsetTop;
    onDown(xx, yy);
}
function touchMoveHandler(e) {
    var touch = e.touches[0];
    var target = touch.target;
    var xx = touch.pageX - target.offsetLeft;
    var yy = touch.pageY - target.offsetTop;
    onMove(xx, yy);
}
function touchEndHandler(e) {
    onUp();
}