惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
博客园 - 叶小钗
The Cloudflare Blog
D
DataBreaches.Net
J
Java Code Geeks
G
Google Developers Blog
L
LangChain Blog
N
Netflix TechBlog - Medium
Stack Overflow Blog
Stack Overflow Blog
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
小众软件
小众软件
量子位
Apple Machine Learning Research
Apple Machine Learning Research
P
Proofpoint News Feed
博客园_首页
罗磊的独立博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
腾讯CDC

九仞之行

别让评论憋在文末了:让想法跟着文字串门 - 九仞之行 缓存复仇记:挡在我完美缓存前面的,是那个更傻的缓存 - 九仞之行 我的缓存会自己补货:LruLink 与三个并行功能分支 - 九仞之行 Maltose:15 天重构博客,先写了 29 份 ADR 才敢写代码 - 九仞之行 告别素材文件,WPF 内嵌视频实现高颜值动态窗口背景 - 九仞之行 如何让neovim 集成 jupyter - 九仞之行 放弃吧?不!Hue 在 Linux ARM64 上的绝境求生指南 - 九仞之行 HomeAssistant 折腾日记——nginx 反向代理配置 - 九仞之行 使用udisk来让Linux自动挂载USB硬盘和U盘 - 九仞之行 《震耳欲聋》观后感(带剧透) - 九仞之行 让SDKMAN的JDK在macOS上「合法上岗」的全套骚操作 - 九仞之行 告别误触复制!tmux 鼠标滚动的正确开启姿势 - 九仞之行 inode 到底是什么,Windows 中是否有类似实现? - 九仞之行 在Arch Liunx里只用Intel Ultra 7 155H跑stable-diffusion-webui - 九仞之行 浙江财经大学校庆50周年之“三行代码作情诗” - 九仞之行
我以为能给站点请个会动的吉祥物,结果来了个 64×64 的方块人...
Styunlen · 2026-08-22 · via 九仞之行

第 2 篇的灶台 3,那道叫 mc-live2d 的菜,今天端上来给你看看。
点菜时我以为端上来的是二次元立绘,结果后厨端上来一个……方块人。还会游泳。


🤯 开场:一次“货不对板”的请客

先说结论:这个吉祥物,我请了两天,换了两次“技术方案”,最后它学会游泳了。

事情要从一个很朴素的愿望说起——我想给博客右下角放一个会动的小人,当吉祥物(就是那种网站角落的看板娘,mascot,站点形象代言人)。

然后我打开搜索框,输入了“Live2D”。

这是我今天犯的第一个错误。

因为我的“吉祥物素材”,是 Minecraft 皮肤——一张 64×64 像素的平面 PNG 贴图(就是那张决定你角色长啥样的方块皮)。

我想要的:会眨眼、会说话、会摆姿势的 Live2D 老婆……不是,吉祥物。

现实是:它是一张像素贴图,而 Live2D 需要的是 .moc3 模型(一种带骨骼绑定的动画模型),两者之间没有自动转换工具。

就像你想请一位 3D 建模师,结果来的是位平面设计师——不是不能合作,但一开始的期望全错位了。

场景还原:
我:我想要会动的二次元立绘!
现实:这是你的 64×64 贴图。
我:那它至少能动吧?
现实:它能……游泳。划水那种。


🐒 踩坑 01:先请来了第一位师傅(minecraft-skin-viewer)

没有 Live2D,那就退而求其次:用 three.js(一个 3D 渲染库)把皮肤贴图直接贴到方块人几何体上——做一个“3D 方块立绘”,这也是 Minecraft 本体干的事。

第一步我选了开源库 minecraft-skin-viewer,它把“方块人 3D 渲染”封装好了:

import { SkinViewer } from "minecraft-skin-viewer";

const viewer = new SkinViewer({
  canvas,               // 挂到哪个画布
  skin: "/skins/styunlen.png",  // 皮肤贴图
  width: 180,
  height: 180,
});

效果:右下角出现一个方块人,能旋转、能呼吸浮动,还挺像回事。

但用了两天,问题来了:

这个库的 API 是“黑盒”——我想让方块人“看鼠标”(头转向鼠标方向),但它的头部部件(head)不是公开 API,我只能靠整身旋转 + 各种 hack(类型全靠 as any 硬撑,懂的人都懂这种痛)。

这就好比:你雇了个员工,但他不给你留工位钥匙——你只能透过窗户指挥他。


💡 灵光 01:两天后,我换了位师傅(skinview3d)

两天后我发现了 skinview3d(同一个生态的另一个库),一个词总结:部件级 API

import { SkinViewer } from "skinview3d";

const viewer = new SkinViewer({ canvas, skin, width: 180, height: 180 });

