Skip to content

Frequently Asked Questions

Olivier Biot edited this page Apr 5, 2026 · 85 revisions

Table of Contents

  1. How to get started with melonJS
  2. How to optimize your game for performance
  3. How to package your game for desktop or mobile
  4. How to access object properties from another object
  5. How to use the object pooling feature
  6. Handling pointer events on overlapping objects
  7. Using form inputs in your game
  8. Resolving collisions properly
  9. What is the difference between video.init() and new Application()?
  10. How to use WebGL vs Canvas renderer
  11. How to handle screen resize and scaling
  12. How to debug my game
  13. How to load and switch between levels

1. How to get started with melonJS:

The fastest way to create a new game:

npm create melonjs my-game
cd my-game
npm install
npm run dev

This scaffolds a ready-to-run project with TypeScript, Vite, and the debug plugin. You can also start from the boilerplate or follow the Platformer Tutorial.

For a minimal setup without scaffolding:

import { Application, Text } from "melonjs";

const app = new Application(800, 600, {
    parent: "screen",
    scale: "auto",
});

app.world.addChild(new Text(400, 300, {
    font: "Arial",
    size: 48,
    textAlign: "center",
    text: "Hello World!",
}));

2. How to optimize your game for performance:

  • Use Object Pooling to reduce memory usage by reusing objects and lowering garbage collection pressure.
  • Use a Texture Atlas to batch draw calls. Unless you target WebGL1 only, texture sizes don't need to be power-of-two, but keeping them reasonable helps GPU memory.
  • Avoid using Text extensively during gameplay — prefer BitmapText, especially when using WebGL.
  • The game resolution directly impacts performance. Try lowering the resolution in your Application constructor.
  • If you use TexturePacker, disable texture rotation, as it forces extra rotation calculations when drawing sprites.
  • Use PNG images with alpha channel instead of the "transparent color" setting in Tiled tilesets.
  • For tile layers with very few tiles, avoid pre-rendering — sparse layers draw faster dynamically.
  • Use radians directly for rotation angles instead of converting from degrees. A full turn is Math.PI * 2, a half turn is Math.PI, a quarter turn is Math.PI * 0.5.
  • Access object properties directly (this.last_animation === "walk") instead of calling methods (this.isCurrentAnimation("walk")) when performance matters.
  • Try a lower fps rate — does your game really require 60fps?
  • Use the Chrome DevTools Performance profiler to locate bottlenecks.

3. How to package your game for desktop or mobile:

Desktop:

  • Electron — a popular Chromium-based wrapper for packaging web apps as native desktop applications (Windows, Mac, Linux).
  • Tauri — a modern, lightweight alternative to Electron using the system's native webview.
  • NW.js — a similar alternative, originally known as Node-Webkit.

Mobile:

  • Cordova — wraps your game in a native mobile app shell for iOS and Android.
  • Capacitor — a modern alternative to Cordova by the Ionic team.
  • Progressive Web App (PWA) — add a manifest and service worker to make your game installable from the browser.

Web:


4. How to access object properties from another object:

  • Get a reference to the target object using app.world.getChildByName():
// In your Stage's onResetEvent(app)
const player = app.world.getChildByName("player")[0];

// Then use it anywhere
const playerPos = player.pos;
player.doSomething();
  • getChildByName() returns an array — if you have one player object, it will be the first element.
  • Call it once and store the reference — don't call it every frame.

5. How to use the object pooling feature:

Object Pooling reduces the overhead of frequently creating and destroying objects (like bullets or particles). Instead of creating new instances each time, objects are recycled from a pool.

First, register your class with the pool (do this once, e.g. in your game setup):

import { pool } from 'melonjs';

pool.register("laser", LaserEntity, true);

The third argument true enables recycling. Your class must implement onResetEvent() to properly reinitialize its state.

Then create instances using pool.pull() instead of new:

// Pull from pool instead of new LaserEntity(...)
const myLaser = pool.pull("laser", this.pos.x, this.pos.y);
app.world.addChild(myLaser, 3);

When the object is removed from the world, it's automatically returned to the pool for reuse.

Note: classes registered with pool.register() are also automatically available as Tiled object factories — objects placed in a Tiled map with a matching class or name will be instantiated using the registered constructor.


6. Handling pointer events on overlapping objects:

When multiple objects overlap on screen, pointer events can conflict. A common example is registering events on the viewport rect from multiple objects.

The recommended approach is to register the event in a single top-level object and delegate using the event system:

import { input, event } from 'melonjs';

// Register once in a top-level object (e.g. in onResetEvent(app))
input.registerPointerEvent("pointerdown", app.viewport, (e) => {
    event.emit("pointerdown", e);
});

// Listen from any object
event.on("pointerdown", this.onPointerDown, this);

// Clean up when done
event.off("pointerdown", this.onPointerDown, this);

7. Using form inputs in your game:

You can overlay native HTML form elements on top of the melonJS canvas. This gives you built-in keyboard handling, focus management, copy/paste, and mobile virtual keyboard support.

