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.

Sprites with different images dimensions in GameMaker Studio 2

When you try to import multiple images into a single sprite in GameMaker Studio 2, you could end up with unexpected results. Specifically, if the images have different sizes (width and height), the sprite size will be equal to that of the largest image, but all the other images will be imported stretched to fill the canvas (effectively ruining the sprite).

Continue reading Sprites with different images dimensions in GameMaker Studio 2

Advanced Animation Control in GameMaker Studio 2 – Method 1

Let’s say that you have a sprite with a complex animation (i.e. variable frame rate). As you can see from the following image, each frame will play at a specific time (I use a simple Photoshop script to export the frames, I’ll write an article about it later).

This is the folder’s content, exported from Photoshop. File naming scheme is important.

Continue reading Advanced Animation Control in GameMaker Studio 2 – Method 1

Scale 2D pixel art games using surfaces to avoid pixel decimation in GameMaker Studio 2

Much has been written about resolution scaling in pixel art games. It usually comes down to this simplistic rule: always resize 2D games by integer values (2x, 3x, 4x, 5x, etc) so pixel art will always look correct.

I wrote that myself; to make a good looking low-res pixel art game on modern monitors, you should stick with a 384×216 resolution and scale it up 5 times to get a perfect 1920×1080 (1080p) game.

That’s still true-ish. But the problem I was trying to address wasn’t pixel distortion. It was pixel decimation. Let’s see how to solve it using any resolution you want to use. Continue reading Scale 2D pixel art games using surfaces to avoid pixel decimation in GameMaker Studio 2

GameMaker Studio 2 Linux Configuration

This article will guide you through the setup of a Xubuntu 16.04 LTS Virtual Machine to test, compile and run your GameMaker Studio 2 projects on Linux.

I’m choosing Xubuntu instead of Ubuntu simply because Xubuntu is less resource hungry (and my notebook is 5 years old). The resulting package will run just fine on Ubuntu machines as well.

Continue reading GameMaker Studio 2 Linux Configuration

Room Start Event Execution Order for Persistent Objects in GameMaker: Studio

It’s a well known fact that GameMaker: Studio 1.4 follows this order of events:

  1. Create Event of each instance
  2. Instance Creation Code of each instance
  3. Game Start Event (this will only be run in the very first room of the game)
  4. Room Creation Code
  5. Room Start Event of all instances

You can set the order in which specific instances are created within the room editor itself. But what about the Room Start event of persistent instances? Such instances are carried over from room to room. What if we have a room with another, newly created instance, that also executes a Room Start event?

room_01                 room_02
obj_persistent -------->obj_persistent
                        obj_new

I made a few tests and it seems that GameMaker, for this specific case, uses Depth. That is: the smaller/lower the depth value of the instance, the sooner it will execute its Room Start event.

I’m talking about instances and not objects because if we change the instance’s depth via code, GameMaker will use that value, not the one defined inside the object editor.

Now jump into your code and have dozens of objects with different conflicting Room Start events (I’m kidding, please don’t)

How I Scale Fuzeboy Resolution on Mobile and Desktop Devices

Fuzeboy's still in development so it's only natural that sometimes I take time to rewrite stuff, to fix things, to experiment and so on. We try, we break, we fix, we extend, we change. We evolve.
A scene from Fuzeboy. There's only one way to view pixel art... and that is with pixel perfect scaling.
One issue we faced from the start, is the game resolution. What we knew was that we wanted pixel perfect scaling no matter what. Remember that this game will be both for mobile and desktop.Here's my solution as of today.

The Ideal Game Resolution

Fuzeboy cares about its height. The width depends on the monitor aspect ratio (within reasonable limits). The ideal height of Fuzeboy is around 240px. Keep this in mind.

