Knight's Waltz —— 物理驱动的俯视角剑斗游戏(Three.js + Rapier,无游戏引擎)

英文原作者:saomin_huang
原始链接:https://discourse.threejs.org/t/knights-waltz-a-top-down-physics-sword-dueling-game-three-js-rapier-no-game-engine/93523


03_戰鬥動作1

引言

在 Three.js 社区 Showcase 板块中,开发者 saomin_huang 分享了一款小巧的浏览器游戏 Knight's Waltz(武士圓舞曲)。这是一款自上而下视角的 1v1 剑斗游戏,核心特色是:剑的挥动完全由角色身体旋转带动,伤害值根据剑刃接触点的相对速度实时计算。整个游戏没有使用任何预设攻击动画——所有动作都由物理引擎驱动。

游戏可直接在浏览器中免费游玩(无需安装,支持键盘或触屏),官方地址:


02_主畫面鐵匠鋪

技术栈与核心实现

该项目采用了以下技术组合:

  • Three.js —— 负责渲染(rendering)
  • Rapier(rapier2d-compat,WASM) —— 负责物理模拟(physics simulation)
  • TypeScript + Vite —— 开发语言与构建工具
  • 无任何上层游戏引擎 —— 完全基于底层库手工搭建

值得注意的是,虽然标题中写的是“Three.js + Rapier”,但实际使用的是 2D 物理引擎 Rapier 的兼容版本(rapier2d-compat),而渲染则是 3D 的 Three.js。这种 2D 物理 + 3D 渲染的混合方式,既保证了物理计算的轻量高效,又保留了视觉表现力。

原文并未附带可直接运行的核心代码片段,核心实现位于 GitHub 仓库:
源码(GPL-3.0):https://github.com/saomin0317/knight-duel

我们仍能从技术描述中提取出以下关键设计:

  • 剑的质量与惯性(mass and inertia):剑不是动画,而是具有物理属性的刚体。
  • 格挡机制:盾牌碰撞体(shield collider)物理性地“接住”来袭的剑刃。
  • 耐力系统(stamina system):限制玩家持续旋转,否则最优策略会变成“像陀螺一样转个不停”(being a beyblade)。
  • 可断肢(severable limbs):KayKit 的 CC0 角色模型将头、手臂、身体作为独立网格(mesh),断肢只需将对应网格重新挂到新的刚体上,交给 Rapier 接管。
  • 移动端音频兼容:Safari 的 Web Audio 无法解码 OGG 格式,因此背景音乐采用 AAC/M4A,而音效仍保留 OGG。
  • 双语与在线功能:完整支持英语/繁中运行时切换、云存档,以及官方版本上的服务器权威排行榜。

Knight's Waltz — a top-down physics sword-dueling game (Three.js + Rapier, no game engine)

3 个核心优化要点

1. 纯物理驱动的战斗与伤害计算

整个战斗模型都是物理化的:剑挥动来自身体旋转产生的扭矩(torque),伤害基于剑刃接触点的相对速度实时计算。没有传统的攻击动画制片,取而代之的是“刚性体链”(rigid body chain)对手臂和剑的模拟。开发者花费大量时间调整 质量、阻尼(damping)和惯性 等参数,直到“剑的手感”(sword feel)令人满意。这种方式的优点是真实感极强,缺点是无法像关键帧动画那样艺术化地控制每一帧姿势。为了平衡,加入了耐力系统防止玩家无限旋转,避免“陀螺战术”成为最优解。

2. 程序化断肢:利用模型网格分离与刚体重组

游戏实现了肢体切断效果,而且几乎“零成本”。KayKit 的 CC0 角色模型本来就提供了头、手臂、身体等 独立网格(mesh)。当需要断肢时,只需将对应的 mesh 重新父级(re-parenting)到一个新的刚体 上,然后让 Rapier 接管后续物理模拟。这意味着断肢后的残肢会自然掉落、滚动、与环境碰撞,而不需要额外制作动画或特效。这是一种非常巧妙的资源复用方案。

3. 跨浏览器音频解码兼容优化

一个容易被忽视的移动端坑:Safari 的 Web Audio API 无法解码 OGG 格式。开发者在官方版本中让背景音乐使用 AAC/M4A 封装,而音效(SFX)仍保留 OGG 以节省体积。这个细节看似微小,但直接决定了 iOS 用户能否正常听到背景音乐。对于任何面向移动端的 Web3D 项目,音频格式的跨浏览器兼容性都是一项必须处理的优化点。


开发故事与社区互动

saomin_huang 并非程序员出身,他在回复中坦言这款游戏是与 Claude(AI 编程代理) 合作完成的:Claude 编写代码、运行并修复 bug,而他负责游戏设计、测试和决策。他在博客中详细记录了从零到上线的两天开发历程:
《3D isn't as hard as you think: shipping a 40-year-old gaming memory in two days》
https://satsumacreative.tw/en/blog/e3-web-3d-not-that-hard.html

游戏的原型灵感来自他大约 40 年前在 Apple II 上玩过的一款俯视角武士决斗游戏,但始终找不到名字。有意思的是,社区成员 manthrax 猜测是《The Bilestoad》,但作者确认并不是同一款。

另一位用户 meded90 分享了其使用关键帧动画制作的《魔兽争霸 3》风格角色,并与作者讨论了“物理模拟 vs 关键帧动画”的取舍。作者的观点很中肯:物理动画带来“免费”的真实运动,但代价是无法精确控制姿势;关键帧动画则更适合教学和艺术化表达。


GLSL / WebGL 专业术语检查

虽然原文没有直接出现 GLSL 着色器代码,但涉及大量 WebGL/Three.js 渲染和物理工程术语,现整理如下:

原文术语建议中文译名备注
Three.jsThree.js不翻译,保留原名
Rapier (rapier2d-compat)Rapier(2D 兼容版)物理引擎
WASMWebAssembly二进制指令格式
rendering渲染WebGL 上下文中的绘制
rigid body刚体物理模拟基本单元
collider碰撞体有时译为“碰撞盒”更形象
damping阻尼用于抑制振荡
torque扭矩 / 转矩驱动旋转的物理量
mass and inertia质量与惯性刚体属性
mesh网格三维模型几何数据
re-parenting重新父级化变换层级调整
Web Audio API网页音频接口Web 音频标准
keyframe animation关键帧动画传统动画技术
physics simulation物理模拟与渲染同步
stamina system耐力系统游戏机制,非物理术语

结论:原文主要涉及 WebGL 渲染上下文和物理引擎术语,未出现 GLSL 着色器代码。上述术语在中文技术社区已有成熟译法,建议保持一致性。


参考链接


深度解读结语:Knight's Waltz 是一个典型的“小而精”的 Web3D 作品。它没有庞大的游戏引擎,也没有复杂的动画系统,却通过物理驱动实现了独特的剑斗手感。断肢机制几乎零成本,音频兼容做了贴心处理,充分展示了在浏览器中构建沉浸式物理游戏的可能性。对于想要入门 Three.js + Rapier 的开发者来说,这是一个极佳的学习样本。