☰
从零集成 Live2D 看板娘:模型资源、SDK 运行与避坑指南
2026/10/26 17:26:08 网站建设 项目流程

简介:HTML集成Live2D的demo资源,面向希望为网页嵌入可交互2D角色的前端开发者。压缩包共612个文件、17.29MB,主要包含mtn动作数据、json模型配置、png贴图、mp3音频、moc模型文件及少量HTML、JS和说明文档;moc定义模型主体,json描述配置与动作组,mtn存放动画片段,png与mp3分别提供贴图和语音,解压后即可对照运行;包内按模型、动画、音频分目录组织,便于按需检索。示例覆盖Live2D库引入、模型配置解析、Canvas画布创建、引擎初始化、动态更换人物、触摸交互以及与其他组件联动等关键流程;触摸交互能针对模型不同部位触发相应动作与表情,开发者可参照代码快速搭建自己的网页版Live2D互动页面。目前已有1433人学习下载,适合游戏、教育、娱乐类网站增加参与度,也可作为初中级前端开发者学习Live2D实际集成的参考资料。

1. 一个 html 页面能装下 live2D,但 Demo 跑不起来多半卡在“运行时”上

在 HTML 里集成一个 live2D 看板娘,第一眼看上去是件小事:下载一个免费 live2d 模型资源,放进项目目录,写几行初始化,页面里就该出现一个会呼吸、会眨眼、能跟着鼠标转头的小人。实际动过手的同学应该都记得第一次双击 index.html 时的白屏,以及控制台里那条 CORS 报错。live2D 不是一张动图,而是一套需要运行时逐帧解析、在 WebGL 上下文中重绘的程序,HTML 只是它的外壳。这篇笔记从一个能跑起来的 demo 开始拆:模型资源里到底装了什么,怎样让官方 SDK 的 demo 在本地跑通、再换成自己的模型,以及把角色做得“能碰、能动、能适配页面”时要调的参数和绕不开的坑。适合想把 live2D 真正部署到个人网站、博客或小产品里的前端和全栈同学。

2. 从 model3.json 看穿 live2D 模型:demo 背后的资源结构与两代格式

2.1 一个 live2D 角色不是“一张图”,而是一套可变形资源树

很多新手拿到 live2d 模型下载文件后,习惯把整个目录往页面里一扔就开始配路径,结果要么白屏,要么角色摆出一个诡异的 T 型姿势。原因在于:live2D 角色的“身体”由分层贴图和网格变形共同构成,贴图只负责颜色和纹理,真正的表情、头部转动、头发摆动,是由运行时根据参数值驱动网格顶点位移得到的。

一个最常见的 Cubism 4 模型目录长这样:

models/ shizuku/ shizuku.model3.json # 模型入口文件 shizuku.moc3 # 编译后的模型几何与网格数据 textures/ texture_00.png # 分层贴图,一个角色可能有多张 texture_01.png motions/ idle.motion3.json # 待机动作 tap_body.motion3.json # 点击身体时的反应动作 physics/ shizuku.physics3.json # 物理模拟参数,控制头发/衣物摆动 expressions/ smile.exp3.json # 表情预设

这里最容易误解的是.moc3文件。它保存了模型的网格结构、变形器和参数定义,但浏览器无法像读 JSON 那样解析它,必须交给 live2D 的 Core 运行时去解码和计算。所以哪怕你完全看懂了 model3.json,也绕不开官方 SDK 里的那层“黑匣子”。工程上我们要做的,是把资源树原样保留,只通过 model3.json 去指引加载顺序。

文件或目录职责缺失时的典型表现
model3.json声明 moc3、纹理、动作、物理、表情的路径和映射关系无法加载模型,控制台报 404
moc3模型网格、变形器、参数定义没有 moc3,model3.json 无从解析
textures分层绘制的角色贴图角色白模或局部透明
motions动作时间线与参数曲线角色待机不眨眼、不呼吸
physics3.json头发、衣物等柔体随运动摆动运动时衣服头发僵硬不动
expressions预设表情参数组合表情动作无法被触发

