Optimized Decals (high phone performance)


GM version: GM: Studio 1.4.1763 Standard (2016-10-06)
Target platform: ALL
Download: https://www.mediafire.com/?1qu0av575re71p9
Links: N/A
Latest change: 2017-26-05
Level: Beginner

Good day!
This is my first time doing a tutorial or explaining something, so excuse me if this post gets a bit messy.

A short introduction
Today I have decided to explain how to implement decals. Decals are pretty much static images that don't move and aren't interactable. They can be useful when working on shooter games when drawing blood or some static floor particles. There is quite a bit of ways to implement them, however it's important to optimize them as much as you can considering there might be a lot of them thrown around the room.

What we are trying to accomplish is to create a simple function that will add a certain sprite (along with x, y and angle of that sprite) to the decals list and not care about them. This means we don't want any performance drops regardless of how many of them we placed in the room.

This is a short example of decals implemented im my upcoming android game.
As you can see in the bottom left corner, frame rate is stable at 200 fps before and after decals were added.

I will try to structure the following part the best I can.
We will need only one object. This will preferably be our objController. We will store our decals in a temporary 2D array. This array will be flushed each second and sprites from the array will be added to the surface.
Let's get to work!

To draw decals, we will use an array and one surface. So we will first have to initialize our array and create our surface.
Create Event
globalvar dList, dIndex, decalSurface;
dIndex = 0;
for(i=0; i<200; i++) {
    dList[i,0] = noone;        //sprite index
    dList[i,1] = 0;                //image index
    dList[i,2] = 0;                //angle
    dList[i,3] = 0;                //x
    dList[i,4] = 0;                //y

decalSurf = surface_create(room_width,room_height);
alarm[0] = 60;
As you can see in the code snipped above, I'm using 3 global variables and one alarm. Let me explain what the variables above are used for.
  • dList - This variable represents an array for our decals. Keep in mind this array is temporary and will be flushed each second. (The time is changeable. Simply change the alarm[0] value.) As you can see, this is a 2D array meaning we can store multiple properties of a sprite in it. We will store sprite index in the first position, image index in second, image angle in third, and coordinates in last two positions of this array.
  • dIndex - This variable will keep track of the decal amount. Each time we add a decal to the list, dIndex will be increased by 1. We can use this variable when looping through our array and drawing the temporary decals. Again, this variable will reset each second. (Also depends on alarm[0].)
  • decalSurf - This is our actual decal surface. All the flushed sprites will be added to this surface and drawn to the screen. We can add as much sprites as we want to this surface, performance will not be affected.
Now comes the draw event. This is where we will draw our temporary decals and the surface itself.
Draw Event
if !surface_exists(decalSurf) {
    decalSurf = surface_create(room_width,room_height);
} else {

for(i=0; i<dIndex; i++) {
     draw_sprite_ext(dList[i,0], dList[i,1], dList[i,3], dList[i,4], 1, 1, dList[i,2], -1, 1);
I think the code above is self explanatory. The first few lines draw our surface if it exists, and create it if it's lost in memory. (Happens quite often.)
The FOR loop will loop through our dList as much times as there is decals in that list.(As said before, dIndex keeps track of the decal number.) It will draw every sprite written in the list on the specified coordinates with a certain angle.

This will now draw each sprite we add to the array once every frame. We could end this here, but this will limit the amount of decals to 200. And it's not the best method of doing it as it's very slow if you decide to increase the array size. And believe me, 200 decals is not a lot.

With that said, let's flush the array and add our decals to a surface. As we will do this once every second, we will handle the code in the alarm[0] event.
Alarm[0] Event
for(i=0; i<dIndex; i++) {
    if dList[i,0] != noone {
        draw_sprite_ext(dList[i,0], dList[i,1], dList[i,3], dList[i,4], 1, 1, dList[i,2], -1, 1);
        dList[i,0] = noone;
dIndex = 0;

alarm[0] = 60;
Again, quite straight forward code. We're looping through the array, just like in draw event. This time, we're making sure the sprite exists on that position. (Just for safety.) As you can see, since we're working with surfaces it's important to use surface_set_target to let GameMaker know we're drawing to the surface. This function is very slow on phones, so it's important not to use it in each step. This is exactly why we draw to the surface each second and why we use a temporary array to store decals until they're added to the surface. It's like a queue.
After we draw the sprite to the surface, we delete is from that position in the array. (dList[i,0] = noone)
Once we looped through the whole array, we reset the temporary decal amount. (dIndex = 0)
Finally, we reset the surface target to let GameMaker know we're drawing to the application surface from now on and reset our alarm to 60 steps.

Now that all we completed all the complicated steps, let's make a simple function that will add sprites to the list.
Let's call our function addDecal.
///addDecal(sprite index,image index,image angle,x,y);
dList[dIndex,0] = argument0;
dList[dIndex,1] = argument1;        
dList[dIndex,2] = argument2;
dList[dIndex,3] = argument3;
dList[dIndex,4] = argument4

dIndex ++;

if dIndex >= 200 {
    dIndex = 0;
As you can see, we pass the function arguments to the next free place in our dList array. dIndex comes in handy again.

Great work! To add a decal, simply run the addDecal function and pass it required arguments. This will store your sprites in the temporary array dList and draw them to the screen. After one second passes, it will flush the dList and draw the sprites to the decalSurf. decalSurf is always drawn and it's optimized to handle unlimited amount of sprites.
Thank you for reading!
Last edited: