@flare-engine/physics

中文文档
0.4.12发布于 2026/11/13Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 2677

AABB collision, spatial index, raycasting

原始使用文档
文档目录5 节
  1. Install
  2. Usage
  3. Resolving a collision
  4. Raycasting the world
  5. License

@flare-engine/physics

Built-in AABB / Circle collision detection — CollisionWorld with enter/exit signals, SpatialIndex uniform spatial-hash broadphase, CollisionWorld.raycast for broadphase-backed ray queries plus a standalone raycast for an explicit target list, and overlapInto for minimum-translation resolution. Trigger / layer / mask filtering. Solid-tile grid movement: buildSolidGrid / isBlockedAABB / moveAndSlideInto (structural SolidTile input, no tilemap dep, half-open AABB-vs-tile, axis-separated slide, zero-alloc), with isSolidAt / setSolidAt to read or flip one cell at runtime. There is no third-party physics dependency: rigid-body response lives in the first-party @flare-engine/dynamics, and the planck.js option evaluated in the design docs was never adopted. Measured cost by collider count — with the capture date and run-to-run spread — is published in benchmarks. Those rows time a whole physics frame (move every collider, then step()), not step() in isolation. Colliders wrap at a hard field boundary so density is constant for the whole sample window, and a probe fails the capture if one escapes; read the rows as an order-of-magnitude guide, which is where the <1000 figure above comes from.

@flare-engine/physics answers did these overlap; @flare-engine/dynamics answers where do they end up. Neither package depends on the other — reach for @flare-engine/dynamics when bodies need to push each other around, not just report contact.

Install

bun add @flare-engine/physics

Usage

import { CollisionWorld, AABB, createCollider } from '@flare-engine/physics';

declare const entityA: number;
declare const x: number, y: number;

const world = new CollisionWorld();
world.onCollisionStart.add((pair) => console.log(pair.entityA, pair.entityB));

const colliderA = createCollider(AABB(32, 32), { layer: 1, mask: 2 });
world.addCollider(entityA, colliderA, x, y);
world.step();

Resolving a collision

shapeOverlap answers whether two shapes collide, which leaves you knowing there is a problem and not how to fix it. overlapInto gives the minimum translation vector — the shortest push that separates them:

import { AABB, CircleShape, createOverlap, overlapInto } from '@flare-engine/physics';

const mtv = createOverlap(); // reuse across pairs — no per-pair allocation
if (overlapInto(AABB(32, 32), 0, 0, CircleShape(8), 18, 4, mtv) !== null) {
  // mtv.x / mtv.y is a unit axis pointing from a toward b; mtv.depth is how far
  // b must move along it to just stop touching.
}

Returns null when the shapes are apart, leaving your buffer untouched. Concentric circles have no meaningful axis, so (1, 0) is returned rather than NaN from normalising a zero vector.

Touching is not colliding. Every shape pair is strict: two shapes whose edges exactly meet do not overlap, in shapeOverlap and overlapInto alike, so the two always agree. A zero-radius circle collides only when strictly inside a box, as a zero-size box does.

Raycasting the world

import { CollisionWorld } from '@flare-engine/physics';

declare const world: CollisionWorld;

// Nearest hit along +x, limited to colliders on layers 1 and 2.
const hit = world.raycast(0, 0, 1, 0, 500, 0b011);
if (hit !== null) {
  // hit.entity / hit.distance / hit.point / hit.normal
}

CollisionWorld.raycast walks only the broadphase cells the ray's bounding box touches, and filters by the layer bits colliders already carry (mask defaults to every layer). The standalone raycast(ox, oy, dx, dy, maxDist, targets) export remains for casting against an explicit list that is not in a world.

The returned hit record is reused between calls — read it, or copy what you need, before casting again. That is what keeps a per-frame ray allocation-free; the standalone helper by contrast returns a fresh hit (plus two nested Vec2s) from each call that hits, which the caller may keep.

See API spec.

License

Apache-2.0

版本与发布记录

@flare-engine/physics 共发布 17 个版本,首次发布于 2026/06/10;过去 12 个月发布了 17 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.12latest2026/11/133 天前
0.4.112026/11/123 天前
0.4.102026/11/087 天前
0.4.82026/11/0610 天前
0.4.72026/11/0411 天前
0.4.62026/10/3115 天前

关于 @flare-engine/physics 的常见问题

@flare-engine/physics 是什么?

AABB collision, spatial index, raycasting

如何安装 @flare-engine/physics?

在项目目录运行 npm install @flare-engine/physics,或使用 pnpm add / yarn add / bun add。

@flare-engine/physics 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@flare-engine/physics 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@flare-engine/physics 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@flare-engine/physics 可以商用吗?

@flare-engine/physics 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@flare-engine/physics 的下载量有多少?

根据 npm 官方下载统计,@flare-engine/physics 在 2026/10/15 – 2026/11/13 这 30 天内被下载 2,677 次,日均约 89 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@flare-engine/physics 还在维护吗?

最新版本 0.4.12 发布于 2026/11/13(3 天前);至今共发布 17 个版本,其中过去 12 个月发布了 17 个;首次发布于 2026/06/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。