news 2026/10/13 6:20:45

planck.js 版本演进与架构变迁:从 Box2D 移植到 TypeScript 引擎的完整技术脉络

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
planck.js 版本演进与架构变迁:从 Box2D 移植到 TypeScript 引擎的完整技术脉络
  • 游戏开发

【免费下载链接】planck.js

2D JavaScript Physics Engine

项目地址:https://gitcode.com/gh_mirrors/pl/planck.js
点击查看免费下载

planck.js 是一个以 TypeScript/JavaScript 实现的 2D 物理引擎(MIT 协议,源自 Box2D 的跨平台 HTML5 游戏开发移植)。本文以仓库根目录的 CHANGES.md 变更记录为主线,逐版还原 0.1 → 0.2 → 1.0.0 → 1.1.0 的演进脉络,并结合 src、vite.config.mts 等源码证据,解读每一次重构背后的设计动机与 API 迁移方式,帮助读者理解当前引擎的模块结构、事件系统与命名规范,并掌握从旧版 API 升级到新版的关键对照。

版本演进总览

当前仓库 package.json 版本为 1.5.0,而 CHANGES.md 记录了从早期移植到现代化重构的四个关键阶段:

版本核心变化性质
0.1目录重排、类合并、标志位改布尔字段、事件系统引入大范围架构重构
0.2TypeScript 定义加入、向量组合运算 API 重命名、关节构造器清理API 命名规范化
1.0.0全量 TypeScript 迁移、性能优化语言与运行时升级
1.1.0Vite 构建迁移工程化升级

从变更记录看,planck.js 的演进方向非常清晰:先是内部架构去 Box2D 化(0.1),再是公开 API 规范化(0.2),随后是类型系统与构建工具链的现代化(1.0.0 / 1.1.0)。

0.1:架构重构——从"忠实移植"到"JavaScript 原生"

0.1 是信息量最大的一次版本,几乎重写了整个引擎的组织方式。以下按主题分组解读。

目录结构与文件组织

source code directory layout changed, classes moved around!

从当前仓库的目录布局可以反推这次调整的结果:

  • 物理与碰撞基础类型集中在 src/common(Vec2、Vec3、Mat22、Mat33、Rot、Transform、Sweep、Jacobian等);
  • 碰撞算法集中在 src/collision(AABB、BroadPhase、DynamicTree、Distance、Manifold、Shape、TimeOfImpact),形状实现在 src/collision/shape(CircleShape、EdgeShape、PolygonShape、ChainShape、BoxShape及各Collide*配对碰撞函数);
  • 动力学与求解器集中在 src/dynamics(Body、Fixture、Contact、Joint、World、Solver),关节实现位于 src/dynamics/joint。

b2Class renamed to Class

早期版本中所有类都继承自一个b2Class基类,0.1 起统一改名为Class。当前源码中已完全搜不到b2Class或GrowableStack的痕迹,说明这两项已彻底移除。

Dumping is removed, Drawing is moved to testbed

序列化/反序列化能力被拆分:场景"dump"功能被移除,绘图相关能力移入 testbed(即planck-with-testbed构建产物,见 vite.config.mts 的buildTestbed分支)。这也解释了package.json中./with-testbed导出子路径的存在——核心引擎与可视化测试台被明确解耦。

GrowableStack replaced with native array, Timer is changed

Box2D 风格的自增长栈数据结构被原生数组取代;计时器改为基于Date.now()的轻量实现,见 src/util/Timer.ts(now()直接返回Date.now(),diff(time)计算差值)。

数学与几何 API 拆分

Math is changed and split

Vec2.Min/Max renamed to Vec2.Upper/Lower and several other methods added

数学工具被拆分到多个文件(Vec2、Vec3、Mat22、Mat33、Rot、Transform、Sweep等,见 src/common),同时Vec2.Min/Max更名为Vec2.Upper/Lower,并补充了若干新方法。当前 Vec2.ts 中可以看到大量以matrix.*前缀出现的内部向量运算辅助,以及setVec2、add、setCombine、addCombine、subCombine、setMul、addMul、subMul等方法(见 Vec2.ts)。

