31
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Live2DのUnitySDKをコードリーディング

Last updated at Posted at 2015-02-25

Live2DのUnity SDKにSampleApp1という全機能入りの難しいソースがあります。
コードを読んでたけど、よくわからなかったので図にしてみました。

この図が全てではないけど、ざっくりとこんなかんじ

##SampleApp1クラス関連図
SampleApp1.png

画像を大きく作りすぎたので、全体を見たい人は画像をDLするか新しいタブで画像表示して下さいね

##SampleApp1の基本な流れ

1)各Live2DモデルにアタッチされてるLappModelProxy.csから始まる
2)メインソースのLAppModel.csでJSONパース → モーション再生や物理演算処理をしています
 ※ SampleApp1には、テキストアセット(bytes拡張子)に変換するエディタ拡張もあり

SampleApp1を解析した結果、
model.jsonから物理演算やポーズjsonのファイルパスを取得し、JSONパースする実装ができました♪

2.png

もっと良いやり方があるかもと思いますが、とりあえずやりたい事はできた!

##ソースコード
L2DPhysics.cs(物理演算用クラス)とL2DPose.cs(ポーズ用クラス)は、SampleApp1から持ってきました。
2015-02-25_13h56_41.png

メインのSimpleModel.csの中でjsonの中身を物理演算クラスとポーズクラスに渡しています。
mocとmtnはSampleApp1のソースを参考にしつつ、自前でパースしてます。

2015/09/17追記
Live2D Unity SDK2.1からクリッピングマスク使用モデルの場合はlive2DModel.updateのタイミングが変更

SimpelModel.cs
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using live2d.framework;
using live2d;


[ExecuteInEditMode]
public class SimpleModel : MonoBehaviour 
{
    public TextAsset modelJson;                     // model.json
    private TextAsset mocFile;                      // モデルファイル
    private Texture2D[] textures;                   // テクスチャファイル
    private TextAsset[] mtnFiles;                   // モーションファイル
    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;                      // モーション番号


    /// <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);


        // モデルを読み込む
        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>
    void Update()
    {
        // モーション再生が終了した場合
        if (motionManager != null && motionManager.isFinished())
        {
            motioncnt = 0;
            // モーションをロードする
            motion = Live2DMotion.loadMotion(mtnFiles[motioncnt].bytes);
            // フェードインの設定
            motion.setFadeIn(mtnFadeines[motioncnt]);
            // フェードアウトの設定
            motion.setFadeOut(mtnFadeoutes[motioncnt]);
            // モーション再生
            motionManager.startMotion(motion, false);
            // 音声再生
            if (soundFiles[motioncnt] != null)
            {
                audio.clip = soundFiles[motioncnt];
                audio.Play();
            }
        }
    }


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

        // 再生中のモーションからモデルパラメータを更新
        if(motionManager != null )
        {
            motionManager.updateParam(live2DModel);

        }
        // ポーズの設定
        if (pose != null) pose.updateParam(live2DModel);
        // 物理演算の設定
        if (physics != null) physics.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);
        // フェードインの設定
        motion.setFadeIn(mtnFadeines[cnt]);
        // フェードアウトの設定
        motion.setFadeOut(mtnFadeoutes[cnt]);
        // モーション再生
        motionManager.startMotion(motion, false);
        // 音声再生
        if (soundFiles[cnt] != null)
        {
            audio.clip = soundFiles[cnt];
            audio.Play();
        } 
    }
}

ちなみにValueという型は、Live2DUnity.dll内でもつJSONデータ保持する型です。

2015/10/14追記
ちなみに顔の向きを取得するタッチ判定は、Main CameraにアタッチされているMyGameController.csで取得しています。
そのため"Main Camera"というGameObject名が存在していてこのスクリプトがアタッチされていないといけません。
(Main CameraオブジェクトのTagもMainCameraであること!)

31
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?