凹みTips

id:hecomi が興味をもった C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Unity から Node.js を起動時に裏で実行・通信して諸々の処理を肩代わりしてもらう方法考えてみた

f:id:hecomi:20140415010624p:plain

はじめに

Unity C# の世界で完結して色々と実行してくれるのはライブラリの利用者側から見るととても楽ですが、プロトタイプをそれで作ろうとすると結構大変です。そこで、Processing なり oF なり自分の慣れ親しんだ環境で作成したものを別途動かし、そこから OSC なりなんなりで通信して Unity の世界に反映させる、ということが良くやられていると思います。しかしながら、これを配布して別の人に使ってもらおうと考えると、別途色々と動かしてもらわないとならず大変です。

そこで、ここをうまい具合にやる方法を思いついたのでご紹介します。

アイディア

アイディアとしては、何かしらのコマンドStreamingAssets 下に置いたバイナリSystem.Diagnostics.Process から実行するというシンプルなものです。先行事例だと、Unity でリアルタイムにリップシンクする MMD4Mecanim LipSync Plugin を作ってみた - 凹みTips では、Open JTalk を実行して Wave ファイルを StreamingAssets 内のテンポラリディレクトリに生成、それを読み込んで 3D サウンドとして Unity の世界で再生、再生後 rm コマンドで消す、ということをやっています。これを応用して、Awake 時に Node.js でサーバを立ち上げ、適宜 OSC WebSocket 等でやり取りをし、OnApplicationQuit で終了する、みたいなことをやれば、裏で自動的にサーバの立ち上げと立ち下げをやってくれる形になります。ここを詳しくご紹介したいと思います。

ダウンロード

今回の実験用のプロジェクトは以下で公開しています(Mac 用、Windows もバイナリ入れ替えれば動くと思います)。

そのうち機能拡充して Unity 側と Node.js 側併せてライブラリ化します。

原理実験

原理実験をしてみます。まず、StreamingAssets 直下に Node.js のバイナリ(私は nodebrew でビルドしたものを利用)を配置し、以下の様なコードを書きます。

NodeJS.cs
using UnityEngine;
using System.Collections;

public class NodeJS : MonoBehaviour
{
    private static readonly string NodeBinPath =
        System.IO.Path.Combine(Application.streamingAssetsPath, "node");

    private System.Diagnostics.Process process_;
    public string scriptPath = "";

    public bool isRunning { get; set; }

    void Awake()
    {
        isRunning = false;
        Run();
    }

    void OnApplicationQuit()
    {
        if (process_ != null && !process_.HasExited) {
            process_.Kill();
            process_.Dispose();
        }
    }

    void Run()
    {
        process_ = new System.Diagnostics.Process();
        process_.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
        process_.StartInfo.FileName    = NodeBinPath;
        process_.StartInfo.Arguments   = scriptPath;
        process_.EnableRaisingEvents   = true;
        process_.Exited += OnExit;
        process_.Start();
        isRunning = true;
    }

    void OnExit(object sender, System.EventArgs e)
    {
        isRunning = false;
        if (process_.ExitCode != 0) {
            Debug.LogError("Error! Exit Code: " + process_.ExitCode);
        }
    }
}

このスクリプトを適当な GameObject にアタッチし、Script Path で適当な js をフルパスで指定してあげます。例えば以下の様なものを書いてみます。

require('http').createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello, world!');
}).listen(8080);

f:id:hecomi:20140414214916p:plain

そしてゲームを実行し、ブラウザから http://localhost:8080/ にアクセスしてみます。

f:id:hecomi:20140414215107p:plain

無事表示されました。ゲームを終了して再度アクセスすると Not Found になり、サーバが立ち下がっているのが分かります。

通信をしてみる

OSC で通信することにしてみます。まず JS 側の用意をします。Unity では JS は Unity の JS としてコンパイルされてしまうので、Assets 以下の普通のディレクトリに置くことは出来ません。そこで、コンパイルされない WebPlayerTemplates というディレクトリを用意すると便利ですが、こちらはビルドすると見えなくなってしまいます。そこで StreamingAssets 以下に "." から始まるディレクトリを用意し、ここに一連のファイルを置くとビルド時にも含まれ且つコンパイルされない形になります。

