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

凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Unity 5.1 から導入された新しいネットワーク機能の UNET について詳しく調べてみた

はじめに

Unity 5.1 よりマルチプレイヤ用のネットワークシステム(UNET)が追加されました。

古い Network 機能は今後 5.x のどこかのタイミングで廃止される予定です。

UNET は低レイヤのカスタマイズから、抽象化された高レベルな API 群、Unity のエディタ拡張から簡単に利用できるコンポーネント群およびプロファイラとの統合、そしてマッチメイキングといったマルチプレイヤゲーム向けのサービスまでを提供する、Unity 5 を代表する機能の一つです。ロードマップでは Phase.3 まで描かれていて、現在は Phase.1 が提供された段階です。

現状、英語・日本語ともに下回りを含め詳しく解説した文字ベースの資料がドキュメント以外にほとんどなく、ドキュメントはすごい良くまとまっているものの分量が多いので、どこを学習の基点としたら良いか決めるのが難しいと思います。そこで自身の備忘録も兼ねて一通り機能を調べてまとめてみました。機能がとても多いので全てはカバーできないですが、取り敢えず一通り眺めればドキュメントの読み方が分かるようなところを目指して、分かりやすそうな順で解説を書いています。私の理解を元に、ちょっと込み入ったところも書いてみました。間違っている所があればご指摘頂けると助かります。

ただし、UNET は現在も色々と変更が行われているようで、例えばドキュメントのコードから結構変更が行われていてそのままだと動かないものが多いので、今後、下記情報も古くなる可能性があることにご留意下さい。

環境

デモ

試しに 3D チャットを作ってみました。さくらの VPSLinux ビルドしたサーバを動かし、そこへ複数のクライアントからつなぐデモです。サーバ側で動いてる AI もいます。サーバ・クライアント共に同じプロジェクトをビルドしています。

f:id:hecomi:20150814215723p:plain

どれくらいのクライアントの接続に耐えられるか、またいつ止まるかは不明ですが、デモアプリは以下からダウンロードできます。

教材

サンプルプロジェクト

以下のスレッドで HLAPI / LLAPI のサンプルがいくつか配布されています。

以下はインベーダーゲームのサンプルのキャプチャです。

f:id:hecomi:20150811194006p:plain

HLAPI / LLAPI

UNET には大きく分けて 2 種類のユースケースをカバーする機能を提供しています。

  • マルチプレイヤ用のゲームを簡単に作りたい
    • High Level API (HLAPI)
      • 下回りの実装を意識すること無くデータのシリアライズ・デシリアライズやマッチメイキングなどのマルチプレイヤ向けの基本機能を提供
  • ネットワークインフラやより複雑なマルチプレイヤゲームを作りたい
    • NetworkTransport API(LLAPI)
      • OS が提供するネットワーク機能の上に乗る薄いレイヤ
      • UDP と WebSocket の 2 つのプロトコルをサポート

f:id:hecomi:20150808150902p:plain

Getting Started

最初から各論に入ると分からなくなるので、まずは UNET の感触をつかむために、椿さんの記事の内容を元にその詳細を解説しようと思います。詳しくは椿さんのエントリを見ていただきたいのですが、箇条書きにすると以下のような手順になります。作業は5分位で出来る内容です。

  • 適当にオブジェクトを配置
  • シーンに NetworkManager および NetworkManagerHUD をアタッチした Game Object を配置
  • Asset > Import Package > Characters をインポートして FPS Controller を配置
  • 配置した FPS ControllerNetworkIdentity および NetworkTransform をアタッチ
    • NetworkIdentityIs Player Authority をチェック
    • NetworkTransformTransform Sync ModeSync Character Controller に変更
  • FPS Controller にカメラなどリモートで不要なコンポーネントを disabled にするスクリプト DisableRemotePlayerBehaviours をアタッチ(自前で用意、後述)して不要にするコンポーネントをインスペクタ上で登録
  • FPS Controller を Prefab 化
  • NetworkManagerSpawn Info > Player PrefabFPS Controller の Prefab を指定
  • ビルドして実行 & Editor 上でも実行して、Editor 上では Lan Host(H) を、ビルドしたアプリでは Lan Client(C) をクリック
  • 片方を動かすともう一方で動く
using UnityEngine;
using UnityEngine.Networking; // 新ネットワーク機能の名前空間

// NetworkBehaviour では isLocalPlayer などネットワーク系の
// プロパティやメソッドにアクセスできる
public class DisableRemotePlayerBehaviours : NetworkBehaviour
{
    public Behaviour[] behaviours;

    void Start()
    {
        // 登録されたコンポーネントをリモート側で disabled にする
        if (!isLocalPlayer) {
            foreach (var behaviour in behaviours) {
                behaviour.enabled = false;
            }
        }
    }
}

f:id:hecomi:20150807194437p:plain

解説

雰囲気は掴めたと思うので、具体的に上記手順で何をしていたか、各コンポーネントがどういう役割をしているのか、順に見て理解を深めていきましょう。

Network Manager

f:id:hecomi:20150808151226p:plain

Network Manager は HLAPI を使って作られたマルチプレイヤゲームで必要な機能群が一通り揃ったコンポーネントです。

以下の様な機能を提供してくれます。

  • ネットワークでの役割の管理
    • サーバかクライアントかホスト(サーバ + ローカルクライアント)のいずれか
    • Network Manager HUD を同時にアタッチするとサンプルの UI が表示
  • オブジェクト生成の管理
    • 開始時に Instantiate する Player Prefab を登録する
    • 動的に生成する Prefab を登録する
    • 登録した Prefab を NetworkServer.Spawn(GameObject) に渡すと全クライアントで生成
  • シーンの管理
    • オフラインシーンとオンラインシーンを管理
    • オンラインになると自動でシーン遷移
    • Build Settings で Scenes In Build にシーンを登録すると Menu ScenePlay Scene にシーンをドラッグ&ドロップできるようになる
  • デバッグ
    • ネットワーク遅延やパケロスのシミュレーション
    • コネクションの状態やローカル・リモートのオブジェクトのリストの表示
  • マッチメイキング
    • Unity Multiplayer サービスとの連携(後述)
  • カスタマイズ
    • いくつかの関数は virtual になっていて継承してカスタム出来る
    • 例) Player Prefab 生成時の OnServerAddPlayer()
    • NetworkLobbyManager はこれを継承してロビーを作れるようにしたもの

先程の例では最初の2つの項目を利用した形になります。

NetworkIdentity

f:id:hecomi:20150808155819p:plain

NetworkIdentity はネットワーク同期でのコアとなるコンポーネントで、同期するオブジェクトには必ず付ける必要があります。ここにはネットワーク内で共通の Scene ID(どのシーンに属するか)や Network ID(ネットワーク内で一意に決まる ID)、Asset ID(どのアセットを利用するか)や他のコンポーネントから利用する様々なフラグ(例えば localPlayerAuthorityNetworkTransform が参照)といった情報が含まれています。

