diff --git a/packages/examples/src/examples/platformer/entities/HUD.ts b/packages/examples/src/examples/platformer/entities/HUD.ts index 4fb735c4b..4347bd4bf 100644 --- a/packages/examples/src/examples/platformer/entities/HUD.ts +++ b/packages/examples/src/examples/platformer/entities/HUD.ts @@ -165,8 +165,10 @@ class UIContainer extends Container { // Use screen coordinates this.floating = true; - // make sure our object is always draw first - this.z = Number.POSITIVE_INFINITY; + // Draw order comes from the `addChild(child, z)` in `play.ts`. + // `renderable.z` is not a property — the accessor is `depth`, an alias + // for `pos.z` — so assigning it here only ever made an expando that + // nothing read, and the container was ordered by `autoDepth` instead. // give a name this.name = "HUD"; diff --git a/packages/examples/src/examples/platformer/entities/controls.ts b/packages/examples/src/examples/platformer/entities/controls.ts index 72baa0ead..588c881d5 100644 --- a/packages/examples/src/examples/platformer/entities/controls.ts +++ b/packages/examples/src/examples/platformer/entities/controls.ts @@ -213,8 +213,10 @@ export class VirtualJoypad extends Container { // Use screen coordinates this.floating = true; - // make sure our object is always draw first - this.z = Number.POSITIVE_INFINITY; + // Draw order comes from the `addChild(child, z)` in `play.ts`. + // `renderable.z` is not a property — the accessor is `depth`, an alias + // for `pos.z` — so assigning it here only ever made an expando that + // nothing read, and the container was ordered by `autoDepth` instead. // give a name this.name = "VirtualJoypad"; diff --git a/packages/examples/src/examples/platformer/play.ts b/packages/examples/src/examples/platformer/play.ts index e27bdbc51..7b6b00d05 100644 --- a/packages/examples/src/examples/platformer/play.ts +++ b/packages/examples/src/examples/platformer/play.ts @@ -17,6 +17,9 @@ import UIContainer from "./entities/HUD"; import { MinimapCamera } from "./entities/minimap"; import { gameState } from "./gameState"; +/** draw order for the screen-space overlays — above every level layer */ +const HUD_Z = 100; + export class PlayScreen extends Stage { private virtualJoypad?: VirtualJoypad; private HUD?: UIContainer; @@ -40,14 +43,15 @@ export class PlayScreen extends Stage { if (typeof this.HUD === "undefined") { this.HUD = new UIContainer(); } - app.world.addChild(this.HUD); + // explicit z: the HUD draws over the level + app.world.addChild(this.HUD, HUD_Z); // display if debugPanel is enabled or on mobile if (plugin.cache.debugPanel?.panel.visible || device.touch) { if (typeof this.virtualJoypad === "undefined") { this.virtualJoypad = new VirtualJoypad(); } - app.world.addChild(this.virtualJoypad); + app.world.addChild(this.virtualJoypad, HUD_Z); } // vignette post-effect + built-in color grading (always applied last). diff --git a/packages/melonjs/skills/melonjs-performance/SKILL.md b/packages/melonjs/skills/melonjs-performance/SKILL.md index 6cf5bbe0b..9198360af 100644 --- a/packages/melonjs/skills/melonjs-performance/SKILL.md +++ b/packages/melonjs/skills/melonjs-performance/SKILL.md @@ -81,11 +81,17 @@ Two rules that cause subtle bugs when missed: successfully recycled object never sees it. Pair event subscriptions with `onActivateEvent` / `onDeactivateEvent` instead, or you leak handlers. -Engine classes are poolable too: `pool.pull("Tween", target)`. The registered -names carry no `me.` prefix — `Entity`, `Collectable`, `Trigger`, `Light2d`, +Engine classes are poolable too: `pool.pull("Tween", target)`. The canonical +names are unprefixed — `Entity`, `Collectable`, `Trigger`, `Light2d`, `Particle`, `Sprite`, `NineSliceSprite`, `Renderable`, `Text`, `BitmapText`, `ImageLayer`, `Tween`, `ColorLayer`. +`pool.register` additionally aliases every name under an `me.` prefix, pointing +at the same entry, so `pool.pull("me.Tween")` resolves identically — and the +same alias is registered with the Tiled object factory, which is why a map +authored against melonJS 1.x still finds its classes. Prefer the unprefixed +name in new code; do not "correct" an `me.`-prefixed one, it is not broken. + ## `scale()` is multiplicative A pooled sprite arrives carrying its previous life's transform, and calling diff --git a/packages/melonjs/skills/melonjs-physics/SKILL.md b/packages/melonjs/skills/melonjs-physics/SKILL.md index 3eb53436c..1c1736ca3 100644 --- a/packages/melonjs/skills/melonjs-physics/SKILL.md +++ b/packages/melonjs/skills/melonjs-physics/SKILL.md @@ -120,6 +120,19 @@ this.body.setVelocity(vx, vy); this.body.force.x = this.body.maxVel.x; ``` +`maxVel` is what turns a force into a speed, so the two are set together. Use +the setters rather than writing the vectors — both take an optional THIRD +argument for the depth axis, left unchanged when omitted, so a 2D call keeps +its behaviour: + +```js +this.body.setMaxVelocity(3, 15); // walk speed, jump speed +this.body.setFriction(0.4, 0); // ground drag, none vertically +``` + +`friction` here is per-axis per-step velocity damping, not a surface +coefficient — see the built-in quirks below. + Under matter and planck, `syncFromPhysics()` copies the engine body's position back onto `renderable.pos` after every step, so a direct write is simply erased — no error. The built-in world integrates `pos` in place instead, so a write diff --git a/packages/melonjs/skills/melonjs-ui-and-text/SKILL.md b/packages/melonjs/skills/melonjs-ui-and-text/SKILL.md index 87225033b..616260796 100644 --- a/packages/melonjs/skills/melonjs-ui-and-text/SKILL.md +++ b/packages/melonjs/skills/melonjs-ui-and-text/SKILL.md @@ -129,6 +129,40 @@ Three things matter here: real accessor is `depth` (an alias for `pos.z`); `addChild(child, z)` sets it for you. +### Buttons: extend the handlers, do not bind listeners + +`UISpriteElement` is a `Sprite` that already registers itself for pointer +events, so a button is made by overriding methods rather than by wiring +`registerPointerEvent`: + +| handler | fires | returns | +|---|---|---| +| `onClick(event)` | pressed | `false` to stop the event propagating | +| `onRelease(event)` | pressed and released | `false` to stop propagating | +| `onOver(event)` | pointer enters | — | +| `onOut(event)` | pointer leaves | — | +| `onHold()` | pressed and held | — | + +```js +class MuteButton extends UISpriteElement { + constructor(x, y) { + super(x, y, { image: atlas, region: "speaker.png" }); + this.setOpacity(0.5); + } + onOver() { this.setOpacity(1.0); } + onOut() { this.setOpacity(0.5); } + onClick() { + audio.muteAll(); + return false; // consumed — do not fall through to the world + } +} +``` + +The pointer still has to reach it: a renderable with `isKinematic = true` — the +default on a plain `Renderable` — is skipped by the broadphase and receives +nothing. `UISpriteElement` and `UIBaseElement` clear it for you; anything else +you make clickable has to clear it itself. + ### In a 3D scene, a HUD needs a SMALL depth `floating` opts a renderable out of the camera transform. It does **not** opt it