TestOverlap moved from Collision to Distance

testOverlap从 Collision 模块移入 Distance 模块,当前位于 src/collision/Distance.ts:它通过设置input.useRadii = true调用Distance算法计算两形状最近距离,并以output.distance < 10.0 * EPSILON判定是否重叠。同时 Distance.ts 保留了 legacy 导出(Distance.testOverlap、Distance.Input、Distance.Output、Distance.Proxy、Distance.Cache),方便旧代码平滑迁移(Distance.ts)。

Distance method, DistanceInput/Output and SimplexCache merged into a stateful Distance class

原独立的Distance函数、DistanceInput、DistanceOutput、SimplexCache被合并进一个"有状态"的 Distance 模块。当前 Distance.ts 中DistanceInput、DistanceOutput、SimplexCache仍作为独立类存在(分别见 Distance.ts、Distance.ts、Distance.ts),但Distance本体变成了可复用的函数式接口,配合模块级复用对象(simplex、output、cache等)减少 GC 压力,内部统计stats.gjkCalls、stats.gjkIters也可用于基准测试(Distance.ts)。

Shape.computeDistanceProxy is added

形状基类Shape新增抽象方法computeDistanceProxy(src/collision/Shape.ts),各形状(CircleShape、EdgeShape、PolygonShape、ChainShape)分别实现,为 GJK 距离算法提供支持点查询代理。Distance内部通过shape.computeDistanceProxy(this, index)装载代理(Distance.ts)。

接触与碰撞架构合并

Contact and Collide classes merged, Contact registration/creation is changed

ContactSolver/VelocityConstraint/PositionConstraint merged with Contact

ContactManager/ContactListener merged into World (and Contact)

这三条变更合起来看:原先分散的碰撞、求解、管理职责被压缩。当前 src/dynamics/Contact.ts 一个文件内同时包含VelocityConstraintPoint、速度约束求解(initVelocityConstraint、solveVelocityConstraint)与位置约束求解(solvePositionConstraint、solvePositionConstraintTOI),即旧版ContactSolver/VelocityConstraint/PositionConstraint的逻辑已被合并进 Contact 类。而ContactManager/ContactListener的职责则并入 src/dynamics/World.ts 与 Contact。

Manifold files is added, WorldManifold.Initialize moved to Manifold.getWorldManifold

新增独立的 src/collision/Manifold.ts,原WorldManifold.Initialize变为Manifold.getWorldManifold。该方法接收世界变换与形状半径,将局部流形换算为世界坐标下的法线、接触点与分离距离(见 Manifold.ts),并处理e_circles、e_faceA、e_faceB三种流形类型。Manifold.ts 还静态导出clipSegmentToLine、ClipVertex、getPointStates、PointState(Manifold.ts)。

ContactFilter.ShouldCollide moved to Fixture?

碰撞过滤判定下沉到 Fixture 层,与 Fixture 的过滤数据(group、categoryBits、maskBits)耦合更紧密。碰撞配对阶段仍由 BroadPhase 的updatePairs驱动(src/collision/BroadPhase.ts),测试见 src/test/Collision.test.ts。

求解流程重组

Island/World.Solve/SolveTOI moved to new Solver

Position/Velocity correction objects assigned to owner bodies

孤岛(Island)管理与世界级求解(含 TOI 连续碰撞求解)被移入新的 src/dynamics/Solver.ts(其中定义TimeStep、ContactImpulse等求解输入输出类型),位置/速度矫正对象则归属到各自的 Body 上,消除了大量临时对象传递。

二进制标志位 → 布尔字段

binary flags changes to boolean fields (awakeFlag, autoSleepFlag, bulletFlag, fixedRotationFlag, activeFlag, enabledFlag, islandFlag, touchingFlag, filterFlag, bulletHitFlag, toiFlag, clearForces, newFixture, locked)

