Screen Tearing / Wavy Effect in GameMaker Studio 2 (using Surfaces)

After playing Environmental Station Alpha, I decided I wanted to implement the screen tearing effect Hempuli is using in his game. I didn’t know how he achieved it so I had to start from scratch and think about different approaches.

Knowing nothing about shaders, I was left with surfaces. I jotted down some code and immediately hit a wall; after asking around in the yoyogames forums, reading other’s comments, I could finally come up with a pretty decent solution.

Actual Code

I wrote the code so it could be easily hackable. This goes into the create event of your game controller object.
// handy shorter names
dw = display_get_width()
dh = display_get_height()

tearings_surface  	= surface_create(dw, dh)    // We'll draw on this surface
tearings_y          = 0
band_num            = 16                        // How many bands you want on screen
band_height         = dh / band_num
tearings_x_offset   = 32                        // How much you want to displace the bands horizontally
tearing_speed       = 4                       	// Change this to speed up/slow down the tearings
I place the following code inside a draw_post event of my controller.
// Create surface if it doesn't exits
if !surface_exists(tearings_surface)
	tearings_surface = surface_create(display_get_width(), display_get_height())
	
// Let's set the target to our surface
surface_set_target(tearings_surface)
draw_clear_alpha(c_black, 0)

// We draw parts of our application surface on tearings surface
for (var current_band = 0; current_band < band_num * 2; current_band++)
{
	draw_surface_part(application_surface, 0, band_height * current_band - tearings_y, dw, band_height, sin( (degtorad(360) / band_num ) * current_band) * tearings_x_offset , band_height * current_band - tearings_y)
}

// Always reset the target surface
surface_reset_target()

// Draw the actual surface
draw_surface_stretched(tearings_surface, -tearings_x_offset, 0, dw + tearings_x_offset * 2, dh)

// Move the Tearings
tearings_y = (tearings_y + tearing_speed) % (band_height * band_num)

That's it.

And this is how I make that effect. I will implement a similar version for vertical tearings (for underwater levels? maybe?). Hope you find it useful.

A note about surfaces: remember to free the surfaces if you don’t need them anymore, otherwise it will lead to memory leaks.

Guide to develop low resolution 2D platformers with smooth movement and pixel perfect collisions in GameMaker Studio 2 (with slopes)

Low resolution is great. It saves CPU power, especially with the collision code I talk about in this article, and it makes it easier to work with the project (like when building huge rooms or making edits to sprites). You’ll see that sometimes there’s little to no reason to scale up your sprites. It’s just a waste of resources.

With the collision code I use objects move by whole pixels. This makes for perfect collisions but it usually leads to jittery movements. Luckily there’s a simple solution.

The Movement and Collision Code

This is the most practical collision code I ever came across on the web. I read about it some time ago on Zack Bell‘s blog and I subsequently adapted it slightly to suit my needs. It basically remains the go-to code for 2D low res platformers. It’s like… unbeatable. Think holy grail of platformer movement and collision.

I’m using the following hierarchy.

 obj_solid
  |
  |_ obj_slope
|_obj_slope_rx
|_obj_slope_lx

You can use a different hierarchy for your collisions, just adapt the scr_platformer_move code. I’m using fall through platforms so I use the obj_solid_top.

This script must be called from the create event of your active/moving objects.

/// @desc       Initialize Platformer Vars
/// @func       scr_platformer_init()

/// This script is usually called in the create event

// Initialize the variables used for movement code
xVel = 0                // X Velocity
yVel = 0                // Y Velocity

xVelSub = 0             // X Sub-pixel movement
yVelSub = 0             // Y Sub-pixel movement

This code should run at the end of your movement velocities calculations. Ideally at the end of your object’s step event (normal step event is fine).

(open in pastebin)

Collision Masks

It’s of fundamental importance that you take extra care when dealing with collision masks. Make sure they behave the expected way especially when flipping your objects around. Most of the times the error lies in the origin or in the symmetry of a mask.

Wrong Collision Mask

Here’s a sample of a wrong collision mask. Counter intuitively I placed the origin in the exact middle of the mask. It will result in asymmetric mask behavior when mirroring it (i.e. when turning left or right in the game). This mask will create collision issues and probably get objects stuck inside walls or slopes. 

This mask is wrong.

Correct Collision Mask

It took me a while to understand how the origin pointer looks and behaves. This is a centered, symmetric collision mask… I’ll be honest: it absolutely doesn’t look like that to me. But trust me, this is the right one.

This mask is correct.

Let's fix the jittery movement!

It’s all about surfaces. We need to:

  1. Disable the automatic drawing of the application surface.
  2. Resize the application surface to the correct, hi resolution size.
  3. Draw our sprites with sub-pixel offsets.
  4. Draw the stretched application surface manually in a post_draw event.

Considerations

Can you see what’s going on here? Objects still move by whole pixels. Their collisions are still being calculated for whole numbers only. Still, we draw the sprites with sub-pixel precision!

The loops for collision checks have to run for very low numbers/distances. This means ultra-smooth movement, ultra high performances and very low disk/ram resource usage (compared to up-scaled pixel art).

Still jittery on slopes? Let's fix it

If you download the attached project you’ll see how I solved the slopes jittery movement. 

I’m using simple trigonometry to find the Y position given the X position on a slope. I’m still using whole pixels to compute collisions but I use the following snippet just to draw the sprite of the player.

// Check for slope offset
slope = collision_rectangle(bbox_left, bbox_bottom +1, bbox_right, bbox_bottom + 1, obj_slope, true, true)

