Skip to content

jengine-swarm

Flocking / swarm AI for jEngine - spatial-grid boids that scale to thousands of entities.

KindBuilt-in: linked into the stock runtime and editor viewport
Sourcepackages/jengine-swarm
Version0.1.0
Entry pointjengine_swarm::SwarmPackage

The stock runtime, web build and editor viewport are compiled with this package, so there is nothing to link. A project that has a settings.enabled_packages list must name it there, since that list is an allow-list once it exists; a project without the key gets every built-in.

{ "settings": { "enabled_packages": ["jengine-swarm"] } }

A scene that uses its components must be a v2 scene ("version": 2): a v1 scene has no field for a package component and keeps it as inert custom data.

Scene keyRust type
swarm_spawnerSwarmSpawnerpackage only: needs a v2 scene
boidBoidpackage only: needs a v2 scene
  • projects/engine-test-lab/assets/scenes/131-swarm-steering.scene
  • projects/engine-test-lab/assets/scenes/61-swarm.scene
  • projects/visual-showcase/assets/scenes/17-murmuration.scene

From packages/jengine-swarm/src/lib.rs.

Swarm AI for jEngine — flocking that scales to thousands of entities.

A jEngine package: it registers its own components and a system, and the runtime’s PackageHost runs that system each frame. See docs/writing-packages.md.

One entity carries a SwarmSpawner. The package’s system keeps the boid population matched to the spawner’s count — spawning and despawning as needed — then flocks them with a spatial grid (see flock).

Spawning from inside the package rather than from a script is deliberate. Creating thousands of entities through the script bridge would be thousands of deferred commands per frame, and the whole point here is to measure the simulation, not the bridge.

A script sets Entity.set_custom(spawner, "Swarm", "count", n). The system reads that override in preference to the authored value.

The same way, and in every runtime: Swarm/target_x, target_y and target_pull draw the flock toward a point (a murmuration wheeling over its roost), and threat_x, threat_y, threat_radius make every boid inside the radius flee it (a falcon through a murmuration). The authored target_pull and threat_strength are the defaults a script’s values override; with neither set the flock only keeps to its area, as it always did.

Steering goes through CustomComponents rather than script commands of its own. That choice predates register_script_commands - when it was made, a package’s script functions were installed by the native Lua bridge only - and it still has one advantage: a script can read the values back with Entity.get_custom, which a command (returning nothing) cannot offer.

It writes Transform directly instead of steering a Velocity2D and letting the engine integrate. The runtimes order their velocity pass differently relative to the package phases — native integrates after PreUpdate, the editor viewport before it — so relying on that would make the same scene behave differently in each, which is useless for a scene whose job is comparing their frame rates.