GameMaker: Supporting high-density screens in HTML5 games

While developing HTML5 games using GameMaker Studio, it may eventually come to your attention that the game does not render at native resolution, depending on the device, looking variously blurry as result. This small post is about fixing that.

Why does it happen

For reasons that can be promptly described as "the user may desire to be able to read text", higher-density screens will lie about screen dimensions and element sizes to have existing pages behave largely-correctly by default.

This separates the concepts of "screen pixels" and "page pixels".

How to fix it

devicePixelRatio variable exists for this purpose and reports the multiplier for page-to-screen pixels. Making use of it in GameMaker is not very hard, and, in fact, almost identical to the first code snippet on MDN.

First you would want to create a new extension (via context menu on Extensions section in GameMaker resource tree) and add a JS file to it. Then open that file in extension's file directory via an external editor.

You would need exactly two functions here - one to report devicePixelRatio, and one to change canvas.style.width / canvas.style.height (as the built-in window_set_size will only change canvas.width / canvas.height).

function browser_get_device_pixel_ratio() {
    return window.devicePixelRatio || 1;
}

function browser_stretch_canvas_ext(canvas_id, w, h) {
    var el = document.getElementById(canvas_id);
    el.style.width = w + "px";
    el.style.height = h + "px";
}

Then you would want to expose the functions by adding them via GameMaker extension UI:
(browser_get_device_pixel_ratio needs 0 arguments while browser_stretch_canvas_ext needs 3)


(note: argument/return types don't matter for GML and JS extensions)

Then you would want to add a GML file to the extension. The only reason why this is needed at all is to avoid hardcoding the canvas ID that our JS function takes (so far it's always been "canvas", but you never know).

#define browser_stretch_canvas
/// (width, height)
return browser_stretch_canvas_ext(window_handle(), argument0, argument1);

Similarly, you would want to expose that single 2-argument script via the extension UI.

With that done, the helper extension is complete and ready to use.

How to use

The flow is pretty simple and, again, not unlike the MDN example:

  • Get scaling factor via browser_get_device_pixel_ratio.
  • Calculate true size by multiplying screen size by scaling factor.
  • Set your room/view/application_surface/window sizes to true size.
  • Force correct screen size by passing it to browser_stretch_canvas_ext.

Here's an example of that:

var w = browser_width;
var h = browser_height;

// find screen pixel dimensions:
var rz = browser_get_device_pixel_ratio();
var rw = w * rz;
var rh = h * rz;

// update room/view size:
room_width = rw;
room_height = rh;
camera_set_view_size(view_camera[0], rw, rh);
view_wport[0] = rw;
view_hport[0] = rh;

// resize application_surface, if needed
if (application_surface_is_enabled()) {
    surface_resize(application_surface, rw, rh);
}

// set window size to screen pixel size:
window_set_size(rw, rh);

// set canvas size to page pixel size:
browser_stretch_canvas(w, h);

Downloads

Downloadable versions of the extension and a small sample project can be found on itch.io.

There is obviously more to good mobile scaling than just correct DPI, but this offers a good starting point while that's a story for another day.

Have fun!

Related posts:

6 thoughts on “GameMaker: Supporting high-density screens in HTML5 games

  1. Very nice!
    Are we supposed to need some mouse position scaling/adjusting with this?
    Also is there any fix to Ricky’s get_string_async problem?
    This is very useful, always wondered why games were blurry on some browsers but not others. This helps a lot with that!

    • So long as you are using an up-to-date version of GM (GMS2 or last few versions of GMS1), you should not need extra hacks for mouse position.

      You’d probably want to overwrite popup position using CSS instead (via !important), or use a custom popup (see browser_input_capture).

  2. Awesome post!

    window_get_width, window_get_height (and maybe other related functions) will return incorrect values when the canvas is scaled this way and devicePixelRatio > 1, though.

    It is easy to deal with by dividing the value of the function by browser_get_device_pixel_ratio().
    window_get_width()/browser_get_device_pixel_ratio() will be enough.

    Other problem I noticed is the input box created by get_string_async(). Its position also get totally messed up. I couldn’t find a fix for this one, as it seems like the coordinates values are overwritten every frame by the runner.
    I wonder if other things also get messed up…

    Ideally, GMS2 should offer options to deal with browser dpi and handle it all internally.

    • Yeah, GMS2 definitely should. This is a pretty fundamental and necessary feature in 2020

      Have you reported a bug to them? I’ll do it too and they will most likely fix it at some point

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.