We would love you to play this but your browser doesn't support HTML5 canvas.
Just use a different browser and you can happily play this game.

How to add an outline (stroke) to sprites in GameMaker

In the above interactive example you can see some rather nifty outlines around the images when you mouse over them. While most of the time in GameMaker you will only want fairly simple outlines this article will mostly look at doing complex borders around sprites.

So what am I doing different?

In GameMaker one of early realisations someone might come to by themselves is how you can draw an outline around something by simply drawing a sprite 4 times in each direction and colouring those in and ta da! you have an online. However after a while you will start to see some of the limitations of this; you can’t do transparent borders, you can only do solid colours, you definitely cannot do clever animated things, and I’m sure there are many other problems you might get like with depth.

What I’m going to do is a clever technique called masking, now I feel obliged to say here that this is far less efficient than the previous method, however if you want something more than a solid outline this is the best way I can think of to do it and as you can see it is running smoothly in the html5 version (which is notorious for performing code very poorly) and I am using masking on 10 objects simultaneously each of which hasn’t been optimised so is actually redrawing the whole screen each time rather than just the area around the object (and you are able to code yours more efficiently but for my purposes these are good enough for me)

So what is masking?
For us it is going to be using a surface to remove parts we don’t want on another surface using draw_set_blend_mode(bm_subtract). Now for those that don’t know what a surface is you don’t need to be worried by how complex the GameMaker manual makes it sound because you can just see it as a canvas we can draw on and then draw onto the screen just like we would a sprite.

If you are new to Masking in GameMaker I have a guide here.

how does the outline work?

You will see in my code I have two surfaces: surfacehole and surfaceoutline. surfacehole is a negative silhouette of the sprite including the border. In my mind I see it to be like a hole punch we will use to erase anything we don’t want from surfaceoutline. surfaceoutline is just what we will draw on the screen under the original sprite. Sounds easy right? Well it is, we just need to find out how to make these two surfaces.

How to add an outline (stroke) to sprites in GameMaker, Image Borders
  1. Now this is the only part of my code I don’t like, in order to find out where the location of the border will be drawn I actually draw the image 8 times offset by a little each time just like I explained in the simple example, this works well for most things including putting outlines on text, however if your image is long and fiddley you might find things poke out in ways you don’t expect, of course this can be solved by just drawing more than 8 directions and using lengthdir_x() and lengthdir_y() however there is the pedantic programmer part of me that dislikes this solution.
  2. Using surface_set_target(surfacehole) we set it so GameMaker draws onto the surface we made and with draw_set_blend_mode(bm_subtract) we make it so when drawing rather adding to the image we remove that part of the image. This is the basic concept behind masking.
  3. This is where we fill a surface with the image we want to outline the sprite. To make it easier I have used draw_sprite_tiled(spr_grid,0,x,y) to basically fill the whole surface with a repeated pattern.
  4. We subtract the whole of the hole punch we made in step two with the image we made in step three, making an image that is slightly bigger than the original sprite and filled with the outline we want.

Now all we have to do is draw the surface made in step 4 and then draw the original sprite over the top, now we are done! Happy days!

So now let’s look at the code to do this, and I think you will be surprised to find its actually less code than it took me to explain how it worked.

The Code :
/// Draw Event

// For performance this should be as small as the image you want to draw including the border 
if (! surface_exists(surfacehole)) {surfacehole = surface_create(room_width, room_height); }
if (! surface_exists(surfaceoutline)) {surfaceoutline = surface_create(room_width, room_height); }

surface_set_target(surfacehole) /// draw onto the punch out surface

draw_set_blend_mode(bm_subtract) // dont draw image but remove the inverse from what we are drawing onto 
draw_clear(c_black) // clear anything from the previous frame in case the image is being animated 

