Live2DのUnity版でシンプル実装

Live2DのSampleApp1プロジェクトは全部入りですが、複雑で難しいです。
最も簡単なSimpleプロジェクトをカスタムしてモーション・音声が再生できるシンプル実装してみました。

# Live2Dで音声・モーション付きのシンプル実装
でき上がりは、UGUIボタン操作でモーション再生するかんじ
![2015-02-18_21h27_36.png](https://qiita-image-store.s3.amazonaws.com/0/15028/7770db30-c4f9-c7cf-076f-07484cec5427.png)

InspectorはこんなかんじでスクリプトとAudio Sourceだけ。
PathにはResources配下のフォルダ名をセットしてます。
![2015-02-18_21h27_25.png](https://qiita-image-store.s3.amazonaws.com/0/15028/b380ed54-88a2-b396-3e99-ce2c145a9b1a.png)

フォルダ構成はResourcesの下にLive2Dモデルをいれておきます。
ここにあるmodel.jsonをInspectorにセットしています。model.jsonはLive2D Cubism Modelerから出力できます。
![2015-02-18_21h29_43.png](https://qiita-image-store.s3.amazonaws.com/0/15028/a228c1d6-388b-45f0-d4ed-7ee5ee46dfd1.png)

##ソースコード
モーションボタンが押されない限り、配列0番目のモーションがループ再生される仕様です。
配列0番目のモーションはmodel.json上で指定します（model.jsonもソースコードの下に書きました！）

**2015/09/17追記**
**Live2D Unity SDK2.1からクリッピングマスク使用モデルの場合は[live2DModel.updateのタイミングが変更](http://sites.cybernoids.jp/cubism-sdk2/2-1-sdk-updated)**


```csharp:SimpleModel.cs
using UnityEngine;
using System;
using System.Collections;
using System.Text.RegularExpressions;
using live2d;


[ExecuteInEditMode]
public class SimpleModel : MonoBehaviour 
{
    public string path;                       // モデルパス
    public TextAsset modelJson;                 // model.json
    private string modelpath;                  // モデルパス
    private TextAsset mocFile;                  // モデルファイル
    private Texture2D[] textures;               // テクスチャファイル
    private TextAsset[] mtnFiles;               // モーションファイル
    private int[] mtnFadeines;                 // フェードイン
    private int[] mtnFadeoutes;                // フェードアウト
    private AudioClip[] soundFiles;             // 音声ファイル
	private Live2DModelUnity live2DModel;
    private Live2DMotion motion;                // モーションクラス
    private MotionQueueManager motionManager;     // モーション管理クラス
    private Matrix4x4 live2DCanvasPos;
    private int motioncnt = 0;                 // モーション番号


    /// <summary>
    /// 初期処理
    /// </summary>
	void Start () 
	{
        // Live2D初期化
        Live2D.init();
        // model.jsonを読み込む
        Json_Read();
        // モーション管理クラスのインスタンス作成
        motionManager = new MotionQueueManager();
        // モーションのインスタンス作成
        motion = Live2DMotion.loadMotion(mtnFiles[motioncnt].bytes);
        // モーションの再生
        motionManager.startMotion(motion, false);

        // Live2Dモデルの表示位置計算
        float modelWidth = live2DModel.getCanvasWidth();
        live2DCanvasPos = Matrix4x4.Ortho(0, modelWidth, modelWidth, 0, -50.0f, 50.0f);
	}


    /// <summary>
    /// model.jsonを読み込む
    /// </summary>
    void Json_Read()
    {
        // model.jsonを読み込む
        char[] buf = modelJson.text.ToCharArray();
        Value json = Json.parseFromBytes(buf);

        modelpath = path + "/";

        // モデルを読み込む
        mocFile = new TextAsset();
        mocFile = (Resources.Load(modelpath + json.get("model").toString(), typeof(TextAsset)) as TextAsset);
        live2DModel = Live2DModelUnity.loadModel(mocFile.bytes);

        // テクスチャを読み込む
        int texture_num = json.get("textures").getVector(null).Count;
        textures = new Texture2D[texture_num];

        for (int i = 0; i < texture_num; i++)
        {
            // 不要な拡張子を削除
            string texturenm = Regex.Replace(modelpath + json.get("textures").get(i).toString(), ".png$", "");
            textures[i] = (Resources.Load(texturenm, typeof(Texture2D)) as Texture2D);
            live2DModel.setTexture(i, textures[i]);
        }

        // モーションとサウンドを読み込む(motions配下のタグを読み込む)
        Value mtnpath = json.get("motions").get("null");
        int mtn_num = mtnpath.getVector(null).Count;
        mtnFiles = new TextAsset[mtn_num];
        soundFiles = new AudioClip[mtn_num];
        mtnFadeines = new int[mtn_num];
        mtnFadeoutes = new int[mtn_num];

        for (int n = 0; n < mtn_num; n++)
        {
            mtnFiles[n] = (Resources.Load(modelpath + mtnpath.get(n).get("file").toString()) as TextAsset);
            // サウンドファイルがあれば入れる            
            if (mtnpath.get(n).getMap(null).ContainsKey("sound"))
            {
                // 不要な拡張子を削除
                string soundnm = Regex.Replace(Regex.Replace(modelpath + mtnpath.get(n).get("sound").toString(), ".mp3$", ""), ".wav$", "");
                soundFiles[n] = (Resources.Load(soundnm, typeof(AudioClip)) as AudioClip);
            }
            //フェードイン
            if (mtnpath.get(n).getMap(null).ContainsKey("fade_in"))
            {
                mtnFadeines[n] = int.Parse(mtnpath.get(n).get("fade_in").toString());
            }
            //フェードアウト
            if (mtnpath.get(n).getMap(null).ContainsKey("fade_out"))
            {
                mtnFadeoutes[n] = int.Parse(mtnpath.get(n).get("fade_out").toString());
            }
        }
    }

    /// <summary>
    /// 更新処理
    /// </summary>
    void Update()
    {
        // モーション再生が終了した場合
        if (motionManager != null && motionManager.isFinished())
        {
            motioncnt = 0;
            // モーションをロードする
            motion = Live2DMotion.loadMotion(mtnFiles[motioncnt].bytes);
            // フェードインの設定
            if (mtnFadeines[motioncnt] != null) motion.setFadeIn(mtnFadeines[motioncnt]);
            // フェードアウトの設定
            if (mtnFadeoutes[motioncnt] != null) motion.setFadeOut(mtnFadeoutes[motioncnt]);
            // モーション再生
            motionManager.startMotion(motion, false);
        }
    }


    /// <summary>
    /// カメラシーンにレンダリング時呼ばれる
    /// </summary>
    void OnRenderObject()
    {
        if (live2DModel == null) return;
        live2DModel.setMatrix(transform.localToWorldMatrix * live2DCanvasPos);
        // アプリが終了していた場合
        if (!Application.isPlaying)
        {
            live2DModel.update();
            live2DModel.draw();
            return;
        }

        // 再生中のモーションからモデルパラメータを更新
        motionManager.updateParam(live2DModel);
        // 頂点の更新
        live2DModel.update();
        // モデルの描画
        live2DModel.draw();
    }


    /// <summary>
    /// モーションチェンジ
    /// </summary>
    /// <param name="filenm"></param>
    void Motion_change(string filenm) 
    {
        int cnt = 0;
        for (int m = 0; m < mtnFiles.Length; m++)
        {
            if (mtnFiles[m].name == filenm)
            {
                break;
            }
            cnt++;            
        }
        // モーションのロードをする
        motion = Live2DMotion.loadMotion(mtnFiles[cnt].bytes);
        // フェードインの設定
        if (mtnFadeines[cnt] != null) motion.setFadeIn(mtnFadeines[cnt]);
        // フェードアウトの設定
        if (mtnFadeoutes[cnt] != null) motion.setFadeOut(mtnFadeoutes[cnt]);
        // モーション再生
        motionManager.startMotion(motion, false);
        // 音声再生
        if (soundFiles[cnt] != null)
        {
            audio.clip = soundFiles[cnt];
            audio.Play();
        } 
    }

    // UGUIから呼び出す処理１
    public void Motion_idle1()
    {
        Motion_change("tetudau.mtn");
    }

    // UGUIから呼び出す処理２
    public void Motion_idle2()
    {
        Motion_change("nurupo.mtn");
    }
}
```

model.jsonはこんなかんじでmoitons-null配下の1行目をアイドルモーションとしてます

```json:model.json
{
	"version":"Sample 1.0.0",
	"model":"model/model.moc",
	"textures":[
		"model/model.1024/texture_00.png"
	],
	"motions":{
		"null":[
			{"file":"motions/idle.mtn"}, 
			{"file":"motions/chotto.mtn","sound":"sounds/kei_voice_020_1.mp3","fade_in":100,"fade_out":100},
			{"file":"motions/nurupo.mtn","sound":"sounds/kei_voice_035_1.mp3","fade_in":100,"fade_out":100},
			{"file":"motions/otukare.mtn","sound":"sounds/kei_voice_017_2.mp3","fade_in":100,"fade_out":100},
			{"file":"motions/tetudau.mtn","sound":"sounds/kei_voice_023_2.mp3","fade_in":100,"fade_out":100}
		],
		"idle":[
			{"file":"motions/idle.mtn"}
		]
	},
	"physics":"embed://Sample Physics.json"
}
code
```

HierarchyウィンドウでCreateボタンでUI-Buttonして、
UGUIボタンからOnClick()イベントを呼べばモーションと音声再生できるシンプル実装の出来上がりです！
![2015-02-18_21h34_14.png](https://qiita-image-store.s3.amazonaws.com/0/15028/139689bf-25c3-bd64-b843-5c13d95b8591.png)



あと以前MiniJSON使ったものをブログに書いたけど、Live2DのライブラリにはJson解析コードが含まれていて、外部のものを使う必要ありませんでした～。


参考URL：[Live2DでJSON対応のUnityコード書いた](http://ameblo.jp/chicktack123/entry-11876984070.html)