これらは Inspector 下部もしくは Inspector のモードを Debug にすることで確認することが出来ます。

f:id:hecomi:20150808153652p:plain

NetworkTransform

f:id:hecomi:20150808155718p:plain

NetworkTransform はその名の通りネットワーク内で Transform コンポーネントを同期する役割をします。同期のモード(単純に Transform の値を同期するか、RigidBody に追従するか、Character Controller に追従するかなど)や同期の頻度、補間の設定などが可能です。

NetworkTransformVisualizer コンポーネントをアタッチして Visualizer Prefab を設定すると生値と補間の様子を見ることが出来ます。

f:id:hecomi:20150808162716p:plain

後述しますが、Photon の PhotonTransformView の様に細かな補間の設定は出来ないようなので色々と調整したい場合は自前で書く必要があります。

NetworkAnimator

例では出てきませんでしたが、似たものに Animator を同期する NetworkAnimator もあります。

f:id:hecomi:20150812193856p:plain

指定した Animator の変数が自動で Inspector に出てきてチェックしたものを同期してくれます。ただあくまでも定期的な同期なので、すぐにかつ確実に同期が必要な場合、別途対応する必要があります。これは後述します。

サーバ・クライアント・ホスト

NetworkManager のところでサーバ、クライアント、ホストに触れましたが、理解を深めるためにこれらについてちょっと見ておきましょう。

まず大前提として、UNET では同じゲームのコードでクライアント・サーバ共に動かしています。サーバ専用の言語を覚えたりする必要はありません。

UNET では基本的には一つのサーバに複数のクライアントがぶら下がる形になります。ただ専用のサーバがない場合はいずれかのクライアントがサーバの役割も担うことになります。これがホストです。ホストではサーバとクライアントが同じプロセスで動作(同じシーンやオブジェクトを共有)しています。

f:id:hecomi:20150809154610p:plain

こうしてサーバに対してローカルなクライアントとリモートなクライアントが出来るのですが、プログラマはこれらのホストにローカルなクライアントかリモートなクライアントかを意識することなくプログラムできるようになっています。ただし、サーバかクライアントかといったことや、参照しているオブジェクトが各クライアントから見てローカルなのかリモートなのかは強く意識する必要があります。

例えば、先ほど操作するプレイヤにアタッチした NetworkIdentity コンポーネントIs Player Authority にチェックを入れましたが、これによって各クライアント毎に自身のプレイヤの所有権が与えられ、isLocalPlayer フラグが true になります。

f:id:hecomi:20150809164453p:plain

ネットワーク間で動作するコンポーネント

さて、今度はコードからの利用を見て行きましょう。先ほどの NetworkTransform の代わりになるようなものを書いてみます。簡単のために位置だけ同期するコードを書いてみます。

自前でネットワーク関連のコンポーネントを作成するには、MonoBehaviour の代わりに、これを継承してネットワーク機能を付加した NetworkBehaviour を継承します。NetworkBehaviourNetworkIdentity と共に動作します。いくつか特殊な記法が存在します。

using UnityEngine;
using UnityEngine.Networking;

public class Player_SyncPosition : NetworkBehaviour
{
    // SyncVar Attribute をつけたプロパティはネットワーク越しで共有される
    [SyncVar]
    private Vector3 syncPos;

    public float easing = 0.25f;

    // Unity Engine から呼ばれる関数(e.g. Start / OnCollisionEnter) に
    // ClientCallback Attribute をつけるとクライアント側だけで実行される(サーバ側は空実装)
    // 同様に ServerCallback Attribute もある
    [ClientCallback]
    void Update()
    {
        // サーバ側に現在位置を送信
        if (isLocalPlayer) {
            TransmitPosition();
        } else {
            LerpPosition();
        }
    }

    // Client Attribute をつけると Client のみ実行される(サーバでは空実装になる)
    // 同様に Server Attribute もある
    [Client]
    void TransmitPosition()
    {
        CmdProvidePositionToServer(transform.position);
    }

    // サーバ側で実行されるコマンド
    // クライアント側からサーバ側へコマンドを送る時はこれが必要
    // Command Attribute と Cmd-prefix な関数をセットで定義
    [Command]
    void CmdProvidePositionToServer(Vector3 pos)
    {
        syncPos = pos;
    }

    void LerpPosition()
    {
        transform.position = Vector3.Lerp(transform.position, syncPos, easing);
    }
}

これを NetworkTransfom の代わりにアタッチすると、滑らか(目的位置に徐々に近づけるコードなのでキビキビでなくヌルッと動く)に位置が同期されます。

コメントにも解説を入れましたが、NetworkBehaviour には特別ないくつかの Attribute やルールが存在し、これらを利用してネットワーク越しで情報をやり取りするコードを簡潔に書けるような仕組みが用意されています。一通り機能を見てみましょう。

  • 変数の同期
  • ネットワーク機能関連のコールバック
    • いくつかの virtual 関数群が用意されている(override して利用、詳細はマニュアル参照)
      • OnStartServer
      • OnStartClient
      • OnSerialize
      • OnDeSerialize
      • OnNetworkDestroy
      • OnStartLocalPlayer
      • OnRebuildObservers
      • OnSetLocalVisibility
      • OnCheckObserver
    • いくつかのコールバックは後で解説します
  • サーバ / クライアントでの関数の切り分け
    • Client アトリビュートをつけるとクライアントのみで実行される関数になる(サーバだと直ぐに return される)
    • Server アトリビュートをつけるとサーバのみ
    • Unity のコールバックにつける ClientCallbackServerCallback もある
      • 基本的には ClientServer と同じ、Warning を発生しない(?)
  • クライアントからサーバへのコマンドの送信
    • Cmd から始まる関数に Command アトリビュートをつけるとサーバで実行されるクライアントから呼び出せる関数になる
  • サーバからクライアントの RPC(リモートプロシージャコール)
    • Rpc から始まる関数に ClientRpc アトリビュートをつけるとクライアントで実行されるサーバから呼び出せる関数になる
  • ネットワーク越しのイベントの登録
    • Event から始まるイベントに SyncEvent アトリビュートをつけるとクライアントで呼び出されるイベントをサーバから発火出来る
    • ClientRPC が単純な呼び出しに対しイベントを用意して他のスクリプトから利用することが可能
    • Unity - Scripting API:

これらをうまいこと利用してロジックを組んであげればゲームが出来るのが何となくイメージできるのではないでしょうか。チュートリアル動画ではこれらをうまく利用してゲームを作成しているのでサンプルとして見ると参考になると思います。

