Skip to content

Commit 0b4e3b1

Browse files
obiotclaude
andauthored
Fix sprite flicker broken with multi-camera setups (#1357)
* Fix sprite flicker broken with multi-camera setups The flicker visibility was toggled per draw() call, so with 2 cameras (e.g., default + minimap) it toggled twice per frame, canceling out. Replace the per-frame boolean toggle with time-based flickering: - Track elapsed time in update() instead of toggling state - Compute visibility in draw() from elapsed time (read-only, no mutation) - Consistent ~15 flashes/sec regardless of frame rate or camera count Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review: extract constant, fix initial visibility, reset elapsed - Extract FLICKER_INTERVAL_MS constant (33ms, ~15 flashes/sec) - Invert parity check so first frame starts visible, not invisible - Always reset elapsed when flicker() is called (even if already flickering) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5ebd52e commit 0b4e3b1

File tree

2 files changed

+22
-10
lines changed

2 files changed

+22
-10
lines changed

packages/melonjs/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
- Path2D: fix `quadraticCurveTo()` and `bezierCurveTo()` segment count — was using `arcResolution` directly (2 segments), now computes adaptive segment count based on control polygon length for smooth curves.
3232
- Text: fix textBaseline y offset for multiline text — "bottom"/"middle" used single line height instead of total text height, causing misaligned bounding boxes
3333
- BitmapText: fix bounds offset direction for textAlign/textBaseline — bounds were shifted in the wrong direction for "right"/"center"/"bottom"/"middle"
34+
- BitmapText: fix bounding box width — last glyph now uses `max(xadvance, xoffset + width)` to capture full visual extent
35+
- BitmapText: fix bounding box height — uses actual glyph extents (`maxBottom - minTop`) instead of `capHeight` which was too short for glyphs with descenders
36+
- BitmapText: fix baseline positioning — "middle"/"bottom"/"alphabetic"/"ideographic" shifts now use real glyph metrics and total text height, correctly centering and aligning text on baseline reference points
37+
- BitmapText: fix bounding box y offset — box starts at first visible glyph pixel (`glyphMinTop * scale`) instead of draw origin
38+
- BitmapText: optimize bounds calculation — precompute `glyphMinTop`/`glyphMaxBottom` once in font parsing; cache `measureText` results in `setText`/`resize` instead of recomputing on every `updateBounds`
39+
- Camera2d: fix floating containers with Infinity bounds not rendering — containers with default `Infinity` dimensions had cleared bounds, causing `isVisible()` to return false and preventing update/draw of children (e.g., HUD elements)
40+
- Sprite: fix `flicker()` not working with multi-camera setups — visibility was toggled per draw call (once per camera), so with 2 cameras the toggle canceled out. Now uses time-based flickering (~15 flashes/sec) that is frame-rate independent and multi-camera safe
3441
- Application: `Object.assign(defaultApplicationSettings, options)` mutated the shared defaults object in both `Application.init()` and `video.init()` — creating multiple Application instances would corrupt settings. Fixed with object spread.
3542
- Text/Light2d: fix invalid `pool.push` on CanvasRenderTarget instances that were never pool-registered (would throw on destroy)
3643
- CanvasRenderTarget: `destroy(renderer)` now properly cleans up WebGL GPU textures and cache entries (previously leaked in Light2d)

packages/melonjs/src/renderable/sprite.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { on } from "../system/event.ts";
66
import { TextureAtlas } from "./../video/texture/atlas.js";
77
import Renderable from "./renderable.js";
88

9+
// flicker interval in ms (~15 flashes per second)
10+
const FLICKER_INTERVAL_MS = 33;
11+
912
/**
1013
* additional import for TypeScript
1114
* @import {Vector2d} from "../math/vector2d.js";
@@ -141,7 +144,7 @@ export default class Sprite extends Renderable {
141144
isFlickering: false,
142145
duration: 0,
143146
callback: null,
144-
state: false,
147+
elapsed: 0,
145148
};
146149

147150
// set the proper image/texture to use
@@ -329,8 +332,10 @@ export default class Sprite extends Renderable {
329332
if (this._flicker.duration <= 0) {
330333
this._flicker.isFlickering = false;
331334
this._flicker.callback = undefined;
332-
} else if (!this._flicker.isFlickering) {
335+
this._flicker.elapsed = 0;
336+
} else {
333337
this._flicker.callback = callback;
338+
this._flicker.elapsed = 0;
334339
this._flicker.isFlickering = true;
335340
}
336341
return this;
@@ -677,8 +682,8 @@ export default class Sprite extends Renderable {
677682

678683
//update the "flickering" state if necessary
679684
if (this._flicker.isFlickering) {
680-
this._flicker.duration -= dt;
681-
if (this._flicker.duration < 0) {
685+
this._flicker.elapsed += dt;
686+
if (this._flicker.elapsed >= this._flicker.duration) {
682687
if (typeof this._flicker.callback === "function") {
683688
this._flicker.callback();
684689
}
@@ -696,12 +701,12 @@ export default class Sprite extends Renderable {
696701
* @param {Camera2d} [viewport] - the viewport to (re)draw
697702
*/
698703
draw(renderer) {
699-
// do nothing if we are flickering
700-
if (this._flicker.isFlickering) {
701-
this._flicker.state = !this._flicker.state;
702-
if (!this._flicker.state) {
703-
return;
704-
}
704+
// do nothing if we are flickering (time-based, frame-rate independent)
705+
if (
706+
this._flicker.isFlickering &&
707+
Math.floor(this._flicker.elapsed / FLICKER_INTERVAL_MS) % 2 !== 0
708+
) {
709+
return;
705710
}
706711

707712
// the frame to draw

0 commit comments

Comments
 (0)