凹みTips

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

Unity × Razer Hydra でキャラクターの腕を IK で自在に動かす(はずだった)

はじめに

Razer Hydra は両手の位置および角度を取得できる両手持ちのコントローラです。位置検知の仕組みは、Base Station と呼ばれる丸い球体を備えた別体にて磁気を発生させ、それをコントローラ側で受けて位置を検知しているようです。

日本で売っているお店が見当たらなかったのですが、@GOROman さんに教えてもらいAmazon UK から購入出来ました。

コントローラ自体は 2011 年夏に発売されたものですが、最近は Oculus Rift との組み合わせで注目されているようです。

動画でデモされているのは「Tuscany VR Demo」です。

Oculus Rift と Razer Hydra を持っていれば実際にダウンロードして遊ぶことができます。バーチャルの世界に自分の両手を持ち込める感覚はとても面白いですし、可能性を感じます。
先月(2013/6/27)Wireless バージョンの発表もあり、今後の動向にも注目したいです。

Unity Plug-in

SDK のダウンロードと使用

Razer Hydra の開発用 SDK が以下にて公開されています:

私は Unity 用の Plug-in を使いました。Asset Store からダウンロードして「SixenseHandController」シーンを開くと、次のような画面が表示されます。

手は Mecanim で動いており、トリガを引くと握るアクション、ボタン1を押すと指をさすアクションなどをします。位置と回転はコントローラの位置・傾きに応じた動きをします。

仕組み

シーンの構造は、「Left Hand」と「Right Hand」、「Sixense Input」からなっています。それぞれの手には「SixenseObjectController」を継承した「SixenseHandController」スクリプトがアタッチされています。「SixenseObjectController」は、それぞれのコントローラの位置・回転をオブジェクトに伝えるものです。Cube を作成してこれをアタッチするとコントローラの位置に応じて動いたり回転するオブジェクトが簡単に出来ます。「SixenseHandController」はその上で、Mecanim による手のアニメーションを制御する動作や、画面に「スタートボタンを押してくれ」メッセージを出したりする動作をします。例えばボールを握るアクションは次のように書かれています。

// Grip Ball
if ( Hand == SixenseHands.RIGHT ? controller.GetButton(SixenseButtons.TWO) : controller.GetButton(SixenseButtons.ONE)  )
{
	m_animator.SetBool( "GripBall", true );
}
else
{
	m_animator.SetBool( "GripBall", false );
}

このスクリプトを自分の使用シーンに応じた形に書き換えて上げれば色々出来そうです。
なお、このスクリプト中の「controller」は引数で与えられる「SixenseInput.Controller」クラスのインスタンスです。「Sixense Input」空ゲームオブジェクトに「SixenseInput」スクリプトをアタッチすることでインスタンス化しており、「Sixense Input」には「SixenseInput」スクリプトがアタッチされていて、このスクリプトではコントローラからパラメタを取得する「SixensePlugin」スクリプトをラップしている形になります。なので自分のシーンで使う際は同じように何らかの空ゲームオブジェクトに SixenseInput.cs をアタッチすることが必要になります。

MMD for Unity と合わせて使ってみる

MMD for Unity で表示した自キャラの腕と連動させて見ようと思ってやってみました。結論から言うと挫折しました。。。が、挫折記録をメモしておきます。

腕の IK をつくる

まず、位置に応じて腕全体が動いてくれるように専用の IK を追加します。足の IK をコピーして腕の IK を作ります(足の IK については MMD for Unity で IK が切れているモデルを修正する - 凹みTips をご覧ください)。次にそれぞれの「CCDIKSolver」 の「Target」および「Chanins」の対象を足のものから腕のものへつなぎ変えます。そしてそれぞれのゲームオブジェクトの位置を腕や指先に調整し、最後に大本のオブジェクトにアタッチされている「MMDEngine」の「Ik_list」に対象の IK を追加します。これで腕を IK として動かせるようになります。ただ、そのままでは腕があらぬ方向へ曲がってしまうので CCDIKSolver を少し改造して腕の動きを制限します。CCDIKSolver.cs を見ると、ひざの項目で制限をかけて処理をしていることが分かります。

// 本来なら設定値に基づいてやるけど、とりあえず膝限定
if (!bone.name.Contains("ひざ"))
	return;

// オイラー角を取得
var v = bone.localEulerAngles;

// y,z回転を無効化
if (adjust_rot(v.y) == adjust_rot(v.z))
{
	v.y = adjust_rot(v.y);
	v.z = adjust_rot(v.z);
}

// 逆に曲がらないように、制限してあげる
if (v.x < 90 && v.x > 2 && ((v.y == 0 && v.z == 0) || (v.y == 180 && v.z == 180)))
	v.x = 360 - v.x * 0.99f;

ここを次のように書き換えてみます。

if (bone.name.Contains("ひざ")) {
	// オイラー角を取得
	var v = bone.localEulerAngles;

	// y,z回転を無効化
	if (adjust_rot(v.y) == adjust_rot(v.z))
	{
		v.y = adjust_rot(v.y);
		v.z = adjust_rot(v.z);
	}

	// 逆に曲がらないように、制限してあげる
	if (v.x < 90 && v.x > 2 && ((v.y == 0 && v.z == 0) || (v.y == 180 && v.z == 180)))
		v.x = 360 - v.x * 0.99f;

	bone.localRotation = Quaternion.Euler(v);
}
else if (bone.name.Contains("右ひじ")) {
	var v = bone.localEulerAngles;
	// 角度制限を書く。↓は正直適当です...
	if (v.x > 10  && v.x < 180) v.x = 10;
	if (v.x > 180 && v.x < 350) v.x = 350;
	if (v.y > 10  && v.y < 180) v.y = 10;
	if (v.y > 180 && v.y < 350) v.y = 350;
	if (v.z > 180) v.z = 180;
	bone.localRotation = Quaternion.Euler(v);
}
else if (bone.name.Contains("右腕")) {
	// 頑張る
}
else if (bone.name.Contains("左ひじ")) {
	// 頑張る
}
else if (bone.name.Contains("左腕")) {
	// 頑張る
}

頑張るの部分は頑張って書いて下さい。私は挫折しました。。
なお、VMD によって腕にモーションが付加されていると動きません。これは、Twitter で教えてもらった手法ですが、「右肩」を「右肩_」などと適当にリネームすればアニメーションが当たらなくなり、回避出来ます。

Razer Hydra とつなぐ

先に解説しましたように「SixenseObjectController」を IK にアタッチすれば動くようになります。ただ、手の角度がひじの角度にも影響されたりするため、まともに動いてくれません。手先 IK を切ってしまえばまだ見れる形にはなります。
ここらで力尽きました。

おわりに

どなたか意志を受け継いで IK 付きの手を作ってくれることを期待します。。私は Razer Hydra + IK で動かすのは一旦諦めて、Kinect のボーンから腕を動かす方をチャレンジしてみます。