macOSのUnityでマルチディスプレイ時の注意点 InputModuleをハックする

Unity、少し前からPCでのマルチディスプレイに対応している。
https://docs.unity3d.com/ja/540/Manual/MultiDisplay.html

対応しているのだが、macではマルチディスプレイ時に主ディスプレイ以外のマウスイベントを拾ってくれなくなる問題(少なくとも5.6.1f1では)があった。これのソリューションをまとめておく。

概要

簡単に箇条書きすると以下のようになる。EventSystemを利用するのはUnityのGUIパーツに反応させるため。もし使わないなら不要。

  1. node.jsの'osx-mouse’でマウスイベントを取得
  2. ‘node-osc’を利用しOSCでUnityに送信
  3. UnityOSCで受け取り
  4. EventSystemのInputModuleが持つBaseInputを継承したクラスに値を渡し、擬似的なマウス入力を実現

Node.jsのサンプルコード

var mouse = require('osx-mouse')();
var osc = require('node-osc');

var client = new osc.Client('127.0.0.1', 12000);

mouse.on('move', function(x, y) {
    console.log("move "+x, y);
    var m = new osc.Message("/mousePos");
    m.append(x);
    m.append(y);
    client.send(m);
});

mouse.on('left-down', function(x, y) {
    console.log("left-down "+x, y);
    var m = new osc.Message("/mouseDown");
    m.append(x);
    m.append(y);
    client.send(m);
});

mouse.on('left-up', function(x, y) {
    console.log("left-up "+x, y);
    var m = new osc.Message("/mouseUp");
    m.append(x);
    m.append(y);
    client.send(m);
});

mouse.on('left-drag', function(x, y) {
    console.log("left-drag "+x, y);
    var m = new osc.Message("/mouseDragged");
    m.append(x);
    m.append(y);
    client.send(m);
});

すんなり書けて楽だった。

Unity側

OSC自体の扱いは省略。

まず標準のStandAloneInputModuleを継承したクラス、今回はマルチディスプレイのタッチパネルだったのでTouchPanelInputModuleという名前のクラスを作り、BaseInputを入れ替えられるようにする。BaseInputはPCにおいては各フレームごとのマウス入力の状態が入ってるクラスだ。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class TouchPanelInputModule : StandaloneInputModule {

	public void SetInput(BaseInput input){
		m_InputOverride = input;
	}

}

次に、そのBaseInputを継承したToucPanelInputを作る。OSCで送られてくるマウスイベントを元にこのTouchPanelInputにSetMouseEventしてやることでマウス入力が行われるようになる。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class TouchPanelInput : BaseInput {
	private Vector2 mousePos;
	private bool mouseButton = false;
	private bool mouseButtonDown = false;
	private bool mouseButtonUp = false;
		
	public void SetMouseEvent(Vector2 pos, bool mouseOn, bool mouseDown, bool mouseUp){
		this.mousePos = pos;
		mouseButton = mouseOn;
		mouseButtonDown = mouseDown;
		mouseButtonUp = mouseUp;
	}

	public override Vector2 mousePosition {
		get {
			return mousePos;
		}
	}

	public override Vector2 mouseScrollDelta {
		get {
			return base.mouseScrollDelta;
		}
	}

	public override bool GetMouseButton (int button)
	{
		if (button == 0) {
			return mouseButton;
		}
		return false;
	}

	public override bool GetMouseButtonDown (int button)
	{
		if (button == 0) {
			return mouseButtonDown;
		}
		return false;
	}

	public override bool GetMouseButtonUp (int button)
	{
		if (button == 0) {
			return mouseButtonUp;
		}
		return false;
	}

	public override bool GetButtonDown (string buttonName)
	{
		return false;
	}
}

OSCで送られてくるマウスイベントからいい感じにBaseInputみたいにするのに下記クラスを作成したが、名前OSCMouseInputManagerとかAdapterくらいのほうが良かった気がする。あとこいつの機能、上記のTouchPanelInputに持たせたほうがいいと思う。

