# GameMaker

Publish GameMaker projects to the web via the GX.games (Opera GX) WebAssembly export.

Source: https://docs.wavedash.com/engines/gamemaker

<GithubLink href="https://github.com/wvdsh/examples/tree/main/example-gamemaker" />
<PlaytestLink href="https://wavedash.com/playtest/gamemaker-example/1101cc2f-d727-4021-8f06-092f18010068" />

GameMaker ships to the web via its **GX.games** (Opera GX) export target, which produces a WebAssembly runner plus a flat set of static files. Wavedash hosts those files directly.

## Export your game

First, set your build target. In the top right of the GameMaker workspace, click the **Target Device** dropdown to open the Target Platform manager, then select **GX.games** as the Platform and **GMS2 VM** as the Output. Click Apply.

Then in the IDE: **Build → Create Executable**. This produces a `.zip` containing:

- `index.html`
- `runner.js`, `runner.wasm`
- `runner.data`, `audio-worklet.js`
- `game.unx` (your packed game)
- `runner.json`

Unzip into `build/` and point `upload_dir` at it.

## SDK integration

GameMaker reaches JavaScript through an **extension**. Create one in your project (e.g. `ext_wavedash`) with a single `.js` file that wraps `Wavedash`:

<Urgent>
**Calling `Wavedash.init()` is required.** Your game stays hidden behind the Wavedash loading screen until you do. Call it once your game is ready to play.
</Urgent>

```javascript
// extensions/ext_wavedash/ext_wavedash.js
function wavedash_js_init() {
  const Wavedash = window.Wavedash;
  if (Wavedash) Wavedash.init({ debug: true });
  return 1.0;
}

function wavedash_js_update_progress(progress) {
  const Wavedash = window.Wavedash;
  if (Wavedash) Wavedash.updateLoadProgressZeroToOne(progress);
  return 1.0;
}
```

Register each function in the extension editor — make sure `External Name` matches the JS function name, `Argument Count` matches the number of arguments, and the extension is marked for the HTML5 / Opera GX target.

Then wrap them in a GML script so the rest of your code doesn't touch the extension directly:

```gml
// scripts/scr_wavedash/scr_wavedash.gml
function wavedash_init() {
    wavedash_js_init();
}

function wavedash_update_progress(_progress) {
    wavedash_js_update_progress(_progress);
}
```

## Load progress and init

Wavedash expects two calls: a progress update while your game loads, and a single `init` once the player can interact. Put them in your root controller's Create event:

```gml
// obj_game Create event
wavedash_update_progress(0.3);  // controller created
// ... initialize game state ...
wavedash_update_progress(0.7);  // state ready
// ... load sprites, set up room ...
wavedash_update_progress(1);    // all done
wavedash_init();
```

## wavedash.toml

```toml
game_id = "YOUR_GAME_ID_HERE"
upload_dir = "./build"
entrypoint = "index.html"
```

## Filling the viewport

The GX.games export's generated `index.html` defines an `ensureAspectRatio()` function that fits the canvas within both `window.innerWidth` and `window.innerHeight`, so on iframes that are short relative to your room's aspect, the canvas is bound by height and leaves horizontal margins. There's no project option to change this — see [GameMaker-Bugs #5498](https://github.com/YoYoGames/GameMaker-Bugs/issues/5498) — but `ensureAspectRatio` is a global, so you can replace it from your extension JS:

```javascript
// extensions/ext_wavedash/ext_wavedash.js
(function () {
  window.ensureAspectRatio = function () {
    if (!window.startingAspect) return;
    const c = document.getElementById("canvas");
    if (!c) return;
    c.classList.add("active");
    c.style.width = window.innerWidth + "px";
    c.style.height = (window.innerWidth / window.startingAspect) + "px";
  };
})();
```

The IIFE runs when the runner loads, after the generated `index.html` has defined `ensureAspectRatio` as a global. The runner's own `ResizeObserver` then calls our version on every viewport change.

For the **render inside the canvas** to fill the full canvas (not just a corner of it):

1. **Game Options → GX.games → Scaling: Full scale** (`option_operagx_scale: 1`). Fixed scale keeps the render at native resolution, leaving black space; Full scale stretches the room to the canvas drawing buffer.
2. **Room size must match your game's playfield.** If your draw code uses constants like `FIELD_W = 960; FIELD_H = 540` but your room is `1366×768`, GameMaker draws the game in the upper-left quadrant of the room and stretches that whole room (game + empty space) to the canvas — leaving most of the canvas black. Match the room dimensions to what you actually draw.

## Known issues

**`show_message` and alerts.** Wavedash hosts games in a sandboxed iframe without `allow-modals`. GameMaker's `show_message` compiles to `alert()`, which silently fails. Remove all `show_message` calls or replace them with in-game UI.

**Ad and mobile extensions.** If your project includes Google Mobile Ads, IAP, or other mobile-only extensions, make sure they exit early or are stripped for web builds. Leftover ad snippets in the exported `index.html` can cause console errors.

**Debug output bar.** The GX.games export template includes a `<div id="output-container">` at the bottom of the page with "Toggle Stats", "Copy Share Url", and "Log State" buttons plus a debug textarea. There's no GameMaker option to disable it. Hide it from your extension JS:

```javascript
const hideDebugUI = () => {
  const el = document.getElementById("output-container");
  if (el) el.hidden = true;
};
if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", hideDebugUI);
} else {
  hideDebugUI();
}
```

**HTML5 target.** The legacy HTML5 export ships pure JavaScript with no WebAssembly runner ([#5498](https://github.com/YoYoGames/GameMaker-Bugs/issues/5498)) — use GX.games even if you're not publishing to Opera GX. WebGL is available on both targets.
