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文件。

加载好后,可以看到:

unity 2d global valume 会 影响 底图_2d

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 2d global valume 会 影响 底图_2d

//如图所示 中间栏下方可以通过拖动来改变参数从而改变模型的显示效果,在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();

unity 2d global valume 会 影响 底图_优先级_03

15/ 设置模型某一部分的不透明度

//live2DModel.setPartsOpacity(string partId, 0); 
//partId获得方式见下图,要使用整个文件夹的id,不要选择文件夹里小部件。

unity 2d global valume 会 影响 底图_ide_04

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,内容如下:

unity 2d global valume 会 影响 底图_2d_05

链接:https://pan.baidu.com/s/13MLVGX-CwmTjN9ENJ600ew

在导入sdk2.x的基础上,再导入这个包。

使用:(此处使用haru模型资源,见共享链接里)

新建个空物体,挂上MeshFilter组件,拖入Live2D_Canvas资源;
挂上LAppModelProxy组件,Path填写模型json文件在Resources下相对地址,包含后缀名;
挂上MyGameController组件,运行。

unity 2d global valume 会 影响 底图_优先级_06

可根据需求修改。(如关闭运行时日志LAppDefine.DEBUG_LOG)

/*
  *开始运动。
  *检查您是否可以播放,如果不能这样做,则无所事事。
  *如果您可以自动播放,请阅读文件并进行播放。
  *如果它有声音,它也会播放。
  *如果您有关于淡入和淡出的信息,请在此处设置。 如果没有初始值。
  *group:动作名称,如下图红框,见模型的json文件
  *no:索引,0开始
  */
lAppModelProxy.GetModel().StartMotion(string group, int no, int priority);
如:lAppModelProxy.GetModel().StartMotion("tap_body",0,2);

unity 2d global valume 会 影响 底图_优先级_07

//播放表情
//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文件,根据里面内容,修改外面各文件的名称和相对路径。