@newkrok/nape-js 3.30.3 → 3.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +340 -1
- package/dist/index.d.ts +340 -1
- package/dist/index.js +1 -1
- package/llms-full.txt +174 -0
- package/llms.txt +10 -0
- package/package.json +1 -1
package/llms-full.txt
CHANGED
|
@@ -1517,6 +1517,180 @@ Generate random points inside a polygon for use as Voronoi sites.
|
|
|
1517
1517
|
|
|
1518
1518
|
---
|
|
1519
1519
|
|
|
1520
|
+
## Helpers
|
|
1521
|
+
|
|
1522
|
+
Higher-level building blocks layered on top of the core engine. Each is a thin, optional module — import only what you need.
|
|
1523
|
+
|
|
1524
|
+
### CharacterController
|
|
1525
|
+
|
|
1526
|
+
Velocity-based 2D platformer controller. Wraps a dynamic body and provides ground/slope/wall raycasts, coyote-time tracking, one-way platform support, and moving-platform inheritance.
|
|
1527
|
+
|
|
1528
|
+
```typescript
|
|
1529
|
+
import { CharacterController, Body, BodyType, Vec2, Capsule } from "@newkrok/nape-js";
|
|
1530
|
+
|
|
1531
|
+
const player = new Body(BodyType.DYNAMIC, new Vec2(100, 100));
|
|
1532
|
+
player.shapes.add(new Capsule(36, 18));
|
|
1533
|
+
player.allowRotation = false;
|
|
1534
|
+
player.isBullet = true;
|
|
1535
|
+
player.space = space;
|
|
1536
|
+
|
|
1537
|
+
const cc = new CharacterController(space, player, {
|
|
1538
|
+
maxSlopeAngle: Math.PI / 4, // walkable slope cap (default: PI/4)
|
|
1539
|
+
oneWayPlatformTag: platformCbType, // optional — auto-creates a PreListener
|
|
1540
|
+
characterTag: playerCbType, // required if oneWayPlatformTag set
|
|
1541
|
+
filter: customFilter, // raycast InteractionFilter (default: auto-excludes player shapes)
|
|
1542
|
+
down: new Vec2(0, 1), // override "down" — see planet platformer
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
// Each frame, AFTER space.step():
|
|
1546
|
+
const result = cc.update();
|
|
1547
|
+
result.grounded; // boolean
|
|
1548
|
+
result.groundNormal; // Vec2 | null
|
|
1549
|
+
result.groundBody; // Body | null
|
|
1550
|
+
result.onMovingPlatform; // boolean
|
|
1551
|
+
result.slopeAngle; // radians
|
|
1552
|
+
result.wallLeft; // boolean
|
|
1553
|
+
result.wallRight; // boolean
|
|
1554
|
+
result.timeSinceGrounded; // seconds (for coyote-time)
|
|
1555
|
+
|
|
1556
|
+
// Override "down" each frame for radial-gravity worlds:
|
|
1557
|
+
cc.setDown(downX, downY);
|
|
1558
|
+
```
|
|
1559
|
+
|
|
1560
|
+
**Gotchas:**
|
|
1561
|
+
- The controller does **not** set velocity itself — your code does (typical pattern: read input, compute target velocity, write `body.velocity`). The controller only provides raycast queries and the auto-PreListener for one-way platforms.
|
|
1562
|
+
- `oneWayPlatformTag` requires `characterTag` — without it the auto-listener can't tell which body is the character.
|
|
1563
|
+
- For radial-gravity / planet-platformer scenarios, set `down` to the unit vector from player to "ground" each frame; walls are detected perpendicular to it.
|
|
1564
|
+
|
|
1565
|
+
### RadialGravityField
|
|
1566
|
+
|
|
1567
|
+
Point-source gravity field — pulls bodies toward an anchor with a chosen falloff law. Replaces the manual `for (body of space.bodies) body.force = ...` loops common in orbital / planet / multi-body gravity scenarios.
|
|
1568
|
+
|
|
1569
|
+
```typescript
|
|
1570
|
+
import { RadialGravityField, RadialGravityFieldGroup } from "@newkrok/nape-js";
|
|
1571
|
+
|
|
1572
|
+
// Mario-Galaxy-style planet pulling everything toward its center.
|
|
1573
|
+
const field = new RadialGravityField({
|
|
1574
|
+
source: planetBody, // Vec2 (fixed point) or Body (auto-tracking)
|
|
1575
|
+
strength: 800000,
|
|
1576
|
+
falloff: "inverse-square", // "inverse-square" (default) | "inverse" | "constant" | (d) => number
|
|
1577
|
+
scaleByMass: true, // default true → Newtonian; false → constant accel
|
|
1578
|
+
maxRadius: 250, // hard cutoff — bodies farther than this get 0 force
|
|
1579
|
+
minRadius: 1, // clamp distance for falloff calc (singularity guard)
|
|
1580
|
+
softening: 100, // adds to d² in inverse-square (smooths near-source)
|
|
1581
|
+
bodyFilter: (body) => body !== sun, // optional per-body predicate
|
|
1582
|
+
enabled: true,
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
// Each frame, BEFORE space.step():
|
|
1586
|
+
field.apply(space); // adds force to every eligible dynamic body in space
|
|
1587
|
+
space.step(1 / 60);
|
|
1588
|
+
|
|
1589
|
+
// Compose multiple fields:
|
|
1590
|
+
const group = new RadialGravityFieldGroup();
|
|
1591
|
+
group.add(field);
|
|
1592
|
+
group.add(new RadialGravityField({ source: moon, strength: 50000 }));
|
|
1593
|
+
group.apply(space); // runs all member fields once
|
|
1594
|
+
|
|
1595
|
+
// Compute the force on a specific body without applying it:
|
|
1596
|
+
const f = field.forceOn(body); // Vec2
|
|
1597
|
+
|
|
1598
|
+
// Move the field at runtime (Vec2 source — Body sources auto-track):
|
|
1599
|
+
field.getPosition(); // { x, y }
|
|
1600
|
+
field.enabled = false;
|
|
1601
|
+
field.strength = 1200000;
|
|
1602
|
+
```
|
|
1603
|
+
|
|
1604
|
+
**Gotchas:**
|
|
1605
|
+
- `body.force` is **persistent** across `space.step()` — nape never zeroes it. `apply()` *adds* to existing force, so per-frame field application accumulates unbounded if you don't clear `body.force` yourself each frame. Pattern: `body.force = new Vec2(0, 0)` before `field.apply()`.
|
|
1606
|
+
- `scaleByMass: true` produces real Newtonian behavior; switch to `false` for direct acceleration (simpler tuning for arcade games).
|
|
1607
|
+
- Set `softening` (inverse-square only) to avoid extreme accelerations when bodies pass close to the source.
|
|
1608
|
+
- Static and kinematic bodies are always skipped (they don't respond to force anyway).
|
|
1609
|
+
- For planet platformers where multiple wells overlap and the player should only feel one at a time, use `bodyFilter` to gate the player against `_currentPlanet` while letting other dynamic bodies feel every well they're inside.
|
|
1610
|
+
|
|
1611
|
+
### Tilemap (`buildTilemapBody`, `meshTilemap`)
|
|
1612
|
+
|
|
1613
|
+
Turns a 2D tile grid into a physics body using greedy meshing — collapses adjacent solid tiles into the minimal set of axis-aligned rectangles. Cuts shape count by 5–50× on typical level data, which directly speeds up broadphase + narrowphase.
|
|
1614
|
+
|
|
1615
|
+
```typescript
|
|
1616
|
+
import {
|
|
1617
|
+
buildTilemapBody, meshTilemap, tiledLayerToGrid, ldtkLayerToGrid,
|
|
1618
|
+
} from "@newkrok/nape-js";
|
|
1619
|
+
|
|
1620
|
+
// Hand-authored grid (1 = solid, 0 = empty)
|
|
1621
|
+
const grid = [
|
|
1622
|
+
[1, 1, 1, 1, 1],
|
|
1623
|
+
[1, 0, 0, 0, 1],
|
|
1624
|
+
[1, 0, 0, 0, 1],
|
|
1625
|
+
[1, 1, 1, 1, 1],
|
|
1626
|
+
];
|
|
1627
|
+
|
|
1628
|
+
const body = buildTilemapBody(grid, {
|
|
1629
|
+
tileSize: 32, // square — or { w: 32, h: 24 } for non-square
|
|
1630
|
+
position: new Vec2(0, 0), // top-left of the map in world space
|
|
1631
|
+
merge: "greedy", // "none" | "rows" | "greedy" (default: greedy)
|
|
1632
|
+
solid: (v, x, y) => v !== 0, // default: any non-zero is solid
|
|
1633
|
+
material: customMaterial, // applied to every generated polygon
|
|
1634
|
+
filter: customFilter,
|
|
1635
|
+
cbTypes: [groundCbType],
|
|
1636
|
+
bodyType: BodyType.STATIC, // default STATIC — also accepts KINEMATIC for moving levels
|
|
1637
|
+
body: existingBody, // optional: append shapes to a body that already exists
|
|
1638
|
+
});
|
|
1639
|
+
body.space = space;
|
|
1640
|
+
|
|
1641
|
+
// Pure geometry (no Body) — useful for streaming chunks or precomputing meshes:
|
|
1642
|
+
const rects = meshTilemap(grid, { tileSize: 32, merge: "greedy" });
|
|
1643
|
+
// rects: Array<{ x, y, w, h }> in tile coordinates
|
|
1644
|
+
|
|
1645
|
+
// Parse external level editors:
|
|
1646
|
+
const grid1 = tiledLayerToGrid(tiledJson.layers[0]); // Tiled JSON tile layer
|
|
1647
|
+
const grid2 = ldtkLayerToGrid(ldtkJson.levels[0].layerInstances[0]); // LDtk IntGrid
|
|
1648
|
+
```
|
|
1649
|
+
|
|
1650
|
+
**Gotchas:**
|
|
1651
|
+
- The generated polygons are axis-aligned boxes — no slopes. For sloped terrain combine with hand-authored polygons or use marching squares.
|
|
1652
|
+
- Greedy merging is the right default; only use `merge: "rows"` if you need to preserve per-row stripes (e.g. for per-tile properties stored on shapes), or `"none"` for one polygon per cell when you intend to replace cells dynamically.
|
|
1653
|
+
- `tiledLayerToGrid` / `ldtkLayerToGrid` only consume the data + dimension fields — they don't depend on the full Tiled/LDtk JSON shape, so you can pass a hand-shaped subset.
|
|
1654
|
+
- For destructible terrain, rebuild the body when the grid changes (`body.shapes.clear()` then call `buildTilemapBody(grid, { ..., body })`).
|
|
1655
|
+
|
|
1656
|
+
### TriggerZone
|
|
1657
|
+
|
|
1658
|
+
Sensor-based zone with `onEnter` / `onExit` callbacks — wraps the BEGIN/END `InteractionListener` plumbing so you don't have to wire it up by hand.
|
|
1659
|
+
|
|
1660
|
+
```typescript
|
|
1661
|
+
import { TriggerZone } from "@newkrok/nape-js";
|
|
1662
|
+
|
|
1663
|
+
const zone = new TriggerZone(space, body, {
|
|
1664
|
+
type: InteractionType.SENSOR, // default — also accepts COLLISION
|
|
1665
|
+
onEnter: (interactor) => { /* ... */ },
|
|
1666
|
+
onExit: (interactor) => { /* ... */ },
|
|
1667
|
+
filter: filterCbType, // optional CbType filter
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
zone.destroy(); // remove the listeners
|
|
1671
|
+
```
|
|
1672
|
+
|
|
1673
|
+
### createConcaveBody
|
|
1674
|
+
|
|
1675
|
+
Decomposes a concave polygon outline into convex pieces and adds them all to a single body — needed because nape's `Polygon` shape is convex-only.
|
|
1676
|
+
|
|
1677
|
+
```typescript
|
|
1678
|
+
import { createConcaveBody, Vec2 } from "@newkrok/nape-js";
|
|
1679
|
+
|
|
1680
|
+
const body = createConcaveBody(
|
|
1681
|
+
[new Vec2(0, 0), new Vec2(100, 0), /* ... */], // CCW outline (or GeomPoly)
|
|
1682
|
+
{
|
|
1683
|
+
bodyType: BodyType.DYNAMIC, // default
|
|
1684
|
+
position: new Vec2(200, 100), // body's world position (vertices are local)
|
|
1685
|
+
material: customMaterial,
|
|
1686
|
+
filter: customFilter,
|
|
1687
|
+
},
|
|
1688
|
+
);
|
|
1689
|
+
body.space = space;
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
---
|
|
1693
|
+
|
|
1520
1694
|
## Common Patterns
|
|
1521
1695
|
|
|
1522
1696
|
### Adding Bodies to Space
|
package/llms.txt
CHANGED
|
@@ -95,6 +95,16 @@ function update() {
|
|
|
95
95
|
- [computeVoronoi](https://newkrok.github.io/nape-js/api/functions/computeVoronoi.html): Raw Voronoi diagram computation from point set
|
|
96
96
|
- [generateFractureSites](https://newkrok.github.io/nape-js/api/functions/generateFractureSites.html): Generate random fracture site points within a polygon
|
|
97
97
|
|
|
98
|
+
## Helpers
|
|
99
|
+
|
|
100
|
+
Higher-level building blocks layered on top of the engine — opt-in modules.
|
|
101
|
+
|
|
102
|
+
- [CharacterController](https://newkrok.github.io/nape-js/api/classes/CharacterController.html): Velocity-based 2D platformer controller with ground/slope/wall raycasts, coyote-time, one-way platforms, moving-platform inheritance, and an overridable `down` direction (radial-gravity worlds)
|
|
103
|
+
- [RadialGravityField](https://newkrok.github.io/nape-js/api/classes/RadialGravityField.html) / [RadialGravityFieldGroup](https://newkrok.github.io/nape-js/api/classes/RadialGravityFieldGroup.html): Point-source gravity field with `inverse-square` / `inverse` / `constant` / custom falloff, `maxRadius` / `softening` / `minRadius`, body filter, mass scaling — replaces hand-rolled `body.force = ...` loops for orbital, planet, and multi-body scenarios
|
|
104
|
+
- `buildTilemapBody` / `meshTilemap` / `tiledLayerToGrid` / `ldtkLayerToGrid`: Greedy-meshed collision body from a 2D tile grid (5–50× fewer shapes vs one-polygon-per-cell), with built-in parsers for Tiled JSON tile layers and LDtk IntGrid layers
|
|
105
|
+
- [TriggerZone](https://newkrok.github.io/nape-js/api/classes/TriggerZone.html): Sensor zone with `onEnter` / `onExit` callbacks — wraps the BEGIN/END `InteractionListener` plumbing
|
|
106
|
+
- [createConcaveBody](https://newkrok.github.io/nape-js/api/functions/createConcaveBody.html): Decomposes a concave outline into convex polygons and packs them into a single body
|
|
107
|
+
|
|
98
108
|
## Enums
|
|
99
109
|
|
|
100
110
|
- [BodyType](https://newkrok.github.io/nape-js/api/classes/BodyType.html): STATIC, DYNAMIC, KINEMATIC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newkrok/nape-js",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.31.0",
|
|
4
4
|
"description": "High-performance 2D physics engine for TypeScript & JavaScript — rigid bodies, constraints, fluid simulation, raycasting, and deterministic multiplayer. Tree-shakeable, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|