Scaling up my game 4x without resizing a single sprite (GM:S 1.4)

So I was developing a low res game (480 x 270) in GameMaker: Studio 1.4. The moment comes when I need to test it on mobile. I create my APK, load it up on the phone and there it is… a horrible mess of graphic artifacts!

The camera movement was all jittery, the zoom effect made all the tiles look funny and overall it just felt wrong.

The problem was obviously the very low resolution of the game. The camera (the view), along with every other object in the game, can move only by whole “game pixels”. That means that if you divide the actual display resolution by the game resolution (480 horizontal pixels in my case) you get the minimum “screen pixels” that any object can be moved/re-drawn. No in-between values are possible.

This is actually hi-res. Just scaled up 4x (background still old-size) - 1 game pixel is now 1 view pixel. But sprites are 4x bigger so they look low-res (fake it).

On a 1920 pixels wide screen, that’s 4 “screen pixels” per “game pixel”. So the camera (along with every other objects) can move only by 4 screen pixels at once and no less. This means jittery movement.

Since I wanted the camera to be able to move at least 1/4th of a game pixel, I had to upscale the whole game by a factor of 4… I had to go Full HD to keep my low res game enjoyable.

That meant resizing 175 sprites, move their origins, check their collision masks, resize the tilesets and backgrounds, adjust the speeds, re-make the whole levels and probably fix other bugs… unless…

Unless I used this code in the room start event of my controller.

global.game_scale = 4


// Increase the room size
room_width  = room_width  * global.game_scale
room_height = room_height * global.game_scale

    
// Object resize
with(all)
{
    x = x * global.game_scale
    y = y * global.game_scale
    image_xscale = image_xscale * global.game_scale
    image_yscale = image_yscale * global.game_scale
}

// Tile resize
var num = tile_get_count();
for (var i = 0; i < num; i++;)
{
    var tid = tile_get_id(i)
    var tx  = tile_get_x(tid)
    var txs = tile_get_xscale(tid)
    var ty  = tile_get_y(tid)
    var tys = tile_get_yscale(tid)

    tile_set_scale(tid, global.game_scale, global.game_scale)
    tile_set_position(tid, tx * global.game_scale, ty * global.game_scale)
}

I also increased the view size by 4 (and resized the application surface accordingly) so the objects in the game looked exactly like before (size-wise). Now everything is 4 times bigger.

I still have to resize any in-game created instance and modify the speeds of some of my objects… but at least I didn’t have to resize 175 sprites. Now the camera moves and zooms smoothly. Everything can move 1/4th of a game pixel without looking jittery.

Separation of Concerns in GameMaker Studio

I recently got into the (bad) habit of using just one single controller object for my games. It manages everything from input to video. The logic behind this was that the less objects GameMaker has to manage, the less resources it will use.

In principle that’s true. In reality, switching from 5-6 controllers down to 1 doesn’t make any difference at all in terms of performance. We’re not talking about hundreds of objects; we’re talking about 6. I mean… what was I thinking?

So I took a step back and returned to my old (good) habit of having multiple, distinct, autonomous and self-contained controllers. Each concerned only with doing his own thing.

These are my controllers now.

I’ll admit it: the Game controller is still somewhat generic; all the others define their own variables and run their own code independently. Data deals with game data like options and statistics. Video deals with app surface drawing, display resolution managing and view/camera stuff. Audio and Input are pretty self-explanatory too.

Log deals with the log console, but it will soon become Dev. A Development controller that enables development mode actions and tools like a debug console, a log viewer and so on. When a game is released, the Dev controller must be simply removed from the first init room.

The added benefit is that now I can export a single object into any other project, where and when I need it. That’s neat.