オブジェクトの "Spawn" を理解する

次に動的なオブジェクトの生成について見て行きましょう。

ドキュメントでは頻繁に "Spawn" という単語が出てきます。これは "Instantiate" とは区別して使われていて、"Instantiate" が Object.Instantiate() によってオブジェクトを生成するのに対し、"Spawn" はネットワークに接続されたクライアント全てにおいてオブジェクトを生成することを意味しています。

UNET では、オブジェクトがサーバ上で変更されたり破棄されるとその通知が各クライアントへ伝わります。また、生成後に新しいクライアントがサーバに接続した際も、その新しいクライアント上で既に生成済みのオブジェクトが生成されます。

オブジェクトを "Spawn" するためには、対象のオブジェクトを NetworkServer.Spawn() に渡す必要があります。NetworkServer クラスはサーバの状態や機能をまとめたクラスです。

もちろん直接オブジェクトの参照をネットワーク越しに渡すことは出来ません。そこでこれが上手く動くためには、各クライアントで何のオブジェクトを生成するか各クライアントが把握している必要があります。この役割を果たすのが NetworkIdentity の時に見た Asset ID です。そして Asset ID は事前に登録しておく必要があります。

登録する方法は NetworkManager を利用している際はインスペクタから、それ以外はコードから行う必要があります。インスペクタから行う場合は NetworkManagerSpawn Info > Registered Spawnable Prefabs に対象の Prefab を登録します。

f:id:hecomi:20150809185313p:plain

コードで書く場合は ClientScene.RegisterPrefab() で登録します。

例えばキャラクタから弾を発射してみます。NetworkManager に弾の Prefab を登録し、以下の様なコードを書いてプレイヤにアタッチします。

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class ShootBullet : NetworkBehaviour
{
    public GameObject bulletPrefab;
    public KeyCode shootKey = KeyCode.Space;
    public float forwardSpeed = 10f;
    public float upSpeed = 5f;
    public float duration = 3f;

    [ClientCallback]
    void Update()
    {
        if (isLocalPlayer && Input.GetKeyDown(shootKey)) {
            var forward = Camera.main.transform.forward;
            var up = Camera.main.transform.up;
            var velocity = forward * forwardSpeed + up * upSpeed;
            CmdShoot(velocity);
        }
    }

    [Command]
    void CmdShoot(Vector3 velocity)
    {
        var bullet = Instantiate(bulletPrefab);
        bullet.transform.position = transform.position + velocity.normalized * 0.5f;
        var rigidbody = bullet.GetComponent<Rigidbody>();
        if (rigidbody) {
            rigidbody.velocity = velocity;
        }
        NetworkServer.Spawn(bullet);
        StartCoroutine(DestroyBullet(bullet));
    }

    [Server]
    IEnumerator DestroyBullet(GameObject bullet)
    {
        yield return new WaitForSeconds(duration);
        NetworkServer.Destroy(bullet);
        Destroy(bullet); // 要るか要らないかまだ不明...
    }
}

f:id:hecomi:20150810181213p:plain

生成・破棄をサーバ側で行うようにしています。サーバで Instantiate して、NetworkServer.Spawn() で全てのクライアントでも生成、時間が経ったら NetworkServer.Destroy() で全てのクライアントから破棄しています。UNET ではこういった、クライアントからサーバへの命令なのか、サーバからクライアントへの命令なのかのルールを守る必要があります。

f:id:hecomi:20150810191632j:plain

シリアライズ・デシリアライズ

SyncVars

SyncVar は前述の通りです。知らなくても全く問題ない内容(そうなるように頑張ってくれている)ですが、公式ブログにどうやって実装しているかの解説が書いてあります。

内部的にはパフォーマンスや帯域節約のために SyncVar アトリビュートを適用した変数のうち、変更されたものに Dirty フラグをセットするようになっているのですが、この Dirty フラグを自動でセットするように変数をプロパティに置き換えるコードジェネレーションが内部で走っています。ユーザコードを大量に置換すると問題が起きやすいので、ここでは Mono.Cecil という IL レベルでコードをあれこれするライブラリを利用しています。

WebGL のコード変換プロセスもアレでしたが、Unity の中には黒魔術屋さんが沢山いそうですね。

SyncLists

話題が逸れたので戻していきます。SyncVar は単一の変数にしか効きませんでしたが、リストで使いたい場合に組み込みの同期用リストがいくつか用意されています。

  • SyncListString
  • SyncListFloat
  • SyncListInt
  • SyncListUInt
  • SyncListBool

またユーザ定義型の構造体をリスト化出来る SyncListStruct<T> も用意されています。

独自シリアライズ・デシリアライズ

NetworkBehaviour のちらっと見ましたが、OnSerialize()OnDeSerialize() というコールバックが NetworkBehaviour の virtual 関数として用意されています。ここでは複雑なシリアライズ・デシリアライズの記述が可能です。が、自前で Dirty フラグを意識したりと結構大変そうです。以下のマニュアルの最下部にサンプルコードが載っています。

Network Message

Send() 系の関数が NetworkServerNetworkClientNetworkConnection に実装されています(NetworkConnectionNetworkClient なら一つ、NetworkServer なら複数持っている各接続をまとめたクラス)。引数に MessageBase を継承したクラスのインスタンスを指定して使う形になります。

コードとしては、 Serialize(NetworkWriter)Deserialize(NetworkReader) を継承するクラスを作成することで複数のパラメタをパックすることが出来ます。メッセージを受けとってゴニョゴニョ処理するような場合や SyncVar が対応していない型(Byte Array 等)を送りたいときなど、SyncVar ではカバーできないケースに使うと良いと思います。

また、EmptyMessageIntegerMessageStringMessage は予め用意されています。

余談ですが、ものによって DeSerialize だったり Deserialize だったりするのは修正されるのかな...。

Channel / QoS

概要

これまで色々と見てきましたが、SyncVarSend はどういった通信路を経由して送るのかお任せの状態でした。例えば位置や姿勢は 100 回に 1 回メッセージが届かなかったとしても特に影響はありませんが、ダメージやステートなどが同期されないと、あるクライアントでは敵が生きていて別のクライアントでは死んでる、みたいな状態が起こってしまいます。

UNET ではどういった通信路を利用して同期したりメッセージを送りあったりするかを指定する Channel を複数本用意することができ、それぞれの Channel に QoS を指定できるようになっています。QoS は Quality of Service のことで、一般的には送信するデータの扱い・品質を意味します。

