Skip to content

Commit 87f3404

Browse files
obiotclaude
andauthored
Add multi-camera support with minimap example (#1310)
* Add multi-camera support with minimap example Add screenX, screenY, zoom, autoResize, isDefault, worldView, setViewport, worldProjection and screenProjection to Camera2d for proper multi-camera rendering. Add visibleInAllCameras to Renderable for per-camera floating element filtering. Implement setProjection in CanvasRenderer to properly apply ortho projection as canvas 2D transform. Auto-flush in WebGL setProjection to prevent batched quads from rendering with wrong projection. Add minimap camera example to platformer with viewport highlight and player marker. Includes 95+ new unit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address Copilot review feedback on PR #1310 - Guard against duplicate minimap camera in onResetEvent - Store and cleanup CANVAS_ONRESIZE listener in minimap destroy() - Save/restore lineWidth in minimap postDraw - Validate zoom setter to reject zero/negative values (fall back to 1) 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 b572236 commit 87f3404

14 files changed

Lines changed: 1074 additions & 60 deletions

File tree

packages/examples/src/examples/platformer/createGame.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const createGame = () => {
2424
!video.init(800, 600, {
2525
parent: "screen",
2626
scaleMethod: "flex-width",
27-
renderer: video.WEBGL,
27+
renderer: video.AUTO,
2828
preferWebGL1: false,
2929
depthTest: "z-buffer",
3030
subPixel: false,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Camera2d, event, game, level } from "melonjs";
2+
3+
const MINIMAP_WIDTH = 180;
4+
const MINIMAP_HEIGHT = 100;
5+
6+
/**
7+
* A minimap camera that shows a zoomed-out view of the entire level
8+
* in the top-right corner of the screen.
9+
*/
10+
export class MinimapCamera extends Camera2d {
11+
private boundOnResize: (w: number) => void;
12+
13+
constructor() {
14+
super(0, 0, MINIMAP_WIDTH, MINIMAP_HEIGHT);
15+
this.name = "minimap";
16+
this.screenX = game.viewport.width - MINIMAP_WIDTH - 10;
17+
this.screenY = 10;
18+
19+
// prevent canvas resize from resetting this camera's dimensions/bounds
20+
this.autoResize = false;
21+
22+
// reposition on canvas resize (keep anchored to top-right)
23+
this.boundOnResize = (w: number) => {
24+
this.screenX = w - MINIMAP_WIDTH - 10;
25+
};
26+
event.on(event.CANVAS_ONRESIZE, this.boundOnResize);
27+
28+
const currentLevel = level.getCurrentLevel();
29+
if (currentLevel) {
30+
const lw = currentLevel.cols * currentLevel.tilewidth;
31+
const lh = currentLevel.rows * currentLevel.tileheight;
32+
this.setBounds(0, 0, lw, lh);
33+
this.zoom = Math.min(MINIMAP_WIDTH / lw, MINIMAP_HEIGHT / lh);
34+
}
35+
}
36+
37+
/**
38+
* Draw minimap overlays: viewport highlight, player marker, and border.
39+
*/
40+
override postDraw(renderer: any): void {
41+
const viewport = game.viewport;
42+
const screenPx = 1 / this.zoom; // 1 screen pixel in world units
43+
const savedLineWidth = renderer.lineWidth;
44+
45+
// main camera's visible area in world space
46+
const view = viewport.worldView;
47+
renderer.setGlobalAlpha(0.9);
48+
renderer.setColor("#ffffff");
49+
renderer.lineWidth = 1.5 * screenPx;
50+
renderer.strokeRect(view.left, view.top, view.width, view.height);
51+
52+
// player position marker
53+
const players = game.world.getChildByProp("name", "mainPlayer");
54+
if (players.length > 0) {
55+
const player = players[0];
56+
const markerSize = 4 * screenPx;
57+
renderer.setGlobalAlpha(1.0);
58+
renderer.setColor("#00ff00");
59+
renderer.fillEllipse(
60+
player.pos.x + player.width / 2,
61+
player.pos.y + player.height / 2,
62+
markerSize,
63+
markerSize,
64+
);
65+
}
66+
67+
// minimap border
68+
renderer.setGlobalAlpha(0.8);
69+
renderer.setColor("#ffffff");
70+
renderer.lineWidth = 2 * screenPx;
71+
renderer.strokeRect(
72+
0,
73+
0,
74+
MINIMAP_WIDTH / this.zoom,
75+
MINIMAP_HEIGHT / this.zoom,
76+
);
77+
78+
// restore lineWidth
79+
renderer.lineWidth = savedLineWidth;
80+
81+
// restore the camera context
82+
super.postDraw(renderer);
83+
}
84+
85+
/**
86+
* Cleanup event listeners.
87+
*/
88+
override destroy(): void {
89+
event.off(event.CANVAS_ONRESIZE, this.boundOnResize);
90+
super.destroy();
91+
}
92+
}

packages/examples/src/examples/platformer/play.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { audio, device, game, level, plugin, Stage } from "melonjs";
22
import { VirtualJoypad } from "./entities/controls";
33
import UIContainer from "./entities/HUD";
4+
import { MinimapCamera } from "./entities/minimap";
45
import { gameState } from "./gameState";
56

67
export class PlayScreen extends Stage {
@@ -14,6 +15,11 @@ export class PlayScreen extends Stage {
1415
// load a level
1516
level.load("map1");
1617

18+
// add a minimap camera (reuse if already present)
19+
if (!this.cameras.has("minimap")) {
20+
this.cameras.set("minimap", new MinimapCamera());
21+
}
22+
1723
// reset the score
1824
gameState.data.score = 0;
1925

packages/melonjs/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## [18.2.0] (melonJS 2)
44

55
### Added
6+
- Camera2d: added proper multi-camera support
7+
- Platformer example: minimap camera showing a zoomed-out view of the full level with viewport highlight and player marker
68

79
### Changed
810
- TypeScript: convert leaf modules to TypeScript — plugin, camera, particles emitter, state, audio
@@ -15,6 +17,7 @@
1517
- UITextButton: fix `bindKey` settings type from `string` to `string | number`
1618
- Application: fix constructor `options` parameter not being optional
1719
- Application: fix `getUriFragment()` unsafe cast by making `url` parameter optional
20+
- CanvasRenderer: `setProjection()` now properly applies the projection matrix as a canvas 2D transform
1821

1922
### Performance
2023

0 commit comments

Comments
 (0)