Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,30 +163,29 @@ Browse all examples [here](https://melonjs.github.io/melonJS/examples/)
### Basic Hello World Example

```JavaScript
import * as me from "https://cdn.jsdelivr.net/npm/melonjs/+esm";

me.device.onReady(function () {
// initialize the display canvas once the device/browser is ready
if (!me.video.init(1218, 562, {parent : "screen", scale : "auto"})) {
alert("Your browser does not support HTML5 canvas.");
return;
}

// set a gray background color
me.game.world.backgroundColor.parseCSS("#202020");

// add a font text display object
me.game.world.addChild(new me.Text(609, 281, {
font: "Arial",
size: 160,
fillStyle: "#FFFFFF",
textBaseline : "middle",
textAlign : "center",
text : "Hello World !"
}));
import { Application, Text } from "https://cdn.jsdelivr.net/npm/melonjs/+esm";

// create a new melonJS application
const app = new Application(1218, 562, {
parent: "screen",
scale: "auto",
backgroundColor: "#202020",
});

// set a gray background color
Comment on lines +169 to +175
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this example, backgroundColor passed to new Application(...) only affects the parent element CSS background (per ApplicationSettings docs), while app.world.backgroundColor.parseCSS(...) controls the rendered clear color. Having both (and the comment “set a gray background color”) is confusing—consider either removing the option or adjusting the comments to reflect the distinction.

Suggested change
const app = new Application(1218, 562, {
parent: "screen",
scale: "auto",
backgroundColor: "#202020",
});
// set a gray background color
// `backgroundColor` here sets the parent element CSS background
const app = new Application(1218, 562, {
parent: "screen",
scale: "auto",
backgroundColor: "#202020",
});
// set the rendered world clear color to gray

Copilot uses AI. Check for mistakes.
app.world.backgroundColor.parseCSS("#202020");

// add a font text display object
app.world.addChild(new Text(609, 281, {
font: "Arial",
size: 160,
fillStyle: "#FFFFFF",
textBaseline: "middle",
textAlign: "center",
text: "Hello World !",
}));
```
> Simple hello world using melonJS 2
> Simple hello world using melonJS

Documentation
-------------------------------------------------------------------------------
Expand Down
47 changes: 18 additions & 29 deletions packages/examples/src/examples/platformer/createGame.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DebugPanelPlugin } from "@melonjs/debug-plugin";
import {
Application,
audio,
device,
event,
Expand All @@ -9,7 +10,6 @@ import {
pool,
state,
TextureAtlas,
video,
} from "melonjs";
import { CoinEntity } from "./entities/coin.js";
import { FlyEnemyEntity, SlimeEnemyEntity } from "./entities/enemies.js";
Expand All @@ -19,63 +19,52 @@ import { PlayScreen } from "./play.js";
import { resources } from "./resources.js";

export const createGame = () => {
// init the video
if (
!video.init(800, 600, {
parent: "screen",
scaleMethod: "flex-width",
renderer: video.AUTO,
preferWebGL1: false,
depthTest: "z-buffer",
subPixel: false,
})
) {
alert("Your browser does not support HTML5 canvas.");
return;
}
// create a new melonJS Application
const app = new Application(800, 600, {
parent: "screen",
scaleMethod: "flex-width",
renderer: 2, // AUTO
preferWebGL1: false,
depthTest: "z-buffer",
subPixel: false,
});

// register the debug plugin
plugin.register(DebugPanelPlugin, "debugPanel");

// initialize the "sound engine"
// initialize the sound engine
audio.init("mp3,ogg");

// allow cross-origin for image/texture loading
loader.setOptions({ crossOrigin: "anonymous" });

// set all ressources to be loaded
// preload all resources
loader.preload(resources, () => {
// set the "Play/Ingame" Screen Object
// set the Play screen
state.set(state.PLAY, new PlayScreen());

// set the fade transition effect
state.transition("fade", "#FFFFFF", 250);

// register our objects entity in the object pool
// register entity classes in the object pool
pool.register("mainPlayer", PlayerEntity);
pool.register("SlimeEntity", SlimeEnemyEntity);
pool.register("FlyEntity", FlyEnemyEntity);
pool.register("CoinEntity", CoinEntity, true);

// load the texture atlas file
// this will be used by renderable object later
// load the texture atlas
gameState.texture = new TextureAtlas(
loader.getJSON("texture"),
loader.getImage("texture"),
);

// add some keyboard shortcuts
event.on(event.KEYDOWN, (_action, keyCode /*, edge */) => {
// change global volume setting
// keyboard shortcuts for volume and fullscreen
event.on(event.KEYDOWN, (_action, keyCode) => {
if (keyCode === input.KEY.PLUS) {
// increase volume
audio.setVolume(audio.getVolume() + 0.1);
} else if (keyCode === input.KEY.MINUS) {
// decrease volume
audio.setVolume(audio.getVolume() - 0.1);
}

// toggle fullscreen on/off
if (keyCode === input.KEY.F) {
if (!device.isFullscreen()) {
device.requestFullscreen();
Expand All @@ -85,7 +74,7 @@ export const createGame = () => {
}
});

// switch to PLAY state
// switch to the Play state
state.change(state.PLAY);
});
};
28 changes: 17 additions & 11 deletions packages/examples/src/examples/platformer/entities/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
audio,
Body,
collision,
game,
input,
level,
Rect,
Expand Down Expand Up @@ -61,9 +60,6 @@ export class PlayerEntity extends Sprite {

this.multipleJump = 1;

// set the viewport to follow this renderable on both axis, and enable damping
game.viewport.follow(this, game.viewport.AXIS.BOTH, 0.1);

// enable keyboard
input.bindKey(input.KEY.LEFT, "left");
input.bindKey(input.KEY.RIGHT, "right");
Expand Down Expand Up @@ -150,7 +146,16 @@ export class PlayerEntity extends Sprite {
}

/**
** update the force applied
* called when added to the game world
*/
onActivateEvent() {
const app = this.parentApp;
// set the viewport to follow this renderable on both axis, and enable damping
app.viewport.follow(this, app.viewport.AXIS.BOTH, 0.1);
}

/**
* update the force applied
*/
update(dt) {
if (input.isKeyPressed("left")) {
Expand Down Expand Up @@ -192,12 +197,13 @@ export class PlayerEntity extends Sprite {

// check if we fell into a hole
if (!this.inViewport && this.getBounds().top > video.renderer.height) {
const app = this.parentApp;
// if yes reset the game
game.world.removeChild(this);
game.viewport.fadeIn("#fff", 150, () => {
app.world.removeChild(this);
app.viewport.fadeIn("#fff", 150, () => {
audio.play("die", false);
level.reload();
game.viewport.fadeOut("#fff", 150);
app.viewport.fadeOut("#fff", 150);
});
return true;
}
Expand All @@ -211,7 +217,7 @@ export class PlayerEntity extends Sprite {
}

/**
* colision handler
* collision handler
*/
onCollision(response, other) {
switch (other.body.collisionType) {
Expand All @@ -228,7 +234,7 @@ export class PlayerEntity extends Sprite {
) {
// Disable collision on the x axis
response.overlapV.x = 0;
// Repond to the platform (it is solid)
// Respond to the platform (it is solid)
return true;
}
// Do not respond to the platform (pass through)
Expand Down Expand Up @@ -286,7 +292,7 @@ export class PlayerEntity extends Sprite {
});

// flash the screen
game.viewport.fadeIn("#FFFFFF", 75);
this.parentApp.viewport.fadeIn("#FFFFFF", 75);
audio.play("die", false);
}
}
Expand Down
16 changes: 8 additions & 8 deletions packages/examples/src/examples/platformer/play.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { audio, device, game, level, plugin, Stage } from "melonjs";
import { type Application, audio, device, level, plugin, Stage } from "melonjs";
import { VirtualJoypad } from "./entities/controls";
import UIContainer from "./entities/HUD";
import { MinimapCamera } from "./entities/minimap";
Expand All @@ -11,7 +11,7 @@ export class PlayScreen extends Stage {
/**
* action to perform on state change
*/
override onResetEvent() {
override onResetEvent(app: Application) {
// load a level
level.load("map1");

Expand All @@ -27,14 +27,14 @@ export class PlayScreen extends Stage {
if (typeof this.HUD === "undefined") {
this.HUD = new UIContainer();
}
game.world.addChild(this.HUD);
app.world.addChild(this.HUD);

// 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();
}
game.world.addChild(this.virtualJoypad);
app.world.addChild(this.virtualJoypad);
}

// play some music
Expand All @@ -44,15 +44,15 @@ export class PlayScreen extends Stage {
/**
* action to perform on state change
*/
override onDestroyEvent() {
override onDestroyEvent(app: Application) {
// remove the HUD from the game world
if (this.HUD) {
game.world.removeChild(this.HUD);
app.world.removeChild(this.HUD);
}

// remove the joypad if initially added
if (this.virtualJoypad && game.world.hasChild(this.virtualJoypad)) {
game.world.removeChild(this.virtualJoypad);
if (this.virtualJoypad && app.world.hasChild(this.virtualJoypad)) {
app.world.removeChild(this.virtualJoypad);
}

// stop some music
Expand Down
26 changes: 10 additions & 16 deletions packages/examples/src/examples/ui/ExampleUI.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Application as App,
type Application,
ColorLayer,
game,
loader,
Stage,
state,
Expand Down Expand Up @@ -41,7 +42,6 @@ class ButtonUI extends UISpriteElement {
fillStyle: "black",
textAlign: "center",
textBaseline: "middle",
offScreenCanvas: video.renderer.WebGLVersion >= 1,
});
}

Expand Down Expand Up @@ -123,7 +123,6 @@ class CheckBoxUI extends UISpriteElement {
textAlign: "left",
textBaseline: "middle",
text: offLabel,
offScreenCanvas: true,
});

this.getBounds().width += this.font.measureText().width;
Expand Down Expand Up @@ -204,8 +203,8 @@ class UIContainer extends UIBaseElement {
}

class PlayScreen extends Stage {
override onResetEvent() {
game.world.addChild(
override onResetEvent(app: Application) {
app.world.addChild(
new ColorLayer("background", "rgba(248, 194, 40, 1.0)"),
0,
);
Expand Down Expand Up @@ -243,21 +242,16 @@ class PlayScreen extends Stage {
panel.addChild(new ButtonUI(30, 250, "green", "Accept"));
panel.addChild(new ButtonUI(230, 250, "yellow", "Cancel"));

game.world.addChild(panel, 1);
app.world.addChild(panel, 1);
}
}

const createGame = () => {
if (
!video.init(800, 600, {
parent: "screen",
scale: "auto",
scaleMethod: "flex-width",
})
) {
alert("Your browser does not support HTML5 canvas.");
return;
}
const app = new App(800, 600, {
parent: "screen",
scale: "auto",
scaleMethod: "flex-width",
});

const resources = [
{ name: "UI_Assets-0", type: "image", src: `${base}img/UI_Assets-0.png` },
Expand Down
8 changes: 8 additions & 0 deletions packages/melonjs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
- Application: new `canvas` getter, `resize()`, and `destroy()` convenience methods
- Application: `GAME_INIT` event now passes the Application instance as parameter
- Stage: `onResetEvent(app, ...args)` now receives the Application instance as first parameter, followed by any extra arguments from `state.change()`
- Stage: `onDestroyEvent(app)` now receives the Application instance as parameter
- Container: default dimensions are now `Infinity` (no intrinsic size, no clipping) — removes dependency on `game.viewport`. `anchorPoint` is always `(0, 0)` as containers act as grouping/transform nodes
- ImageLayer: decoupled from `game` singleton — uses `parentApp` for viewport and renderer access; `resize`, `createPattern` and event listeners deferred to `onActivateEvent`
- TMXTileMap: `addTo()` resolves viewport from the container tree via `getRootAncestor().app` instead of `game.viewport`
- Input: pointer and pointerevent modules now receive the Application instance via `GAME_INIT` event instead of importing the `game` singleton
- video: `video.renderer` and `video.init()` are now deprecated — use `new Application(width, height, options)` and `app.renderer` instead. `video.renderer` is kept in sync via `VIDEO_INIT` for backward compatibility
- EventEmitter: native context parameter support — `addListener(event, fn, context)` and `addListenerOnce(event, fn, context)` now accept an optional context, eliminating `.bind()` closure overhead and enabling proper `removeListener()` by original function reference
- EventEmitter: `event.on()` and `event.once()` no longer create `.bind()` closures when a context is provided

Expand All @@ -25,6 +31,8 @@
- Application: prevent white flash on load by setting a black background on the parent element when no background is defined
- WebGLRenderer: `setBlendMode()` now tracks the `premultipliedAlpha` flag — previously only the mode name was checked, causing incorrect GL blend function when mixing PMA and non-PMA textures with the same blend mode
- TMX: fix crash in `getObjects(false)` when a map contains an empty object group (Container.children lazily initialized)
- Container: fix `updateBounds()` producing NaN when container has Infinity dimensions (skip parent bounds computation for non-finite containers, derive bounds from children only)
- Container: fix circular import in `BitmapTextData` pool registration (`pool.ts` ↔ `bitmaptextdata.ts`)
- EventEmitter: `removeAllListeners()` now correctly clears once-listeners (previously only cleared regular listeners)
- Loader: fix undefined `crossOrigin` variable in script parser, unsafe regex match in video parser, missing error parameter in video/fontface error callbacks, `fetchData` Promise constructor antipattern and silent error swallowing

Expand Down
Loading
Loading