if slope
{
    var slope_height    = abs(slope.bbox_bottom - slope.bbox_top)
    var slope_base      = abs(slope.bbox_right - slope.bbox_left)
    var angle           = arctan(slope_height / slope_base)

    // Slope to the right
    if object_is_ancestor(slope.object_index, obj_slope_rx)
    {
        if bbox_right < slope.bbox_right slope_spr_y = slope.bbox_bottom - (bbox_right + xVelSub - slope.bbox_left) * tan(angle) else slope_spr_y = slope.bbox_top } // Slope to the left else if object_is_ancestor(slope.object_index, obj_slope_lx) { if bbox_left > slope.bbox_left
            slope_spr_y = slope.bbox_top + (bbox_left + xVelSub - slope.bbox_left) * tan(angle)
        else
            slope_spr_y = slope.bbox_top
    }
}
else
    slope_spr_y = 0    // Not on slopes
And so in the draw event of the player I use the following:
// Slope Y Position
if (slope_spr_y != 0)
    var yspr = slope_spr_y
else
    var yspr = y + yVelSub

draw_sprite_ext(sprite_index, image_index, x + xVelSub, yspr, image_xscale, image_yscale, 0, c_white, image_alpha)

Conclusions

This system might not be perfect and I’m open to new solutions. Let me know if you have a better system to obtain smooth movements using low resolution assets.

Choosing the right resolution for a 2D Platformer

In this post I’ll try to write down the thinking process I made when I chose to develop Kren at a 384×216 sceen resolution.

Tiles

Everything starts with the tiles. If you make tiles your measurement unit, you’re going to be able to simplify the visualization process. I made this kind of reasoning: how many 16px square tiles do I want displayed vertically?

Can you count the tiles? (hint: it's 15x10)
Can you count the tiles? (hint: it’s 15×10)

I wanted to be able to show at least 10 tiles. Which then I realized was a pretty low number for a Desktop game. That meant having a vertical resolution of 160px. That was the resolution of Metroid Zero Mission, on a Game Boy Advance. Granted, it looked great even on an emulator, on a Desktop PC… but… did I really want to go with that little number of tiles? Let’s look at how others approached the problem…

This is what the player sees on the monitor. Notice the small character and the whole room is in the view.
This is what the player sees on the monitor in Axiom Verge. Notice the small character. Also the whole room is in the view.

Axiom Verge, a more modern desktop platformer, is displaying almost 17 tiles vertically, at a vertical resolution of 270px. So I had to stop and ponder a little. Was it enough? Was it too much?

Too much indeed. Axiom Verge displays gigantic environments around one of the smallest player character I’ve ever seen. The player’s height to screen height ratio is absurd. But it works, for that game.

I wanted to keep a more claustrophobic feeling. Given I didn’t want to mess with tile size, I had to display less tiles. Simply by reducing the resolution.

Aspect Ratio

Metroid Zero Mission has a resolution of 240×160. It’s a 3:2 aspect ratio and it’s not what most computers use today (or even back in the days). In fact the 16:9 aspect ratio is now almost a universal standard. Or at least you can find it in most of the TV sets, desktops and notebooks you see around.

Back to Axiom Verge. It’s 16:9, which is great. But I already chose not to go with a resolution as high as 480×270 because I want less tiles on the screen.

Don’t get me wrong. If you want to be able to display a great number of 16×16 tiles on the screen, 480×270 is a really great option. I would advise you go with that resolution! It’s 1/16th the pixel area of a Full HD monitor. Which means it scales pretty well without pixel interpolation artifacts (you multiply 480 and 270 by 4 and you get the 1920×1080 Full HD resolution).

Next, I looked at the closest 16:9 resolution available with almost the same vertical resolution as the Game Boy Advance: 288×162.

I made a few tests at this resolution and even though I liked it (a lot), it doesn’t scale well. There’s no easy way to scale the 288×162 resolution to perfectly fit a 1920×1080 monitor. That means problems. Also, even though I liked the effect, it really is a little bit ugly. And maybe a little too much claustrophobic, even for my game; there’s barely enough space to fit 10 tiles, vertically. Given my player’s height is a bit more than 2 tiles, it meant huge player (yes, you can draw a different character), really small “field of view” and ugly pixels.

The 384 x 216 Compromise

I finally found the optimal compromise resolution: 384×216. Multiply that by 5 and you get the Full HD resolution. This should avoid pixel interpolation distortions. It also allows me to display 13.5 tiles vertically. This is not that far from the Game Boy Advance look, but it sits nicely between Metroid Zero Mission and Axiom Verge.

Testing the resolution and the view size. Notice the size of the Player and the smaller visible area.
What the hell is this?! (just a fun way to test player size, resolutions and ratios). It gave me familiar elements to gauge my observations.

I don’t want the player to feel small, inside gigantic environments. I want the players to feel the environment around them. Leaving him/her with little air. But not as little as in Metroid ZM on the GBA. All with a modern 16:9 look.

I found my own resolution with this process and a bit of testing. It might not be the best approach (in fact I’m sure I might have missed some other considerations) but for now it’s working great. Your mileage may vary so I recommend a lot of testing.

Testing the platformer engine I’ve been working on

Still in early planning stages. Yet testing the platformer engine made in GameMaker.
Still in early planning stages. Yet testing the platformer engine I made in GameMaker.

I’ll write a longer post soon. You can watch the video on youtube with some audio effects as well.

I’m still early into the planning stages of the game. I just needed a platformer engine I can work with to build the game. It’s going to be a Metroidvania type of game.

More on that later.