2.2 model3.json 写的不是模型,是“资源编排表”

打开一个 model3.json,你会发现它跟模型本身长得一点不像:没有坐标,没有关键帧,全是路径和映射。它的核心职责是告诉运行时:moc3 在哪里、贴图有几张、每张贴图按什么顺序上传到 GPU、哪些动作组可以循环、点击不同区域要触发哪条动作。

{ "Version": 3, "FileReferences": { "Moc": "shizuku.moc3", "Textures": ["textures/texture_00.png", "textures/texture_01.png"], "Motions": { "Idle": [ { "File": "motions/idle.motion3.json", "Loop": true } ], "TapBody": [ { "File": "motions/tap_body.motion3.json" } ] }, "Physics": "physics/shizuku.physics3.json", "Expressions": [ { "Name": "Smile", "File": "expressions/smile.exp3.json" } ] } }

注意这里的FileReferences里的路径是相对 model3.json 所在目录解析的,不是相对页面根目录。很多 demo 换到自己项目里就翻车,因为把 model3.json 放到了新目录,纹理路径却还写着旧目录的相对位置。另外,Textures数组的顺序有意义——它对应 Cubism 编辑器里图层的叠放顺序,擅自调整顺序会让部件被遮挡关系错乱。

2.3 Cubism 2 与 Cubism 4:下载模型前先认清 .moc 和 .moc3

网上能搜到的 live2d 免费模型下载,不少是老资源,入口是model.json,二进制是.moc,对应 Cubism 2 时代的格式。而当前官方 Web SDK 主要面向 Cubism 4+ 的.moc3/.model3.json体系。这两代格式的解析 Core 不通用,加载脚本也完全不同。

维度Cubism 2 老模型Cubism 4+ 新模型
入口文件model.jsonmodel3.json
二进制模型.moc.moc3
物理文件physics.jsonphysics3.json
动作文件motion.jsonmotion3.json
Web SDKCubism 2.1 老框架Cubism Web Framework(新版)

如果你拿一个 .moc 老模型配新版 SDK,常见结果不是“慢一点”,而是直接报Cannot read properties of undefined。这跟你代码写得对不对没关系,纯粹是两代运行时各说各话。所以在动手前先确认模型格式,比调试半天玄学问题靠谱得多。

3. 用官方 SDK 的 demo 跑通最小实例:从 HTML 骨架到模型替换

3.1 把 SDK 和模型资源摆进目录:一个可复现的 demo 结构

官方给的 Cubism Web SDK 一般包含 samples 目录,里面有完整的 TypeScript demo 工程。直接在那个工程里改,是最省力的路径;如果你想把它融进自己的 HTML 站点,常见做法是先把 demo 构建出的产物和Resources目录拷贝出来,形成一个纯静态结构:

. ├── index.html ├── lib/ │ ├── live2dcubismcore.min.js # Core 运行时,负责解析 moc3 │ └── cubismweb.min.js # 由 demo 源码构建出的框架脚本 ├── app.js # 初始化入口,参照 demo 的 main 逻辑 └── Resources/ ├── modelSettings.json # 模型清单,demo 启动时读取它 └── shizuku/ ├── shizuku.model3.json ├── shizuku.moc3 └── textures/

这里给个小建议:不要自己从零封装加载逻辑,而是把官方 demo 里的LAppDelegate、LAppModel、LAppPal等文件原样拷贝进工程。它们已经处理好了 WebGL 上下文创建、纹理上传、帧循环和触摸事件,我们只改初始化参数和模型路径。

3.2 一个最简 HTML 骨架:注意脚本的加载顺序

HTML 侧的事不多,但脚本顺序是关键。live2D 的框架脚本依赖 Core 脚本暴露的全局对象,所以必须先引入 Core,再引入框架产物,最后才是我们的app.js。

<!doctype html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>HTML 集成 live2D demo</title> <style> #live2d-canvas { position: fixed; right: 20px; bottom: 20px; width: 300px; height: 360px; } </style> </head> <body> <!-- Core 必须先加载,框架脚本会引用它的全局对象 --> <script src="lib/live2dcubismcore.min.js"></script> <script src="lib/cubismweb.min.js"></script> <script src="app.js"></script> </body> </html>