Pixel Perfect, Full Screen, No Black Bars (didn't work)

This approach was the first I used. Since not every device has a vertical resolution that's a multiple of 240px, I allowed different devices to show more or less "game world".First I get the display vertical resolution. Then I cycle from 200px to 300px to see which one fits perfectly the device vertical resolution. If I don't find a perfect fit, I give up and just use 240px.

Fuzeboy on Devices, method 1

DeviceResolution (vertical)Game Vertical ResolutionMultiplier
Amazon Kindle Fire 7"1024 x 600 (600)3002
Samsung S3 Mini800 x 480 (480)2402
Asus MeMO Pad 71280 x 800 (800)2004
This doesn't work. It's pixel perfect most of the times, there are no black bars... but you might end up seeing an unplayable 200px vertical resolution on a 7" tablet.
We don't want this to happen... do we?
Let's have a look at an Amazon Kindle Fire 7"
This is better. Or... is it?
There's a huge 100px difference in height from the Asus to the Kindle. The Fire users would have been able to see 50% more game world than the Asus players. That's wrong. Especially since Asus has a higher screen resolution than the Amazon Fire.

Pixel Perfect, Black Bars (in use as of today)

First of all I decided to restrict the range of vertical resolution of the game. Now it's from 230px to 260px. Still a 30px difference but it's bearable.So I still ask for the device vertical resolution, of course. Then I check which number, from 230 to 260, fits best. By that I mean it either fits perfectly or has the lowest remainder.This is the initialization script. It goes inside the create event of the very first object created in the game.I also leave the view settings in the room editor as they are. Disabled. I enable them via code.
///scr_init_display()

// Let's disable the drawing of the App Surface
application_surface_draw_enable(false);

dw = display_get_width()        // Device Display Width
dh = display_get_height()       // Device display height
ar = dw / dh                    // Aspect Ratio

min_h   = 230                   // Minimum Height
max_h   = 260                   // Maximum Height
height  = min_h                 // We start from the minimum
fract   = frac(dh / height)     // This is the fractional part
mult    = floor(dh / height)    // This is integer multiplier

// We cycle from min_h to max_h
for (var h = min_h; h < max_h + 1; h++)
{   
    var new_fract   = frac(dh / h)
    var new_mult    = floor(dh / h)
    
    // If we have a lower remainder, we store
    // the multiplier and the height we're testing
    if new_fract < fract
    {
        fract   = new_fract
        height  = h
        mult    = new_mult
    }
}

// This will show you the found resolution in the debug console
show_debug_message("Found resolution: " + string(height))

// Width gets decided with a simple division
width = floor(dw / mult)

// And made divisible by 2
if width mod 2 != 0
    width--

/***************************************************
  SET THE VIEW AND THE PORT FOR ALL ROOMS
 ***************************************************/
var i   = true;
var rm  = room_next(room);
while (i)
{
    room_set_view(rm, 0, true, 0, 0, width, height, 0, 0, width * mult, height * mult, 0, 0, -1, -1, -1)
    room_set_view_enabled(rm, true)
    if (rm == room_last)
        i = false
    else
        rm = room_next(rm)
}

// Resize the application surface
surface_resize(application_surface, width, height);

// Let the GUI layer be as big as the device screen
display_set_gui_size(dw, dh)

gw = display_get_gui_width()    // GUI width variable
gh = display_get_gui_height()   // GUI height variable

// We'll need these to figure out the touch commands coordinates
wscale = width / (dw / mult)
hscale = height / (dh / mult)

// Let's figure out the App Surface offset (we want it centered)
Xoffset = floor((dw - (width * mult)) / 2);     // Horizontal Offset
Yoffset = floor((dh - (height * mult)) / 2);    // Vertical Offset

/// Go Fullscreen on desktop
if os_type == os_windows
{
    window_set_fullscreen(true)
}
Then we need to draw the App Surface. So in the game controller object, I have the following code in a Post Draw event.
///Draw the App Surface with correct offset

// This line prevents strange artifacts in Fuzeboy.
draw_enable_alphablend(false);

// The real drawing.
draw_surface_ext(application_surface, Xoffset, Yoffset, mult, mult, 0, c_white, 1);
If I run the game on the Amazon tablet, the result is this:
The game now has a height of 260px

Fuzeboy on Devices, method 2

DeviceResolution (vertical)Game Vertical ResolutionMultiplierBlack bars top/bottom
Amazon Kindle Fire 7"1024 x 600 (600)260220px
Samsung S3 Mini800 x 480 (480)24020
Asus MeMO Pad 71280 x 800 (800)260310px
Now the game scales much better.

Let's fix the Touch Controls.

Those touch buttons share the obj_touch parent so I can do this in the controller Begin Step event.
// Add these in the display init script
xoffsetmult = (Xoffset / mult)
yoffsetmult = (Yoffset / mult)
// Touch Controls
for (var dev = 0; dev < 4; dev++)
{
    touch_dev       = dev
    var _xpos = (device_mouse_x_to_gui(dev) / mult) + view_xview - xoffsetmult
    var _ypos = (device_mouse_y_to_gui(dev) / mult) + view_yview - yoffsetmult
    
    var this_button = instance_position(_xpos, _ypos, obj_touch);
    
    if this_button != noone
    {
        if device_mouse_check_button(dev, mb_left)
        { 
            with(this_button)
            {
                touch_press_action()
            }
        }
        
        
        if device_mouse_check_button_pressed(dev, mb_left)
        {
            with(this_button)
            {
                touch_pressed_action()
            }
        }

        
        if device_mouse_check_button_released(dev, mb_left)
        {
            with(this_button)
            {
                touch_released_action()
            }
        }
    }
}

The Future

As of today, I still experiment with the resolution of the game. I haven't found a perfect way to scale low res pixel graphic fullscreen with no black bars and no distortion... simply because such a way doesn't exist.Things change frequently around here, so we'll see if I'm going to stick with this method or not. Feel free to let me know your ideas on this matter.

Optimizing Collision Code

As I said in a previous post about my platformer engine (the one I’m working on for Fuzeboy), I’m using Zack Bell‘s code as a base. Recently I started to look into ways to optimize such code without losing the functionality (slopes are a big feature of that simple collision/movement code).

Looks like I was able to gain some speed here…

And as someone once told me “sharing is how better games are made”… so here it is my “improved” version.

Continue reading Optimizing Collision Code

Basic Platformer Mechanics in GameMaker: Studio

I’m fond of platformers. It’s only natural that I spent the past year studying and refining platformer engines for GameMaker: Studio. Here’s what I actually use for my engine.

Fuzeboy early gameplay

Continue reading Basic Platformer Mechanics in GameMaker: Studio