UNET では以下の QoS が用意されています(参考: (English) All about the Unity networking transport layer – Unity Blog)。

  • Unreliable
    • パケロスの可能性がある
    • 速い
  • UnreliableFragmented
    • Unreliable + 一回のデータ量上限が決まっている
    • 長いログなど
  • UnreliableSequenced
    • Unreliable + 順序が保証されている
    • 映像や音声など
  • Reliable
    • パケロスしない
    • 遅い
    • ダメージやステートなど
  • ReliableFragmented
    • Reliable + データ量上限
    • グループ化されたメッセージなど
  • ReliableSequenced
    • Reliable + 順序保証
    • ファイルの転送など
  • StateUpdate
    • Unreliable + 古いデータは破棄
    • 位置の同期など
  • AllCostDelivery
    • Reliable が RTT に応じて再送するのに対し、一定間隔で再送
    • ショットの発射など

これらは NetworkManager を利用している場合は、Advanced ConfigurationQoS Channel から設定できます。

f:id:hecomi:20150810213545p:plain

利用方法

コードからはアトリビュートの引数として Channel を指定できます。スクリプト単位で指定する場合は NetworkSettings アトリビュートを使用します。

using UnityEngine.Networking;

[NetworkSettings(channel=1,sendInterval=0.2f)]
class MyScript : NetworkBehaviour
{
    [SyncVar]
    int value;
}

ここでは同時に同期の間隔(sendInterval)も指定できます。これらの設定は Inspector に表示されます。

f:id:hecomi:20150810214422p:plain

関数ごとには CommandClientRPC といったアトリビュートの引数で指定できます。

public class Player : NetworkBehaviour {
    // ...

    [Command(channel=1)]
    public void CmdMove(int x, int y) {
        moveX = x;
        moveY = y;
        isDirty = true;
    }
}

こういった細かなチューニングがより良いゲーム体験には必要になってきます。

プロファイラ

UNET では段階的にですがプロファイラとの統合が図られています。現在は以下の 2 つの機能がプロファイラと統合されています。詳細は未だドキュメント化されていないですが、どれだけパケットが流れているか、どのパケットが支配的になっているかといったことが確認可能です。グラフをクリックするとクリックした箇所の詳細が下部に表示されます。

Network Messaging

入出するパケットの流れを見ることが出来ます。

f:id:hecomi:20150811154823p:plain

Network Operations

どのタイミングでオブジェクトの生成・破棄が起きているか、CommandClientRPC がどれだけコールされているか、またそのコールされた関数は何か、といったことが確認できます。

f:id:hecomi:20150811155007p:plain

オブジェクトの Visibility 制御

パフォーマンスの話が続きます。ゲームが広いエリアで沢山のネットワーク関連のオブジェクトが接続されている場合、全てのクライアントに対して全てのオブジェクトを Spawn していると、レンダリングコストもかかりますし、沢山の帯域を消費してしまいますし、新しくユーザが参加した場合も全てのオブジェクトを Spawn するのに時間がかかってログイン時間が長くなってしまったりと色々と悪影響が出てきます。

NetworkProximityChecker

そこで、UNET では NetworkProximityChecker というコンポーネントが用意されていて、これを利用すると設定した距離以上離れると以下のように動作します。

  • ホストと同じローカルクライアントの場合
    • Renderer が disabled になる
  • リモートクライアントの場合
    • Destroy される
    • 新しく接続した場合、範囲外なら Spawn しない

f:id:hecomi:20150811162133p:plain

仕組みとしては Physics を利用しているので、Check MethodPhysics3DPhysics2D どちらでチェックするか選択する必要があります。その上で Vis Range よりも離れると hidden になるという感じです。Force Hidden はプレイヤオブジェクトだと通常は hidden にならないので、無理やり hidden にしたい場合にチェックします。

ただ、現在のところ用意されたコンポーネントはバグが有る(ArgumentOutOfRangeException が発生する)ようで、ワークアラウンドで以下のように継承して利用する必要があるようです。

using UnityEngine;
using UnityEngine.Networking;
 
public class ProximityChecker : NetworkProximityChecker 
{
    public override bool OnCheckObserver(NetworkConnection newObserver)
    {
        return false;
    }
}

ホストでの Visibility の取り扱い

上述したように、ホストでは全てのオブジェクトを管理する必要が有るため、Renderer が disabled になるだけです。ただ、ものによっては同期する必要のない重いスクリプトが付いている場合があります。これらは NetworkBehaviourOnSetLocalVisibility(bool) を利用することで制御することが可能です。

カスタマイズ

NetworkProximityChecker が内部的に何をしていてどうすればカスタマイズ出来るか見て行きましょう。まず、オブジェクトの Visibility の管理は全てサーバ側で行われることを覚えておいて下さい。

NetworkProximityChecker は定期的に NetworkIdentity.RebuildObservers() という関数を呼び、この結果 NetworkBehaviour.OnRebuildObservers(HashMap<NetworkConnection> observers, bool initial) というコールバックが呼ばれます。

この中で近接判定を行います。ここで言う Observer とは各プレイヤのことです。つまり RebuildObservers とは誰が見えているかという情報を更新してくれ、という命令になります。この情報というのが HashMap<NetworkConnection> で、ここに自分が誰から見えているか詰める、というのが近接判定の作業になります。

Observer はプレイヤと言いましたが、具体的にクラスで言うと NetworkConnection のことです。NetworkServer.connections に全てのクライアントに対してのコネクションが入っているのですが、これとは別にサーバ側では各プレイヤのオブジェクトにアタッチされた NetworkIdentityconnectionToClient に、各クライアントへのコネクションが格納されています。これを利用してプレイヤを判定するというのが具体的なコードになります。

以下、NetworkProximityChecker を模倣して書いたスクリプトになります。

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;

public class CustomProximityChecker : NetworkBehaviour
{
    private NetworkIdentity netId_;
    public float interval = 1f;
    public float range = 2f;

    [Server]
    public override void OnStartServer()
    {
        netId_ = GetComponent<NetworkIdentity>();
        StartCoroutine(CheckProximityPeriodically());
    }

    [Server]
    IEnumerator CheckProximityPeriodically()
    {
        bool isInitial = true;
        for (;;) {
            yield return new WaitForSeconds(interval);
            // 全ての OnRebuildObservers を呼ぶ
            netId_.RebuildObservers(isInitial);
            isInitial = false;
        }
    }

    [Server]
    public override bool OnCheckObserver(NetworkConnection newObserver)
    {
        // 新しくユーザが接続した時に呼ばれる。
        // true を返すとそのユーザのシーンに Spawn し、false だと何もしない
        return false;
    }

    [Server]
    public override bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initial)
    {
        // このスクリプトがアタッチされているオブジェクトが各プレイヤから見えていたら
        // observers にそのプレイヤに該当する NetworkConnection を格納する
        // その結果に応じて Spawn や Destroy が各プレイヤのシーンに対して行われる
        var hits = Physics.OverlapSphere(transform.position, range);
        foreach (var hit in hits) {
            var netId = hit.GetComponent<NetworkIdentity>();
            bool isPlayer = (netId != null) && (netId.connectionToClient != null);
            if (isPlayer) {
                observers.Add(netId.connectionToClient);
            }
        }
        return true;
    }
}