class OSCMouseEvent {
	public bool down, up, drag;
	public Vector3 rawPos;
	private int noCount;

	public bool On {
		get{
			return drag || down || up;
		}
	}

	public void SetValues(bool down, bool up, bool drag, Vector3 rawPos){
		this.down = down;
		this.up = up;
		this.drag = drag;
		this.rawPos = rawPos;
		noCount = 0;
	}

	public void Update(){
		down = false;
		up = false;
		noCount++;
		if (noCount > 20) {
			drag = false;
		}
	}
}

注意点としてはEventSystemは同時にひとつのInputModuleしか持てないみたいなので、うまいことModuleのenabledを切り替えてくれ!って感じ。

おわりに

https://bitbucket.org/Unity-Technologies/ui
標準のInputModuleたちはコードが公開されているので大変参考になった。

最初は自分自身でEventSystemの真似事を実装してて、RaycastAllしてヒットしたらInvokeでonClickを呼び出しみたいなことやったのだけど、動くっちゃ動くけど、なぜか最初のクリックには反応せず、2回目以降は反応するなどの中途半端な機能性だったので、がんばってInputModuleとBaseInputをゴリゴリと変えてみたらちゃんと動くようになった。

こんな面倒くさいことせずとも動くようになって頂きたい……。

openFrameworksからのUnity入門

僕は普段、なにか表示系のものを作るときはopenFrameworksを使っているのだけど、今回3Dでグリグリや!という案件があったのでUnityを利用した。ということでoF経験者がUnityに入門する時のノウハウをまとめておく。Unityのバージョンは5.6.1f1、oFはv0.9.8。

Unityの強みと弱み

Unityはゲームエンジンで、ゲームエンジンなので状態や遷移の管理に優れており、3Dオブジェクトの扱いが簡単、強力なGUIによってパラメータの変更がラク、標準で物理演算エンジンが組み込まれているが、一方でテクスチャになにかを描いたりする機能はない。oFとの比較で強みと弱みをまとめると以下のような感じだ。

強み

  • シーン、アニメーションなどの状態管理が優れている
    • ゲームエンジンなのでそのへんのサポートが豊富
    • 特にアニメーションはコード無しでかなり高度なことが出来て大変助かった
  • 強力なエディタのGUI
    • コードに書いたパラメータをごく僅かな手間でGUIのエディタ側から調整可能(oFで言うならofxGUIが簡単に使える感覚だと思う)
  • 強力なGUIライブラリ
    • ゲーム内で使うGUIの方ですね。これもまた強力
  • シェーダーの扱いがoFよりも簡単
    • 使って当たり前のUnity、微妙に手間なoFって感じ
  • 3Dオブジェクトとカメラの扱い
    • 各種3DCGソフトからデータを持ってきて扱うのが簡単
    • カメラもGUIで簡単に扱える
  • 標準で組み込まれた物理演算エンジン
    • ほぼなにも考えずに物理演算を実現できる
  • C#による楽ちんな開発
    • C++、大変なんです
  • 完全なオブジェクト指向
    • コードはそれぞれのオブジェクトに紐付ける形で実装していくため完全なオブジェクト指向になる
    • 紐付けたオブジェクトの管理はUnity側。oFで同様のことをしようとするちょっと手間
  • パーティクルが便利
    • オブジェクトに対するエフェクトはパーティクルを使うことでほぼGUIのみでいい感じに実装出来た
  • 充実したリファレンス
    • しかも使いやすい

