C
SONARI · LIVE2D PIPELINE v2 · 自部署

Candy Live2D 管线 · 直连 4090

从 Sweet Candy 主视觉到 Live2D 模型,全程不依赖第三方。 本工具直接调用你部署的 ComfyUI + See-through API,数据不外传。

未检查

v2 升级了什么

相比 v1,这版本去掉了对 Image to Layers / Komiko 等第三方服务的依赖。

CHANGES

核心变化:第 2 步从"指引第三方"变成"调你的服务器"

v1 第 2 步需要你打开 Image to Layers 网站手动操作, v2 第 2 步直接在浏览器内上传图片到你 4090 服务器的 API, 实时显示 GPU 处理进度,完成后自动跳到第 3 步预览图层。

使用前提

  • 已部署 ComfyUI + See-through 到 4090 服务器
  • 已建立 SSH 隧道(本机 5055 → 服务器 5055),或使用 AutoDL 公网 URL
  • 已配置 API Key(顶部输入框中填好)

右上角"检查连接"按钮可以验证 API 是否正常工作。

典型使用场景

A
本地开发(SSH 隧道) API 地址保持 http://127.0.0.1:5055,确保 SSH 窗口开着
B
团队协作(AutoDL 公网 URL) 把 API 地址改成 https://xxx.gradio.live 这种公网 URL,任何成员都能用
C
生产部署(自有域名 + Nginx) API 地址改成 https://seethrough.your-domain.com,加 HTTPS 和 API Key 鉴权

第 1 步 · 上传 Candy 主视觉

PNG / JPG,建议尺寸 1024×1024 或 2048×2048。文件 ≤ 20MB。

[ + ]
点击或拖拽上传 Candy 图片
推荐:已裁剪好的人物图(透明背景或纯色)

裁剪建议

  • 从 Sweet Candy banner 中只裁出 Candy 人物部分
  • 背景能去就去(透明 PNG 最好),不能去也行
  • 包含完整头部 + 上半身,留出适当空间
  • 避免:复杂背景、多人、被切掉的肢体

第 2 步 · API 拆图层

直接调用你的 4090 服务器,GPU 处理 30-180 秒,自动返回 PSD。

请先上传图片

回到第 1 步上传 Candy 图片,然后再来这里调用 API。

第 3 步 · 预览图层 / 检查质量

检查每个图层,发现问题用 PS / Photopea(免费在线)修补。

需要先完成第 2 步

请回到第 2 步调用 API 处理图片。

第 4 步 · Cubism 中 30-60 分钟完成

本工具不能自动化这步,但我把 Cubism 操作精简到极致。

必备软件

Live2D Cubism Editor 5.0(免费 PRO 试用 42 天)

下载:live2d.com/en/cubism/download/editor

PRO 试用结束后降级 FREE,FREE 限制 30 参数 / 30 部件 / 100 ArtMesh — MVP 阶段够用。

步骤详解

4.1
导入 PSD(2 分钟) Cubism Editor → 文件 → 新建 → 拖拽 PSD 进窗口
4.2
自动生成 Mesh(5 分钟) 选中所有 ArtMesh(Ctrl+A)→ 右键 → Apply Automatic Mesh Generation
4.3
AI Auto-Rigging(10 分钟,核心) 菜单 → Modeling → Auto Generate Face Motion → Angle X / Y / Z → 等 30 秒
4.4
调整头发(15 分钟) 头转时头发可能穿模,在 Parameters 面板调整,参考 Cubism Sample 模型 "Hiyori"
4.5
创建 8 个表情(15 分钟) 调整面部参数到目标 → 菜单 → New Expression。具体参数见下卡
4.6
物理设置(5 分钟,可选) 菜单 → Physics Settings → 头发组 → Pendulum=8, Damping=0.5, Reaction=0.4
4.7
导出 model3.json(2 分钟) File → Export for Runtime → model3.json → candy_v1/

表情预设(直接抄,省 30 分钟)

