Asylum Dev Diary 92 – Code Profiling

This week my goal was to run the extremely useful profiler on Asylum and see what I can do to optimise the code.

First up is the light drawing routines. This is the heaviest code in the game because it has a lot of calculations to do.

When the light engine (Aura2) was released there was a set of collision checking commands that didn’t exist in GMS2. So the way the engine checked on lights and shadows was to iterate through every light to see if it was in the cameras view, and then check every shadow to see if they were within the range of one of the lights. This was done in the GMS2 simple way:

with(lightSource){
  if (in_view(lightSource)){

    with(shadowCaster){
      //check shadows and calculate caster
    }

  }
}

This is the normal way in GMS2 to quickly iterate through every object of a certain type and in most situations this is best. However, when you have very heavy code to run the more lights and shadows on a map will just add more and more work for the game to do. And so the lighting engine was taking up 84% of the frame time. This resulted in the game running (on my system) at 90 to 200fps. Now that sounds great but my computer is pretty fast (4Ghz Ryzen 7 2700X, 16Gb Ram, nVidia 1060) and Asylum needs to be able to run on older machines.

So, what can I do to reduce the processing. Well, a little while ago YoYo Games released an update to GMS2 (v2.2.2 I believe) that added in new collision commands. Previously the collision commands would return the first (closest) object within a certain area or along a certain line. The downside to this is if you wanted to find all the objects within a certain area or line you had to make some pretty complicated workarounds. The new update however extended the commands to all them to return a list of objects in the collision.

As this was released after Aura2 I had to make the changes myself.

The lights in view code was already optimised as I had previously put together routines to switch light objects as they entered and left the screen. So, the main bottleneck was going to be the shadow casters. I updated the code replacing the with(shadowCaster){ .. } with the new area collision commands:

var _list=ds_list_create();
var _light_num=collision_rectangle_list(oCamera.x_,oCamera.y_-400,oCamera.x_+oCamera.width_+400, oCamera.y_+oCamera.height_+400,obj_Aura_ShadowCaster_Parent,false,true,_list,false);

for(var l=0;l<_light_num;l++){
  with(_list[| l]){

    //existing code

  }
}

With the above code I first create an empty list object (_list), I then use the new collision_rectangle_list() command to add all the shadow casters that are within the camera (current view) with a 400px padding for larger lights. And then I can use a for loop to go through only those casters in the area using the with(_list[| l]){ … } command to access the casters.

What was the result. Well, the process now takes 34% of the frame time (down from 84%) and takes just over 1.5ms to process. This has changed the frames per second to 190-480fps (depending on the complexity of the area the player is in). This is a spectacular improvement.

The next bottleneck turned out to be my new shadow caster for the player. I had adjusted the code from last week so that I could apply the fake shadows to other objects in the game world thinking that I could add it to chairs, tables, and everything else. However the drawing of so many sprites was impacting performance. As the sprite images already have a basic shadow on I decided to remove it from those objects. I improved the code so I can use it on certain objects though. The first call of my fake shadow script on each object it’s attached to will now store the angle, distance and strength of the shadow if the script is told the object is static (not moving). This means that on subsequent draw operations it doesn’t need to recalculate all the light information.

Next week I will be looking in to a few more bottlenecks.