// draw all the sprites on with a little offset to make a border
if(distance_to_point(mouse_x,mouse_y) == 0) {
    var distance = 5
    draw_sprite_ext(spr_flower,0,x+distance,y+distance,1,1,image_angle,c_black,1)
    draw_sprite_ext(spr_flower,0,x+distance,y-distance,1,1,image_angle,c_black,1)
    draw_sprite_ext(spr_flower,0,x-distance,y+distance,1,1,image_angle,c_black,1)
    draw_sprite_ext(spr_flower,0,x-distance,y-distance,1,1,image_angle,c_black,1)
    
    draw_sprite_ext(spr_flower,0,x+(distance*1.4),y,1,1,image_angle,c_black,1)
    draw_sprite_ext(spr_flower,0,x-(distance*1.4),y,1,1,image_angle,c_black,1)
    draw_sprite_ext(spr_flower,0,x,y+(distance*1.4),1,1,image_angle,c_black,1)
    draw_sprite_ext(spr_flower,0,x,y-(distance*1.4),1,1,image_angle,c_black,1)
}

// set all the drawing modes back to normal 
draw_set_blend_mode(bm_normal) 
surface_reset_target();



surface_set_target(surfaceoutline) // make the surface that will become the outline

draw_sprite_tiled(spr_texture,0,x,y) // fill this surface with whatever we want to be displayed 

draw_set_blend_mode(bm_subtract)
draw_surface(surfacehole,0,0) // punch out everything outside the border 
draw_set_blend_mode(bm_normal)

surface_reset_target();

draw_surface_ext(surfaceoutline,0,0,1,1,0,c_white,0.2)
draw_self()
    

It’s worth mentioning again that this works in the HTML5 module for GameMaker however does use WebGL so you will need to make sure that you set WebGL to required, however I think its pretty cool to see Masking done in WebGL and HTML5 as it is a really handy technique.

This is a really great trick you can use to highlight buttons in GameMaker and outline buttons on mouse over. It will give your game a really polished look.

Some things you might want to change :

Now the part that everyone is waiting for... How have I done the animated mask in GameMaker? Well that is super easy you see the line:

draw_sprite_tiled(spr_texture,0,x,y)

Well you can just change that to:

offset += 0.3
draw_sprite_tiled(spr_texture,0,x+offset,y+offset)
    

Likewise the examples that use a solid colour are just:

draw_clear(c_black)

Making the stroke border bigger and smaller; On about line 9 you will see the line var distance = 5 to make the outline bigger and smaller you can just change this number, bigger numbers make a bigger border.

Optimisation 1; To make the code easier to read above I put it all in the draw event, this works but really you should save the draw event to have only the most necessary code running in it. For example the surface_create(room_width, room_height) should be put in the create event, but if you do make sure that you still check it exists because as GameMaker says in the manual surfaces are volatile and a devices might remove them from the graphics memory when your program is not being used.

Optimisation 2; Again to make things easier to read I moved things around however I would massively recommend actually checking the mouse distance in the step event, this makes sense because you might have a game pad or a controller that is actually deciding when the stroke should be on and off.

Optimisation 3; I’m pretty sure it is better to make all these surfaces in the step event and then in the draw event all you need to do is draw the outline and then the original sprite on top, again I didn’t do this because the example at the top runs perfectly smoothly at 60fps.

Optimisation 4; if you have many objects making outlines at the same time I would have an outline object, this object would go through everything that wants an outline using a with() function. This means you can have a huge number of objects drawing outlines onto the same surface and would be incredibly efficient because you are only rendering this outline surface to the screen once. Magic!

Optimisation 5; The GameMaker Manual does tell you to use surface sizes that are powers of two like: 16, 128, 512, 1024 etc. I never have and this is might be why I have so many problems with surfaces.

Optimisation 6; Something you should do is only remake the mask and the surface if something has changed. It would be quite simple to cache it with just a flag saying changed=true and I think this will really help.

About the Article:
Easy Difficulty
GameMaker
By David Strachan