Coffee-Break Tutorials: Decal Effects (GML)


Coffee-Break Tutorials: Decal Effects (GML)

In today's coffee-break tutorial we're going to be looking at surfaces, in particular, how to use them to create decal effects in your games. Basically, we're going to be using a surface to "catch" debris, casings, or any other item that we want to leave lying around in the course of a game. This technique is great for adding a bit of visual flair and reinforcing the mayhem that the player is leaving in their wake! It can also be used for more subtle effects, like floor or wall markers or other permanent effects. So, let's get started!

NOTE: This tutorial is for people that use GML. If you prefer to use Drag and Drop™ to make your games, we have a companion article for you here.


SETTING UP

In this tutorial, we'll be building off of the Arena Shooter tutorial (which you can download from the Tutorials section of the GMS2 Start Page). The version we'll be using is a slightly modified one that has additional effects and gameplay, which you can get from the following link:

This tutorial already contains everything we'll need to create our decal effect except the actual surface controller which we'll add in a moment. First however we need to modify a couple of objects in the game so they'll be ready for use when the controller is added.

To start with, open the object obj_fragment and in the create event change the code to be like this:

image_index = random(4);
image_speed = 0;
speed = random_range(2, 5);
friction = 0.1;
direction = random(360);
image_xscale = 0.25 + random(0.75) * choose(-1, 1);
image_yscale = image_xscale;

You can also remove the Animation End event, as it won't be getting used anymore. What we're doing is making it so that the enemy debris fragments fly out and slow down over time, and then remain on-screen. If you test the game now, you'll see that after a few enemies have been destroyed there are fragments everywhere!

Debris Everywhere

This actually looks quite good, but it's incredibly inefficient, as every single one of those fragments is an instance, and over time the sheer number of instances being processed just to create this visual effect will seriously affect performance.

Now, before we fix this, we need to also edit the object obj_splatter as we'll be making it a permanent decal effect too. So, open that object and simply remove the Step Event.


ADDING THE DECAL PARENT

The next thing we're going to do is to create a new object. This object will be the "parent" object for all our instances that we want to make permanent decals in the game. We're doing this so that we can keep our code as concise as possible and have everything in one place. This makes it easier to maintain and edit in the future.

So, make a new object and call it obj_par_decal. We don't need to add any events to this object, as we'll be handling everything in the controller object which we'll make in a moment, but we do need to assign it to the fragment and splatter objects. For that, simply click the Parent button on this object and in the new window that opens up, click the (+) button and select obj_fragment, then again to select obj_splatter. These will now be added to the Children list of the parent object:

Adding Children To Parent Object

From this point on, if you add further instances that you want to be decals, all you need to do is assign them to this parent object and the controller will automatically process them.


THE DECAL CONTROLLER

We need to create another object now to control the decal effects, so make a new object and call it obj_decal_control. This is where all the magic will happen! In this object we need to add a Create Event and then the following code:

decal_surf = -1;

This initialises the variable that we'll be using to create our decal surface, which we'll then create and modify in the Draw Event, so add that now too. In the Draw Event we'll want to have the following code:

if !surface_exists(decal_surf)
    {
    decal_surf = surface_create(room_width, room_height);
    surface_set_target(decal_surf);
    draw_clear_alpha(c_black, 0);
    surface_reset_target();
    }
else
    {
    surface_set_target(decal_surf);
    with (obj_par_decal)
        {
        if (speed <= 0)
            {
            draw_self();
            instance_destroy();
            }
        }
    surface_reset_target();
    draw_surface(decal_surf, 0, 0);
    }

So, what we are doing here is first checking to see if the surface exists and if it doesn't, we create it and clear it with an alpha value of 0 so it's transparent. The next frame, the surface will exist, and so from now on we set it as the draw target, and then check the speed of the instances using the decal parent object. If the speed is 0, then the instance is drawn to the surface and then destroyed. Finally, we rest the draw target and then draw the surface.

All that's left to do now, is add this instance to the room, on the layer lay_splatter and then test the game! If all has gone correctly, you should still see the decal effects, but the instance count will no longer being going through the roof, as the moment any decal instances stop moving, they are captures on the surface and destroyed.


SUMMARY

That's pretty much it for this tutorial! This is a simple effect that gives a very effective result and can be used in a lot of different game types and situations. However, this simplicity comes with a couple of caveats:

  • Surfaces are volatile which means that any change to the screen buffer may destroy them, for example minimising the game or going fullscreen.
  • Large rooms (larger than 2048x2048) may give issues as many devices don't like surfaces so large

Can you think of any ways to address these issues? For the first one, you can get around it in a couple of ways: using the buffer functions to store the surface every time it's drawn to and then checking the buffer if the surface is removed from memory and recreating it from that, or to create a sprite from the surface - since sprites are not volatile - and draw that instead of the surface itself (drawing the sprite back to the surface if it's removed from memory).

For the size issue, you could instead create multiple, smaller surfaces and then "tile" the room with them and have them catch the decal instances only if they are actually being "touched" by them. This is more efficient method and far more compatible, but will require a bit more work to set up as you will need to offset the position of the decal when drawing to the individual surfaces since surfaces are always draw to from the (0, 0) position.

Hopefully this has been helpful and the suggestions made above have given you something extra to work on and learn from as you attempt to add these features to this example!

That's it for another Coffee-break Tutorial! Happy GameMaking!