笔记:live2d4.0 sdk 博客园网页动画
1.准备
1.1下载live2d
live2d官网:https://www.live2d.com 如果下载的慢文章最后有百度云下载链接
1.2安装live2d
一路next,安装好后会有两个文件:
Live2D Cubism Viewer 4.0(这个是查看模型的软件)
Live2D Cubism Editor 4.0(这个是制作模型的软件)
Live2D Cubism Editor 4.0有pro版和free版,用free版就行
1.3 下载sdk
2.制作模型
https://www.bilibili.com/video/av73648216?p=1
这个教程是2.0的,2.0导出的是 .moc 文件,而新版的导出的是 .moc3 文件
注意:导出前要 Ctrl + T 再点击ok一下(生成纹理),不然无法导出
导出后是个文件夹,将文件夹中的 .moc3 文件拖入Live2D Cubism Viewer 4.0软件就能查看效果
3.sdk
官方sdk api:https://docs.live2d.com/cubism-sdk-tutorials/sample-build-web/
3.1运行实例
需要环境:node.js TypeScript Webpack (TypeScript和Webpack安装慢可以使用淘宝镜像)
编辑器打开sdk项目,具体的目录是什么内容可以看项目根目录下的 README.md 文件(windows用户可以使用Typora软件打开.md文件)
- 打开控制台跳转到Demo文件夹下输入 npm install 命令(安装了淘宝镜像的可以使用cnpm install命令安装的快一些)
install命令会根据package.json 文件中的配置下载安装需要的插件
这里已经安装好了,安装好后在Demo文件夹下回多出一个 node_modules 文件夹 - 运行 npm run build命令
运行完成后会在Demo文件夹下生成一个 dist 文件夹,该文件夹下有一个bundle.js,这个就是集合打包好的js文件,在index.html文件中有引用 - 运行 npm run serve命令,启动服务器
4.在浏览器中输入 http://localhost:5000/Samples/TypeScript/Demo/ 就能看到
具体的其他命令可以查看跟Demo文件夹同级的 README.md 文件, 在package.json文件中也能看到
3.2源码
想把一些配置放到html中,比如画布(canvas)的大小位置,模型保存的路径等等信息
否则每次需要改变模型的时候都要改代码,重新编译,麻烦
lappdefine.ts //定义基本的参数
lappdelegate.ts //初始化,释放资源,事件绑定
lapplive2dmanager.ts //模型的管理类,进行模型生成和废弃、事件的处理、模型切换。
lappmodel.ts //模型类,定义模型的基本属性
lappal.ts //读取文件,抽象文件数据(算是工具类)
lappsprite.ts //动画精灵类,(学python时知道了精灵类和精灵组)
lapptexturemanager.ts//纹理管理类,进行图像读取和管理的类
lappview.ts //视图类,生成模型的图像被lapplive2dmanager管理
main.ts //主程序启动程序
touchmanager.ts //事件的管理类(比如移动鼠标,点击鼠标,触摸屏触碰等)
这里所有的类都实行单例模式
3.2.2 界面(index.html)
这里用了flask框架(别问为啥简单方便快)
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=1900"><title>TypeScript HTML App</title><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><!-- Pollyfill script --><script src="https://unpkg.com/core-js-bundle@3.6.1/minified.js"></script><!-- Live2DCubismCore script --><script src="{{ url_for('static', filename='live2d/js/live2dcubismcore.js') }}"></script><!-- Build 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>
3.2.2 管理(message.js)
// 初始化
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);
}
3.2.2 基本参数(lappdefine.ts)
由于使用Webpack打包,Typescript文件中的变量和函数被层层括号包围(封装)变成了内部变量和内部函数(具体可以百度Webpack的打包原理),在外部的js文件是调用不到里面的方法的,所以将一些函数或变量挂载到window下,成为全局变量或函数,使外部的js文件也能调用到
在lappdefine.ts文件最后添加
export const win: any = windowwin.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 是常量只允许在声明的时候赋值,并且只能赋值一次)
3.2.2 主函数(main.ts)
import { LAppDelegate } from './lappdelegate';// 浏览器装入后的处理(打开页面)
window.onload = (): void => {// create the application instanceif (LAppDelegate.getInstance().initialize() == false) {return;}LAppDelegate.getInstance().run();
};//结束时的处理 (刷新或关闭页面)
window.onbeforeunload = (): void => LAppDelegate.releaseInstance(); //lambda 匿名函数
LAppDelegate.getInstance().initialize() 获得这个类的实例并初始化
3.2.3 LAppDelegate.getInstance().initialize()(lappdelegate.ts)
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-ignoregl = 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 ='该浏览器不支持 <code><canvas></code> 标签元素,请切换浏览器重试 .';// 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
onMouseMoved(lappdelegate.ts)
// 鼠标移动后的回掉
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)
3.2.4 this.initializeCubism() (lappdelegate.ts)
public initializeCubism(): void {// setup cubism 设置cubismthis._cubismOption.logFunction = LAppPal.printMessage; //初始化控制台打印信息工具,就是console.logthis._cubismOption.loggingLevel = LAppDefine.CubismLoggingLevel; //指定打印日志的等级Csm_CubismFramework.startUp(this._cubismOption);// initialize cubism 初始化设置cubismCsm_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)调整里面的参数可以调整模型区域的大小(玄学的数字为什么没看懂,有待研究)
3.2.5 this._view.initializeSprite() (lappview.ts)
回到 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();}}
到这里初始化的工作基本完成了
3.2.6 LAppDelegate.getInstance().run() (lappdelegate.ts)
回到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就可以移植到任何项目了
4成果
最后的模型是我做的哈哈,比较简单只会摇头眨眼,图片来自于网络,仅供于学习
链接: https://pan.baidu.com/s/1SV-R3OCGlQ37BrPqKzo7lg 提取码: xg29
笔记:live2d4.0 sdk 博客园网页动画相关推荐
- PageRank 计算博客园用户排名
PageRank 通过网页与网页之间的链接关系计算各网页权重,一般权重高的网页特点是:链接向它的网页数量多.链向它的网页其权重也较高.PageRank 就是通过这样的连接关系,一轮轮迭代计算后得出各网 ...
- 用WORD2010写博客,并发送至博客园
博客园网页版写博客总是那么的不爽,特别是右侧那两条滚动条,水瓶座的绝对受不了. 注:本文是根据"六仙庵"的博客,加以整理. 一.配置账号 1.1点击"发布为博客文章&qu ...
- 互评Alpha版本—博客园安卓APP
互评Alpha版本-博客园安卓APP 测评人:徐劭斌 基于NABCD评论作品 1.根据NABCD评论作品的选题: N(Need,需求):博客园的网页版深受软件开发人员的喜爱.博客园是一个非常好的学 ...
- 互评Beta版本--博客园安卓APP
测评人:徐劭斌 博客园安卓APP软件说明书地址:http://www.cnblogs.com/wwd1993/p/7880856.html 博客园安卓APP软件下载git地址:https://git. ...
- 【教程】一步一步教你如何自定义设置——博客园canvas/JS交互动画背景
演示步骤的原文链接:(看完就可以掌握如何设置博客园canvas动画背景) 博客园如何自定义设置canvas/JS动画背景 --2018-10-27 本博客的弹弹弹小球-canvas动画原文链接:(看完 ...
- 笔记:编程的一些建议 - 时间伙伴 - 博客园
笔记:编程的一些建议 0.今天偶然和同学聊到Eric Steven Raymond的<大教堂与集市>,想起可能会有些同学没看过,给个链接吧.http://www.yeeyan.com/ar ...
- bootstrap实现网页手风琴--博客园老牛大讲堂
网页常用有网页的左右导航,其中用到了bootstap框架. 例子一. <!DOCTYPE html> <html><head><meta charset=&q ...
- Spring Security笔记:使用数据库进行用户认证(form login using database) - 菩提树下的杨过 - 博客园...
Spring Security笔记:使用数据库进行用户认证(form login using database) - 菩提树下的杨过 - 博客园 在前一节,学习了如何自定义登录页,但是用户名.密码仍然 ...
- redis源码笔记 - 刘浩de技术博客 - 博客园
redis源码笔记 - 刘浩de技术博客 - 博客园 redis源码笔记 - 刘浩de技术博客 - 博客园 redis源码笔记 记录发现的一个hiredis的bug 摘要: hiredis是redis ...
- 安卓开发笔记——打造属于自己的博客园APP(一)
最近事情比较多,博客更新又落下了,平时有个习惯,喜欢睡前看看博客园里博友的文章,但一直感觉APP市场上下载下来的博客园客户端用起来并不是很舒服,近来发现博客园也有对外开放的数据接口,所以打算自己写个博 ...
最新文章
- Kafka 安装和搭建 (一)
- .zip.001 -- .zip.003解压缩
- 软件测试技术lab1 2017.3.13
- 大一python基础编程试卷_2020大学慕课Python编程基础试题及答案
- 第22篇 js中的this指针的用法
- mongodb 索引1
- MATLAB鲁棒控制器实现
- 人工智能培训:是学不好,还是教不好?3岁半的 BitTiger关闭
- ps图层高级扩展知识
- 调用百度地图API进行当前位置定位失败解决方法
- phpmail通过qq发邮箱失败_请问phpmailer发送Gmail总是失败是什么原因,qq邮件可以发送出去...
- 建立arm linux运行环境,构建 arm-linux 仿真运行环境 (skyeye + arm-linux + NFS)
- linux中etc目录的英文全称
- [lua] 用lua实现扑克游戏发牌的逻辑代码
- 刷机大师乐蛙定制版 v3.4.0 官方版
- java 三点_[Java教程]三点运算符使用方法
- html5 手机相册裁剪,H5的手机图片裁剪特效
- 前端人工智能?TensorFlow.js 学会游戏通关
- 使用成功路径实现数据库状态回传
- iStylePDF把多个PDF合并成一个PDF文件
热门文章
- 神武手游哪个服务器人最多,神武4端游什么老区人最多
- 二叉树模型matlab实现,利用Matlab实现二叉树的树形显示
- 实现简单的滑块验证代码案例
- 已知特征多项式求所有对应的二阶矩阵
- TCP/IP常见英文缩写
- 十三种Java开发工具
- PPI | protein-protein interaction | 蛋白互作分析 | gene interaction | 基因互作
- java中CheckException和UnCheckException的区别
- C++实现经典同步问题(生产者消费者、读者写者、哲学家进餐、吸烟者问题)
- win7 局域网访问网站