Context
melonJS has no 3D shadow system. For 2.5D scenes (paper-thin billboard characters), characters read much better with a simple blob shadow on the ground. Today that's hand-rolled (a flat dark quad / Sprite3d with billboard: false, or a tinted Mesh) — see the "Shadows" caveat in 2.5D Games.
Why a fake shadow is the right answer here, not a shortcut
Worth stating explicitly, since "blob shadow" sounds like a placeholder for real shadows:
A shadow map would be actively wrong for this art style. A paper-thin billboard casts a paper-thin shadow — a line — which reads as broken rather than realistic. What the player needs from a shadow here is contact: where the character is standing, and how far off the ground they are mid-jump. A blob conveys exactly that.
It is also the wrong cost. Real shadows mean a depth pass per light, a shadow map texture, light-space matrices, and PCF sampling in the lit shaders on both backends — to end up looking worse for this case. Out of scope, and pointing the wrong way.
Proposal — the sprite draws its own shadow
The technique is the blob; the design decision is ownership.
The obvious shape — a helper that spawns a paired shadow renderable and syncs it to its target each frame — is the part that would be a hack: two scene-graph nodes for one conceptual object, a depth-sort relationship to get right by hand, teardown that can leak, and a shadow that desyncs the moment someone moves the sprite without going through the helper.
Instead, Sprite3d emits a second, ground-projected quad as part of its own draw. It already emits one quad; a second costs no new render pass, no new node, and nothing to keep in sync.
const hero = new Sprite3d(x, y, { image: "hero", castGroundShadow: true, // or a settings object for size / opacity / fade });
This removes two of the fiddly problems outright:
- No z-fighting decision — the sprite places its own shadow relative to itself, rather than a separate object guessing at the floor plane.
- No sort-order problem — both quads come out of one draw, so the shadow cannot land in front of the character.
Open sub-decisions:
- Size/opacity falling off with height above the ground (a jumping character's shadow shrinking and fading is most of the readability win).
- How the ground plane is established: a fixed Y, or a per-sprite
groundY. - A soft-edged radial texture (generated once, shared) versus an alpha-falloff in the shader.
Acceptance
- One setting renders a soft blob under a character and follows it, with no second renderable to manage.
- Works under
Camera3d; the shadow sits on the floor, under the sprite, with no z-fighting and no sort inversion. - A sprite with the setting off is byte-identical to today.
- Example/wiki snippet.
Scope note: this is a fake shadow helper, not real shadow mapping (out of scope, and deliberately so — see above).