Live2D
參考鏈接:SIKI學院
一、前言:
1/ 官網:www.live2d.com
2/ 下載
需求下載1:Cubism SDK for Unity下載地址:https://www.live2d.com/download/cubism-sdk/
需求下載2:Cubism下載地址:https://www.live2d.com/download/cubism/
Live2D Cubism:美術用的2D建模軟件,下載好後安裝,打開可以免費使用一段時間的專業版。
需求下載3:模型數據下載:https://www.live2d.com/download/sample-data/
3/ 靜態變動態原理
在圖上添加一些點,通過這些點來扭曲圖片,通過障眼法表現出動來動去的感覺。相當於在操作一些圖層。
4/ 軟件版本
2.x用的較多,3.x最新版。本例使用2.x版本。
二、操作
解壓下載好的sdk2.x Live2D_SDK_Unity_2.1.04_2_jp,得到如下目錄:
- framework框架代碼
- lib用到的庫
- sample文件夾下爲案例功能,可以分別打開看下玩下
- tool工具
- ReadMe.txt
1.體驗
分別打開上sample文件夾下工程,體驗下。
2.嘗試
1/ 導入模型數據:
把上面下載好的**[需求下載3]模版數據(如“miku_sample.cmox”)拖入Cubism Editor[需求下載2]**中,這個文件Unity無法使用,需要使用Cubism導出moc文件。
加載好後,可以看到:
2/ 導出moc:
菜單File-Export For Runtime-Export as moc file(for 2.1)根據自己的sdk版本選擇導出2.x或者3.x。
注:若菜單是暗的不能點,則說明是免費版,沒有這個功能。
彈出菜單使用默認選項,然後導出一個moc文件,正是我們所需要的,放在原來的模型文件夾下,和.cmox文件同級。
3/ Unity中查看下載的模型的效果
-
打開上sample文件夾下simple工程,打開Sample場景;
-
把整個模型文件夾拖入unity項目,然後複製一份模型文件夾下最外層的.moc文件,並修改.moc爲.bytes;
-
然後修改Sample場景下的Live2DModel物體上SimpleModel組件的值,mocFile拖入剛剛新的moc的bytes文件,TextureFiles拖入模型的貼圖。
-
運行遊戲,發現新的模型已經正確顯示在場景中了。
3.使用
此處引用老師給的筆記:
Live2D模型製作到顯示的基本流程:
1.初始化模型。
1.製作模型(讀取moc文件)。
2.與貼圖建立關聯。
3.與繪圖環境建立鏈接。(有些平臺不需要)。
4.制定顯示位置與尺寸。
2.更新模型狀態。
1.更新頂點。
2.繪圖。
1/ 創建新的工程,(本人版本Unity2017.4.29),拖入上sdk解壓得到的這幾個文件夾:
framework、lib、tool;
2/ 在[模型下載3]的鏈接裏下載你想要的模型,此處使用"Epsilon",拖到項目裏的Resources文件夾下,並新增Live2dModel.cs添加下列代碼。
3/ 使用Live2D前需要初始化:
using live2d;
//初始化
Live2D.init();
4/ 讀取模型的兩種方式:
//方式1:直接使用runtime文件夾下moc文件
Live2DModelUnity.loadModel(Application.dataPath+ "/Resources/Epsilon/runtime/Epsilon.moc");
//方式2:加載二進制文件並讀取:
//先複製上moc文件,並添加後綴.bytes,文件可放在Resources文件夾下然後加載:
TextAsset mocFile = Resources.Load<TextAsset>("Epsilon/runtime/Epsilon.moc");
Live2DModelUnity live2DModel=Live2DModelUnity.loadModel(mocFile.bytes);
//也可以直接public TextAsset mocFile這樣開放出來,然後直接拖入bytes文件到組件上。
5/ 與貼圖建立關聯(假設已經使用上條的方式2獲得了live2DModel變量)
//方式1:根據路徑
//Texture2D texture2D1 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_00");
//Texture2D texture2D2 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_01");
//Texture2D texture2D3 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_02");
//live2DModel.setTexture(0,texture2D1); //第一個參數爲貼圖順序索引
//live2DModel.setTexture(1, texture2D2);
//live2DModel.setTexture(2, texture2D3);
//方式2:拖入組件public Texture2D[] textures;
for (int i = 0; i < textures.Length; i++)
{
live2DModel.setTexture(i, textures[i]);
}
6/ 指定顯示位置與尺寸(使用正交矩陣與相關API顯示圖像,再由遊戲物體的位置和攝像機的size調整圖像到合適的位置
private Matrix4x4 live2DCanvasPos;
//live2d自身的畫布
float modelWidth = live2DModel.getCanvasWidth();
//創建正交投影矩陣,參數爲正交視口的左,右,下,上,近視口距離,遠視口距離
live2DCanvasPos = Matrix4x4.Ortho(0, modelWidth, modelWidth, 0, -50, 50);
7/ 更新模型狀態:更新頂點
void Update () {
live2DModel.setMatrix(transform.localToWorldMatrix * live2DCanvasPos);
live2DModel.update();
}
8/ 更新模型狀態:繪圖
private void OnRenderObject()
{
live2DModel.draw();
}
然後這個腳本掛在場景中的一個物體上,運行遊戲,可以看到小姐姐的靜態模型了。可以調整攝像機參數控制看到的小姐姐的大小。
下面看動作播放:
動作文件爲runtime/motions文件夾下的mtn文件。
9/ 播放動作:加載動作文件
//方式1:直接加載mtn文件:
//Live2DMotion.loadMotion(Application.dataPath+"路徑");
//方式2:加載bytes文件,加載或者直接拖進組件(同樣,把mtn文件複製一份加.bytes後綴)
//Live2DMotion.loadMotion(mtnFile.bytes);
public TextAsset[] motionFiles;
private Live2DMotion[] motions;
motions = new Live2DMotion[motionFiles.Length];
for (int i = 0; i < motions.Length; i++)
{
motions[i] = Live2DMotion.loadMotion(motionFiles[i].bytes);
}
10/ 設置某一個動畫的一些屬性
motions[0].setLoopFadeIn(false);//重複播放不淡入。
motions[0].setFadeOut(1000); //設置淡入淡出時間,參數單位爲毫秒
motions[0].setFadeIn(1000);
//motions[0].setLoop(true); //動畫是否循環播放
11/ 播放動作
//進行完上述操作後,播放動作:
//動作管理
private MotionQueueManager motionQueueManager;
motionQueueManager = new MotionQueueManager();
motionQueueManager.startMotion(motions[0]);
在Update中播放動作:
void Update () {
live2DModel.setMatrix(transform.localToWorldMatrix * live2DCanvasPos);
//使這個模型播放動作
motionQueueManager.updateParam(live2DModel);
live2DModel.update();
}
動作的bytes拖進去後,發現可以播放了。
12/ 同時播放多個動作(如臉部和身體動作拆開了,需要自由組合播放)
//有幾個動作同時播放,就需要幾個MotionQueueManager
//在上面的步驟11基礎上,再同時播放動作5:
////播放多個動作
//motions[5].setLoop(true);
//private MotionQueueManager motionQueueManagerA;//在函數外部加上這個
motionQueueManagerA = new MotionQueueManager();
motionQueueManagerA.startMotion(motions[5]);
void Update () {
live2DModel.setMatrix(transform.localToWorldMatrix * live2DCanvasPos);
motionQueueManager.updateParam(live2DModel);
motionQueueManagerA.updateParam(live2DModel);//加上這句
live2DModel.update();
}
注意:如果需要多個動作同時播放,儘可能不要設置相同的參數。
13/ 動作的優先級
//優先級
//L2DMotionManager繼承自MotionQueueManager
//優先級的設置標準:
//1.動作未進行的狀態,優先級爲0。
//2.待機動作發生時,優先級爲1。
//3.其他動作進行時,優先級爲2。
//4.無視優先級,強制發生的動作,優先級爲3。
//上述代碼爲測試直接播放某個動作,下面開始把動作串起來
//初始默認播放待機動作,然後有事件過來播放其他動作時,根據優先級播放其他動作,結束了返回默認動作
private L2DMotionManager l2DMotionManager;//目前使用的動作管理器
//初始化 動作的優先級使用:
l2DMotionManager = new L2DMotionManager();
//根據動作優先級播放動作接口:
private void StartMotion(int motionIndex,int priority)
{
if (l2DMotionManager.getCurrentPriority()>= priority)
{
return;
}
l2DMotionManager.startMotion(motions[motionIndex]);
}
//然後在Update中判斷是否有正在播放的動畫,沒有則播放待機
void Update () {
live2DModel.setMatrix(transform.localToWorldMatrix*live2DCanvasPos);
判斷待機動作
if (l2DMotionManager.isFinished())
{
StartMotion(0,1);
}
else if (Input.GetKeyDown(KeyCode.M))//測試高優先級打斷
{
StartMotion(14,2);
}
l2DMotionManager.updateParam(live2DModel);
}
14/ 設置參數
//如圖所示 中間欄下方可以通過拖動來改變參數從而改變模型的顯示效果,在Unity裏也可以通過代碼控制
//設置參數,paramID獲得方式見下圖(2.x版本需要模型源文件,不然不知道參數id,需要建模師給到配置表;3.x版本可以直接設置)
//在Update中設置。
//方式1:live2DModel.setParamFloat(string paramID, float value, float weight = 1影響度);//設置爲指定值
//如: live2DModel.setParamFloat("PARAM_ANGLE_X",1);
//方式2:live2DModel.addToParamFloat(string paramID, float value);//當前值累加
//方式3:live2DModel.multParamFloat(string paramID, float value);//當前值擴大倍數
//上幾個函數都有重載,參數id可以傳入索引int值,其中這個索引可以通過這個函數獲取
int paramAngleX = live2DModel.getParamIndex("PARAM_ANGLE_X");
live2DModel.setParamFloat(paramAngleX,30);
//參數的保存與恢復
//保存與恢復的參數是整個模型的所有參數,並不只是之前同方法裏設置的某幾個參數
//live2DModel.saveParam();
//live2DModel.loadParam();
15/ 設置模型某一部分的不透明度
//live2DModel.setPartsOpacity(string partId, 0);
//partId獲得方式見下圖,要使用整個文件夾的id,不要選擇文件夾裏小部件。
16/ 自動眨眼
private EyeBlinkMotion eyeBlinkMotion;
//初始化
eyeBlinkMotion = new EyeBlinkMotion();
在Update中調用:
eyeBlinkMotion.setParam(live2DModel);
17/ 模型跟隨鼠標轉向與看向
private L2DTargetPoint drag;
//初始化
drag = new L2DTargetPoint();
//在Update中:
//得到的Live2d鼠標檢測點的比例值是-1到1(對應一個live2d拖拽
//管理座標系,或者叫做影響度。)
//然後我們通過這個值去設置我們的參數,比如旋轉30度*當前得到的值
//就會按照這個值所帶來的影響度去影響我們的模型動作
//從而到達看向某一個點的位置
Vector3 pos = Input.mousePosition;//屏幕座標
if (Input.GetMouseButton(0))
{
drag.Set(pos.x/Screen.width*2-1,pos.y/Screen.height*2-1);
}
else if (Input.GetMouseButtonUp(0))
{
drag.Set(0, 0);
}
//參數及時更新,考慮加速度等自然因素,計算座標,進行逐幀更新。
drag.update();
//模型轉向
if (drag.getX()!=0)
{
live2DModel.setParamFloat("PARAM_ANGLE_X",30*drag.getX());
live2DModel.setParamFloat("PARAM_ANGLE_Y", 30 * drag.getY());
live2DModel.setParamFloat("PARAM_BODY_ANGLE_X", 10 * drag.getX());
live2DModel.setParamFloat("PARAM_EYE_BALL_X",drag.getX());
//這裏的第二個參數和下一行的第二個參數都取反,則模型身體扭動,但眼睛會盯着屏幕看。
live2DModel.setParamFloat("PARAM_EYE_BALL_Y",drag.getY());
}
18/ 套用物理運算去設置頭髮的長度重量與受到的空氣阻力
//物理運算的設定
private PhysicsHair physicsHairSideLeft;
private PhysicsHair physicsHairSideRight;
private PhysicsHair physicsHairBackLeft;
private PhysicsHair physicsHairBackRight;
#region 左右兩側頭髮的搖擺
//左測旁邊的頭髮
physicsHairSideLeft = new PhysicsHair();
//套用物理運算
physicsHairSideLeft.setup(0.2f, // 長度 : 單位是公尺 影響搖擺週期(快慢)
0.5f, // 空氣阻力 : 可設定0~1的值、預設值是0.5 影響搖擺衰減的速度
0.14f); // 質量 : 單位是kg
//設置輸入參數
//設置哪一個部分變動時進行哪一種物理運算
PhysicsHair.Src srcXLeft = PhysicsHair.Src.SRC_TO_X;//橫向搖擺
//第三個參數,"PARAM_ANGLE_X"變動時頭髮受到0.005倍的影響度的輸入參數
physicsHairSideLeft.addSrcParam(srcXLeft, "PARAM_ANGLE_X",0.005f,1);
//設置輸出表現
PhysicsHair.Target target = PhysicsHair.Target.TARGET_FROM_ANGLE;//表現形式
physicsHairSideLeft.addTargetParam(target, "PARAM_HAIR_SIDE_L",0.005f,1);
//右側旁邊的頭髮
physicsHairSideRight = new PhysicsHair();
//套用物理運算
physicsHairSideRight.setup(0.2f, // 長度 : 單位是公尺 影響搖擺週期(快慢)
0.5f, // 空氣阻力 : 可設定0~1的值、預設值是0.5 影響搖擺衰減的速度
0.14f); // 質量 : 單位是kg
//設置輸入參數
//設置哪一個部分變動時進行哪一種物理運算
PhysicsHair.Src srcXRight = PhysicsHair.Src.SRC_TO_X;//橫向搖擺
//PhysicsHair.Src srcXRight = PhysicsHair.Src.SRC_TO_Y;
//第三個參數,"PARAM_ANGLE_X"變動時頭髮受到0.005倍的影響度的輸入參數
physicsHairSideRight.addSrcParam(srcXRight, "PARAM_ANGLE_X", 0.005f, 1);
//設置輸出表現
PhysicsHair.Target targetRight = PhysicsHair.Target.TARGET_FROM_ANGLE;//表現形式
physicsHairSideRight.addTargetParam(targetRight, "PARAM_HAIR_SIDE_R",0.005f,1);
#endregion
#region 左右後邊頭髮的搖擺
//左邊
physicsHairBackLeft = new PhysicsHair();
physicsHairBackLeft.setup(0.24f, 0.5f, 0.18f);
PhysicsHair.Src srcXBackLeft = PhysicsHair.Src.SRC_TO_X;
PhysicsHair.Src srcZBackLeft = PhysicsHair.Src.SRC_TO_G_ANGLE;
physicsHairBackLeft.addSrcParam(srcXBackLeft, "PARAM_ANGLE_X",0.005f,1);
physicsHairBackLeft.addSrcParam(srcZBackLeft, "PARAM_ANGLE_Z",0.8f,1);
PhysicsHair.Target targetBackLeft = PhysicsHair.Target.TARGET_FROM_ANGLE;
physicsHairBackLeft.addTargetParam(targetBackLeft, "PARAM_HAIR_BACK_L", 0.005f, 1);
//右邊
physicsHairBackRight = new PhysicsHair();
physicsHairBackRight.setup(0.24f, 0.5f, 0.18f);
PhysicsHair.Src srcXBackRight = PhysicsHair.Src.SRC_TO_X;
PhysicsHair.Src srcZBackRight = PhysicsHair.Src.SRC_TO_G_ANGLE;
physicsHairBackRight.addSrcParam(srcXBackRight, "PARAM_ANGLE_X", 0.005f, 1);
physicsHairBackRight.addSrcParam(srcZBackRight, "PARAM_ANGLE_Z", 0.8f, 1);
PhysicsHair.Target targetBackRight = PhysicsHair.Target.TARGET_FROM_ANGLE;
physicsHairBackRight.addTargetParam(targetBackRight, "PARAM_HAIR_BACK_R", 0.005f, 1);
#endregion
//在Update中:
long time = UtSystem.getUserTimeMSec();//執行時間
physicsHairSideLeft.update(live2DModel,time);
physicsHairSideRight.update(live2DModel,time);
physicsHairBackLeft.update(live2DModel, time);
physicsHairBackRight.update(live2DModel,time);
19/ 表情系統(特殊的動作)
public TextAsset[] expressionFiles;
public L2DExpressionMotion[] expressions;
private MotionQueueManager expresionMotionQueueManager;
public int motionIndex;
//初始化
expresionMotionQueueManager = new MotionQueueManager();
expressions = new L2DExpressionMotion[expressionFiles.Length];
for (int i = 0; i < expressions.Length; i++)
{
expressions[i] = L2DExpressionMotion.loadJson(expressionFiles[i].bytes);
}
在Update中加入測試表情:
if (Input.GetKeyDown(KeyCode.M))
{
motionIndex++;
if (motionIndex >= expressions.Length)
{
motionIndex = 0;
}
expresionMotionQueueManager.startMotion(expressions[motionIndex]);
}
expresionMotionQueueManager.updateParam(live2DModel);
//可以看到使用方式和普通動作是一樣的,只是
//Live2DMotion <-> L2DExpressionMotion,都繼承自AMotion
4.官方demo中的框架
官方sdk2.x中的demo:SampleApp1中使用的框架,可以按照這個來;這裏提取出需要的,打一個package,內容如下:
鏈接:https://pan.baidu.com/s/13MLVGX-CwmTjN9ENJ600ew
在導入sdk2.x的基礎上,再導入這個包。
使用:(此處使用haru模型資源,見共享鏈接裏)
新建個空物體,掛上MeshFilter組件,拖入Live2D_Canvas資源;
掛上LAppModelProxy組件,Path填寫模型json文件在Resources下相對地址,包含後綴名;
掛上MyGameController組件,運行。
可根據需求修改。(如關閉運行時日誌LAppDefine.DEBUG_LOG)
/*
*開始運動。
*檢查您是否可以播放,如果不能這樣做,則無所事事。
*如果您可以自動播放,請閱讀文件並進行播放。
*如果它有聲音,它也會播放。
*如果您有關於淡入和淡出的信息,請在此處設置。 如果沒有初始值。
*group:動作名稱,如下圖紅框,見模型的json文件
*no:索引,0開始
*/
lAppModelProxy.GetModel().StartMotion(string group, int no, int priority);
如:lAppModelProxy.GetModel().StartMotion("tap_body",0,2);
//播放表情
//name爲上圖json中的"expressions"裏的name,如“f01”
lAppModelProxy.GetModel().SetExpression(string name)
//顯隱
lAppModelProxy.SetVisible(true);
//換裝(需要多張圖的衣服部件的佈局一致)
Live2DModelUnity live2DModelUnity = lAppModelProxy.GetModel().GetLive2DModelUnity();
live2DModelUnity.setTexture(int textureNo, Texture2D texture);
5.3.x版本的更新
1/ 動作參數和部位的不透明度參數都可視化了,可以直接在unity面板上調整。
2/ Cubism導出3.x的模型數據時,放到unity裏面可以看到直接是一個可識別的預製文件,可以直接拖到場景使用。
3/ 動畫製作,直接用Animation。
6.網上的破解資源的處理
文件用記事本打開,可以打開的話,根據內容修改文件名和後綴,後綴一般.txt
圖和moc文件一般都是png和moc,不用修改後綴。
若文本內容爲:{“type”:“Live2D Expression”,表示這是個表情;
若文本內容爲:# Live2D Animator Motion Data,表示這是個動作文件;
若文本內容爲:{“version”:"…",“model”:…,“textures”:…,表示這是個模型json文件,根據裏面內容,修改外面各文件的名稱和相對路徑。
然後這個文件夾可以放在項目裏測試使用了。
所需要的資源及2.x版本sdk:
鏈接:SIKI學院
https://pan.baidu.com/s/1KHeuxvdZyYjQPocaveYjbw 提取碼:d5qn