$ cd PATH_TO_YOUR_PROJECT/Assets/StreamingAssets/
$ mkdir .node
$ cd .node
$ npm install node-osc
$ vim osc.js

そして以下の様なスクリプトを書いてみます。

osc.js
var util   = require('util');
var osc    = require('node-osc');
var client = new osc.Client('127.0.0.1', 6666);
var cnt    = 0;
var freq   = 60;

setInterval(function() {
	var x = Math.sin(cnt / freq * Math.PI);
	var y = Math.cos(cnt / freq * Math.PI);
	var z = 0;
	var pos = util.format('%s,%s,%s', x, y, z);
	client.send('NodeJs/Test', pos);
	++cnt;
}, 1000/60);

60 fps で回転する座標を送信する形になっています。

次に受け手の Unity 側の用意の用意をします。まずは、.node を参照するように先ほど作成した NodeJs.cs を書き換えます。

...
public class NodeJs : MonoBehaviour
{
    ...
    private static readonly string NodeScriptPath =
        System.IO.Path.Combine(Application.streamingAssetsPath, ".node/");
    ...    
    void Run()
    {
        ...
        process_.StartInfo.Arguments   = NodeScriptPath + scriptPath;
        ...
    }
    ...
}

そして、Unity 側で OSC を受ける必要があります。これには keijiro さんの unity-osc が軽量で使いやすいためお借りすることにします。


サーバ本体コードは以下のものをお借りします。


こちらのコードでは、"Hoge/Fuga" というアドレスで指定された OSC のメッセージを "Hoge_Fuga" ゲームオブジェクトへ OnOscMessage を SendMessage で伝える形になっています。そこで "NodeJS_Test" という適当な名前をつけた GameObject を作成して、以下のスクリプトをアタッチします。

NodeJsTest.cs

using UnityEngine;
using System.Collections;

public class NodeJsTest : MonoBehaviour
{
    void OnOscMessage(object msg)
    {
        var p = msg.ToString().Split(
            new string[]{","}, System.StringSplitOptions.None);
        transform.position = new Vector3(
            float.Parse(p[0]), float.Parse(p[1]), float.Parse(p[2]));
    }
}

OSC で送られてきたメッセージを元に座標を書き換えています。これで Script Path を osc.js に設定して実行すると以下のようになります。



おわりに

Twitter クライアントとか簡単に作れると思うので、やってみたいと思います。また、WebSocket 使うとブラウザとの連携も簡単にできるので、socket.io を裏で動かして Unity とブラウザでやりとりをする的なネタは別途エントリ書きます。

Netatmo ウェザーステーションを買ってみたので Node.js でいじってみた

はじめに

Netatmo という商品はご存知でしょうか?

屋内と屋外に設置して、「温度」「湿度」「気圧」「二酸化炭素濃度」「騒音」を iOSAndroid アプリからモニタリング出来るガジェットです。IFTTT 連携も出来るので、例えば二酸化炭素がアレになったら通知とか、気圧が 1000 hPa を下回ったらつぶやくとか出来ます。うちの家自動化システムに組み込みたかったので購入して Node.js で色々といじってみました。
f:id:hecomi:20140413002802j:plain
f:id:hecomi:20140414210517p:plain

Netatmo を利用した開発について

Netatmo REST APIs

Netatmo では Netatmo REST APIs が用意されています。REST Private APIREST Public API の2種類が用意されており、その名の通り、Private API の方は、オーナーが許可したアプリからのみアクセス出来る API、Public API の方は Public と設定された機器にアクセスするための API です。ただ後者の方は現在使用できないようで、公式には今年末には利用できるようになると書いてあります。

Netatmo では最初の設定時にユーザ登録が必要です。この際に Netatmo のサーバに自分の手持ちのデバイスが登録され、サーバを介した REST API で自分のデバイスから情報を取得し JSON で返してくれる仕組みになっています。WeMo や Hue などもそうですが、最近はこういった製品が多くて面白いですね(セキュリティ云々ありますが...)。

