参考链接: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,得到如下目录:
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("Epsilon/runtime/Epsilon.moc");
Live2DModelUnity live2DModel=Live2DModelUnity.loadModel(mocFile.bytes);
//也可以直接public TextAsset mocFile这样开放出来,然后直接拖入bytes文件到组件上。
5/ 与贴图建立关联(假设已经使用上条的方式2获得了live2DModel变量)
//方式1:根据路径
//Texture2D texture2D1 = Resources.Load("Epsilon/runtime/Epsilon.1024/texture_00");
//Texture2D texture2D2 = Resources.Load("Epsilon/runtime/Epsilon.1024/texture_01");
//Texture2D texture2D3 = Resources.Load("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