ちなみに OnRebuildObservers()OnCheckObserver() は全ての関連した NetworkBehaviour に対して呼ばれるので、別のクラスに分離しても構いません。サンプルコードは Sphere で見ていましたが、オクルージョンによって判定したりエリアによって判定したり、自分なりのルールをここに付け加えれば、各クライアント毎の処理も減り、その結果やりとりするメッセージも減って帯域も節約できます。

マッチメイキング

概要

いよいよマッチメイキングについて見て行きましょう。

UNET ではマッチメイキングとリレーサーバをサービスとして提供してくれています。

プレイヤはルームを作成して、別のプレイヤはそのルームを検索、参加する、ということが可能になり、お互いに IP を知らなくとも一緒にゲームをプレイできるようになります。現在はプレビュー版で 100 CCU(Concurrent User)までテストできます。

NetworkManager を利用してマッチメイキングを行うと、自動的に UNET のリレーサーバをパケットが経由するようになるため、これによって Firewall や NAT 越えの心配をする必要がなくなります。

マッチメイキングしてみる

まずは登録して試してみましょう。手順は以下のエントリが詳しいです。

登録後、Player Settings の Cloud Project Id に作成したプロジェクトの ID を登録すれば OK です。NetworkManager にマッチメイキングの機能が備わっているので、これを利用すると異なるネットワークからお互いの IP を知ることなく Unity のリレーサーバ経由でマッチングすることが可能です。

f:id:hecomi:20150811194932p:plain

f:id:hecomi:20150811195107p:plain

f:id:hecomi:20150811195646p:plain

コードから制御する

マッチメイキングを制御するには NetworkMatch を利用します。

マニュアルのように自分でコールバック含め作成しても良いのですが、NetworkManager にも機能が備わっているのでそちらを参考に書いてみます。

using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.Match;
using System.Collections;

public class NetworkManagerTest : MonoBehaviour 
{
    private NetworkManager manager_;
    private NetworkMatch match_;

    public string matchName = "hogehoge";
    public uint matchSize = 4U;

    void Start()
    {
        manager_ = GetComponent<NetworkManager>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.S)) {
            Debug.Log("Start Match Maker");
            manager_.StartMatchMaker();
            manager_.matchName = matchName;
            manager_.matchSize = matchSize;
            match_ = manager_.matchMaker;
        }
        if (match_ != null && Input.GetKeyDown(KeyCode.C)) {
            match_.CreateMatch(manager_.matchName, manager_.matchSize, true, "", manager_.OnMatchCreate);
        }
        if (match_ != null && Input.GetKeyDown(KeyCode.L)) {
            Debug.Log("List Matches");
            match_.ListMatches(0, 20, "", manager_.OnMatchList);
        }
        if (match_ != null && Input.GetKeyDown(KeyCode.J)) {
            Debug.Log("Join Match");
            var desc = manager_.matches[0]; // join first room
            match_.JoinMatch(desc.networkId, "", manager_.OnMatchJoined);
        }
    }
}

大変雑ですが、これを NetworkManager と同じ Game Object に取り付け、ホスト側ではサーバからのレスポンスがあったら「S > C」の順でキーを押下、クライアント側では「S > L > J」すると作成したルームに参加することが出来ます。さすがにこのままだとあれなので、この機能を呼ぶように適当に UI を作れば OK です。

Network Transport Layer (LLAPI)

最後に LLAPI です。LLAPI はシステムのソケットの上に乗る薄いレイヤです。

サンプルプロジェクトも上がっているので、気になる方は見てみると面白いと思います。

まだ余り情報がないのと、私がネットワーク周りに疎いので、どなたか詳しく解説してくださると嬉しいです。。

その他

その他気になりそうな点です。

WebGL への対応

LLAPI で対応してますが、HLAPI では今のところ対応していません。

現在対応中とのことです。WebRTC data channel も対応してくれないかな...。

ベータ機能

5.2b では、いくつかのバグ修正と、NetworkTransform の改善、ローカルでの Discovery、ノンプレイヤなオブジェクトの Authority の設定などが含まれているようです。

専用サーバ

ホスト前提のゲームでなく、MMO の様にどこかでサーバが動いていて、みんながそこにアクセスするようなことをしたい場合は、Linux ビルドしたアプリをバッチモードでどこかのサーバでヘッドレスに起動して運用するのが良いのではないかと思います。

コンソールから -batchmode 引数をつけるか、Linux のビルドオプションに Headless Mode があるので、それを指定してサーバ側で起動するのでも良いと思います。

f:id:hecomi:20150812194328p:plain

冒頭のデモはこの運用でサーバがさくらの VPS 上で動いています。同じプロジェクトのビルドでサーバ・クライアント両方動くのはスゴイですね。多分 AndroidiOS ビルドしても動くのではないでしょうか。

ただちょっとコードに気を使わないと色々と破綻してしまう(例えばホストでテストしているとクライアントの動作も含むため、サーバ単体で動かして初めてミスに気づくなど)ので注意が必要です。

設定

Project Settings に Network の項目が追加されています。現状は Debug LevelSendrate のみ指定できます。

f:id:hecomi:20150812194612p:plain

Animator の即時同期

NetworkAnimator では素早いアクションなどの同期に向いていないので、そういう場合は自前でやり取りする必要があります。そこで、SyncVar とそのアトリビュートhook がとても役立ちます。

public class AnimationTriggerSync : NetworkBehaviour
{
    // ...

    // この SyncVar を通じて各クライアントに変更を通知する
    [SyncVar(hook = "OnIsJumpChangedForRemoteClient")] 
    private bool isJump_ = false;

    // 1. ここが起点
    // 各クライアントは自分のアニメーションのフラグをすぐにセット
    // その他のクライアントも同期するためにまずはサーバのフラグをセット
    [Client]
    public void SetIsJumpForLocalClient(bool isJump)
    {
        if (isLocalPlayer) {
            animator_.SetBool("isJump", isJump);
            CmdProvideIsJumpToServer(isJump);
        }
    }

    // 2. サーバでフラグをセットする
    // これにより hook で設定された関数が各クライアントで呼ばれる
    // channel の QoS は Reliable State Update がおすすめ
    [Command(channel = 2)]
    private void CmdProvideIsJumpToServer(bool isJump) 
    { 
        isJump_ = isJump; 
    }

    // 3. 各クライアントでフラグをセットする
    // ローカルなクライアントにも通知されるが、すでにセット済みなので、
    // リモートなクライアントだけ Animator にフラグをセットする
    [Client]
    private void OnIsJumpChangedForRemoteClient(bool isJump) 
    { 
        if (!isLocalPlayer) {
            animator_.SetBool("isJump", isJump);
        }
    }