这是最影响内部实现的一处改动:原本用单个整数按位存储的十几个状态标志,全部改为独立的布尔字段。好处是代码可读性与调试友好度大幅提升,代价是需要逐一核对语义迁移。以 src/dynamics/Body.ts 为例,当前可见m_bulletFlag、m_autoSleepFlag、m_awakeFlag、m_activeFlag、m_fixedRotationFlag等字段,并通过isBullet/setBullet(Body.ts)、isSleepingAllowed/setSleepingAllowed(Body.ts)、isAwake/setAwake(Body.ts)、isActive/setActive(Body.ts)、isFixedRotation/setFixedRotation(Body.ts)成对暴露。

字符串类型系统

Body.Is[Type]/Set[Type] added

Shape, Body and Joint string types ('circle', 'dynamic', 'mouse-joint', etc.)

引擎从数值枚举走向字符串类型标识,这是一次面向开发者的重大 API 变更:

  • 形状类型:ShapeType = "circle" | "edge" | "polygon" | "chain"(src/collision/Shape.ts),getType()返回对应字符串;
  • 刚体类型:BodyType = "static" | "kinematic" | "dynamic"(src/dynamics/Body.ts),并同时保留Body.STATIC/KINEMATIC/DYNAMIC静态常量与setStatic/setDynamic/setKinematic/setType/getType系列方法(Body.ts);
  • 关节类型:DistanceJoint、FrictionJoint、GearJoint、MotorJoint、MouseJoint、PrismaticJoint、PulleyJoint、RevoluteJoint、RopeJoint、WeldJoint、WheelJoint各自定义static TYPE,如"distance-joint"(DistanceJoint.ts)、"mouse-joint"(MouseJoint.ts)、"wheel-joint"(WheelJoint.ts)等,Joint.getType()返回该字符串(Joint.ts)。

字符串类型的价值在于:序列化、调试输出、以及testbed的 UI 展示(关节样式匹配、形状渲染分支)都可以直接以可读字符串为键,无需维护枚举 ↔ 字符串的映射表。测试中可直接断言b1.getFixtureList().getType() === "circle"(src/test/Basic.test.ts)。

世界监听器 → 事件系统

World listeners changed to events: PostSolve, PreSolve, EndContact, BeginContact, SayGoodby to post-solve, pre-solve, end-contact, begin-contact, remove-body, remove-joint, remove-fixture

这是 0.1 中最具代表性的公开 API 变更:从"接口式监听器"转向"事件发布/订阅"。当前World以on(name, listener)/off(name, listener)/publish(name, ...)三件套实现事件机制(src/dynamics/World.ts),支持的事件包括:

事件名回调参数触发时机
begin-contact(contact)两个 fixture 开始接触(World.ts)
end-contact(contact)两个 fixture 停止接触(World.ts)
pre-solve(contact, oldManifold)接触更新后、进入求解器前,可修改流形或禁用接触(World.ts)
post-solve(contact, impulse)求解完成后,用于读取冲量(World.ts)
remove-body/remove-joint/remove-fixture(body)/(joint)/(fixture)对应实体被显式或隐式移除时(World.ts)

此外还扩展了add-body、add-joint、add-fixture三个新增事件(World.ts)。事件发布点在 Contact 与 World 内部:如beginContact/endContact/preSolve/postSolve均通过this.publish(...)触发(World.ts)。官方文档注明:这些回调中不能创建/销毁世界实体(World.ts),且同一时间步内同一接触可能收到多次回调。

回调类 → 函数

Callbacks classes changed to functions: DynamicTree.query DynamicTree.rayCast BroadPhase.updatePairs BroadPhase.rayCast BroadPhase.query World.queryAAB World.rayCast

Box2D 中以"回调对象 + 虚方法"实现查询的方式被替换为纯函数回调:

  • DynamicTree.query(aabb, callback)与DynamicTree.rayCast(input, callback)(src/collision/DynamicTree.ts);
  • BroadPhase.query、BroadPhase.rayCast、BroadPhase.updatePairs(addPairCallback)(src/collision/BroadPhase.ts);
  • World.queryAABB(aabb, callback)(src/dynamics/World.ts)与World.rayCast(point1, point2, callback)(World.ts),回调返回false可提前终止 AABB 查询,rayCast 回调返回数值可控制射线maxFraction的收缩。