这里直接把 canvas 用 CSS 固定在页面右下角,就形成了常见博客的“看板娘”形态。CSS 里设置的width和height是展示尺寸,不等于 canvas 内部缓冲尺寸,后面第 4 章会讲这个差值怎么影响角色清晰度。

3.3 初始化入口:让 LAppDelegate 把模型“跑起来”

官方 demo 的入口通常长这样:拿到 canvas,调用LAppDelegate.getInstance().init(canvas),之后帧循环和模型加载都在框架内部完成。我们自己写app.js时,保持同样的调用层级就好,不要试图绕过框架去直接操作 moc3。

// app.js —— 参照官方 demo 的 main 逻辑精简 const canvas = document.getElementById('live2d-canvas'); // 初始化会读取 Resources/modelSettings.json 中的第一个模型 const app = LAppDelegate.getInstance(); app.init(canvas); // 窗口尺寸变化时通知框架重新计算 canvas 缓冲 window.addEventListener('resize', () => { app.onResize(); });

这段代码里的init做了三件事:创建并保存 WebGL 渲染上下文、解析模型设置清单并异步加载模型、启动requestAnimationFrame循环。onResize在官方 demo 里对应 canvas 缓冲尺寸的重新计算,如果你的页面布局固定,不监听也影响不大;但页面里存在动态区域或移动端横竖屏切换时,建议保留。

3.4 换掉内置模型:把 demo 资源替换成你的角色

demo 能跑起来之后,接下来的刚需是换角色。操作分两处:一是把你的 live2d 模型资源整个目录放进Resources下,二是修改modelSettings.json或官方 demo 里对应的LAppDefine配置,把模型路径指过去。

[ { "name": "shizuku", "model": "Resources/shizuku/shizuku.model3.json" }, { "name": "my-custom-role", "model": "Resources/my-role/my-role.model3.json" } ]

注意这里model字段的值是相对项目根目录的路径,不是相对Resources目录的路径,框架内部在读取 model3.json 后再把其中相对路径拼到当前模型目录下。替换完成后刷新页面,角色应该出现在 canvas 里。如果没出现,优先打开控制台看有没有 404,有 404 就往 model3.json 里的纹理、动作路径去排查。

# 在项目根目录起一个本地静态服务器,避免 file:// 协议下的跨域问题 python3 -m http.server 7777 # 打开浏览器访问 http://localhost:7777

这一步不是可选项。直接用浏览器双击 index.html,理论上能打开页面,但 fetch 纹理和 moc3 时大概率被 CORS 拦截,这是 live2D 集成里第一个经典翻车现场,第 5 章会展开讲。

4. 让看板娘“听话”:视口、motion 与触摸命中的必调参数

4.1 像素对齐:canvas 缓冲尺寸、DPR 和 CSS 尺寸如何影响角色清晰度

很多人把 canvas 的 CSS 尺寸当成了内部尺寸来调,结果角色要么糊,要么被拉伸变形。canvas 的实际绘制尺寸是width/height属性,而 CSS 尺寸只是它在页面上占的显示面积。官方 demo 里,框架会读取 canvas 的clientWidth和clientHeight,再乘以window.devicePixelRatio来设置缓冲尺寸,牺牲一点性能换高 DPR 屏幕上的清晰度。

参数位置作用
canvas.clientWidth / clientHeightCSS 尺寸决定画布在页面中的视觉大小
canvas.width / canvas.height缓冲区尺寸决定实际渲染像素数
window.devicePixelRatio全局高 DPR 下需乘上它,否则角色发虚
framework 的 LAppModel.viewport框架内部控制模型在画布中的缩放与平移

如果你手动改过 canvas 的 CSS 尺寸,却发现角色比例不对,多半是没让框架重新计算 viewport。我的习惯是固定一个设计尺寸,比如移动端 240×360、桌面端 300×400,不要用百分比自适应,否则角色会被压扁。

