b3d-warhead

The explosion — the scene-side bridge to the pure AOE math in warhead.ts (see COMBAT-DESIGN.md). On detonate(center) it gathers every b3d-destroyable in the scene, resolves the blast (full damage inside fullRadius, falling linearly to 1 at blastRadius, 0 beyond), line-of-sight gates each target with a raycast (a wall between the blast and a target spares it), and applies the damage as an outward shockwave: each target's hit lands on a short delay proportional to its distance, so the effect ripples out (near things die first, far last) in step with the expanding flash — cheap (amounts + delays computed instantly, only application is staggered), not an all-at-once kill. Single-use — a projectile/bomb owns one and fires it on impact; here you can also place one and detonate it directly.

Demo

Steer the reticle with A/D + W/S (left stick), pull the right trigger (or F) to detonate there — the standard controller. Cubes inside the radius take falloff damage (they flash, and die at 0 hp); the wall blocks line of sight, so parking the reticle behind it spares the cubes there. Tune the blast in the ⚙ panel.

import { b3d, b3dController, b3dWarhead, b3dDestroyable, b3dLight, b3dSkybox, b3dGround, label3d, slider3d, toggle3d } from 'tosijs-3d'
import { orbitCam } from 'demo-utils'
import { tosi } from 'tosijs'

const { s } = tosi({ s: { damage: 20, fullRadius: 1.5, blastRadius: 5, los: true } })
const warhead = b3dWarhead({ y: 0.4, damage: s.damage, fullRadius: s.fullRadius, blastRadius: s.blastRadius })

const targets = []
for (let i = 0; i < 24; i++) {
  targets.push(b3dDestroyable({ x: (i % 6) * 1.4 - 3.5, y: 0.4, z: Math.floor(i / 6) * 1.4 - 2, size: 0.8, capacity: 8, color: '#cc4444' }))
}

// Shared reticle position + shoot edge, reachable by both sceneCreated and drive.
const state = { rx: 0, rz: -3, shootWas: false }

const scene = b3d(
  {
    gamepad: 'left_stick,right_trigger',
    scenePanelOpen: true,
    scenePanel: () => [
      label3d({ text: 'Warhead', bold: true }),
      slider3d({ label: 'damage', value: s.damage, min: 1, max: 60, step: 1 }),
      slider3d({ label: 'full radius', value: s.fullRadius, min: 0, max: 5, step: 0.1 }),
      slider3d({ label: 'blast radius', value: s.blastRadius, min: 1, max: 12, step: 0.5 }),
      toggle3d({ label: 'line of sight', value: s.los }),
    ],
    sceneCreated(el, BABYLON) {
      orbitCam(el, { alpha: -Math.PI / 2.3, beta: Math.PI / 3, radius: 16, target: [0, 0.5, 0] })
      // a wall for line-of-sight blocking
      const wall = BABYLON.MeshBuilder.CreateBox('wall', { width: 6, height: 2.5, depth: 0.4 }, el.scene)
      wall.position.set(0, 1.25, 3.2)
      const wm = new BABYLON.StandardMaterial('wm', el.scene)
      wm.diffuseColor = new BABYLON.Color3(0.4, 0.42, 0.48)
      wall.material = wm
      // a glowing reticle you steer across the ground
      const reticle = BABYLON.MeshBuilder.CreateTorus('reticle', { diameter: 1.3, thickness: 0.12 }, el.scene)
      reticle.isPickable = false
      const rmat = new BABYLON.StandardMaterial('rmat', el.scene)
      rmat.emissiveColor = new BABYLON.Color3(1, 0.85, 0.2)
      rmat.disableLighting = true
      reticle.material = rmat
      el.scene.onBeforeRenderObservable.add(() => reticle.position.set(state.rx, 0.05, state.rz))
    },
  },
  b3dLight({ y: 1, intensity: 0.85 }),
  b3dSkybox({ timeOfDay: 10 }),
  b3dGround({ width: 30, height: 30, color: '#5a6b52' }),
  b3dController({
    mapping: 'biped',
    drive(input, dt) {
      state.rx = Math.max(-6, Math.min(6, state.rx + input.turn * 7 * dt)) // A/D
      state.rz = Math.max(-5, Math.min(6, state.rz + input.forward * 7 * dt)) // W/S
      const shoot = input.shoot > 0.5 || input.sprint > 0.5
      if (shoot && !state.shootWas) {
        warhead.damage = s.damage.value
        warhead.fullRadius = s.fullRadius.value
        warhead.blastRadius = s.blastRadius.value
        warhead.los = s.los.value ? 'on' : 'off'
        warhead.x = state.rx
        warhead.z = state.rz
        warhead.detonate() // blast at the reticle (F · glass button · XR trigger)
      }
      state.shootWas = shoot
    },
  }),
  warhead,
  ...targets,
)
preview.append(scene)
tosi-b3d { width: 100%; height: 100%; }

Attributes

Attribute Default Description
damage 20 Full damage D (direct hit + within fullRadius)
fullRadius 1.5 Within this distance the blast deals full D
blastRadius 5 Falloff from D (at fullRadius) to 1 here; 0 beyond
los 'on' Line-of-sight occlusion (a wall between blast + target spares it)
x,y,z 0 Detonation point when detonate() is called with no argument