前因

模之屋的时候发现,里面居然有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.jsPixiJS 渲染引擎
    live2d.min.jsCubism 2 运行库(pio/kurumi 模型需要)
    live2dcubismcore.min.jsCubism 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 2Cubism 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"}
        ]

结语

修复之后就可以使用啦