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 |