Cardboardを使ってUnityChanが寄ってくるアプリを作る


前回：[Unity初心者が5分でGoogle Cardboard SDKを試してみた](http://qiita.com/hy3/items/4596ffde7d8ccee77f3d)

Amazonでぽちった[Google Cardboard](http://www.amazon.co.jp/gp/product/B00OII74OU)が届きました。早速先日の記事で作ったアプリを動かしてみましたが、30秒で飽きました。だだっ広い世界に箱が二個置かれているだけなのだから当たり前ですね。せめて何か動きが欲しいところです。

動きをつけるならば決まった動きを再生するのが簡単ですが、決まったレールの上を動きたくはありません。そんな人生はまっぴらです。願わくば僕の意思に合わせて世界が動いた方が面白いです。それが可愛い女の子なら言うことはありません。

というわけで、[UnityChan](http://unity-chan.com/)を見つめたらこちらに寄ってくるアプリを作ってみようと思います。

# 下調べ

Cardboard SDKのリファレンスを見ていたら[GazeInputModule](https://developers.google.com/cardboard/unity/reference#gazeinputmodulecs)というのを発見しました。英語はよくわかりませんが、Magic The Gatheringのカード名でGazeと言えば「凝視」を意味するので、多分視点による入力にはこれを使うのでしょう。

読んでみると詳細は[ここ](https://developers.google.com/cardboard/unity/guide#gui_in_vr)を読めと書いてありました。頑張って読みます。

>If you wish to interact with 3D objects in the scene, add a PhysicsRaycaster component to the Event Camera. A script on each interactive object must be able to respond to the generated events. An EventTrigger is a good choice, or you can implement some of the standard Unity Event interfaces on your own scripts. The object needs a collider component as well.

要約するとscene上の3Dオブジェクト動かすにはPhysicsRaycaster使えと書いてあります。GazeInputModuleさんがなんとかしてくれると思っていましたが、違いました。南無三！

（GazeInputModuleさんの名誉のために補足すると、これはあくまでもuGUIの操作用っぽいですね。）

というわけで、どうせなら思いっきりシンプルにPhysics.Raycastを使って実装してみようと思います。

# Raycastについて

あえて絵で説明するとこんな感じに使おうと思います。

![Raycast.png](https://qiita-image-store.s3.amazonaws.com/0/76172/372814f1-8351-51dd-e130-028cbcf97f8b.png "Raycast.png")

余計わからなくなりました。
まあ有名なので説明不要かとも思いますが、指定した方向に透明のビームを発射して何かにぶつかったか、ぶつかった場合はそれが何かを検出してくれるメソッドですね。これを使ってカメラの正面にUnityChanが立っていたら、カメラに向かって歩かせようと思います。

# 実際に作ってみる

さて、下調べも済んだので実際に作ってみることにしましょう。

## UnityChanを配置する

[公式サイト](http://unity-chan.com/)から3Dモデルデータをダウンロードしてプロジェクトにインポートします。手順は端折ります。
インポートが終わったら、以下のPrefabをHierarchyに追加しておきます。

UnityChan > Prefabs > unitychan

### 場所を決める

カメラを原点に置いているので、X軸かZ軸に沿って配置すると、後で動かす時にすごく簡単な計算で済む気がします。なお、Y軸に沿うと空中に浮くか地面に沈むのでやめてください。

今回は(X, Y, Z) = (5, 0, 0)に配置しました。初期状態でカメラはZ軸の正方向に向いているので、この座標は左手系（デフォルト）だとカメラの右方向になります。

### 必要なコンポーネントを付ける

便利そうなのでCharacterControllerを付けておきます。デフォルトだとコライダの位置と大きさがキャラと合わないので、目測で調整しておきます。僕は以下のように合わせました。

Center: X=0, Y=8.5, Z=0
Radius: 0.3
Height: 1.6

CenterとHeightの関係が少し空中に浮くように見えますが、なんとなくつま先の位置が地面と合ってしまったのでこれで行きます。 

### 余計なコンポーネントを外す

そのままだと画面上に余計なボタンが表示されてしまうので、思い切って「Idle Changer」コンポーネントと「Face Update」コンポーネントは外しちゃいましょう。なんとかなります。

![setting.png](https://qiita-image-store.s3.amazonaws.com/0/76172/f80e7554-834d-d86e-1403-f7ebb76ed6cf.png "setting.png")


### Animator Controllerをつくる

棒立ち無表情のまま寄ってくるとホラーになってしまうので、止まっている時は立っているアニメーションを、動いている時は走るアニメーションを再生するようにします。
Animator Controllerを使うとこれが実現できるのですが、以下のページの説明が判りやすかったです。

http://www.gaprot.jp/pickup/unity-mecanim/vol1/
（細かなファイル名が最新のUnityChanと違うみたいなので適宜読み替えましょう。）

上記を参考にIsWalkingというBool型のParameterを作って、trueの場合は立ったアニメーション、falseの場合は走るアニメーションに切り替わる様に設定しました。後でScriptからこのパラメータを切り替える様にします。

![animator.png](https://qiita-image-store.s3.amazonaws.com/0/76172/5ca789bd-b4d2-6744-e1c9-0c2d449b4866.png "animator.png")



## Raycastの発射元探し

ビームはカメラの正面に発射しなければいけません。つまり、視点に連動して向きが変わるオブジェクトを探す必要があるわけです。脳筋の僕はゴリ押しで探します。

アプリを再生した状態でOptionキー（WindowsならAltキーかな）を押したままマウスカーソルを動かすと視点が動くので、各GameObjectを順番に選択して、Inspectorに表示されるRotationが変化する物を探します。
どうやらCardboardMainの子オブジェクトになっている「Head」が正解みたいです。普通でした。

![CardboardMain_Head.png](https://qiita-image-store.s3.amazonaws.com/0/76172/8b0346d4-f178-68a4-021a-6394db6464e8.png "CardboardMain_Head.png")

## Headに付けるScriptを書く

ごりごり書きます。大まかにはtransform.forward方向に適当な長さのRaycastを発射して、ぶつかったオブジェクトがあらかじめ決めたtargetだったらそのオブジェクトをこちらに動かす様にします。
targetにはあらかじめInspectorからUnityChanをセットしておきます。

ぶつかったものを引き寄せるビームということで、名前はTracterBeamにしておきます。スタートレックは名作です。

```csharp:TracterBeam.cs
using UnityEngine;
using System.Collections;

public class TractorBeam : MonoBehaviour {
    public Transform target;
    Animator animator;
    CharacterController controller;
    int isWalkingID;

    float speed = 2.0f;
    float rotateSpeed = 1080f;

	void Start() {
        animator = target.GetComponent<Animator>();
        controller = target.GetComponent<CharacterController>();
        isWalkingID = Animator.StringToHash("IsWalking");
	}

	void Update() {
        RaycastHit hit;
        bool isCatched = false;
		if (Physics.Raycast(transform.position, transform.forward, out hit, 100)) {
            if (hit.transform == target) {
                isCatched = true;
                come();
            }
        }

        if (!isCatched) {
            leave();
        }
	}

    void come() {
        lookCamera();
        if (target.position.x - transform.position.x > 0.6f) {
            animator.SetBool(isWalkingID, true);
            controller.Move(Vector3.left * speed * Time.deltaTime);
        } else {
            animator.SetBool(isWalkingID, false);
        }
    }

    void leave() {
        if (target.position.x - transform.position.x < 5.0f) {
            lookOpposite();
            animator.SetBool(isWalkingID, true);
            controller.Move(Vector3.right * speed * Time.deltaTime);
        } else {
            lookCamera();
            animator.SetBool(isWalkingID, false);
        }
    }

    void lookCamera() {
        Quaternion q = Quaternion.LookRotation(Vector3.left);
        controller.transform.rotation = Quaternion.RotateTowards(
            controller.transform.rotation, q, rotateSpeed * Time.deltaTime);
    }

    void lookOpposite() {
        Quaternion q = Quaternion.LookRotation(Vector3.right);
        controller.transform.rotation = Quaternion.RotateTowards(
            controller.transform.rotation, q, rotateSpeed * Time.deltaTime);
    }
}

```

あまりリファクタリングせずに適当に組んだら可読性さんがお亡くなりになりました。とりあえずHeadに付けます。

# 動かしてみる

さあ、見つめるだけで女の子が寄ってくる世界が出来上がりました！長い人生の冬が終わり、ついに僕に春が来たと言っても過言ではないでしょう。

いざ再生！！！！

![failure.gif](https://qiita-image-store.s3.amazonaws.com/0/76172/d9138524-f5ea-70a4-76d1-0d50114ca23c.gif "failure.gif")

どうやら僕にはバーチャルでも冬しか来ないみたいです。

# 直しました。

comeメソッド内で呼んでいるcontroller.Moveメソッドの第一引数がleftではなくrightになっていただけでした。
（先述のコードは正しく直した後の物です）

直すついでに寄ってきたら表情がニッコリするようにしてみました。

![success.gif](https://qiita-image-store.s3.amazonaws.com/0/76172/ef327560-1ca2-e6e3-2db4-99d0104291e0.gif "success.gif")

か、かわいい（*´ω`）