Game Maker Tips : perfect collisions with moving blocs

Of course the camera has to lag behind and make it look laggy.

 

Hi!

I spent quite a lot of time trying to figure out a way to push my character object with a moving block and what was, in my mind, really easy turned out to be a nightmare. I spent quite a lot of time trying various logics to no success. The object was not aligned and clipped haphazardly in front of the moving block.

Turns out, the problems were the place_meeting() and collision_rectangle() functions. They don’t trigger very accurately, and the two functions return result a bit different in the decimals realm. When moving an object with a speed inferior to 1, this speed is insufficient to trigger the collision every frame. From my observation, when the player is pushed by a block and perfectly aligned with it, the next frames return no collision despite the block colliding and moving toward the player.

To solve the issue, I simply check the collision 1 pixel ahead in the direction of the moving block. This way I don’t rely on the weird way the backed-in functions works. I then align the object to the block, without the additional pixel and I get a reliable collision detection every frame, since I leave the object inside the collision area. Doing so in a loop, you might wonder how the loop ends if the object ends inside the collision area. That’s why I add the ID of the colliding objects to a list, deactivate them, and only reactivate them once the loop is over.

My explanations may not be very easy to follow, let’s dive into the code!

Create the script ! Obviously for it to work you will have to change the variables “hsp” to the one you use in your project. “hsp” is my horizontal speed, maybe the variable you use is different. And I think it’s the only one you’ll have to change actually. xscale_width and yscale_height are the number of times you upscaled a single 16*16 block. If you use a different type of block, you’ll have to make adjustment to the code. I plan on doing 9 slices on my moving blocs, that’s why I designed them this way. When using the script, you can just pass xscale and yscale as arguments:

scr_move_pushed_horizontal(image_xscale, image_yscale);

 

/// @desc scr_move_pushed_horizontal
/// @arg xscale_width
/// @arg yscale_height

//Block is an upscaled 16*16 block, those variables carry it’s size if the block is bigger
xscale_width = argument[0];
yscale_height = argument[1];

 

Here we create a list, the simplest Data Type that I know in GM, just like a grocery list really. You stuff variables inside, they get stacked at the end of the list. If you never used those, check the GM’s help, it’s well explained.

 

//Creating the list to store the colliding objects
var pushable_list = ds_list_create();

 

The algorithm will use a loop to make it’s actions, in plain english it read as follow : as long as an object that can be pushed is colliding with the bloc, the bloc “absorb” it at it’s x coordinate to align it (meaning that the bloc can be at x = 25.98, the player will be placed at a coordinate inside the bloc that ends up with .98 too) , then the bloc pushes the object away one pixel at a time. Result is, the object is perfectly aligned with the bloc border.

 

//Collision loop
while (place_meeting(x+sign(hsp), y, par_pushable)) //Collision with an additional pixel in the moving direction
{
   //Getting the ID of an object colliding
   var pushable;
   pushable = collision_rectangle(x+sign(hsp), y, x+xscale_width*16+sign(hsp), y+yscale_height*16, par_pushable, false, true)
   if (instance_exists(pushable))
   {
      pushable.x = x; //Allign the object to the moving block coordinates
      while(place_meeting(x, y, pushable)) //Then push it away intill it’s outside the block
      {
          pushable.x += sign(hsp)*1;
      }
   }
   

That’s the part where we stuff the list with the object the bloc is pushing. We keep track of all of them, and deactivate them when they no longer collide. This way the initial condition of the loop can stop being true. We check for collision a little further than the block is, so if we didn’t do that, the condition would be true forever, stucking the program in a infinite loop. Also, we can push as many as needed.

   //Adding the object to the list, then deactivating it to allow the loop to end
   //If multiple objects, the loop keeps going until all of them stored in the list and deactivated
   ds_list_add(pushable_list, pushable);
   instance_deactivate_object(pushable);
}

Finally, we reactivate all the objects we pushed, based on the list we created. We tick them off of the list as we do so, and when the list is empty we destroy it.

//If the list is not empty, reactivate the objects stored inside, and then delete them from the list.
while (!ds_list_empty(pushable_list))
{
   to_activate = ds_list_find_value(pushable_list, 0)
   instance_activate_object(to_activate);
   ds_list_delete(pushable_list, 0);   
}

//Destroy the list
ds_list_destroy(pushable_list);

I’ll repost the whole script for copy and paste purpose. Using it as is is fine, but try to understand the code rather than relying entirely copying it, that’s how I tend to learn stuff at least.

 

/// @desc scr_move_pushed_horizontal
/// @arg xscale_width
/// @arg yscale_height

//Block is an upscaled 16*16 block, those variables carry it’s size if the block is bigger
xscale_width = argument[0];
yscale_height = argument[1];

//Creating the list to store the colliding objects
var pushable_list = ds_list_create();

//Collision loop
while (place_meeting(x+sign(hsp), y, par_pushable)) //Collision with an additional pixel in the moving direction
{
   //Getting the ID of an object colliding
   var pushable;
   pushable = collision_rectangle(x+sign(hsp), y, x+xscale_width*16+sign(hsp), y+yscale_height*16, par_pushable, false, true)
   if (instance_exists(pushable))
   {
      pushable.x = x; //Allign the object to the moving block coordinates
      while(place_meeting(x, y, pushable)) //Then push it away until it’s outside the block
      {
          pushable.x += sign(hsp)*1;
      }
   }
   

   //Adding the object to the list, then deactivating it to allow the loop to end
   //If multiple objects, the loop keeps going until all of them stored in the list and deactivated
   ds_list_add(pushable_list, pushable);
   instance_deactivate_object(pushable);
}

//If the list is not empty, reactivate the objects stored inside, and then delete them from the list.
while (!ds_list_empty(pushable_list))
{
   to_activate = ds_list_find_value(pushable_list, 0)
   instance_activate_object(to_activate);
   ds_list_delete(pushable_list, 0);   
}

//Destroy the list
ds_list_destroy(pushable_list);

Any question, hit me up on Twitter! @Skaz_ Hope it helps and that I neither over explained it or missed important details.

Keep learning!

– Skaz

 

Leave a Reply

Your email address will not be published. Required fields are marked *