前因
逛模之屋的时候发现,里面居然有live2d模型的分享,遂下载下来想放到网站上体验,但是发现模型版本是Live2D Cubism 4,而原始插件Pio只支持到Live2D Cubism 2,于是便想将Live2D SDK更新一下,支持最新的模型。
实施方案
- 修改模型发现逻辑,同时识别 model.json(Cubism 2)和 *.model3.json(Cubism 3/4),并将画布从 canvas改为 div容器
用 pixi-live2d-display 替换 loadlive2d()
修改模型发现逻辑
foreach($load as $key => $value){ $single = substr($value, 26); // 优先检测 Cubism 3/4 的 model3.json $model3_files = glob($value . "/*.model3.json"); if(!empty($model3_files)){ $models[$single] = ucfirst($single) . " (Cubism 3/4)"; } // 再检测 Cubism 2 的 model.json else if(file_exists($value . "/model.json")){ $models[$single] = ucfirst($single); } };下载SDK
cdn感觉不如本地稳定
文件 用途 pixi.min.js PixiJS 渲染引擎 live2d.min.js Cubism 2 运行库(pio/kurumi 模型需要) live2dcubismcore.min.js Cubism 4 运行库(Nahida 模型需要) pixi-live2d-display.min.js 统一加载器,同时支持 Cubism 2/3/4 替换旧api
重写 pio.js,用 pixi-live2d-display 的 PIXI.live2d.Live2DModel.from() 替换旧的 loadlive2d(),同时保留所有原有交互功能。
主要修改部分:// 加载模型(支持 Cubism 2 model.json 和 Cubism 3/4 *.model3.json) const loadModel = async (modelUrl) => { if (!window.PIXI || !PIXI.live2d) { console.error("pixi-live2d-display 未加载"); return; } // 首次加载时创建 PIXI.Application if (!current.app) { const width = current.container.clientWidth || 280; const height = current.container.clientHeight || 250; current.app = new PIXI.Application({ width: width, height: height, backgroundAlpha: 0, view: current.container.tagName === "CANVAS" ? current.container : undefined }); // 如果容器不是 canvas,将 PIXI 创建的 canvas 插入容器 if (current.container.tagName !== "CANVAS") { current.container.innerHTML = ""; current.container.appendChild(current.app.view); current.app.view.style.width = width + "px"; current.app.view.style.height = height + "px"; } } // 移除旧模型 if (current.model) { current.app.stage.removeChild(current.model); current.model.destroy(); current.model = null; } try { const model = await PIXI.live2d.Live2DModel.from(modelUrl, { autoInteract: false }); // 自适应缩放 const scaleX = current.app.view.width / model.width; const scaleY = current.app.view.height / model.height; const scale = Math.min(scaleX, scaleY); model.scale.set(scale); // 居中 model.x = (current.app.view.width - model.width * scale) / 2; model.y = (current.app.view.height - model.height * scale) / 2; current.model = model; current.app.stage.addChild(model); } catch (e) { console.error("Live2D 模型加载失败:", e); } };Bug修复
- 后台没有配置"交互提示扩展",PHP 端 json_decode 得到的是 null,传到 JS 后 prop.content 就是 undefined,访问 .referer 就报错了。加上 prop.content = prop.content || {};
- function getCanvas() 和 function getLoader() 定义在方法内部,第二次访问页面时会报 Cannot redeclare 致命错误。
- 设置 anchor 为中心点,让模型以中心为基准定位,解决偏移问题。Math.min 会让模型按较短的边来缩放,确保整个模型都在画布内,不会有任何部分被裁切。如果有空白区域也只是某一侧留白,不会影响显示。
表情添加
由于没有表情相关的代码,需要新加,以下是特性对比
| 特性 | Cubism 2 | Cubism 3/4 |
|---|---|---|
| 眼球物理(眼珠晃动惯性) | 无 | 有 - 眼球有弹性和延迟 |
| 表情系统 (Expression) | 无独立表情文件 | 有 - .exp3.json 独立表情 |
| 嘴型同步 (LipSync) | 简单 | 有 - ParamMouthOpenY |
| 自动眨眼 (EyeBlink) | 需手动实现 | 内置 - ParamEyeLOpen/ParamEyeROpen |
| 高级物理引擎 | 简单单摆 | 多层级弹簧物理 (120fps) |
// 触发表情(仅 Cubism 3/4 模型支持)
const triggerExpression = (name) => {
if (!current.model) return;
const model = current.model;
// pixi-live2d-display 的 expression() 方法
if (model.internalModel && model.internalModel.motionManager) {
// Cubism 4 模型
if (model.internalModel.settings && model.internalModel.settings.expressions) {
const expressions = model.internalModel.settings.expressions;
if (expressions.length === 0) return;
if (name) {
model.expression(name);
} else {
// 随机触发
const randIdx = Math.floor(Math.random() * expressions.length);
model.expression(randIdx);
}
return true;
}
// Cubism 2 model.json 中的 expressions
if (model.settings && Array.isArray(model.settings.expressions) && model.settings.expressions.length > 0) {
const randIdx = Math.floor(Math.random() * model.settings.expressions.length);
model.expression(randIdx);
return true;
}
}
return false;
};
// 获取可用的表情名称列表
const getExpressionNames = () => {
if (!current.model) return [];
const model = current.model;
const names = [];
if (model.internalModel && model.internalModel.settings && model.internalModel.settings.expressions) {
model.internalModel.settings.expressions.forEach((exp) => {
const expName = exp.Name || exp.File.replace(/\.exp3?\.json$/, "");
names.push(expName);
});
}
if (model.settings && Array.isArray(model.settings.expressions)) {
model.settings.expressions.forEach((exp) => {
names.push(exp.Name || exp.file || exp);
});
}
return names;
};model3.json 的 FileReferences 里没有 Expressions 字段,只有 Moc、Textures、Physics、DisplayInfo。需要把 Expressions 和 Motions 配置补进 model3.json。
"DisplayInfo": "Nahida_1080.cdi3.json",
"Expressions": [
{"Name": "Angry", "File": "Angry.exp3.json"},
{"Name": "Happy1", "File": "Happy1.exp3.json"},
{"Name": "Sad1", "File": "Sad1.exp3.json"},
{"Name": "Sad2", "File": "Sad2.exp3.json"},
{"Name": "Shy", "File": "Shy.exp3.json"},
{"Name": "shy_normal", "File": "shy_normal.exp3.json"},
{"Name": "Wink", "File": "Wink.exp3.json"},
{"Name": "Halfeyes", "File": "Halfeyes.exp3.json"},
{"Name": "StarEye", "File": "StarEye.exp3.json"},
{"Name": "HandChange", "File": "HandChange.exp3.json"},
{"Name": "kusa", "File": "kusa.exp3.json"},
{"Name": "black", "File": "black.exp3.json"},
{"Name": "mouthchange", "File": "mouthchange.exp3.json"}
]结语
修复之后就可以使用啦
评论已关闭