@flare-engine/shapes

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

Procedural 2D shape primitives for Skia — atomic primitives + SVG path escape hatch, composable for fast game prototyping

原始使用文档
文档目录9 节
  1. Why
  2. Install
  3. Usage
  4. Primitives
  5. Composing entities
  6. Bounds
  7. Adding a new primitive
  8. Glow
  9. License

@flare-engine/shapes

Procedural 2D shape primitives for react-native-skia. 12 named atomic shapes + an SVG path escape hatch — designed for fast 2D game prototyping where you compose entities from primitives instead of importing sprite art.

Why

Mobile and indie game prototypes don't need sprite sheets to start. Compose ships, enemies, pickups, and UI shapes from primitives, ship a playable build, and replace shapes with art only after the mechanics lock — exactly the workflow Unity Shapes2D and Defold prototyping threads document. Shape-first prototyping keeps the iteration loop tight: a project can reach a playable state immediately and swap in sprite art only after mechanics lock.

Install

bun add @flare-engine/shapes @shopify/react-native-skia

@shopify/react-native-skia is an optional, type-only peer — src/draw.ts imports it via import type only (for the injected Skia / canvas / paint handles), so it never enters the runtime bundle. Skip it entirely on web/CanvasKit; add it only where you actually render on native Skia.

Usage

import type { Skia as SkiaModule, SkCanvas, SkPaint } from '@shopify/react-native-skia';
import { drawShape, type ShapeSpec } from '@flare-engine/shapes';

// `Skia` from react-native-skia is a value; the handle type is `typeof Skia`
// (mirrors `type Skia = typeof SkiaModule` inside src/draw.ts).
type Skia = typeof SkiaModule;

function demo(skia: Skia, canvas: SkCanvas, paint: SkPaint): void {
  // Compose any entity from primitives.
  const shape: ShapeSpec = { kind: 'circle', r: 24, fill: 0xff8844cc };
  drawShape(skia, canvas, paint, shape, /* x */ 100, /* y */ 200, /* rot */ 0);

  // Need a custom shape? Use the SVG escape hatch:
  const star: ShapeSpec = {
    kind: 'path',
    d: 'M 128 0 L 168 80 L 256 93 L 192 155 L 207 244 L 128 202 L 49 244 L 64 155 L 0 93 L 88 80 Z',
    boundsR: 256,
    fill: 0xffc9a24a,
  };
  drawShape(skia, canvas, paint, star, 0, 0, 0);
}

Primitives

13 spec kinds covering every common 2D shape. All accept optional fill (ARGB int), stroke, strokeWidth, ox/oy (local offset), rot (radians), and glow: 'friendly' | 'hazard' | { color, width }.

KindFieldsUse case
circlerBullets, pickups, eyes
rectw, hWeapons, HUD bars, frames
trianglesizeShip hulls, arrows, sigils
ngonn, rHex/pentagon/octagon hulls
starpoints, rOuter, rInnerSigils, sparks, pickups
ringrAuras, target markers
polylinepoints: number[]Bolts, freehand strokes
gradient-rectw, h, colorA, colorB, axis: 0|1HP bars, sky strips
arcr, startAngle, endAngle, sector?Half-circle, 1/4-moon, 1/8-moon
annulusrOuter, rInner, startAngle?, endAngle?Donut, half-donut, partial rings
trapezoidtopW, botW, hShip cockpits, pedestals
pathd, boundsREscape hatch — any SVG path string
shiphull, weapons[], engineComposite (3-element rule)

Composing entities

import type { ShapeSpec } from '@flare-engine/shapes';

// A ship: hull + 2 weapons + engine, painted back-to-front.
const playerShip: ShapeSpec = {
  kind: 'ship',
  hull: { kind: 'triangle', size: 28, fill: 0xffc9a24a },
  weapons: [
    { kind: 'rect', w: 6, h: 12, fill: 0xffc9a24a, ox: -14, oy: 8 },
    { kind: 'rect', w: 6, h: 12, fill: 0xffc9a24a, ox: 14, oy: 8 },
  ],
  engine: { kind: 'square', size: 20, fill: 0xff806020 },
};

