Live2DのSampleApp1プロジェクトは全部入りですが、複雑で難しいです。
最も簡単なSimpleプロジェクトをカスタムしてモーション・音声が再生できるシンプル実装してみました。
Live2Dで音声・モーション付きのシンプル実装
InspectorはこんなかんじでスクリプトとAudio Sourceだけ。
PathにはResources配下のフォルダ名をセットしてます。
フォルダ構成はResourcesの下にLive2Dモデルをいれておきます。
ここにあるmodel.jsonをInspectorにセットしています。model.jsonはLive2D Cubism Modelerから出力できます。
ソースコード
モーションボタンが押されない限り、配列0番目のモーションがループ再生される仕様です。
配列0番目のモーションはmodel.json上で指定します(model.jsonもソースコードの下に書きました!)
2015/09/17追記
Live2D Unity SDK2.1からクリッピングマスク使用モデルの場合はlive2DModel.updateのタイミングが変更
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から呼び出す処理1
public void Motion_idle1()
{
Motion_change("tetudau.mtn");
}
// UGUIから呼び出す処理2
public void Motion_idle2()
{
Motion_change("nurupo.mtn");
}
}
model.jsonはこんなかんじでmoitons-null配下の1行目をアイドルモーションとしてます
{
"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()イベントを呼べばモーションと音声再生できるシンプル実装の出来上がりです!
あと以前MiniJSON使ったものをブログに書いたけど、Live2DのライブラリにはJson解析コードが含まれていて、外部のものを使う必要ありませんでした~。