弱み

  • プロジェクトが壊れてエディタが起動できなくなることがしばしば起きる
    • まぁoFでも起こるけど、頻度はUnityのほうが多い印象
    • git管理は必須
  • エディタがよく落ちる
    • 諦めてこまめにセーブした
  • GUIで簡単にいじれる故にプロジェクトを開くと何某かを変更してたりする
  • テクスチャに描画するのは弱いというかほぼ想定外
    • 豊富なメソッドがあるoFと比較すると貧弱というか無い
    • 簡単な図形もペイントソフトなどで制作する必要があるのはちょっと手間
    • テクスチャの描画に関しては他のソフトに任してテクスチャを投げよう
  • メッシュをいじるのもoFのほうがやりやすい印象
  • OSCがちょっとむずい
    • これについては後述する
  • macでマルチディスプレイ時に主ディスプレイ以外のマウスイベント取得が不可能
    • 他アプリでマウスイベントを取得 -> OSC -> UnityのイベントシステムのInputModuleをいじって実現。別記事で紹介する

OSCの罠

UnityOSCを使うわけだけど、その辺に転がってるコード使うと罠にはまる。参考になるのはこのQiitaの記事だけ。oFとの罠も書いてある。OSC使うときはまず、ここを参考にコードを書こう。
UnityOSCではまる - Qiita

書籍

f:id:miso_engine:20170710150440j:plain
Unity自体の書籍としては、入門書は『Unity5の教科書』を利用した。この入門書で一通りの3Dゲームが作れるようになり、Unityの主要な機能を知ることが出来た。『Unity ゲームエフェクト入門』ではパーティクルを利用したゲームエフェクトの作り方がレシピ集として載っており、とても便利だった。

Tips

  • 回転をTransform.eulerAnglesに代入してやろうとすると360度以上の回転は失敗という罠がある
    • そもそもリファレンスにTransform.Rotateを使えとは書いてあるのでごめんなさいだけど
    • 角度でやるよりも4元数(=Quaternion)使うほうが定石感あった
  • 滑らかな移動や回転をしたいときにMathfやVector3にいい感じのメソッドが用意されている
  • RaycastHitが他のオブジェクトから0.51以上離さないとTexcoordがVector2.zero
  • UIのButtonはイケるイケナイの状態が3つある
    • ゲームオブジェクトのActiveSelf、Button自体のenableとinteractableの3つ

実際Unityどうよ

今回のプロジェクト3Dをグリグリ、物理演算使って動かしたいだったのでUnityピッタリだった。oFだけでやるよりも工数削減出来たと思う。目論見外のメリットとしてはアニメーションの実装と管理が圧倒的に楽というのがあった。今後派手なヤツはすべてUnityで実装すると思う。テクスチャの生成に関してはoFと組み合わせて使ったのでまぁ適材適所感が強い。oFとUnityの連携は今回はファイルでファイルのパスを投げるだけの簡易的なものにしたが、もっと高度なやり方もあるので、今後探っていきたい。

人との距離をセンサで検知する

センサによって人を検知する方法は山ほどあるが、条件によってどのセンサを使うかは大きく変わってくる。今回は人がいるいないや動き以外に人との距離もセンシングしたいという条件で、センサを選定した。

人を検知するセンサの種類

  • 赤外線センサ
  • レーザーセンサ
  • カメラ
  • 3次元センサ(ex. Kinect, Xtion)
  • 超音波センサ
  • 焦電センサ

ざっと上げてこんなところである。今回は小規模なシステムにしたかったので、カメラと3次元センサは外した。また、超音波センサは音波を反射しやすい硬いものでないと正確な距離が出ないので選定から外し、焦電センサは人の動きがあるかどうかを見ているため、距離が分からないので外した。

試したセンサと結果

f:id:miso_engine:20170424182218j:plain

Pololuのレーザーは一番値がキチンと出て良いのだけど、いかんせん短い。あとの2つはセンシング範囲によって選びましょう。3つともセンサからまっすぐにしか反応しない。

余談

センサ試すとき、写真のように固定出来る治具を簡単にレーザーカットして、ゴム足つけたら開発すごくラクになったのでオススメです。

VL53L0X Time-of-Flight 距離センサモジュール

VL53L0X Time-of-Flight 距離センサモジュール