logo

Babylon.js Market

Asteroid

shooter · space-combat-sim

Asteroid

1
Rubble the Space-Rock WarriorBonus figureRubble the Space-Rock Warrior
Unlock

Install with the CLI:

bjs download Asteroid

Asteroid

An oriented-bounding-box space rock for a 6-DOF shooter — a hitbox that tracks the rock's silhouette, shrinks as you shoot it, and kills anything that rams it.

What it does

AsteroidComponent is pure data: per-axis half-extents (hx/hy/hz) baked from the same seed the renderer stretches the procedural rock mesh with, so the box hugs the silhouette instead of a generous bounding sphere that would blow you up in empty space. radius is the mean radius (broad-phase culling + AI avoidance); the original* fields are the build-time size the live values shrink away from. AsteroidCollisionSystem runs two passes per frame. Bullets vs rock (priority 55, ahead of the generic bullet-impact pass): it sweep-samples each round into the rock's local frame, AABB-tests, and on a hit consumes the shot, emits bullet.hit, punches a surface ripple, imparts a tiny radius-scaled push + spin, shrinks the rock 1.5% of its original radius, and despawns it below 40% of original size. Ships vs rock: the ship is a sphere, so it grows each half-extent by the ship radius and point-tests — a rammer punches a big ripple and dies through the shared destroyShip path while the rock flies on. The impact ripple ring buffer is non-serializable visual state, so it lives in a System-side WeakMap keyed by the component, never on the pure-data component.

Use it in a scene

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add Asteroid
    bjs download Asteroid

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "Asteroid1": {
    "tags": ["asteroid"],
    "components": {
      "Transform6DOF": { "x": 40, "y": 6, "z": 150 },
      "Asteroid": { "radius": 58, "hx": 59, "hy": 36, "hz": 80 },
      "Renderable": {
        "shape": "asteroid",
        "sx": 44, "sy": 44, "sz": 44,
        "r": 0.4, "g": 0.33, "b": 0.28,
        "meshSeed": 1337
      }
    }
  }
}

Props

  • radius (number, default 10) — current mean radius; shrinks per bullet hit. Drives broad-phase culling and the shrink/despawn ratio.
  • hx / hy / hz (number, each default = radius) — current OBB half-extents in the rock's local frame; all rescale in lockstep with radius.
  • originalRadius (number, default = radius) — build-time reference; radius shrinks toward it and 40% of it is the despawn floor.
  • originalHx / originalHy / originalHz (number, each default = the matching live extent) — build-time references; live extent = original * (radius / originalRadius).

Events

  • Emits bullet.hit (BulletEvents.HIT, re-used from the Bullet component — Asteroid declares no events constant of its own) on each bullet-vs-rock contact — { shooterId, targetId, part: 'asteroid', partHp: 9999, x, y, z, dirX, dirY, dirZ }. This is the observer channel (hit-flash, SFX); the rock's shrink/despawn is applied inline, not routed through a Health event.
  • Ships-vs-rock contacts route the rammer through the shared destroyShip helper (from the SpaceShooterHealth component), which owns its own kill/debris events — AsteroidCollisionSystem itself emits nothing extra there.
  • Listens for nothing. Both passes are driven off entity queries, not the bus: asteroids are gathered by the asteroid tag, bullets by SpaceShooterBullet, and rammers by the system query.

Dependencies

Notes

  • The asteroid tag is required. AsteroidCollisionSystem finds rocks with world.getEntitiesByTag('asteroid'), not by the AsteroidComponent alone — a rock carrying the component but missing the tag is invisible to every collision pass. This is the #1 gotcha.
  • Hitbox and mesh scale are separate numbers: Renderable.sx/sy/sz size the drawn rock, Asteroid.hx/hy/hz are the collision half-extents. Keep them in the same proportion when hand-tuning or the box drifts off the silhouette.
  • Shrink is per bullet: radius -= originalRadius * 0.015 (extents rescaled to radius / originalRadius each hit); the rock despawns once radius < originalRadius * 0.40. Want a durable rock? Give it a large originalRadius.
  • Runtime state stays off the component — the ripple buffer lives in a System-side WeakMap, so serialize() round-trips cleanly with only the eight size numbers.

More like this

Parts
SpaceShooterBullet
SpaceShooterHealth
Transform6DOF
Velocity6DOF
AiPilot
Animation
ArcCamera
Bullet
CameraFollow
ChaseCamera6DOF
ChaseCameraTarget
DirectionalLight
Enemy
EnemySpawner
EnvironmentTexture
FlightIntent
FreighterCar
FreighterChain
FreighterHead
GameConfig
Health
HemisphericLight
Jump
KeyboardInput
KeyboardMover
Lifetime
LineOfSight
MachineGun
Mesh
MeshPrimitive
Missile
MissileLauncher
MissionDirector
MoneyField
Movement
Obstacle
ObstacleField
OilBlob
Physics
PlayerFlightInput
PlayerInput
PlayerWalkAnimator
RespawnTimer
Score
Shadow
ShipFlight
ShipLoadout
ShooterCamera
SkeletonAnimator
SpaceDust
SpaceShooterScore
TwinStickShooter
WaveDirector
Waypoint
WaypointTrack
WorldOriginAnchor

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search