// A pickup: ring + inner glow circle.
const amulet: readonly ShapeSpec[] = [
  { kind: 'ring', r: 9, stroke: 0xffc9a24a, strokeWidth: 2, glow: 'friendly' },
  { kind: 'circle', r: 3, fill: 0xffff2b2b },
];

Bounds

import {
  shapeBoundsBox,
  shapeBoundsRadius,
  singleShapeRadius,
  type ShapeSpec,
} from '@flare-engine/shapes';

declare const playerShip: ShapeSpec;

// For atlas tile sizing — max half-extent across a composition. Includes each
// shape's ox/oy offset, a stroke-width margin where a stroke is actually
// painted, and the full extent of a glow ring (+5.5 friendly, +2.25 hazard,
// +2.5 + width for a themed ring), so a tile sized from this fully contains
// what `drawShape` paints.
const tileRadius = shapeBoundsRadius([playerShip]);

// For hitboxes — per-axis extents with the same margins, so an asymmetric
// ship gets no dead space where a radius would. `shapeBoundsBoxInto(specs, out)`
// is the zero-alloc form for a caller-owned box.
const hit = shapeBoundsBox([playerShip]);
const hitWidth = hit.maxX - hit.minX;
const hitHeight = hit.maxY - hit.minY;

// For glow halo sizing — radius of one primitive.
const glowR = singleShapeRadius({ kind: 'circle', r: 14 });

Adding a new primitive

Three ways, in order of friction:

  1. Use the path escape hatch. SVG d="..." strings parse natively via Skia.Path.MakeFromSVGString. Same syntax as Figma / Inkscape / <path>. Zero engine change.

  2. Author a composition. Wrap multiple primitives in a ShapeSpec[] array. No new code at all.

  3. Add a named primitive. When (1)/(2) aren't ergonomic enough or you need a perf-optimized branch:

    1. Extend the ShapeSpec union in src/types.ts
    2. Add a case 'kind': branch in drawShape (src/draw.ts)
    3. Add a bounds branch in singleShapeRadius (src/bounds.ts)
    4. Add tests in tests/draw.test.ts and tests/bounds.test.ts

    Tsc catches a missing step 3: singleShapeRadius returns number, so an unhandled kind fails to compile. It does not catch a missing step 2 — drawShape returns void and its switch has no default, so a new kind without a draw branch compiles and silently draws nothing.

Glow

Set glow on any primitive and drawShape paints a ring behind the body. Two named halos come for free, and a GlowSpec object paints a ring in your own palette:

GlowColorUse
'friendly'safe-green #6FA66B at α=0.25, single 3 px ring at r+4Player ships, pickups, safe markers
'hazard'hazard-orange #E84B2A at α=0.6, double 0.5 px ring at r+1 and r+2Enemy bullets, danger cues
{ color, width }one width px ring in color (ARGB), inner edge 2.5 px outside the bodyAny theme

{ color: friendlyGlowArgb(), width: 3 } paints exactly what 'friendly' paints. The bounds helpers widen by 2.5 + width for the object form, so an atlas tile or hitbox grows with the ring. A non-finite or negative width paints no ring and adds no reach. applyGlow(canvas, paint, cx, cy, r, color, width) draws the themed ring directly for custom compositing.

import type { GlowSpec, ShapeSpec } from '@flare-engine/shapes';

const brandGlow: GlowSpec = { color: 0x40c9a24a, width: 4 };
const relic: ShapeSpec = { kind: 'star', points: 5, rOuter: 12, rInner: 5, fill: 0xffc9a24a, glow: brandGlow };

License

Apache-2.0

版本与发布记录

@flare-engine/shapes 共发布 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/shapes 的常见问题

@flare-engine/shapes 是什么?

Procedural 2D shape primitives for Skia — atomic primitives + SVG path escape hatch, composable for fast game prototyping

如何安装 @flare-engine/shapes?

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

@flare-engine/shapes 支持 TypeScript 吗?

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

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

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

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

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

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

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

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

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

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

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