// 部件级操作:直接转头,不动整身
viewer.playerObject.skin.head.rotation.y = 0.5;  // 头往右转

关键差异:

minecraft-skin-viewer(前任)skinview3d(现任)
头部控制❌ 不公开,只能 hackskin.head 直接操作
类型安全❌ 全靠 as any✅ 全类型,零 hack
动画内置旋转/浮动可插拔动画(IdleAnimation/SwimAnimation)

两天前刚上的库,两天后我把它卸了。 代码重写,但方向对了——从此我有了“眼睛跟随”的基础。


🏊 主角:游泳系统——一个方块人为什么要游泳

吉祥物站右下角不动,很呆。我想让它偶尔游动一下。

需求听起来简单:“随机朝某个方向移动一段距离。”但实现起来全是魔鬼细节:

细节 1:距离要是“屏幕自适应”的

游太短没存在感,游太长可能撞出屏幕。于是:

// 游动距离 = 屏幕对角线 × 随机比例(25% ~ 100%)
const ratio = 0.25 + Math.random() * 0.75;   // 0.25 ~ 1.0
const dist = Math.hypot(vw, vh) * ratio;      // hypot = 对角线长度

手机和显示器都能游得“恰到好处”——游对角线的 1/4 到一整条。

细节 2:撞墙要反弹,而且得预计算

“移动到屏幕边缘”意味着要撞边界反弹。但如果我们用“移动中实时检测碰撞”,会出现卡在边缘抖动的 bug。

解法:游泳前先把整条反弹轨迹算好(simulateSwimPath),然后照着轨迹走:

// 预计算:从当前位置出发,沿 angle 方向游 dist 距离,
// 撞到视口边界就反弹,返回整条轨迹 + 终点
const path = simulateSwimPath(start, angle, dist, bounds);

就像游泳运动员下水前先看好赛道——我们是先算好撞墙路线再出发

细节 3:朝向要和游泳方向同步(这是最骚的)

方块人游动时,应该侧身朝游泳方向,而不是永远脸朝观众。

skinview3d 里,模型的旋转用欧拉角(Euler,绕三个轴转的角度组合)。关键是把旋转顺序改成 YXZ

viewer.playerObject.rotation.order = "YXZ";  // 先侧身,再朝目标方向

这样 yaw(水平转向)能扫过长轴,让方块人侧着身子游——就像真的在划水,而不是平移的纸片人。

细节 4:划水次数要对上时间

游泳动画(SwimAnimation)有固定节奏,屏幕位移的速度要和划水同步——不然会出现“动画在划水,人却平移”的割裂感。

const swimMs = Math.round(dist / SWIM_SPEED);        // 距离 ÷ 速度 = 总时长
viewer.animation.speed = 3 / (swimMs / 1000);        // 让 3 次划水刚好游完总时长

目标:游完全程恰好划 3 下。比考游泳教练证还讲究。


🐒 彩蛋坑:移除“收起”功能,因为它毫无意义

原设计里,看板娘可以点击收起/展开(右下角消失/出现)。

但实际用下来:这个功能毫无意义——谁会主动把吉祥物收起来?它又不挡视线(半透明、右下角)。

所以我把收起功能整个删了,让它永远挂在那。顺带把“拖动”做成全画布拖动——现在你可以在屏幕上随意挪它,它会记住位置(sessionStorage 持久化,SPA 页面切换不丢)。

删除功能有时候比添加功能更正确——砍掉没人用的按钮,是对用户最好的尊重。


🎉 结尾:这个方块人的自我修养

回顾一下,这个“吉祥物”最终变成了:

  • 👀 眼睛跟随:鼠标靠近,头转过来看你(半径 350px)
  • 🌬️ 呼吸:待机时轻微浮动
  • 🏊 游泳:随机 360° 方向,屏幕自适应距离,撞墙反弹,侧身划水
  • 🖱️ 可拖动:全画布拖动,位置记忆

不是我最初想要的 Live2D。但它是我的 MC 皮肤,是方块人的形状,而且它会游泳——这个组合意外地有点可爱。

它教会我的事:技术选型时,“能不能达到理想效果”比“听起来高级”重要一万倍。
我想要的是会动的吉祥物,现实给了我会游泳的方块人——接受现实,然后把它做到最好。


🔮 下篇预告

第 3 篇到这里。吉祥物到位,缓存会自己补货,页面能丝滑切换……

然后,第 4 篇,最黑暗的一章来了。

我给缓存装了自动补货员,自我感觉良好。
直到有一天我发现:货架上摆的全是过期面包,而补货员……根本没被叫醒过。

下一篇:《缓存复仇记:挡在我完美缓存前面的,是那个更傻的缓存》