live2d官网:https://www.live2d.com 如果下载的慢文章最后有百度云下载链接
一路next,安装好后会有两个文件:
Live2D Cubism Viewer 4.0(这个是查看模型的软件)
Live2D Cubism Editor 4.0(这个是制作模型的软件)
Live2D Cubism Editor 4.0有pro版和free版,用free版就行
https://www.bilibili.com/video/av73648216?p=1
这个教程是2.0的,2.0导出的是 .moc 文件,而新版的导出的是 .moc3 文件
注意:导出前要 Ctrl + T 再点击ok一下(生成纹理),不然无法导出
导出后是个文件夹,将文件夹中的 .moc3 文件拖入Live2D Cubism Viewer 4.0软件就能查看效果
官方sdk api:https://docs.live2d.com/cubism-sdk-tutorials/sample-build-web/
需要环境:node.js TypeScript Webpack (TypeScript和Webpack安装慢可以使用淘宝镜像)
编辑器打开sdk项目,具体的目录是什么内容可以看项目根目录下的 README.md 文件(windows用户可以使用Typora软件打开.md文件)
想把一些配置放到html中,比如画布(canvas)的大小位置,模型保存的路径等等信息
否则每次需要改变模型的时候都要改代码,重新编译,麻烦
lappdefine.ts //定义基本的参数
lappdelegate.ts //初始化,释放资源,事件绑定
lapplive2dmanager.ts //模型的管理类,进行模型生成和废弃、事件的处理、模型切换。
lappmodel.ts //模型类,定义模型的基本属性
lappal.ts //读取文件,抽象文件数据(算是工具类)
lappsprite.ts //动画精灵类,(学python时知道了精灵类和精灵组)
lapptexturemanager.ts//纹理管理类,进行图像读取和管理的类
lappview.ts //视图类,生成模型的图像被lapplive2dmanager管理
main.ts //主程序启动程序
touchmanager.ts //事件的管理类(比如移动鼠标,点击鼠标,触摸屏触碰等)
这里所有的类都实行单例模式
这里用了flask框架(别问为啥简单方便快)
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=1900">
<title>TypeScript HTML Apptitle>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='live2d/css/live2d.css') }}"/>
<style>
html, body {
margin: 0;
background-color: #22d7dd;
}
style>
<script type="text/javascript" src="{{ url_for('static', filename='live2d/js/jquery.js') }}">script>
<script src="https://unpkg.com/[email protected]/minified.js">script>
<script src="{{ url_for('static', filename='live2d/js/live2dcubismcore.js') }}">script>
<script src="{{ url_for('static', filename='live2d/js/bundle.js') }}">script>
head>
<body>
1234567890
<div class="live2d-main">
<div class="live2d-tips">div>
<canvas id="live2d" width="280" height="250" class="live2d">canvas>
<div class="tool">
<span class="fui-home">span>
<span class="fui-chat">span>
<span class="fui-eye">span>
<span class="fui-user">span>
<span class="fui-photo">span>
<span class="fui-info-circle">span>
<span class="fui-cross">span>
div>
div>
body>
<script src="{{ url_for('static', filename='live2d/js/message.js') }}">script>
<script type="text/javascript">
var resourcesPath = '/live2d/model/'; // 指定资源文件(模型)保存的路径
var backImageName = ''; // 指定背景图片
var modelDir = 'Haru,Hiyori,Mark,Natori,Rice,zwt'; // 指定需要加载的模型
init(); // 初始化模型,属于message.js文件
script>
html>
// 初始化
function init(){
var resourcesPaths = `${resourcesPath}`;
var backImageNames = `${backImageName}`;
var modelDirString = `${modelDir}`;
var modelDirs = modelDirString.split(',');
initDefine(resourcesPaths, backImageNames, modelDirs); // lappdefine.ts开放的接口用于初始化常量被编译到bundle.js文件里
}
// 监听复制(这里简单添加了一些事件,可以添加更多的事件,比如报时等)
(function() {
document.addEventListener('copy',(e)=>{
e.preventDefault();
e.stopPropagation();
showMessage('你都复制了些什么呀,能让我看看吗?', 5000, true); // 显示信息
})
}());
// 工具栏的点击事件
$('.tool .fui-home').click(function (){
});
$('.tool .fui-eye').click(function (){
});
$('.tool .fui-chat').click(function (){
});
$('.tool .fui-user').click(function (){
});
$('.tool .fui-info-circle').click(function (){
});
$('.tool .fui-cross').click(function (){
});
$('.tool .fui-photo').click(function (){
});
function showMessage(text, timeout, flag){
if(flag || sessionStorage.getItem('waifu-text') === '' || sessionStorage.getItem('waifu-text') === null){
if(Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1)-1];
//console.log(text);
if(flag) sessionStorage.setItem('waifu-text', text);
$('.live2d-tips').stop();
$('.live2d-tips').html(text).fadeTo(200, 1);
if (timeout === undefined) timeout = 5000;
hideMessage(timeout);
}
}
function hideMessage(timeout){
$('.live2d-tips').stop().css('opacity',1);
if (timeout === undefined) timeout = 5000;
window.setTimeout(function() {sessionStorage.removeItem('waifu-text')}, timeout);
$('.live2d-tips').delay(timeout).fadeTo(200, 0);
}
由于使用Webpack打包,Typescript文件中的变量和函数被层层括号包围(封装)变成了内部变量和内部函数(具体可以百度Webpack的打包原理),在外部的js文件是调用不到里面的方法的,所以将一些函数或变量挂载到window下,成为全局变量或函数,使外部的js文件也能调用到
在lappdefine.ts文件最后添加
export const win: any = window
win.initDefine=function(resourcesPath: string, backImageName: string, modelDir: string[]){
ResourcesPath = resourcesPath;
BackImageName = backImageName;
ModelDir = modelDir;
ModelDirSize = modelDir.length;
}
这里将initDefine挂载到window下是函数能在外部调用,函数在message.js中调用到
(注意:这里要将ResourcesPath 、BackImageName 、ModelDir 、ModelDirSize变量声明成let属性 const 是常量只允许在声明的时候赋值,并且只能赋值一次)
import { LAppDelegate } from './lappdelegate';
// 浏览器装入后的处理(打开页面)
window.onload = (): void => {
// create the application instance
if (LAppDelegate.getInstance().initialize() == false) {
return;
}
LAppDelegate.getInstance().run();
};
//结束时的处理 (刷新或关闭页面)
window.onbeforeunload = (): void => LAppDelegate.releaseInstance(); //lambda 匿名函数
LAppDelegate.getInstance().initialize() 获得这个类的实例并初始化
public initialize(): boolean {
// 创建画布
// canvas = document.createElement('canvas');
// canvas.width = LAppDefine.RenderTargetWidth;
// canvas.height = LAppDefine.RenderTargetHeight;
// 原来是用js动态在网页上创建画布,画布的长宽在lappdefine.ts指定,现在直接在html中已经有了画布直接拿过来使用就行
canvas = <HTMLCanvasElement>document.getElementById("live2d"); // index.html中的id为live2d的画布
canvas.width = canvas.width;
canvas.height = canvas.height;
canvas.toDataURL("image/png");
// 这个是index.html工具栏中的眼睛图标,点击眼睛图标就切换下一个模型
// 正规来说应该留个切换模型的口子,在message.js中调用,因为懒就直接在这里写了
fui_eye = <HTMLSpanElement>document.getElementsByClassName("fui-eye")[0];
// 初始化gl上下文 (代码段结束后有解释)
// @ts-ignore
gl = canvas.getContext('webgl',{alpha: true }) || canvas.getContext('experimental-webgl');
if (!gl) {
alert('Cannot initialize WebGL. This browser does not support.\n不能初始化WebGL,该浏览器不支持WebGL,请切换浏览器重试');
gl = null;
document.body.innerHTML =
'该浏览器不支持 <canvas>
标签元素,请切换浏览器重试 .';
// gl初期化失敗
return false;
}
// 向DOM添加画布
// document.body.appendChild(canvas);
if (!frameBuffer) {
frameBuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
}
// 透明设置
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
const supportTouch: boolean = 'ontouchend' in canvas; //是否支持触碰(触摸屏)
if (supportTouch) { // 没有触屏电脑(两种事件都要注册)
// 注册触摸相关的回掉函数 (触摸屏)
canvas.ontouchstart = onTouchBegan;
canvas.ontouchmove = onTouchMoved;
canvas.ontouchend = onTouchEnded;
canvas.ontouchcancel = onTouchCancel;
} else {
// 注册鼠标相关的回呼函数
canvas.onmousedown = onClickBegan;
// canvas.onmousemove = onMouseMoved; //原来是在画布上注册鼠标移动事件,鼠标移出画布就监听不到
window.onmousemove = onMouseMoved; //对整个window窗口监听,是角色跟随鼠标,需要对鼠标坐标获取做调整
canvas.onmouseup = onClickEnded;
fui_eye.onmousedown = (): void => { // 工具栏眼睛图标点击事件
const live2DManager: LAppLive2DManager = LAppLive2DManager.getInstance();
live2DManager.nextScene();
};
}
// AppView的初始化
this._view.initialize();
// Cubism SDK的初始化
this.initializeCubism();
return true;
}
contextType参数有以下四种:
注:早期WebGL的context,还不能通过正式的名称webgl来获取,必须使用experimental-webgl来获取context对象。
“2d”,创建一个CanvasRenderingContext2D对象作为2D渲染的上下文。
“webgl”(或“experimental-webgl”),创建一个WebGLRenderingContext对象作为3D渲染的上下文,只在实现了WebGL 2的浏览器上可用,实验性特性。
“webgl2”,创建一个WebGL2RenderingContext对象作为3D渲染的上下文,只在实现了WebGL 3的浏览器上可用。
“bitmaprenderer”,创建一个ImageBitmapRenderingContext,用于将位图渲染到canvas上下文上,实验性特性。
原文链接:https://blog.csdn.net/acoolgiser/article/details/85800799
// 鼠标移动后的回掉
function onMouseMoved(e: MouseEvent): void {
// if (!LAppDelegate.getInstance()._captured) { // 判断是否单击,原来是要按住鼠标左键图像才会跟着鼠标动
// return;
// }
if (!LAppDelegate.getInstance()._view) { //获得lappview.ts的实例对象
LAppPal.printMessage('view notfound');
return;
}
// e.clientX和e.clientY获取的坐标点都是以左上角为原点
const rect = (e.target as Element).getBoundingClientRect();
// const posX: number = e.clientX - rect.left;
// const posY: number = e.clientY - rect.top;
let posX: number = e.clientX;
let posY: number = e.clientY - window.innerHeight + canvas.height;
// 图像在网页的坐下角,简单处理坐标将超过画布边界坐标就等与边界坐标
posX = (posX > canvas.width) ? canvas.width : posX;
posY = (posY < 0) ? 0 : posY;
LAppDelegate.getInstance()._view.onTouchesMoved(posX, posY);// 这个就不做解释,就是转换坐标,调用LAppLive2DManager类重新绘制图像
}
假设屏幕是一个九宫格(万能神奇的九宫格哈哈)
回到**initialize()**函数,在initialize()后有两个初始化的函数
this._view.initialize() (lappview.ts)主要就是指定一些图像的参数,例如画面的范围相对,设置当前矩阵的放大率等
this.initializeCubism()(lappdelegate.ts)
public initializeCubism(): void {
// setup cubism 设置cubism
this._cubismOption.logFunction = LAppPal.printMessage; //初始化控制台打印信息工具,就是console.log
this._cubismOption.loggingLevel = LAppDefine.CubismLoggingLevel; //指定打印日志的等级
Csm_CubismFramework.startUp(this._cubismOption);
// initialize cubism 初始化设置cubism
Csm_CubismFramework.initialize();
// load model 加载模型
LAppLive2DManager.getInstance();
// 更新时间
LAppPal.updateTime();
this._view.initializeSprite();
}
Csm_CubismFramework.initialize()底层的初始化设置
LAppLive2DManager.getInstance()模型管理类的初始化,单例模型没什么好说的,注意这个类在构造方法中会加载模型下段代码所示:
// (lapplive2dmanager.ts)
public changeScene(index: number): void {
this._sceneIndex = index;
if (LAppDefine.DebugLogEnable) { //要是调试的情况下打印信息
LAppPal.printMessage(`[APP]model index: ${this._sceneIndex}`);
}
// 从ModelDir[]中保存的目录名称
// 要使目录名和model 3.json的名字一致。
const model: string = LAppDefine.ModelDir[index];
const modelPath: string = LAppDefine.ResourcesPath + model + '/';
let modelJsonName: string = LAppDefine.ModelDir[index];
modelJsonName += '.model3.json'; //拼接生成模型路径
this.releaseAllModel(); //清除原来显示的模型
this._models.pushBack(new LAppModel()); // 推入管理栈堆
this._models.at(0).loadAssets(modelPath, modelJsonName); //加载模型,lappmodel.ts异步请求服务器模型资源
}
// 构造器
constructor() {
this._viewMatrix = new Csm_CubismMatrix44();
this._models = new Csm_csmVector<LAppModel>();
this._sceneIndex = 0;
this.changeScene(this._sceneIndex); //第一次加载模型
}
这里在往下深入就是 lappmodel.ts 加载定义相关的模型信息例如模型的大小等
loadAssets(modelPath, modelJsonName)异步加载模型的json文件到缓存中===>
CubismModelSettingJson(buffer, size)模型json文件的缓存,和缓存大小===>
this.setupModel(setting: CubismModelSettingJson); 根据模型json文件中的信息异步请求去加载模型及相关的文件(例如动作文件、物理文件等)===>
loadModel(buffer: ArrayBuffer) 模型文件缓存,去加载生成模型===>
this._modelMatrix = new CubismModelMatrix(this._model.getCanvasWidth(), this._model.getCanvasHeight());根据画布的大小去生成模型===>
this.setHeight(1.0);根据画布的高度去生成一个正方形模型坐标系(4*4)调整里面的参数可以调整模型区域的大小(玄学的数字为什么没看懂,有待研究)
回到 LAppDelegate.initialize()(lappdelegate.ts),在initialize()的最后会调用 this._view.initializeSprite()方法
// 进行图像的初始化,一些不重要的元素初始化。这里有一个齿轮设置的图像,里面的内容替换成了眼睛的图标,没用所以注释掉,还加了一个背景图片加载的判断,没有背景图片就不加载
public initializeSprite(): void {
const width: number = canvas.width;
const height: number = canvas.height;
const textureManager = LAppDelegate.getInstance().getTextureManager(); // 从LAppDelegate类中得到纹理管理器
const resourcesPath = LAppDefine.ResourcesPath;
let imageName = '';
// 背景图像初始化
imageName = LAppDefine.BackImageName;
if(imageName != "" && imageName != null){ //如果指定了背景图片,就加载
// 由于异步,创建回调函数
const initBackGroundTexture = (textureInfo: TextureInfo): void => {
const x: number = width * 0.5; //背景图片出现宽度的位置
const y: number = height * 0.5; //背景图片出现高度的位置
const fwidth = textureInfo.width * 2.0; //背景图片的宽度
const fheight = height * 0.95; //背景图片的高度
this._back = new LAppSprite(x, y, fwidth, fheight, textureInfo.id); //绘制背景图片
};
textureManager.createTextureFromPngFile( //回掉函数
resourcesPath + imageName,
false,
initBackGroundTexture
);
}
// 齿轮图像初始化 (原来是右上角有一个齿轮的图片,点击齿轮图片切换模型)
// imageName = LAppDefine.GearImageName;
// // 齿轮初始化后的回掉函数
// const initGearTexture = (textureInfo: TextureInfo): void => {
// const x = width - textureInfo.width * 0.5; //出现在右上角
// const y = height - textureInfo.height * 0.5;
// const fwidth = textureInfo.width;
// const fheight = textureInfo.height;
// this._gear = new LAppSprite(x, y, fwidth, fheight, textureInfo.id);
// };
// textureManager.createTextureFromPngFile(
// resourcesPath + imageName,
// false,
// initGearTexture
// );
// 创建阴影
if (this._programId == null) {
this._programId = LAppDelegate.getInstance().createShader();
}
}
到这里初始化的工作基本完成了
回到main.ts文件接下来就是执行LAppDelegate.getInstance().run()方法,没啥好说的,就是不断循环刷新画布,达到动画的效果
// 执行处理
public run(): void {
// 主循环
const loop = (): void => {
// 确认有无实例
if (s_instance == null) {
return;
}
// 更新时间
LAppPal.updateTime();
// 画面的初始化
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 启动深度测试
gl.enable(gl.DEPTH_TEST);
// 附近的物体将远处的物体遮盖起来
gl.depthFunc(gl.LEQUAL);
// 清除彩色缓冲区和深度缓冲区 (加上这一句会导致有些浏览器背景变成黑色,而不是透明)
// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.clearDepth(1.0);
// 透明设置
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// 绘图更新
this._view.render();
// 循环递归调用
requestAnimationFrame(loop);
};
loop();
}
最后打包bundle.js,赋值bundle.js文件和Core核心文件live2dcubismcore.js就可以移植到任何项目了
最后的模型是我做的哈哈,比较简单只会摇头眨眼,图片来自于网络,仅供于学习
链接: https://pan.baidu.com/s/1SV-R3OCGlQ37BrPqKzo7lg 提取码: xg29