目录
首先导入Live2D SDK2.1里面的一些包
Live2D需要初始化代码,只有Live2D初始化之后才可以使用里面的函数/API
写在:Live2DModel.cs
初始化live2D
首先要导入live2D命名空间:
using live2d;
如果想要使用Lived2D里面的API以及模型的创建,需要在开始使用Lived2D前至少调用一次,一次都没有调用或者连续调用都可能会导致执行程序不稳定,相当于是引入了一个大环境
Live2D.init();
全部运行结束后,可以释放Live2D占用的全部资源(没有初始化过就不能释放!!!!)
Live2D.dispose();
我们本程序中会一直使用Live2D的环境,所以不用释放,注释掉这一句
Live2DModelUnity:这个 类专门负责Unity加载和读取Live2D的模型
Live2DModelUnity.loadModel(,)
在括号里打逗号,按动键盘的上下键,可以看API种有哪几种重载(官方推荐的一种是二进制的形式进行加载,一种是直接给他一个MOC文件的路径,从而直接读取moc文件)
模型载入(读取)[写在Start()里]
载入方法一:
我们可以把Epsilon文件夹放在Resources文件夹下,这样之后可以直接用.load方法加载
Live2DModelUnity.loadModel(Application.dataPath + "Resources/Epsilon/runtime/Epsilon.moc");
Application.dataPath默认的是Assets根目录(记得要写.moc!!!!!)
之后我们返回Unity创建一个空物体,命名为:Live2DModle,并将脚本挂载上去
显示加载失败
我们发现少了个反斜杠在这里,修改后:
Live2DModelUnity.loadModel(Application.dataPath + "/Resources/Epsilon/runtime/Epsilon.moc");
载入方法二:
把.moc文件复制一份,并在后面加上.bytes
这是个文本类型的文件,我们要用TextAsset来接收它:
TextAsset mocFile = Resources.Load<TextAsset>("Epsilon/runtime/Epsilon.moc");
Live2DModelUnity.loadModel(modelFile.bytes);
最后,为了方便赋值,我们可以在类中定义一个共有类型变量,便于我们拖拽赋值
public TextAsset modelFile;
给模型赋贴图
我们得明确具体是给哪个模型贴图,模型文件加载完之后,实际上是有一个返回值的,其类型是Live2DModelUnity(鼠标放在函数上就可以看到)
,我们可以设置他的位置,贴图,甚至更新他的一些参数,所以模型变量我们要先取到
1、定义出来
private Live2DModelUnity live2DModel;
2、将之前读取的模型文件赋值给live2DModel[在Start()里]
live2DModel=Live2DModelUnity.loadModel(modelFile.bytes);
3、加载贴图(只有通过路径建立关联)[在Start()里]
Texture2D texture2D0 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_00");
Texture2D texture2D1 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_01");
Texture2D texture2D2 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_02");
live2DModel.setTexture(0, texture2D0);
live2DModel.setTexture(1, texture2D1);
live2DModel.setTexture(2, texture2D2);
当然,这样用代码查找之后关联看起来会很繁琐,我们也可以定义共有变量数组textures
public Texture2D[] textures;
并编写代码,使其通过拖拽赋值:(for循环写了for之后按两下Tab Tab可自动帮忙补全)
for (int i = 0; i < textures.Length; i++)
{
live2DModel.setTexture(i, textures[i]);
}
到这一步还不能正常看到图形,因为图形比例太大了,我们得设置矩阵的行列位置,矩阵使用上代码基本是固定的
设置矩阵
1、定义画布位置
private Matrix4x4 live2DCanvasPos;
2、给4*4矩阵初始化
//指定显示位置与尺寸(固定写法)
float modelWidth = live2DModel.getCanvasWidth();
live2DCanvasPos = Matrix4x4.Ortho(0, modelWidth, modelWidth, 0, -50, 50);
3、 把当前的矩阵设置给我们当前的模型[在Update()里完成]
live2DModel.setMatrix(transform.localToWorldMatrix * live2DCanvasPos);
4、更新模型定点信息或参数信息 [在Update()里完成]
live2DModel.update();
5、绘图,绘图方法放在一个固定的生命周期函数中OnRenderObject()[Unity中固定的生命周期函数]
private void OnRenderObject()
{
live2DModel.draw();
}
把相机设置成正交相机
我们可以通过相机的size值调整视图中小姐姐的大小
动画(动作)设置
动画不是自己去播放的,是动作管理者(MotionQueueManager)播放的
//设置某些动画的属性
//重复播放不淡入
motions[0].setLoopFadeIn(false);
//设置淡入淡出时间(以毫秒为单位计算)
motions[0].setFadeOut(1000);
motions[0].setFadeIn(1000);
//动画是否循环播放
motions[0].setLoop(true);
1、定义出动作管理者
private MotionQueueManager motionQueueManager;
2、在Start()函数里初始化动作管理者
motionQueueManager = new MotionQueueManager();
3、在Start()函数里,告诉动作管理者播放哪个动画
motionQueueManagerA.startMotion(motions[0]);
4、动作的播放对应于某一个模型,要将当前设定的参数告诉哪一个模型,在Update()中,每个动作都要更新到对应的模型中身上
motionQueueManager.updateParam(live2DModel);
注意:动作文件也不可以直接使用.mtn文件,得转换成.bytes文件,像之前一样,CtrlC+CtrlV创建副本,再将副本加后缀.bytes
键入M切换动作代码:[写在Update()中]
添加一个动作索引:
public int motionIndex;
if (Input.GetKeyDown(KeyCode.M))
{
motionIndex++;
if (motionIndex>=motions.Length)
{
motionIndex = 0;
}
motionQueueManager.startMotion(motions[motionIndex]);
}
两个动作同时播放
如果要两个动画同时播放时,尽量不要让两个参数同时改变(比如不要同时改变嘴的形状)
需要几个动作同时播放就需要几个动作管理者,如:
1、定义出动作管理者
private MotionQueueManager motionQueueManagerA;
2、在Start()函数里初始化动作管理者
motionQueueManagerA = new MotionQueueManager();
3、告诉动作管理者播放哪个动画
motionQueueManagerA.startMotion(motions[5]);
4、与之前相同,在Update()中,每个动作都要更新到对应的模型中身上
motionQueueManagerA.updateParam(live2DModel);
动作优先级设置
l2DMotionManager继承自MotionQueueManager,其实内容和MotionQueueManager差不多,就是加了一些优先级设置的东西
优先级设置标准:
1、动作未进行时的状态,优先级为0
2、待机状态发生时,优先级为1
3、其他动作发生时,优先级为2
4、无视优先级,强制发生的动作,优先级为3
使用方法:
1、初始化[在Start()里完成]
l2DMotionManager = new L2DMotionManager();
2、封装一个播放动作的优先级方法,如果正在播放的动画比新播放的动画优先级高或者优先级相同,则播放当前优先级的动画,反之播放新的动画
private void StartMotion(int motionIndex,int priority)
{
//得到正在播放的动画的优先级
if (l2DMotionManager.getCurrentPriority()>=priority)
{
return;
}
l2DMotionManager.startMotion(motions[motionIndex]);
}
3、判断待机动作(当前是否有动画正在播放)并调用我们封装的播放动画的函数[在Update()里完成]
if (l2DMotionManager.isFinished())
{
StartMotion(0, 1);
}
else if (Input.GetKeyDown(KeyCode.M))
{
StartMotion(14, 2);
}
4、我们要在每次动画播放完毕时,得更新一下动作数据
l2DMotionManager.updateParam(live2DModel);
现在小姐姐按下M键会改变动作,之后又会恢复成Idle动作!之前不会恢复!!!!
所有代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using live2d;
using live2d.framework;
public class Live2DModel : MonoBehaviour
{
//模型显示
public TextAsset modelFile;
public Texture2D[] textures;
private Live2DModelUnity live2DModel;
private Matrix4x4 live2DCanvasPos;
//动作播放
public TextAsset[] motionFiles;
private Live2DMotion[] motions;
//动作不是自己播放的,是由一个动作管理控制播放的
private MotionQueueManager motionQueueManager;
private MotionQueueManager motionQueueManagerA;
//private Live2DMotion live2DMontionIdle;
//优先级设置标准:
//1.动作未进行时的状态,优先级为0
//2.待机状态发生时,优先级为1
//3.其他动作发生时,优先级为2
//4.无视优先级,强制发生的动作,优先级为3
private L2DMotionManager l2DMotionManager;
public int motionIndex;
// Start is called before the first frame update
void Start()
{
//初始化
Live2D.init();
//释放
//Live2D.dispose();
//读取模型
//Live2DModelUnity.loadModel(Application.dataPath + "/Resources/Epsilon/runtime/Epsilon.moc");
//TextAsset mocFile = Resources.Load<TextAsset>("Epsilon/runtime/Epsilon.moc");
live2DModel=Live2DModelUnity.loadModel(modelFile.bytes);
//与贴图建立关联(只有通过路径建立关联)
//Texture2D texture2D0 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_00");
//Texture2D texture2D1 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_01");
//Texture2D texture2D2 = Resources.Load<Texture2D>("Epsilon/runtime/Epsilon.1024/texture_02");
//live2DModel.setTexture(0, texture2D0);
//live2DModel.setTexture(1, texture2D1);
//live2DModel.setTexture(2, texture2D2);
for (int i = 0; i < textures.Length; i++)
{
live2DModel.setTexture(i, textures[i]);
}
//指定显示位置与尺寸(固定写法)
float modelWidth = live2DModel.getCanvasWidth();
//创建正交视口,前四个参数代表正交视口的左右下上
live2DCanvasPos = Matrix4x4.Ortho(0, modelWidth, modelWidth, 0, -50, 50);
//播放动作
//实例化动作对象
//live2DMontionIdle = Live2DMotion.loadMotion(Application.dataPath + "");
//TextAsset mtnFile = Resources.Load<TextAsset>("");
//live2DMontionIdle = Live2DMotion.loadMotion(mtnFile.bytes);
motions = new Live2DMotion[motionFiles.Length];
for (int i = 0; i < motions.Length; i++)
{
motions[i] = Live2DMotion.loadMotion(motionFiles[i].bytes);
}
//设置某些动画的属性
//重复播放不淡入
//motions[0].setLoopFadeIn(false);
//设置淡入淡出时间(以毫秒为单位计算)
//motions[0].setFadeOut(1000);
//motions[0].setFadeIn(1000);
//动画是否循环播放
//motions[0].setLoop(true);
//motionQueueManager = new MotionQueueManager();
//motionQueueManager.startMotion(motions[0]);
//motionQueueManagerA = new MotionQueueManager();
//motionQueueManagerA.startMotion(motions[5]);
//动作的优先级使用
l2DMotionManager = new L2DMotionManager();
}
// Update is called once per frame
void Update()
{
//设置当前模型的位置
live2DModel.setMatrix(transform.localToWorldMatrix * live2DCanvasPos);
//if (Input.GetKeyDown(KeyCode.M))
//{
// motionIndex++;
// if (motionIndex >= motions.Length)
// {
// motionIndex = 0;
// }
// motionQueueManager.startMotion(motions[motionIndex]);
//}
//motionQueueManager.updateParam(live2DModel);
//motionQueueManagerA.updateParam(live2DModel);
//判断待机动作(当前是否有动画正在播放)
if (l2DMotionManager.isFinished())
{
StartMotion(0, 1);
}
else if (Input.GetKeyDown(KeyCode.M))
{
StartMotion(14, 2);
}
l2DMotionManager.updateParam(live2DModel);
live2DModel.update();
}
//生命周期函数
private void OnRenderObject()
{
live2DModel.draw();
}
//封装一个播放动作的优先级方法
private void StartMotion(int motionIndex,int priority)
{
//得到正在播放的动画的优先级
if (l2DMotionManager.getCurrentPriority()>=priority)
{
return;
}
l2DMotionManager.startMotion(motions[motionIndex]);
}
}