4.2 motion 动作参数:循环、淡入淡出、优先级和 idle 兜底

motion3.json 里除了关键帧数据,还控制动作的循环与过渡行为。官方 demo 的角色一般自带 Idle 组,但下载的第三方免费模型不一定内置完整动作组,这时需要手动补一个呼吸动作,或者确认现有动作的Loop字段是否为true。

{ "Version": 3, "FadeInTime": 0.5, "FadeOutTime": 0.5, "Loop": true, "Curves": [] }

FadeInTime和FadeOutTime控制动作切换时的淡入淡出秒数,设得太小,动作切换会显得生硬;设得太大,点击反馈会有明显延迟。Loop只对 Idle 这类循环动作有意义,一次性触发的点击动作建议保持false。另外 motion 有优先级概念,框架里通常用startMotion(group, no, priority)触发动作,优先级高的动作会打断低优先级的动作,合理设置优先级才能做出“点击时打断待机、播完再回来”的效果。

4.3 触摸命中:hitTest 判定区域与坐标换算公式

让角色对点击有反应,核心是两步:先把鼠标或触摸坐标换算成 canvas 内部缓冲坐标,再调用框架的hitTest检测是否命中某个区域,最后触发对应动作。很多人的坑在于只做了第一步的“视觉换算”,没考虑 canvas 缓冲尺寸和 CSS 尺寸的差异。

// 把页面事件坐标换算成 canvas 缓冲坐标 function toCanvasPoint(event) { const rect = canvas.getBoundingClientRect(); return { x: (event.clientX - rect.left) * (canvas.width / rect.width), y: (event.clientY - rect.top) * (canvas.height / rect.height) }; } canvas.addEventListener('pointerdown', (event) => { const point = toCanvasPoint(event); // 官方 demo 的命中区域名称通常有 Head、Body 等 if (model.hitTest('Head', point.x, point.y)) { model.startMotion('TapHead', 0, 2); } else if (model.hitTest('Body', point.x, point.y)) { model.startMotion('TapBody', 0, 2); } });

这里的canvas.width / rect.width就是缓冲尺寸与展示尺寸的比值。如果 canvas 被 CSS 的transform: scale()缩放,还需要把getBoundingClientRect()返回的宽高作为基准,而不是写死某个数字。坐标换算正确后,点击反馈才谈得上“指哪打哪”,否则就是角色身体在手柄上方,实际判定区域却偏到一边,典型的“点了没反应”。

5. 避坑排查:live2D 集成里最常翻车的 5 个事故现场

5.1 双击 index.html 白屏:file:// 协议下的跨域卡点

现象:页面能打开,console 里一串报错,模型和纹理全部加载不出来。 原因:浏览器安全策略限制,file://协议下 fetch 本地 JSON 和图片会被 CORS 拦截。live2D 的加载链路里有大量 fetch 调用,白屏几乎是必然结果。 解决:不要双击 HTML,而是起一个本地静态服务。用 VS Code 的 Live Server 插件或最简单的python3 -m http.server 7777都行。这不算什么高端操作,但能一次省掉后面三个小时的无头排查。

5.2 模型显示成纯白或单色块:纹理路径与格式的坑

现象:角色能加载,但贴图是白色块,或者某些部位显示成乱纹。 原因:model3.json 里的Textures路径写错,最常见是大小写问题;另一个常见原因是纹理 PNG 格式异常,比如用了 CMYK 色彩模式或带 Alpha 通道的原生 WebP 改动,模型编辑期没有问题,但运行时贴图上传失败。 解决:打开 model3.json,逐条核对FileReferences.Textures的路径与磁盘目录是否完全一致,Linux 服务器上尤其注意文件名大小写。贴图本身用工具重新导出为 RGBA 的 PNG 再放回去,基本能解决。

5.3 控制台报 Cannot read properties of undefined:两代 Core 混用

