logo

Babylon.js Market

OilBlob

shooter · space-combat-sim

OilBlob

1
Unlock

Install with the CLI:

bjs download OilBlob

OilBlob

A giant permanent spherical rock for a 6-DOF shooter that answers gunfire with lethal ejecta — an "asteroid that shoots back."

What it does

OilBlobComponent is pure data — just radius and a build-time originalRadius; the blob never shrinks, so the ripples and drops ARE the entire visible response. OilBlobImpactSystem (priority 53, ahead of BulletImpactSystem's 52) runs three passes per frame. Bullets vs blob: a swept-sphere sweep against the 95%-radius collision sphere (oilBlobSphereRadius); a fresh hit emits wingman.oilblob.hit, imparts a tiny radius-scaled push + spin, and consumes the round. Ships vs blob: a rammer punches a huge ripple (amp 0.8) and dies through the shared destroyShip path; the blob is unaffected. Ripple aging: each impact plays out over ~10s of real time before it's dropped. wingman.oilblob.hit routes back into onHit, which ejects the Worthington-jet return-fire: one pooled damage: 1000 swept-sphere drop along the jet that one-shots any ship part and cascades by halving its size/speed each time it strikes another blob (dying off below 0.4u). A follow-up shot into a still-open crater is cavity-held and ejects nothing. Drops live on a World drop pool (acquire/release), and the per-blob impact ring buffer is non-serializable runtime state kept in a System-side WeakMap, never on the 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 OilBlob
    bjs download OilBlob

    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
{
  "OilBlob1": {
    "tags": ["oilblob"],
    "components": {
      "Transform6DOF": { "x": 0, "y": 0, "z": 220 },
      "OilBlob": { "radius": 55 },
      "Renderable": {
        "shape": "oil-blob",
        "sx": 55, "sy": 55, "sz": 55,
        "r": 0.17, "g": 0.15, "b": 0.12,
        "meshSeed": 2027
      }
    }
  }
}

Props

  • radius (number, default 50) — the blob's size; permanent, never shrinks on hit. The collision sphere is 95% of this (oilBlobSphereRadius), so blobs read slightly tighter than they're drawn.
  • originalRadius (number, default = radius) — build-time reference radius; defaults to whatever radius you pass.

Events

  • Emits wingman.oilblob.hit (OilBlobEvents.HIT) on every bullet, drop, or ram contact — { targetId, x, y, z, dirX, dirY, dirZ, inheritedSize?, inheritedSpeed?, amp? }. The system also listens for this (see below), so a hit and its return-fire are the two ends of one event.
  • Emits bullet.hit (BulletEvents.HIT) alongside each bullet-vs-blob hit for HUD/score parity with the asteroid path — { shooterId, targetId, part: 'asteroid', partHp, x, y, z, dirX, dirY, dirZ }.
  • Listens for wingman.oilblob.hit (OilBlobEvents.HIT) — onHit records the ripple and, on a fresh crater, ejects one pooled return-fire drop; a cavity-held follow-up ejects nothing.

Dependencies

Transform6DOF — the blob's pose; the impact passes read it to place the collision sphere and to orient each ripple. Renderable (shape: "oil-blob" + meshSeed) — the visible rock and its ripple shader; seedless it degrades to a plain sphere. Velocity6DOF (optional on the blob) — receives the bullet's tiny push/spin. The System also spawns its return-fire drops as pooled entities carrying Transform6DOF + Velocity6DOF + Lifetime + Bullet (damage 1000) + Renderable, and the ram pass needs enemy/player ships to carry Parts (destroyed via SpaceShooterHealth's destroyShip).

Notes

  • The System registers the World drop pool (and the shared debris pool for rammed ships) in onInitialize — return-fire drops are acquire/release, never per-shot createEntity.
  • Priority 53 beats BulletImpactSystem (52): a shot grazing a blob on its way to a ship is consumed by the blob first.
  • Cavity-held suppression: a follow-up shot within ~32° of a still-open crater (impact age < 0.20) holds the cavity open and ejects no drop — only fresh areas return fire.
  • Cascade: a drop that strikes another blob spawns a child at half size and speed; the chain dies below 0.4u. You need at least two blobs in the scene to see it, which is why the example ships OilBlob1 and OilBlob2.
  • Blobs are permanent by design — nothing shrinks, nothing despawns. Ripples age out over ~10s and the drops expire on their own Lifetime.
  • The impact ring buffer lives in a WeakMap keyed by the component, so serialize() stays clean (radius + originalRadius only) and save/load round-trips correctly.

More like this

Lifetime
Parts
Renderable
SpaceShooterBullet
SpaceShooterHealth
Transform6DOF
Velocity6DOF
AiPilot
Animation
ArcCamera
Asteroid
Bullet
CameraFollow
ChaseCamera6DOF
ChaseCameraTarget
DirectionalLight
Enemy
EnemySpawner
EnvironmentTexture
FlightIntent
FreighterCar
FreighterChain
FreighterHead
GameConfig
Health
HemisphericLight
Jump
KeyboardInput
KeyboardMover
LineOfSight
MachineGun
Mesh
MeshPrimitive
Missile
MissileLauncher
MissionDirector
MoneyField
Movement
Obstacle
ObstacleField
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