// idle (默认微笑) ParamMouthOpenY: 0.3, ParamMouthForm: 1.0 ParamEyeLOpen: 1.0, ParamEyeROpen: 1.0 ParamBrowLY: 0, ParamBrowRY: 0 // cheering (加油) ParamMouthOpenY: 0.6, ParamMouthForm: 1.0 ParamEyeLOpen: 1.2, ParamEyeROpen: 1.2 ParamBrowLY: 0.3, ParamBrowRY: 0.3 // excited (兴奋) ParamMouthOpenY: 0.8, ParamMouthForm: 1.0 ParamEyeLOpen: 1.0, ParamEyeROpen: 1.0 ParamBrowLY: 0.5, ParamBrowRY: 0.5 // euphoric (大奖!) ParamMouthOpenY: 1.0, ParamMouthForm: 0.5 ParamEyeLOpen: 1.5, ParamEyeROpen: 1.5 ParamBrowLY: 0.8, ParamBrowRY: 0.8 // tense (屏息) ParamMouthOpenY: 0, ParamMouthForm: -0.3 ParamEyeLOpen: 0.7, ParamEyeROpen: 0.7 ParamBrowLY: 0.4, ParamBrowRY: 0.4 // sad (难过) ParamMouthOpenY: 0.2, ParamMouthForm: -0.8 ParamEyeLOpen: 0.6, ParamEyeROpen: 0.6 ParamBrowLY: -0.5, ParamBrowRY: -0.5 // cozy (温柔) ParamMouthOpenY: 0.3, ParamMouthForm: 1.0 ParamEyeLOpen: 0.5, ParamEyeROpen: 0.5 ParamBrowLY: -0.2, ParamBrowRY: -0.2 // sleepy (困倦) ParamMouthOpenY: 0.7, ParamMouthForm: 0 ParamEyeLOpen: 0.3, ParamEyeROpen: 0.3 ParamBrowLY: -0.4, ParamBrowRY: -0.4
必加配置

自动呼吸 + 自动眨眼(5 分钟)

{ "EyeBlinkParameters": { "Interval": [4000, 6000], "BlinkClose": 100, "BlinkClose2Open": 80 }, "BreathParameters": { "ParamBreath": { "Cycle": 3.5, "Range": 0.5, "Weight": 1.0 } } }

第 5 步 · PixiJS 集成代码

把 model3.json 接入 Sweet Candy 项目。

1. 安装依赖

npm install pixi-live2d-display # 还需 Live2D Cubism Core # 下载 live2dcubismcore.min.js 放 public/ # index.html 里引入

2. CandyController 类

// src/components/CandyController.ts import * as PIXI from 'pixi.js'; import { Live2DModel } from 'pixi-live2d-display'; export type EmotionState = 'idle' | 'cheering' | 'excited' | 'euphoric' | 'tense' | 'sad' | 'cozy' | 'sleepy'; export class CandyController { private model!: Live2DModel; private currentEmotion: EmotionState = 'idle'; private emotionTimer: number | null = null; private readonly DURATIONS: Record<EmotionState, number> = { idle: Infinity, cheering: 3000, excited: 4000, euphoric: 6000, tense: 500, sad: 8000, cozy: Infinity, sleepy: Infinity, }; private readonly PRIORITY: Record<EmotionState, number> = { idle: 0, sleepy: 1, cozy: 2, sad: 3, cheering: 4, tense: 5, excited: 6, euphoric: 7, }; async load(app: PIXI.Application, modelUrl: string) { try { this.model = await Live2DModel.from(modelUrl); this.model.scale.set(0.18); this.model.x = app.screen.width - 80; this.model.y = 80; app.stage.addChild(this.model); this.enableEyeTracking(app); } catch (err) { console.warn('Live2D failed:', err); this.fallbackToStaticImage(app); } } setEmotion(target: EmotionState) { if (this.PRIORITY[target] < this.PRIORITY[this.currentEmotion]) return; this.currentEmotion = target; this.model?.expression(`exp_${target}`); if (this.emotionTimer !== null) clearTimeout(this.emotionTimer); const duration = this.DURATIONS[target]; if (duration !== Infinity) { this.emotionTimer = window.setTimeout(() => this.setEmotion('idle'), duration); } } private enableEyeTracking(app: PIXI.Application) { app.stage.interactive = true; app.stage.on('pointermove', (e) => { this.model.focus(e.data.global.x, e.data.global.y); }); } private fallbackToStaticImage(app: PIXI.Application) { const sprite = PIXI.Sprite.from('/assets/candy_static.png'); sprite.width = 60; sprite.height = 60; sprite.x = app.screen.width - 80; sprite.y = 80; app.stage.addChild(sprite); } }

3. 接入 Slot 游戏事件

const candy = new CandyController(); await candy.load(app, '/assets/candy_v1/candy.model3.json'); spinButton.on('click', () => { candy.setEmotion('cheering'); showBubble('cheering'); startReelsSpin(); }); onReelsAboutToStop(() => candy.setEmotion('tense')); onSpinResult((result) => { if (result.multiplier >= 20) candy.setEmotion('euphoric'); else if (result.multiplier >= 5) candy.setEmotion('excited'); else if (lastNoBigWinStreak >= 5) candy.setEmotion('sad'); }); setupIdleDetector(60000, () => candy.setEmotion('sleepy'));
完成

整套管线已经走通

预期表现:玩家点 SPIN 时 Candy 紧张,reels 停时屏息,中奖欢呼。 预期会话时长 +25-40%。先灰度 5% 用户跑 24 小时,数据正向再扩大。