    // ...
}

これでローカルなプレイヤはネットワークに繋がっていない時と同じように動き、リモートなクライアントは最小限の時間で同期されます。省略しますが、トリガの場合は適当に int をインクリメントしてフックするとか、Command して ClientRPC するか、キー判定をサーバ側でやって ClientRPC するかのいずれかになると思います。

開発

クライアントのテストをするためにいちいちビルドをしていると大変なので、エディタを複数立ち上げる方式が便利そうです。

おわりに

一通り機能を見てみましたがいかがだったでしょうか。概要さえ掴めてしまえば予備知識無しでは難解だったドキュメントも読めるようになるのではないかと思います。

現状の UNET はホストを前提とした設計になっているのですが、デモの様にサーバを立ててしまえば今後様々なユースケースに対応してくれると考えています(サービス側で dedicated server を動かすとか...)。いまのところ Phase.2 の情報が少なくてなかなか読めないところはありますが、今後のアップデートも注目していきます。

Maker Faire Tokyo 2015 にレゴ x ハードウェア x プロジェクションなシューティングゲーム LITTAI を出展してきた

f:id:hecomi:20150805233244j:plain

はじめに

前回の記事で書いたように 8/1、8/2 に東京ビッグサイトにて開催された MFT 2015 へ友達と趣味で制作していた LITTAI というゲームを出展してきました。お越しくださった皆様、ありがとうございました!

LITTAI は Lit Table Interface の略の造語で、以下の様なコンセプトを掲げるゲームです。

  • テーブル上で実際にものを操作しながら遊ぶゲーム
  • 操作するものはリアルタイムにカスタマイズ可能
  • ハードウェアとゲームの世界が連動

基本的なコンセプトは以前出展した Mont Blanc Pj. とほぼ同じで、こういった技術を使いつつ遊び手に技術を意識されないようなゲームの新しい遊び方を模索するのが目的です。

製作期間はネタ出しで 1 ヶ月、実験・制作が 2 ヶ月半くらいです。私は認識部とゲーム部のソフトウェア全般を担当しました。ハードウェア全般は前作同様 id:jonki、展示用のランキング制作を id:AMANE にやってもらいまいした。そちらの詳細は彼らに任せることとして、本エントリでは LITTAI の全体の概要紹介と、私の担当部の認識・ゲーム部分の詳細について紹介したいと思います。

LITTAI とは

MFT 2015 でデモした内容は以下になります。

デモの流れとしては、最初はフリーモードで説明を行い、スコアの表示された丸い石を長押しするとゲームを開始します。ゲーム中は適宜ブロックをつけたり外したりしながら敵と戦う形となり、ハードウェアのボタンを押すと弾が発射され、発射される弾はブロックの付け方で変化してレーザになったりシールドになったりします。弾を打つのにエネルギーを消費するので、砲台をつけすぎるとすぐ弾切れになってしまったり、機体を大きくし過ぎると敵の攻撃を喰らい易くなるので適度にするのがコツです。

敵を倒したり弾を相殺したりアイテムを取ったりするとスコアが増え、逆に死んだり長押しをしてボムを打つとスコアを消費する感じです。スコアの表示された丸い石を打つとスコアが増えるので、適当な場所においてこいつを撃ちながら敵も倒していく、というのがハイスコアのコツで、何人かのお子さんは何回か遊びに来てハイスコアアタックしてくれました。

最後の獲得スコアが表示されて、ランキング表示用のパソコンにキャプチャした機体と一緒にスコアが表示され、はじめに戻る、という流れです。

筐体・機材

全体図はこんな感じです。

f:id:hecomi:20150805190514p:plain

f:id:hecomi:20150805173114j:plain

島忠や Amazon で仕入れた部材で組み立てました。プロジェクタは小型で持ち運びしやすく 500 lm な QUMI Q5 を利用しています。QUMI Q5 は上下 44 度、左右 36 度の画角のため、広い面積をカバーできるよう光路を稼ぐために、一度地面に設置したミラーで反射させてから上に映しています。スクリーンはアクリル板に 40 g の大型トレーシングペーパを貼ったものになっていて、比較的明るい場所でもリアプロジェクションされた光を綺麗に映してくれます。

f:id:hecomi:20150805171335j:plain

そしてこのスクリーン上に置かれた物体を下部から赤外線投光機で照らして赤外線カメラで観測します。これによりプロジェクタで投影された画を無視して純粋にスクリーン上の物体だけ見ることが出来ます。詳しくは後述しますが認識に利用している赤外線投光機・赤外線カメラはいろいろ試した結果、今回は Kinect v2 を利用しています。

ブロック

追記(2015/08/07)ハードウェア側のエントリも投稿されました:

www.jonki.net

ブロックは id:jonki が作ってくれていて、ハードウェアボタンを冠した 6x6 ポチの 3D プリントしたレゴの土台の中に TOCOS の TWE-Lite が入っていて、PC 側に取り付けられた親機(トコスティック)と無線で通信しています。

f:id:hecomi:20150727003743j:plain

TWE-Lite は私も部屋の状態を取得するセンサとして使用していて、大変安価で使いやすくてとても便利です。詳細はミクミンP の記事がとても分かりやすいです。

ブロックの底部は真っ黒なのですが、実はここには可視光カットフィルムを貼っていてその内側に AR マーカが仕込んであります。この AR マーカから機体の位置・角度を取得するとともに AR マーカの ID と TWE-Lite の ID を紐付けして、どのハードウェアボタンが押されたらどの機体から弾を発射するか、というのを判断しています。

テレビ石

テレビ石はとても不思議な石で、絵の上に乗せると下の絵が石の表面に見えるというものです。これは結晶構造が平行で綺麗に並んでいて光が細い単位で全反射して上に伝わることによる効果です。

f:id:hecomi:20150805173530j:plain

また、逆に言えばテレビ石上面のタッチは下面にそのまま伝わることになり、テレビ石上面のタッチが下部のカメラから見ると(少し減衰しますが)スクリーン表面のタッチと同様に見えます。

f:id:hecomi:20150805174916j:plain

またリアプロとテレビ石の組み合わせは相性が良くて、以下の様な構造にすることにより斜めから見てもボケないはっきりとした映像を上面に投影することが可能です(写真左が上部に拡散シートあり、右がなし)。

f:id:hecomi:20150805183959p:plain

f:id:hecomi:20150805174136j:plain f:id:hecomi:20150805174140j:plain

昔に奇石博物館に行って買ってもらって遊んだのを思い出して試してみたら面白かったので採用しました。今回は東急ハンズで購入した人口テレビ石を利用しましたが、光ファイバを充填しても作成することが出来ます。