キーの発行

CREATE AN APP から情報登録すると Client IDClient Secret が発行されます。
f:id:hecomi:20140414002408p:plain

公式の SDK

公式では、PHP / Obj-C / Win8(JS) / Java による SDK が提供されています。

Node モジュールのインストール

私は Node.js で開発を行いたかったので、公式の SDK は使わずに npm に上がっているものを探しました。モジュールは2つ選択肢があるようです。

私は最近更新されている前者の netatmo の方を利用しました。

API を叩いてみる

キーを取得できれば後は README に書いてあるとおりに叩けば OK です。

var netatmo = require('netatmo');

var api = new netatmo({
    'client_id'     : 'CREATE AN APP で得られた Client ID',
    'client_secret' : 'CREATE AN APP で得られた Secrete Key',
    'username'      : 'Netatmo に登録したメールアドレス',
    'password'      : '同パスワード'
});

api.getUser(function(err, user) {
    console.log(user);
});

api.getDevicelist(function(err, devices, modules) {
    console.log(devices);
    console.log(modules);
});

api.getMeasure({
    device_id: 'getUser で得られた結果の devices に書かれたキー',
    scale: 'max',
    type: ['Temperature', 'CO2', 'Humidity', 'Pressure', 'Noise']
}, function(err, measure) {
    measure.forEach(function(data) {
        console.log(data);
    });
});

それぞれの戻り値はパースされた JSON の値がそのまま入っているので仕様を見れば何がどういう形で入っているか分かります。

サンプル

サンプル書いてみました。

var netatmo  = require('netatmo');

var api = new netatmo({
    'client_id'     : 'CLIENT ID',
    'client_secret' : 'SECRET KEY',
    'username'      : 'MAIL ADDRESS',
    'password'      : 'PASSWORD'
});

api.getDevicelist(function(err, devices, modules) {
    devices.forEach(function(device) {
        api.getMeasure({
            device_id: device._id,
            scale: '30min', // この設定で step_time が変化します
            // module_id: 'module の ID', // ここを与えると module(外置き)のデータを取ってきます
            type: ['Temperature', 'CO2', 'Humidity', 'Pressure', 'Noise']
        }, function(err, measure) {
            measure.forEach(function(result) {
                for (var i = 0; i < result.value.length; ++i) {
                    var timestamp = (result.beg_time + result.step_time * i) * 1000;
                    var val = result.value[i];
                    console.log('[%s]\n気温: %s 度, 湿度: %s%, 気圧: %s mbar, 騒音: %s dB, CO2: %s ppm\n',
                        new Date(timestamp), val[0], val[2], val[3], val[4], val[1]);
                }
            });
        });
    });
});

結果:

...
[Sun Apr 13 2014 22:15:00 GMT+0900 (JST)]
気温: 22.9 度, 湿度: 41%, 気圧: 1023 mbar, 騒音: 46 dB, CO2: 565 ppm

[Sun Apr 13 2014 22:45:00 GMT+0900 (JST)]
気温: 22.9 度, 湿度: 42%, 気圧: 1022.9 mbar, 騒音: 46 dB, CO2: 602 ppm

[Sun Apr 13 2014 23:15:00 GMT+0900 (JST)]
気温: 22.9 度, 湿度: 42%, 気圧: 1022.8 mbar, 騒音: 46 dB, CO2: 607 ppm

[Sun Apr 13 2014 23:45:00 GMT+0900 (JST)]
気温: 22.9 度, 湿度: 42%, 気圧: 1022.6 mbar, 騒音: 43 dB, CO2: 644 ppm

[Mon Apr 14 2014 00:15:00 GMT+0900 (JST)]
気温: 22.9 度, 湿度: 43%, 気圧: 1022.5 mbar, 騒音: 38 dB, CO2: 769 ppm
...

API にどういうパラメタを与えるかについてはドキュメントにまとまっています。

おわりに

これで特定の温度や湿度でエアコン調整したり、hue の色変えて空気の綺麗さ表したり出来ます!Arduino で作ろうかと思ってたのですが、既成品使ったほうが機能が豊富だったり他にも流用できるんで面白いですね。

