GameMaker: Recursive drawing

(mouseover/click to play GIF)

Sometimes, you may want to draw a copy of the game's screen into itself - usually for a purpose-specific effect (proper name is Droste effect) like TV screens.

This small post explains the process of doing so.

The idea

Recent versions of GameMaker expose application surface, making it easier to do anything involving manipulations with game's screen.

If you have some familiarity with surfaces, you may have already tried drawing it directly, which kind of works, but also doesn't:

  • You won't see what was drawn after the screen unless it's the last thing that gets drawn.
  • You won't see a screen inside itself (thus no actual "recursion") because it's not drawn to itself yet when drawing it to itself (what a sentence).
  • Drawing a surface directly into itself is generally discouraged as GPU picks how to do this.

Thus you have to get a little more creative.

As per linked document, application surface is created/cleared after Pre-Draw event, and sticks around after everything was drawn into it.

So, if you check it anywhere between Post-Draw event and the next Pre-Draw event, it'll contain the game's screen with everything finished. And copying the contents to a helper surface and drawing it later will produce the intended effect.

The code

Firstly, you would set things up in Create event - in this case, the surface's width, height, and a variable that will hold the surface ID:

surface = -1;
width = 320;
height = 200;

A common pattern is to create and clear surface(s) in Create event.

However, you are going to inevitably have to check that your surfaces haven't gone missing later (e.g. due to minimizing the game), so you may as well do exactly prior to using them.

The Step event is going to do just that:

if (surface_exists(application_surface)) {
	if (!surface_exists(surface)) {
		surface = surface_create(width, height);
	}
	surface_set_target(surface);
	draw_clear_alpha(c_black, 0);
	gpu_set_blendmode_ext(bm_one, bm_src_alpha_sat); // (copy without alpha mixing)
	draw_surface_stretched(application_surface, 0, 0, width, height);
	gpu_set_blendmode(bm_normal);
	surface_reset_target();
}

Here the surface is created if it had gone missing, and application surface is drawn to it.

gpu_set_blendmode + gpu_set_blendmode_ext (draw_set_blend_mode + draw_set_blend_mode_ext in GMS1) are needed to prevent a "ghosting" effect if parts of application surface end up having partial opacity (which you can't see on screen but can see when copying to other surface).

Then you can draw your surface in Draw event as usual:

if (surface_exists(surface)) {
	draw_surface_ext(surface, x, y, image_xscale, image_yscale,
		image_angle, image_blend, image_alpha);
}

Using with multiple/invisible views

If you have multiple views, application surface is going to contain the combined view, which may or may not be something that you want.

Depending on your setup, this can be solved by drawing only the specific part of application surface based on viewport settings, or using view_surface_id to have the views rendered into separate surfaces and then drawing them as you wish in Draw GUI event.

Information from surfaces is then to be copied in Step event, same as with application surface.

In conclusion

There isn't much to this really.

You can download the example file used for the screenshot in this post if you want to:

Download YYZ

Related posts:

2 thoughts on “GameMaker: Recursive drawing

  1. What the performance impact on changing the target and drawing the surface inside another one compared to just copying the application surface to another one with something like surface_copy() instead, if you’re doing a loew-res game?

    • Performance impact for drawing a single surface in a low-res game is next to nonexistent, especially on desktop. As comment notes, you can use surface_copy/surface_copy_ext if you don’t use transparency in a way that produces semi-transparent segments on application surface.

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.