Live2Dモデルの着替えを実装する為に、一般的な「Live2DモデルをUnityに読み込み、SDKがプレハブを作成→プレハブをシーンに配置」という手順を踏まずに、「Live2DモデルをUnityプロジェクトに配置→実行時にスクリプトでシーンに生成」というやり方をやってみました。
モデルの動的読み込み
まず
https://docs.live2d.com/cubism-sdk-tutorials/initializemodel/
こちらの公式SDKチュートリアルに目を通していただきたいのですが、まずモデルデータの配置の仕方から異なります。モデル(model3.json, physics3.json, moc3, テクスチャフォルダ。必要に応じてcdi3.jsonも)はプロジェクト内のStreamingAssetsというフォルダ内に入れる必要があります。
このフォルダは実行プラットフォームに依存せず、このフォルダ内に入れたファイルはそのまま絶対パスで参照する事ができるので動的にファイルを参照して読み込みたい場合はここに置く必要があるようです。
こちらにモデルを入れたフォルダを突っ込み(注意点として、このフォルダに直接モデルを入れた場合、テクスチャ処理の部分でLive2DSDKのインポート処理が失敗する為、プレハブ生成が失敗しシーン上にも変なインスタンスが勝手に作られてしまいます。今回のやり方ではプレハブは関係無いため消せば問題ありませんが、気持ち悪いので一旦別のフォルダにインポートしてから移動させてもいいと思います)、以下のような読み込み処理を書きます。
using System; using System.IO; using Live2D.Cubism.Framework.Json; using UnityEngine; /// <summary> /// Initialize model. /// </summary> public class InitModel : MonoBehaviour { void Start () { //Load model. var path = Application.streamingAssetsPath + "/koharu.model3.json"; var model3Json = CubismModel3Json.LoadAtPath(path, BuiltinLoadAssetAtPath); var model = model3Json.ToModel(); } /// <summary> /// Load asset. /// </summary> /// <param name="assetType">Asset type.</param> /// <param name="absolutePath">Path to asset.</param> /// <returns>The asset on succes; <see langword="null"> otherwise.</returns> public static object BuiltinLoadAssetAtPath(Type assetType, string absolutePath) { if (assetType == typeof(byte[])) { return File.ReadAllBytes(absolutePath); } else if(assetType == typeof(string)) { return File.ReadAllText(absolutePath); } else if (assetType == typeof(Texture2D)) { var texture = new Texture2D(1,1); texture.LoadImage(File.ReadAllBytes(absolutePath)); return texture; } throw new NotSupportedException(); } }
リップシンク、自動まばたきの為の初期化処理
このままでは初期状態のインスタンスがシーン上にできるだけなので、必要な処理を追加していきます。基本的には「プレハブからモデルを用意した場合と同じ作業をスクリプト側に記述する」事になります。
私の場合はこんな感じになりました。
public void LoadModel() { //StreamingAssets以下のパスを指定。実際のフォルダ構成に準ずる var path = Application.streamingAssetsPath + "/Models" + "/" + defaultCostume + "/" + defaultCostume + ".model3.json"; var model3Json = CubismModel3Json.LoadAtPath(path, BuiltinLoadAssetAtPath); cocoModel = model3Json.ToModel(); cocoModel.name = "Coco"; //ゲームオブジェクト名を指定 cocoTransform = cocoModel.gameObject.transform; cocoTransform.position = initPos ;//初期位置 cocoTransform.rotation = Quaternion.Euler(new Vector3(0,0,0));//念の為 cocoTransform.localScale = new Vector3(2f,2f,1f);//適宜サイズ変更 cocoModel.GetComponent<CubismRenderController>().SortingOrder = 200; //背景より手前になる様に適宜変更 cocoModel.GetComponent<CubismRenderController>().SortingMode = CubismSortingMode.BackToFrontOrder;//のっぺらぼうを修正 animator = cocoModel.GetComponent<Animator>(); animator.runtimeAnimatorController = aniCon;//AnimatorControllerを指定 cocoModel.gameObject.AddComponent<CubismAutoEyeBlinkInput>();//自動瞬き。パラメータ設定は適宜 cocoModel.gameObject.AddComponent<CubismEyeBlinkController>();//自動瞬き。パラメータ設定は適宜 cocoModel.gameObject.AddComponent<CubismMouthController>();//リップシンク。パラメータ設定は適宜 cocoModel.gameObject.AddComponent<CubismAudioMouthInput>().AudioInput = voiceSource;//リップシンクの音源。パラメータ設定は適宜 //自動瞬きとリップシンクのパラメータを指定。cdiファイル見て参照 cocoModel.Parameters.FindById("ParamEyeLOpen").gameObject.AddComponent<CubismEyeBlinkParameter>(); cocoModel.Parameters.FindById("ParamEyeROpen").gameObject.AddComponent<CubismEyeBlinkParameter>(); cocoModel.Parameters.FindById("ParamMouthOpenY").gameObject.AddComponent<CubismMouthParameter>(); //視線追従 var clp = cocoModel.Parameters.FindById("ParamAngleX").gameObject.AddComponent<CubismLookParameter>(); clp.Axis=CubismLookAxis.X; clp.Factor = 30f; clp=cocoModel.Parameters.FindById("ParamAngleY").gameObject.AddComponent<CubismLookParameter>(); clp.Axis = CubismLookAxis.Y; clp.Factor = 30f; clp = cocoModel.Parameters.FindById("ParamEyeBallX").gameObject.AddComponent<CubismLookParameter>(); clp.Axis = CubismLookAxis.X; clp.Factor = 1f; clp = cocoModel.Parameters.FindById("ParamEyeBallY").gameObject.AddComponent<CubismLookParameter>(); clp.Axis = CubismLookAxis.Y; clp.Factor = 1f; CubismLookController clc = cocoModel.gameObject.AddComponent<CubismLookController>();//これはアタッチ時にparameterチェックするので後でやる clc.BlendMode = CubismParameterBlendMode.Override; clc.Target = clt; clc.Center = cocoModel.Parts.FindById("Part5").transform; //AnimationClipに指定したイベントを実装したクラスを追加 CocoAnimationEvents cae = cocoModel.gameObject.AddComponent<CocoAnimationEvents>(); cae.seSource = seSource; cae.walkSE = walkSE; }
着替えの実装
Live2Dには動的に着替えを実装する方法が存在しない様なので、基本的には衣装ごとに別モデルを用意する事になります。しかしこれだとモデルに修正が入った場合に全衣装モデル分の修正が発生するため、非常に勝手が悪いです。なのでBuiltinLoadAssetAtPath()の実装を少しいじって、モデルはデフォルト衣装の分だけ用意すれば良いようにしてみました。
public object BuiltinLoadAssetAtPath(Type assetType, string absolutePath) { if (assetType == typeof(byte[])) { return File.ReadAllBytes(absolutePath); } else if (assetType == typeof(string)) { return File.ReadAllText(absolutePath); } else if (assetType == typeof(Texture2D)) { var texture = new Texture2D(1, 1); texture.LoadImage(File.ReadAllBytes(absolutePath.Replace(defaultCostume,nowCostume))); return texture; } throw new NotSupportedException(); }
このハンドラのstaticを外して(多分問題ないと思います)、texture.LoadImage()内のパス指定部分を変更します。これで、absolutePathがテクスチャ読み込み時のみ、
[デフォルトの衣装名]/[デフォルトの衣装名.1024]/texture_00.png
だったのが
[変更後の衣装名]/[変更後の衣装名.1024]/texture_00.png
に置換される様になりました。
新しい衣装を作る手順
新しい衣装画像を作って着替えとして実装するまでの手順は以下の様な感じです。
- デフォルト衣装のモデルを作る際に、着替えの衣装画像がはみ出さない様にアートメッシュを大きめに取っておく。テクスチャアトラスにもスペースに余裕をもたせるように気をつける。
- デフォルト衣装のテクスチャとなった最終的なpsdファイル(またはそれの出力元になったクリスタ等のファイル)Aを開く。
- その画像を作る元となった、手足レイヤーを左右別に分けたり、裸レイヤと衣装レイヤと統合したりする前のファイルBを開く。
- ファイルBの裸レイヤと衣装レイヤを適宜薄く表示して、「裸レイヤに沿った形状」で「デフォルト衣装から余り大きくなりすぎない」感じに、対称定規を適宜使いながら衣装を描いていく。
- ファイルAの各レイヤに適宜新衣装の画像を切り分けて上書きする。ファイルAのレイヤ名は変更しないように気を付ける。
- デフォルト衣装のモデルにPSDファイルを差し替えさせる。※衣装によってはデフォルト衣装で存在した部位が完全に透明に消滅している事がありますが(リボンがあるデフォルト衣装に対しリボン無し衣装など)、その場合テクスチャアトラスには画像差し替え後もリボン画像が残り、リボン画像が割り当てられたリボンレイヤもモデルにそのまま残り、透明な未割り当てのリボンレイヤがモデルに追加されます。その場合は、余計な修正はせずに、モデル書き出し時に「未使用レイヤを破棄」してテクスチャ画像からリボン部分を手作業で消してしまった方が確実だと思います。
- 新衣装のフォルダ(テクスチャフォルダだけが入ってる)をStreamingAssetsフォルダ配下に配置します。この際、前述したパスの置換で参照できるように、デフォルトモデルのフォルダ構成と全く同じ様にする必要があります。※StreamingAssets配下に直接新衣装のフォルダを入れるとテクスチャファイルのアイコンがエクスプローラでの画像ファイルのアイコンとして表示されてしまいますが、特に問題はありません。
- ScriptableObjectを継承したCostumeクラスを作って、パス参照用のモデル名、ゲーム用の表示名、価格、などを管理しやすくします。CostumeをAssets>Createから作成できる様にし、Costumeデータを作成していきます。作ったCostumeファイルはResourcesフォルダに入れて、Listに読み出す様にすると便利に扱えます。
- 着替えの時に初期化処理が入りもたつくので、演出で隠すようにします。
着替え、テクスチャの差分だけでいけたー!
— りゅーあん (@ryuan_p) February 12, 2020
モデル指定するパスをテクスチャ読み込みの時だけ置換するだけで良かった pic.twitter.com/V6jzLf2YMI
コメント
[…] 前回の記事では、UnityでLive2Dモデルをスクリプトから動的にロードする方法について紹介しましたが、なぜかAndroid実機で確認するとモデルの読み込みが失敗してしまいます。monitor.batで […]