class TextInput extends Renderable {
    constructor(x, y, type, maxLength) {
        super(x, y, maxLength * 10, 20);

        this.input = document.createElement("input");
        this.input.type = type;
        this.input.style.position = "absolute";
        this.input.style.left = `${x}px`;
        this.input.style.top = `${y}px`;
        this.input.style.zIndex = "2";

        if (type === "text") {
            this.input.maxLength = maxLength;
        }

        this.parentApp.getParentElement().appendChild(this.input);
    }

    getValue() {
        return this.input.value;
    }

    destroy() {
        this.input.remove();
        super.destroy();
    }
}

Position the parent element with position: relative so the inputs align with the canvas:

#screen {
    position: relative;
}

8. Resolving collisions properly:

melonJS uses a combination of QuadTree spatial indexing, AABB tests, and SAT (Separating Axis Theorem) for collision detection — giving both accuracy and performance.

Collision detection vs. collision response:

Detection tells you if two objects overlap. Response determines what happens — does the object stop, bounce, pass through, trigger an event?

In melonJS, collision responses are handled in the onCollision callback:

onCollision(response, other) {
    // return true to apply the collision response (solid)
    // return false to ignore it (pass-through, trigger only)
    if (other.body.collisionType === collision.types.ENEMY_OBJECT) {
        this.takeDamage();
        return false; // don't stop movement
    }
    return true; // solid collision
}

Common gotchas:

  • Fast-moving objects can pass through thin walls if their velocity exceeds the wall thickness in a single frame. Consider using thicker collision shapes or limiting maximum velocity.

  • Corner collisions can cause unexpected "bumping" perpendicular to momentum. This happens because SAT uses the shortest overlap depth as the response vector, which may not align with the direction of movement.

  • Internal edges between adjacent collision shapes can cause objects to get stuck. Use larger, merged collision shapes when possible instead of many small adjacent ones.


9. What is the difference between video.init() and new Application()?

new Application(width, height, options) is the recommended way to start a melonJS game since version 18.3. It creates the renderer, game world, viewport, and starts the game loop in a single call.

import { Application } from "melonjs";

const app = new Application(800, 600, {
    parent: "screen",
    scale: "auto",
});

video.init() is the legacy entry point — it still works but is deprecated. It internally calls Application.init() on the default game instance.

Key benefits of Application:

  • Explicit ownershipapp.renderer, app.world, app.viewport are all on the instance
  • Clean lifecycleapp.destroy() properly cleans up for SPA/React environments
  • Stage integrationonResetEvent(app) and onDestroyEvent(app) receive the app instance
  • No globals needed — access the app from any renderable via this.parentApp

10. How to use WebGL vs Canvas renderer:

import { Application, video } from "melonjs";

// Auto-detect: tries WebGL first, falls back to Canvas
const app = new Application(800, 600, { renderer: video.AUTO });

// Force Canvas 2D
const app = new Application(800, 600, { renderer: video.CANVAS });

// Force WebGL
const app = new Application(800, 600, { renderer: video.WEBGL });

The rendering API is identical on both backends — your game code doesn't change. WebGL offers better performance for most games (GPU-accelerated batching), while Canvas is a reliable fallback. See the Rendering API page for the full API reference.


11. How to handle screen resize and scaling:

melonJS handles scaling automatically. Set the scale and scaleMethod options:

const app = new Application(800, 600, {
    parent: "screen",
    scale: "auto",           // auto-scale to fit the parent element
    scaleMethod: "flex-width" // stretch width, keep height
});

Available scaleMethod values:

  • "fit" — scale to fit inside the parent, maintaining aspect ratio (letterboxed)
  • "fill-min" / "fill-max" — scale to fill the parent, cropping excess
  • "flex" / "flex-width" / "flex-height" — stretch one or both axes to fill
  • "stretch" — stretch to fill without maintaining aspect ratio

12. How to debug my game:

Install the official debug plugin:

npm install @melonjs/debug-plugin
import { DebugPanelPlugin } from "@melonjs/debug-plugin";
import { plugin } from "melonjs";

plugin.register(DebugPanelPlugin, "debugPanel");

Press S to toggle the debug panel, which shows:

  • FPS and frame timing
  • Object count and draw calls
  • Collision shapes and bounding boxes
  • Velocity vectors
  • QuadTree visualization

You can also use Chrome DevTools Performance profiler, and console.log within update() or draw() methods.


13. How to load and switch between levels:

Use Tiled to create your levels, then load them in your Stage:

import { level, Stage } from "melonjs";

class PlayScreen extends Stage {
    onResetEvent(app) {
        level.load("map1");
    }
}

To switch levels:

// Switch to a different state (which loads a different level)
state.change(state.PLAY, { level: "map2" });

// Or reload the current level
level.reload();

Assets (images, TMX maps, audio) must be preloaded before use:

import { loader } from "melonjs";

loader.preload(resources, () => {
    // assets are ready, start the game
    state.change(state.PLAY);
});

Clone this wiki locally