JokerScriptでLive2Dモデルをマウスドラッグの方向に向かせる

需要がありそうなのでやり方をメモしておきます。


# 開発環境

・Live2D_SDK_Unity_2.1.00_1_jp
・Unity5.2.0f3
・jokerscript_v041
・live2d_for_joker_plugin_031

# マウスドラッグのやり方
Live2D Unity SDKのDemoプロジェクトで一番シンプルなマウスドラッグができます。
要するにこれをJokerScriptに組み込めばOKですっ！
JokerScriptの方もLive2Dのスクリプトは、SimpleModel.csと同名のファイルになっています。

**１）L2DTargetPoint.csをインポート**
JokerScriptの方にマウスドラッグ用のスクリプトをインポートします
![UnityInspector.png](https://qiita-image-store.s3.amazonaws.com/0/15028/1a69af63-f615-e132-8d7c-94ca0fab18ac.png)

**２）SimpleModel.csにマウスドラッグ処理を追加**

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


public class SimpleModel : MonoBehaviour
{
    public TextAsset modelJson;								// JSONファイル
    private TextAsset mocFile;								// Mocファイル
    private Texture2D[] textures;							// テクスチャファイル
    private TextAsset[] mtnFiles;							// モーションファイル
    private string[] mtnFilenms;							// モーションファイル名
	private int[] mtnFadeines;								// フェードイン
	private int[] mtnFadeoutes;								// フェードアウト
	private AudioClip[] soundFiles;							// 音声ファイル
	private TextAsset poseFile;								// ポーズファイル 
	private TextAsset physicsFile;							// 物理演算ファイル 
	private Live2DModelUnity live2DModel;
    private Live2DMotion motion;							// モーションクラス
    private MotionQueueManager motionManager;				// モーション管理クラス
	private L2DPose pose;									// パーツ切り替えクラス
	private L2DPhysics physics;								// 物理演算クラス
    private Matrix4x4 live2DCanvasPos;						// 表示位置
	private int motioncnt = 0;								// ファイル項番
	private EyeBlinkMotion eyeBlink = new EyeBlinkMotion();
	private L2DTargetPoint dragMgr = new L2DTargetPoint();

	
	/// <summary>
    /// 初期化処理
    /// </summary>
    void Start()
    {
        // JSONを読込
        Json_Read();
        // Live2D初期化
        Live2D.init();
        // モーション管理クラスのインスタンス
        motionManager = new MotionQueueManager();
        // モーションのインスタンス
        motion = Live2DMotion.loadMotion(mtnFiles[0].bytes);
        // モーションの再生
        motionManager.startMotion(motion, false);
        // 表示位置
        float modelWidth = live2DModel.getCanvasWidth();
        live2DCanvasPos = Matrix4x4.Ortho(0, modelWidth, modelWidth, 0, -50.0f, 50.0f);
    }


    /// <summary>
    /// 更新処理
    /// </summary>
    void Update()
    {
		// 再生中のモーションからモデルパラメータを更新
		if(motionManager != null )
		{
			motionManager.updateParam(live2DModel);
		}
		// ポーズの設定
		if (pose != null) pose.updateParam(live2DModel);
		// 物理演算の設定
		if (physics != null) physics.updateParam(live2DModel);

		// モーション再生が終了した場合
		if (motionManager != null && motionManager.isFinished())
		{
			// モーションをロードする
			motion = Live2DMotion.loadMotion(mtnFiles[motioncnt].bytes);
			// フェードインの設定
			motion.setFadeIn(mtnFadeines[motioncnt]);
			// フェードアウトの設定
			motion.setFadeOut(mtnFadeoutes[motioncnt]);
			// モーション再生
			motionManager.startMotion(motion, false);
			// 音声再生
			if (soundFiles[motioncnt] != null)
			{
				GetComponent<AudioSource>().clip = soundFiles[motioncnt];
				GetComponent<AudioSource>().Play();
			}
		}
		var pos = Input.mousePosition;
		if (Input.GetMouseButtonDown(0))
		{
			//
		}
		else if (Input.GetMouseButton(0))
		{
			dragMgr.Set(pos.x / Screen.width * 2 - 1, pos.y / Screen.height * 2 - 1);
		}
		else if (Input.GetMouseButtonUp(0))
		{
			dragMgr.Set(0, 0);
		}
		
		
		dragMgr.update();
		live2DModel.setParamFloat("PARAM_ANGLE_X", dragMgr.getX() * 30);
		live2DModel.setParamFloat("PARAM_ANGLE_Y", dragMgr.getY() * 30);
		
		live2DModel.setParamFloat("PARAM_BODY_ANGLE_X", dragMgr.getX() * 10);
		
		live2DModel.setParamFloat("PARAM_EYE_BALL_X", -dragMgr.getX());
		live2DModel.setParamFloat("PARAM_EYE_BALL_Y", -dragMgr.getY());
		
		double timeSec = UtSystem.getUserTimeMSec() / 1000.0;
		double t = timeSec * 2 * Math.PI;
		live2DModel.setParamFloat("PARAM_BREATH", (float)(0.5f + 0.5f * Math.Sin(t / 3.0)));
		
		eyeBlink.setParam(live2DModel);
		// 頂点の更新
		live2DModel.update();
	}
	
	
	/// <summary>
	/// カメラがシーンにレンダリング後に呼ばれる
	/// </summary>
	void OnRenderObject()
	{
		if (live2DModel == null) return;
		live2DModel.setMatrix(transform.localToWorldMatrix * live2DCanvasPos);
		// アプリが終了していた場合
		if (!Application.isPlaying)
		{
			live2DModel.draw();
			return;
		}
        // モデルの描画
        live2DModel.draw();
    }
    
    
    /// <summary>
    /// JSONを読み込む
    /// </summary>
    void Json_Read()
    {
		// model.jsonを読み込む
		char[] buf = modelJson.text.ToCharArray();
		Value json = Json.parseFromBytes(buf);
		
		
		// モデルを読み込む
		mocFile = new TextAsset();
		mocFile = (Resources.Load(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(json.get("textures").get(i).toString(), ".png$", "");
			textures[i] = (Resources.Load(texturenm, typeof(Texture2D)) as Texture2D);
			live2DModel.setTexture(i, textures[i]);
		}

		// モーションの配下のキーを取得
		Dictionary<string, Value> motion_keys = json.get("motions").getMap(null);
		int mtn_tag = 0;
		int mtn_num = 0;
		string[] motion_tags = new string[motion_keys.Count];

		// 読込モーションファイル数カウント用
		foreach (var mtnkey in motion_keys)
		{
			// motions配下のキーを取得
			motion_tags[mtn_tag] = mtnkey.Key.ToString();
			// 読み込むモーションファイル数を取得
			mtn_num += json.get("motions").get(motion_tags[mtn_tag]).getVector(null).Count;
			mtn_tag++;
		}
		// インスタンス化
		mtnFiles = new TextAsset[mtn_num];
		soundFiles = new AudioClip[mtn_num];
		mtnFadeines = new int[mtn_num];
		mtnFadeoutes = new int[mtn_num];

		mtn_tag = 0;
		mtn_num = 0;
		// モーションファイル数分JSON読込
		foreach (var mtnkey in motion_keys)
		{
			// モーションとサウンドを読み込む(motions配下のタグを読み込む)
			Value motionPaths = json.get("motions").get(motion_tags[mtn_tag]);
			int motionNum = motionPaths.getVector(null).Count;

			for (int m = 0; m < motionNum; m++)
			{
				mtnFiles[mtn_num] = (Resources.Load(motionPaths.get(m).get("file").toString()) as TextAsset);
				// サウンドファイルがあれば入れる
				if (motionPaths.get(m).getMap(null).ContainsKey("sound"))
				{
					// 不要な拡張子を削除
					string soundnm = Regex.Replace(Regex.Replace(motionPaths.get(m).get("sound").toString(), ".mp3$", ""), ".wav$", "");
					soundFiles[mtn_num] = (Resources.Load(soundnm, typeof(AudioClip)) as AudioClip);
				}
				//フェードイン
				if (motionPaths.get(m).getMap(null).ContainsKey("fade_in"))
				{
					mtnFadeines[mtn_num] = int.Parse(motionPaths.get(m).get("fade_in").toString());
				}
				//フェードアウト
				if (motionPaths.get(m).getMap(null).ContainsKey("fade_out"))
				{
					mtnFadeoutes[mtn_num] = int.Parse(motionPaths.get(m).get("fade_out").toString());
				}
				mtn_num++;
			}
			mtn_tag++;
		}
				
		// ポーズファイルを読み込む
		if (json.getMap(null).ContainsKey("pose"))
		{
			Value posepath = json.get("pose");
			poseFile = new TextAsset();
			poseFile = (Resources.Load(posepath.toString(), typeof(TextAsset)) as TextAsset);
            // pose.jsonを読み込む
            char[] posebuf = poseFile.text.ToCharArray();
            // パーツ切り替えクラスへ渡す
            pose = L2DPose.load(posebuf);
        }
        
        // 物理演算ファイルを読み込む
        if (json.getMap(null).ContainsKey("physics"))
        {
            Value physicpath = json.get("physics");
            physicsFile = new TextAsset();
            physicsFile = (Resources.Load(physicpath.toString(), typeof(TextAsset)) as TextAsset);
            // physics.jsonを読み込む
            char[] physicsbuf = physicsFile.text.ToCharArray();
            // 物理演算クラスへ渡す
            physics = L2DPhysics.load(physicsbuf);
        }
    }
    
    
    /// <summary>
    /// モーションのチェンジ
    /// </summary>
    /// <param name="storage">モーションファイル名</param>
	/// <param name="idle">アイドリング有無</param>
	public void Motion_change(string storage, string idle)
    {
		int cnt = 0;
		for (int m = 0; m < mtnFiles.Length; m++)
		{
			if (mtnFiles[m].name == storage)
			{
				break;
			}
			cnt++;
		}

		// アイドルフラグがONなら、指定したモーションをアイドリングさせる
		if (idle != "")
		{
			motioncnt = cnt;
		}

		/*
        int cnt = 0;
        string filenm = Regex.Replace (storage, ".bytes$","");
        // モーションファイル名を検索
        foreach (string mtnNm in mtnFilenms)
        {
			if(mtnNm == filenm)
            {
                break;
            }
            cnt++;
        }
        */

        // モーションのロードをする
        motion = Live2DMotion.loadMotion(mtnFiles[cnt].bytes);
		// フェードインの設定
		motion.setFadeIn(mtnFadeines[cnt]);
		// フェードアウトの設定
		motion.setFadeOut(mtnFadeoutes[cnt]);
        // モーション再生
        motionManager.startMotion(motion, false);
        // サウンドがあればボイス再生
        
		if (soundFiles[cnt] != null)
        {
            GetComponent<AudioSource>().clip = soundFiles[cnt];
            GetComponent<AudioSource>().Play();
        }
    }
}
```

はい、これで完成しました！
JokerScriptにはLive2Dのソースは結構わかりやすい形だと思うので、自由にカスタムしちゃって下さい。
![001.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/8126f650-b969-383f-2cb0-85b85b2b0bcb.gif)