が、新規性は無くて後述しますがより進んだ先行研究が有ります(応募した後に気づいた)。。

ソフトウェア全体構成

ここからソフトウェアの方の解説に移ります。

簡略化した全体像は以下になります。

f:id:hecomi:20150804200257p:plain

認識部は主に OpenCV で処理を行い、マーカ認識は ArUco を利用しています。流れとしては、Kinect v2 から取得した赤外線ストリームをホモグラフィ変換して下処理した後、テレビ石の認識(ランドルト環認識)とマーカ認識を行い、マーカ認識では隣接するブロックの検出、ポリゴン化、三角ポリゴン化を行います。認識した結果は JSON にして OSC で送ります。

ゲーム部は Unity で作成していて、その OSC メッセージを受け取り、認識した物体をゲーム中にマージします。三角ポリゴン化された情報を使ってメッシュやコライダを作成したり、位置や回転の補間(30 fps -> 60 fps)を行ったりしつつ、ハードウェアから送られてきたシリアル信号とマージしながらゲームを動かします。

Unity で描画した画は SyphonMad Mapper に送り、画面に認識した位置とぴったり合うように幾何変換してプロジェクタから出力します。

デモを円滑に行うため、多くのパラメタは GUI を作成して環境に合わせてリアルタイムに変更・保存・復元できるようにしてます。今回、Mad Mapper 経由で出力することにしたので、Unity の Editor がそのまま表示できる都合で、認識側だけでなく Unity 側の多くのパラメタも Editor からリアルタイムに変更できるようになったのはかなり便利でした。Mad Mapper 高かった & 単一の矩形の変換しか使ってないですが...

何も整理してない & 直前でバタバタしたコードが混ざってますがコードは以下に公開しています。Mac / Win どちらでも作業できるようにする意味とモジュール化が楽なので UI は Qtで作成しています。

Unity 側はリソース周りを整理してからアップします。

認識概要

デモ

冒頭の動画と同時に撮影した認識時の動画です。

ホモグラフィ変換

なるべく Kinect の画が大きくなるように調整します。

f:id:hecomi:20150805165714p:plain

前処理

基本的にはアンシャープマスクをかけてキャリブ画像との差分を取って輝度をならすためのグラデーションをかけているだけです。

f:id:hecomi:20150805165733p:plain

テレビ石認識

テレビ石はカバーに一部黒い帯をつけてランドルト環(視力検査の形、OpenCV のロゴの形、C)にして、これをまずテンプレートマッチングして検出します。テンプレートマッチングでは傾きは得られないので、得られた ROI に対して中心部から外へ向けて 360 度レイを打って、端まで到達したレイの平均角度をこのテレビ石の角度として採用しています。

f:id:hecomi:20150805165756p:plain

今回はやりませんでしたが、黒い帯を複数つけてパターン化すれば ID も付与できると思います。

そして丸の内側の領域に対してテンプレートマッチングとはやや低めのスレッショルドを与えてタッチ認識を行います。

マーカ認識

マーカ認識には ArUco を使用しています。

ArUco はデフォルトでは cv::adaptiveThreshold を利用する設定になっている(ここ)ので元画像を食わせても認識精度が良くなく、動かしたりするとトラッキングが外れたり場所によって認識が弱かったりして困っていました。そこで cv::threshold を利用する形にして自前で ArUco が内部的に Threshold を一つにしてしまっているところを外側で複数振って食わせて自前で結果をマージしたところ認識が劇的に改善しました。

これでほぼトラッキングが外れなくなりました。

f:id:hecomi:20150805165829p:plain

ポリゴン化

基本的には cv::approxPolyDP を使ってポリゴン化しているのですが、今回は対象がレゴという最小の辺の長さがある程度決まっていて且つ 90 度単位でしか折れ曲がらないことがわかっているので、こういったルールを利用して要らない点を棄却しています。

エッジ認識・エッジパターン認識

棄却処理後の点群に対して、尖った点の位置や方向を認識します。これが Unity 上で武器の場所になります。また尖った点同士の位置や方向の関係を見てパターンの認識も行っています。これによって武器をレーザ化したりシールド化したりします。

三角ポリゴン化

Unity では三角ポリゴンでないと扱えないので、ポリゴンを三角形分割する必要があります。今回はこれを行うために polypartition というライブラリを利用しました。

色々な三角ポリゴン化のアルゴリズムが含まれています。利用法も簡単で結果下記のように短いコードで三角ポリゴン化出来ました。

ゲーム

概要

認識側に時間を費やしてしまったのでゲームは簡単なエフェクトだけ盛ってかなり手抜きな感じにしました。基本的にリッチなグラフィックはプロジェクタの画で潰れてしまうので、コントラストの高いプリミティブベースの画作りでごまかしたつもりです。

f:id:hecomi:20150805230458p:plain

基本的には縦スクロールするシューティングゲームでグローバル座標に敵やアイテムのロケータを置いておき、アクティベーション用のコライダが通過したらローカル座標のステージにロケータに対応したプレファブをインスタンス化して動かす、みたいな感じです。ステージを視覚的に配置できるのはゲームエンジンのお陰ですね。これでステージは前日まで出来てなかったのですが何とか間に合いました。

f:id:hecomi:20150805190051p:plain

送られてきたデータは得られたポリゴンデータから自前でメッシュを作成して MeshFilterMeshCollider に食わせます。 これだけで簡単に自機の形状と当たり判定がぴったり実際の物体に合わさって表示される形になります。

f:id:hecomi:20150805184317p:plain

認識が 30 fps なのに対してゲームは 60 fps で動くので間の点を補間しなければなりません。そこで認識側ではポリゴン点やエッジ点をマーカから見たローカル座標に変換しておいて、Unity 側でマーカの位置・回転を先読みして補間すると同時にポリゴン点なども動かすようにしました。これで認識側から来るメッセージが 1、2 フレ飛んでも割りと滑らかになったと思います。

いつ認識が外れたり別の武器になるとも知れないエッジ点の認識結果と、押した・話したイベントが呼ばれると保証されてないハードウェアの入力との間でいい感じにステートを扱うのが大変でした...。会場の無線の関係もあり、結局ハードウェア自体は今回は ON/OFF しか取っていない仕様だったので本番では弾が出っぱなし or 出ないを避けることが出来ず無念。。次回の課題です。

使用したライブラリ・素材

ゲームを作成するに辺り、keijiro さんのライブラリに多々お世話になりました。

BGM は MusMus さんからお借りしました。

赤外線カメラの選定

いくつかカメラを試しました。

Xtion

  • IR 画は 640x480 @ 30 fps
  • USB をつなげるだけで赤外線投光も含めて動作して便利
  • センサが小さく露光時間が長いせいかブレが大きく、速く動かすとロストする