Unreal Engine 4 を始める前に見たものをまとめてみた

はじめに

4/12 に開催された Unreal Engine 4 ビギナー勉強会に参加してきました。

会場は六本木 森タワーのグリーさんの会議室で行われ、Unreal Engine 4 の概要から Blueprint を利用したゲーム(ブロック崩し)のライブ作成及びその解説と、UE4 の世界観を堪能出来る素晴らしい内容でした。関係者のみなさま、ありがとうございました!

Unreal Engine 4 Overview

Unreal Engine 4 とはなんぞや?に関しては以下のサイトやスライド、ムービーを見ると雰囲気が掴めると思います。

利用はサブスクリプション月額 $19 で台数制限なし*1、登録とダウンロードは公式サイトから可能です。

スライドは公式で上がっているものが分かりやすいです。

動画による紹介は以下をご参照下さい。

  • 勉強会で紹介されていた UE4 で作られたゲーム:


  • GDC 2014 で発表された Feature Trailer:


  • "Infiltrator" Real-time Demo:

ムービーで見られるように物理ベースのマテリアルなどによる表現力の大幅な向上も魅力の一つですが、今回の勉強会の大きなテーマの一つでもあった「Blueprint」によるコードレスなゲーム作成が個人的にはとても魅力的でした。

UE4 Tutorials

まずは長いですが Tutorial を見ることをオススメします(私は未だ全部見れていません...)。

特にエディタ操作は最初いろいろいじるのに必須なので、「UE4 Editor」のところから見るのをオススメします。勉強会で紹介されていましたが、Youtube の字幕機能を使いながらであれば日本語訳はアレですが理解しやすいと思います。動画よりもテキストと絵が良い!という人はドキュメントの方にも載っています。

マウスによるビュー操作が出来ないと結構悲しくなるので以下は必須で見ておくのをオススメします。

Blueprint Overview

Blueprint(ブループリント)は、動的なオブジェクトの制御をビジュアルプログラミング出来るものです。Unity な人は、PlayMaker をイメージして頂けると分かりやすいと思います。

f:id:hecomi:20140413124938p:plain

UE3(UDK)でも Kismet(キズメット)とうビジュアルスクリプティングの環境があったようですが、BP はこの進化版になっているようです。Kismet は Level 用だったようですが、BP では Level に限らず色々なものが操作できるようになっています。コードを書きたい際は、UE3 の時に合った UnrealScript はなくなったとのことで、C++ の世界で書く必要があるようです。が、大体のことは BP で制御出来る上、速度的にも BP からは C++ API を直接参照している形であることから、ロジックを組む分にはそれほど影響はなく、ビジュアルスクリプティングの世界でおおよそ完結するスタイルのようです。必要に迫られた時は BP 上で使えるノードを C++ から作成することも出来るようです。

色々困ったら

公式にリンクが貼ってありますが、Tutorial / Document に加えて、Forum / AnswerHub といったコミュニティや Wiki も用意されているようです。

Google で「hogehoge ue4」とか検索すると AnswerHub が引っかかるので結構色々な情報があると思います。例えば、最初カメラが FPS モード(WASD で進んだり、マウスで回転したり)になっていますが、これを解消する方法については「camera main ue4」とか検索すると、指定したカメラをメインカメラに設定するための BP が載っている AnswerHub が引っかかります。

おわりに

会場でライブコーディングされていたブロック崩しを自分でも作ってみようと思ったのですが、色々引っかかるところもあってなかなか進まなかったので、動画が公開されたら再チャレンジしてみようと思います。また、本も執筆中との情報もありましたので楽しみです。ただ、UE4 をちょっと触ってみた感じ、動作は上位の GPU が無いと辛そうですが、表現力は圧倒的なものがあると思います。クオリティの高いデモを作成したい時には積極的に利用してみたいと思います。
f:id:hecomi:20140413192133p:plain

追記(2014/04/14)

動画が公開されました!@SquashSesame さん、ありがとうございます!

*1:会社とのアカウントは分ける必要があるようですが、会社でも複数台利用可とのこと