现象:加载老模型时,报错出现在框架内部,代码检查了无数遍也没找出问题。 原因:模型是 Cubism 2 的.moc+model.json,而当前项目里挂的是 Cubism 4+ 的 Core 运行时。两代格式的解析器完全不同,新版 Core 不认识老模型。 解决:先确认模型格式。.moc老模型要找老版 Cubism 2.1 Web SDK,.moc3模型才匹配新版框架。下载 live2d 免费模型资源时,优先找标注了 Cubism 4+ 素材的成品模型,这能在源头上替你省掉一整个调试轮次。

5.4 点了模型的头没反应:触摸坐标换算错位

现象:页面其他区域点击正常,但点击角色身体时偶尔有反应、偶尔完全没有,而且判定区域看起来“偏了”。 原因:多半是 canvas 缓冲尺寸和 CSS 展示尺寸不一致,事件坐标直接用clientX/clientY参与 hitTest,没有先做比值换算。CSS 缩放越大,偏移越明显。 解决:统一用getBoundingClientRect()获取实际展示区域,再按canvas.width / rect.width的比例换算事件坐标。如果页面里有 CSS 动画改了 canvas 尺寸,记得在动画结束事件里重新读取一次。

5.5 角色待机十分钟也不动:缺 idle motion 或循环没设

现象:角色加载出来是静态的,既不眨眼也不呼吸,像一张被定住的图。 原因:模型设置里的 Idle 动作组为空,或者 idle.motion3.json 的Loop字段是false。框架只在动作播放完并且没有后续动作时,才让角色回到静止状态,没有任何循环动作就表现为“石化”。 解决:确认 model3.json 里FileReferences.Motions.Idle至少有一条记录,且对应 motion3.json 的Loop为true。如果第三方模型没有提供 idle,就用官方 SDK 自带的呼吸动作模板,只保留Curves里的ParamAngleX、ParamAngleY、ParamBreath等呼吸参数,做成一个 5 秒循环的待机动作,比硬编码去改参数值更可控。

6. 从 demo 走向产品:模型释放、坐标换算与 Vue/React 里的挂载习惯

6.1 换模型前先做三件小事

demo 跑通之后,人会下意识地立刻去换自己的角色模型,然后被各种幺蛾子劝退。我的习惯是先做三件小事:第一,备份一份原始 demo 的Resources目录,任何改动出错都能随时还原,这是最后一道后悔药;第二,用一个已知完好的官方免费模型替换到新路径,先确认路径改动本身没有破坏加载链路,再换目标模型;第三,打开开发者工具 Network 面板,记录下模型加载成功的请求顺序,后面替换模型后只要对比这个顺序就能快速定位是 moc3 解析失败还是纹理上传失败。

6.2 把它变成 Vue/React 里的一个组件,而不是页面全局脚本

很多产品需要把看板娘限制在某个页面或弹窗里,而不是全站常驻。此时不建议把官方 demo 整包塞进应用生命周期,而是把 canvas 挂载逻辑封装进组件,跟随组件挂载和卸载。

// 以 Vue 3 组合式 API 为例的封装思路 import { onMounted, onBeforeUnmount, ref } from 'vue'; const canvasRef = ref(null); let app; onMounted(() => { app = LAppDelegate.getInstance(); app.init(canvasRef.value); }); onBeforeUnmount(() => { // 释放 WebGL 上下文,避免页面层级切换后内存泄漏 app.release(); });

release()或官方对应版本里的销毁方法,会在onBeforeUnmount里清理 GL 资源和帧循环。如果忽略这一步,同一个页面反复进入退出,浏览器 tab 的内存会肉眼可见地往上涨,刷新一下才恢复,这在长时间停留的单页应用里尤其明显。另外,多模型场景下,建议一次只加载一个模型实例,切换角色时先释放旧实例再构建新实例,别把每个角色都常驻内存。

最后一个我自己踩过的教训:任何 live2D 参数改动,都不要在 demo 源码里直接改完就上生产。先把调好的参数固化到模型文件或配置 JSON 里,再用独立页面做一次“换模型 + 换运行环境”的回归测试。确认换了一个目录、换了一台机器也能跑,才算真正攻破这个方向。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询