0.2:API 命名规范化与 TypeScript 定义

向量组合运算重命名

wSet/wAdd/wSub(a, v, b, w) replaced with combine/setCombine/addCombine/subCombine(a, v, b, w)

wSet/wAdd/wSub(a, v) replaced with mul/setMul/addMul/subMul(a, v)

Vec2的加权赋值方法从带w前缀的模糊命名改为语义明确的组合运算命名。旧方法在 Vec2.ts 中仍保留但标记@deprecated,并内部委托给新方法:

  • wSet(a, v, b, w)→setCombine(a, v, b, w),语义为this = a*v + b*w;wSet(a, v)→setMul(a, v),语义为this = a*v(Vec2.ts);
  • wAdd→addCombine/addMul,语义为this += a*v (+ b*w)(Vec2.ts);
  • wSub→subCombine/subMul,语义为this -= a*v (+ b*w)(Vec2.ts)。

注意setCombine的注释特别说明this可能即是w(// this may be w),因此实现先计算局部变量再赋值,避免别名冲突——这是向量库常见的坑。此外Vec2.combine(a, v, b, w)静态方法也基于setCombine实现(Vec2.ts)。

关节构造器清理

Joints constructors cleanup

TypeScript definitions added

各关节构造器从"需要显式传入大量参数"改为统一的definition 对象 + 可选 bodyA/bodyB风格,例如 Joint.ts 支持两种构造形式:new Joint(def)或new Joint(def, bodyA, bodyB),其中bodyA可从def.bodyA解出。这一约定配合0.2引入的 TypeScript 定义文件,让关节创建更类型安全、更易读。

1.0.0:全量 TypeScript 迁移与性能优化

Typescript migration

Performance improvements

1.0.0 将引擎主体(含测试)从 JavaScript 全面迁移到 TypeScript。当前仓库结构可见完整证据:源码全部为.ts文件(src 下的*.ts)、测试使用 Vitest(*.test.ts位于 src/test与 src/serializer/test)、类型测试使用tsd(test-d/planck.test-d.ts)。

性能优化方面,从代码结构可以推断几个方向:

  • 对象复用/池化:模块级复用DistanceOutput、SimplexCache、Simplex等对象([Distance.ts](https://link.gitcode.com/i/397dcbd86cb532fc38a5bf147b3c63f4#L740)),Vec2、Mat22等临时对象通过内部matrix.*工具复用,src/util/Pool.ts 提供对象池;
  • 构建期常量折叠:ASSERT、_ASSERT、CONSTRUCTOR_FACTORY在构建时通过 Vitedefine替换为false/true(vite.config.mts),运行时断言在生产构建中被移除;
  • 统计埋点:stats.gjkCalls、stats.gjkIters等计数器(Distance.ts)服务于 benchmark 基准测试,npm run benchmark会结合 Git 提交信息对比性能(见 package.json)。

1.1.0:Vite 构建迁移

Vite build migration

构建工具链统一迁移到 Vite,由 vite.config.mts 完成以下工作:

  • 双产物构建:核心引擎(planck,入口 src/main.ts)与可视化测试台(planck-with-testbed,入口 testbed/main.ts)分别打包(vite.config.mts),同时输出 ES(.mjs)与 UMD(.js)两种格式(vite.config.mts);
  • 类型声明打包:通过vite-plugin-dts-bundle-generator生成planck.d.ts(vite.config.mts),供package.json的exports字段分发(package.json);
  • License 注入:rollup-plugin-license在每个构建产物头部注入 MIT License 横幅(vite.config.mts);
  • 压缩产物:npm run build额外用 terser 生成planck.min.js与planck-with-testbed.min.js(package.json),分别提供给 jsDelivr/unpkg CDN 使用(package.json)。

从 1.1.0 起,npm run dev/npm run testbed启动 Vite 开发服务器并加载 testbed 交互式测试台,npm run test运行 Vitest 测试,npm run docs:dev基于 Vitepress 预览 docs 文档站(package.json)。

从 0.x 迁移到当前版本的快速对照

综合 0.1 与 0.2 的破坏性变更,整理出面向升级者的核心对照表:

旧写法(0.x)新写法(当前)依据
Vec2.wSet(a, v, b, w)vec.setCombine(a, v, b, w)Vec2.ts
Vec2.wAdd(a, v)vec.addMul(a, v)Vec2.ts
Vec2.wSub(a, v, b, w)vec.subCombine(a, v, b, w)Vec2.ts
Vec2.Min/MaxVec2.Upper/LowerCHANGES.md 0.1 条目
数值类型枚举字符串类型:"circle"、"dynamic"、"mouse-joint"等Shape.ts、Body.ts、MouseJoint.ts
WorldManifold.Initialize(...)manifold.getWorldManifold(wm, xfA, radiusA, xfB, radiusB)Manifold.ts
二进制标志位布尔字段 +isXxx/setXxxBody.ts
监听器接口(BeginContact、SayGoodbye等)事件:world.on("begin-contact", ...)、world.on("remove-body", ...)World.ts
回调对象函数回调:world.queryAABB(aabb, cb)、world.rayCast(p1, p2, cb)World.ts、World.ts
关节多参数构造定义对象构造:new RevoluteJoint({...}, bodyA, bodyB)Joint.ts

延伸阅读

  • CHANGELOG.md:更细粒度的逐版本变更说明;
  • docs/core-concepts.md 与 docs/world/simulation.md:理解 World 事件与求解流程的使用语境;
  • src/dynamics/World.ts 与 src/common/Vec2.ts:事件系统与向量 API 的第一手实现;
  • example 目录下的 60+ 个示例(如Breakable.ts、Pinball.ts、RayCast.ts):新 API 的实际调用范式;
  • vite.config.mts 与 package.json:构建与发布产物全貌。
  • 游戏开发

【免费下载链接】planck.js

2D JavaScript Physics Engine

项目地址:https://gitcode.com/gh_mirrors/pl/planck.js
点击查看免费下载

相关推荐

上一篇:手机号找回QQ号码:Python工具如何帮你3分钟搞定账号关联验证?
下一篇:超越传统OCR:PaddleOCR-VL-1.6-4bit如何处理倾斜文档、表格和多语言内容?

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 6:19:12

C语言数组核心知识点

C语言数组是固定长度、同类型元素的连续内存存储结构&#xff0c;一旦定义长度不可修改&#xff0c;元素类型必须统一&#xff0c;索引从0开始&#xff0c;是C语言最基础的存储结构之一。 一、定义规则 格式为「数据类型 数组名[数组长度]」&#xff0c;长度必须是常量&#xf…

作者头像 李华
网站建设 2026/10/13 6:17:48

Upscayl 免费 AI 图像放大教程:从安装到 4 倍放大的完整流程

Upscayl 免费 AI 图像放大教程&#xff1a;从安装到 4 倍放大的完整流程 【免费下载链接】upscayl &#x1f199; Upscayl - #1 Free and Open Source AI Image Upscaler for Linux, MacOS and Windows. 项目地址: https://gitcode.com/GitHub_Trending/up/upscayl Upsc…

作者头像 李华
网站建设 2026/10/13 6:16:31

Redis Hash底层原理与实战:listpack、渐进式rehash及避坑指南

如果让我在 Redis 的所有数据类型里挑一个“最值回票价”的&#xff0c;我大概率会把票投给 Hash。String 是面子&#xff0c;十个键里八个是它&#xff1b;Hash 才是里子&#xff0c;真正做对象缓存、购物车、计数器聚合、分布式锁的可重入逻辑时&#xff0c;你根本绕不开。尤…

作者头像 李华