RealSense F200

  • IR 画 は 640x480 @ 300 fps で激ヤバ性能(このモードの時の解像度は実質半分くらい)
  • USB つなげるだけで動く
  • うちの PC と相性が悪いのか絶望的に安定しない、使ってると接続が途切れてしばらくしないと使えない

f:id:hecomi:20150804224011p:plain

Leap Motion

  • スペックは調べてないです...、かなり速い
  • リアプロの関係上、真下に取り付けられないため、端につけると対辺の解像度がかなり低く認識できない

Kinect v2

  • IR 画は 512x424 @ 30 fps
  • 別電源が必要
  • 赤外線投光はかなり強く波長は 827-850 nm? 付近
  • ブレはかなり少ない

DC-NCR300U

  • レゴの時に使っていて壊してしまって試し忘れてました...。
  • 1920x1080 @ 15 fps / 1024 x 768 @ 30 fps
  • 歪みが大きいのでキャリブ必要あり

本当は F200 を使いたかったのですが、うちの PC(ALIENWARE 17)とは相性が悪くデモ用途には厳しいのと解像度が足らず断念しました。他にも別途マイクロビジョンさんから VGA 60 fps な赤外線カメラを発注したのですが本番までには間に合わず...。今回は Kinect v2 を利用することにしました。誰か Point Grey のカメラ恵んで下さい。

先行事例・研究

テレビ石・光ファイバ

最初はテレビ石の体験をメインにゲームを作る予定をしていたのですが、色々と調査をした結果、Hasso Plattner Institute の Lumino(2010)や、明治大学 福地研究室の Ficon(2011)が、光ファイバを使用して立体物への応用などより進んだ研究をされています。

後はディズニーリサーチも 2012 に発表しています。向きを変えたりセンサにしてるの面白いですね。

リアプロ

リアプロでマーカ認識するタンジブルなものでは Reactable が有名です。

Reactable に用いられているマーカ認識のソフトウェアコンポーネントは reacTIVision として GPL で公開されています。

通信としてはタンジブルインターフェース用の TUIO という OSC をラップしたプロトコルを使用していて、色々な実装があって面白いです。

液晶型

商品としては Microsoft の PixelSense(旧 Surface)で、Samsung の SUR-40 や Lenovo の HORIZON 2 が商品として出ています。

PixelSene はその名の通り画素中に赤外線センサが入っていて、IR を含むバックライトからの反射を検出することによりディスプレイ上の物体(手指やマーカ等)を検出することが出来ます。

これを用いて、ところてんさんがいくつかゲームを作成されています。

今回の作品の差別化ポイントは、

  • リアルタイムにコントローラを拡張可能
  • ハードウェア連動
  • 上記を2点を踏まえてゲームにした

の3点かな、と思います。コンセプトと同じです。ただ、ハードウェア連動だけどこかもっと進んだものを見たことあった気がするのでご存じの方は教えて下さい。

今回の反省点・次回への展望

認識速度・手法

動画を見るとお分かりいただけるように、かなりレイテンシが大きいです。これは主にプロジェクタ・カメラともに 30 fps で動いていることによって引き起こされています。

人間が操作する物体に直接映像を追従させるとするとこのレイテンシがとても気になります。これを避けるためには、

  1. 見た目で対応する
    • 例えばプロジェクタで照らすのでなくハードウェアそれ自体が光ってコライダや武器の発射をそれにあわせる
    • 先読みをもっとするがぼんやりとブラーを掛けた表現で追従してる感を出す
  2. カメラ・プロジェクタ共に高速なものを利用する
  3. ハードウェアを使って別方式を考える

といったことが考えられます。2 が今回は手に入らなかったので 1. でやろうと思ったのですが、littleBits のようにブロック間に電気を通すことが(我々の)技術・工数的に出来なかったので断念しました。

次は 2. と 3. をやろうと思っていて、私の方は高速なカメラやプロジェクタを使って色々テストしていきたいです。その際、マーカ認識はちょっと重たい処理になるので、次は赤外線チップ LED でパターンを作成してパッシブでなくアクティブな認識にしようと考えています。3. は別方式を id:jonki と話してて思いついたので彼にやってもらおうと思います。

ハードウェアの取りこぼし

会場の無線が悪かったこともありますが、ON / OFF 時のみにイベントを送信する設計がおそらく失敗でした。。そこで次回は、上のアクティブ型の認識と併せて、ボタンを押下した時に特定の LED が光るようにして画像もしくはフォトディテクタベースでボタン押下の認識を行おうかと考えています。その他の即時性の必要ないステートだけ別途無線で送る、みたいな形を試してみます。

ゲーム性

コントローラの拡張などをうまくゲームデザインに盛り込めなかったので次回はもっと工夫したいです。あとハードウェアも前回(Mont Blanc Pj.)は出力のみ、今回は入力のみだったので、次は双方向でなにかできるようにしたいです。

おわりに

会場では「前回もレゴやってましたよね?」と覚えていてくださっていた方も結構いらっしゃいまして嬉しかったです。お越しくださった方ありがとうございました。また素晴らしい場を提供してくださった運営の方々、本当にありがとうございました。来年も出せるよう頑張ろうと思います。

期間が短かったのでもっと色々やりたいことはあったのですが、作業時間と予算・我々の技術考えると結構限界が来て色々出来なくて悔しかったところも多々あります。それらは改善して次回に繋げられればと思います。

個別の具体的なコードの紹介はついては別途記事を起こしていこうと考えています。何かご質問・ご要望などあればご遠慮なくTwitter 等で @hecomi までお尋ねください。

Maker Faire Tokyo 2015 にレゴ x ハードウェア x プロジェクションなゲーム LITTAI を出展します

はじめに

明日・明後日(8/1・8/2)で東京ビッグサイトで開催される Maker Faire Tokyo 2015 に、id:jonkiid:AMANELITTAI というゲームを F-01-04 で出展してきます。

出展内容

出展内容は、以前展示したことのある Mont Blanc Pj. と同じくレゴ x ハードウェア x プロジェクションですが、今回はもっとアクティブにできるゲームを用意しました。

物理的にブロックを組み替えて武器をチェンジする機体で進めていくシューティングゲームです。当日はゲームクリアスコアのランキングも用意する予定です。

前回同様、私は主に認識とゲームを担当、ハードウェアは id:jonki 、ゲームのランキングシステムは id:AMANE にやってもらいました。5 月末に出展決定してから本格的に作り始めたため未だまだ中途半端なところもありますが、実際のブロックをコントローラとして遊べる体験はとても楽しいので、是非会場へお越しの際はお立ち寄り下さい。

おわりに

技術の詳細につきましては展示終了後に随時書いていこうと思います。