Unity+Vuforia+MMD4Mecanimで初音ミクが踊るAndroid用ARアプリを作ってみた

UnityでARアプリが簡単に作れると知って、ちょっと試してみたら、案外簡単にできた。その作り方を簡単なメモとして記録しておく。
作ったARアプリはAndroidアプリ。ARマーカーをカメラで映すと、初音ミクが曲に合わせて踊るというもの。カメラ機能で撮影も可能。CDのジャケットでもARマーカーにできると思う。

曲はラマーズPのWAVEFILEで、モーションはhinoさん作成のもの、モデルはコロン式初音ミクをお借りしました。
ARマーカーは、特徴点が抽出できる絵なら何でも良いようなので、ボカロ関連のイベントを集めたサイトVOCALENDARの宣伝用カードを利用(ステルスマーケティング)。
所要日数は、初めてUnityをインストールしてからチュートリアルで基礎を勉強する期間も含めて2日間かかった。

開発手順

以下、開発手順を箇条書き。

  1. Unityのプロジェクトを新規作成
  2. MMD4Mecanim関連の手順 (ここを参考に)
    1. MMD4Mecanimのパッケージをインポート
    2. MMDモデル(PMX)とモーション(VMD)、曲の音声ファイル(mp3)をプロジェクトにインポート
    3. PMX2FBXでPMX(とVMD)をFBXに変換
  3. Vuforia関連の手順 (ここを参考に)
    1. vuforiaでアカウントを作る
    2. Databaseの作成とマーカーのアップロード
    3. ターゲットのダウンロード
    4. vuforia SDKのダウンロード
    5. パッケージ(ターゲットとVuforia SDK)のインポート
    6. (プロジェクト作成時デフォルトで追加される)Cameraを削除
    7. ARCameraとImageTargetの設定
    8. ライトの設定(Directional lightなど)
  4. 統合手順
    1. ImageTargetの子要素として、FBXに変換したMMDモデルを配置
    2. Animator Controllerを設定 (ここを参考に)
    3. 空のGameObjectを作成し、そこに曲の音声ファイルを適用

以上で、アプリの基本形は作成完了。あとは適用先のプラットフォームにAndroidを選択してビルドすれば完成。

さらに下記のスクリプトを追加して、さらに機能を作り込んだ。

ARマーカーが写っている間だけ動く

ARマーカーが写っている間だけ初音ミクが歌い踊る形にするため、ITrackableEventHandlerスクリプトを作成。
Vuforia SDKが提供するDefaultTrackableEventHandlerを元に、下記のスクリプトを作成。ImageTargetに適用した。

using UnityEngine;

public class ColonMikuTrackableEventHandler : MonoBehaviour,
                                            ITrackableEventHandler
{
    private TrackableBehaviour mTrackableBehaviour;
    private AudioSource mAudioSource;
    private Animator mAnimator;

    void Start()
    {
        mTrackableBehaviour = GetComponent<TrackableBehaviour>();
        if (mTrackableBehaviour)
        {
            mTrackableBehaviour.RegisterTrackableEventHandler(this);
        }
        mAudioSource = FindObjectOfType<AudioSource>();
        mAnimator = FindObjectOfType<Animator>();
        if(mAnimator != null) {
            mAnimator.speed = 0;
        }
    }

    public void OnTrackableStateChanged(
                                    TrackableBehaviour.Status previousStatus,
                                    TrackableBehaviour.Status newStatus)
    {
        if (newStatus == TrackableBehaviour.Status.DETECTED ||
            newStatus == TrackableBehaviour.Status.TRACKED ||
            newStatus == TrackableBehaviour.Status.EXTENDED_TRACKED)
        {
            OnTrackingFound();
        }
        else
        {
            OnTrackingLost();
        }
    }

    private void OnTrackingFound()
    {
        Renderer[] rendererComponents = GetComponentsInChildren<Renderer>(true);
        Collider[] colliderComponents = GetComponentsInChildren<Collider>(true);

        // Enable rendering:
        foreach (Renderer component in rendererComponents)
        {
            component.enabled = true;
        }

        // Enable colliders:
        foreach (Collider component in colliderComponents)
        {
            component.enabled = true;
        }

        if(mAudioSource != null)
        {
            mAudioSource.Play();
        }
        if(mAnimator != null)
        {
            mAnimator.speed = 1;
        }
    }

    private void OnTrackingLost()
    {
        Renderer[] rendererComponents = GetComponentsInChildren<Renderer>(true);
        Collider[] colliderComponents = GetComponentsInChildren<Collider>(true);

        // Disable rendering:
        foreach (Renderer component in rendererComponents)
        {
            component.enabled = false;
        }

        // Disable colliders:
        foreach (Collider component in colliderComponents)
        {
            component.enabled = false;
        }

        if(mAudioSource != null)
        {
            mAudioSource.Pause();
        }
        if(mAnimator != null)
        {
            mAnimator.speed = 0;
        }

        Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " lost");
    }
}

撮影ボタンと終了処理

画面を撮影するボタンの追加と、戻るボタンでアプリを終了する処理のため、下記のスクリプトを作成。空のGameObjectを作成して、このスクリプトを適用した。
撮影には、スクリーンキャプチャをpngで保存するUnityのAPI、Application.CaptureScreenshot()を使用。

using UnityEngine;
using System.Collections;

// キー操作
public class KeyControllScript : MonoBehaviour {
	
	private Rect mShotButtonRect;

	void Start() 
	{
		float w = 80;
		float h = 50;
		mShotButtonRect = new Rect(
			(float)(Screen.width/2 - w/2),
			(float)(Screen.height - h * 1.1),
			w, h);
	}

	// Update is called once per frame
	void Update () {
		if(Input.GetKey(KeyCode.Escape))
		{
			Application.Quit();
		}
	}

	void OnGUI()
	{
		if(GUI.Button(mShotButtonRect, "Shot"))
		{
			string path = "";
			if(SystemInfo.operatingSystem.Contains("Android")) {
				path = "../../../../DCIM/Camera/";
			}
			path = path + "screenshot" + System.DateTime.Now.Ticks.ToString() + ".png";
			Application.CaptureScreenshot(path);
		}
	}
}

撮影した写真は、SDカードの "DCIM/Camera" 以下に格納される。
SDカードに書き込むには、Androidアプリにpermissionを設定する必要がある。
プロジェクトの "Assets/Plugins/Android/AndroidManifest.xml" に、下記のエントリを が並ぶ箇所に追記する。

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />