GMS 2 Tile Collisions with Big Enemies

J

jakejake_

Guest
So I've got a question for something that I haven't come across yet, but most likely definitely will.

I've got my tile collisions set up, with my player sprite's collision box as small or smaller than my 32x32 tile collision box. I know this is a rule, to always try to keep your collision boxes the same size or smaller than the tile collision box.

Basically I'm wondering what one would have to do if he put, say, a boss or any enemy larger than the 32x32 player in the room? How would you make his sprite comply with the tiles?
 
Last edited by a moderator:

Morendral

Member
I don't think it has to be a rule. Tile based collisions check where the bounding boxes are touching in the tile grid. To make it larger, just check at different intervals along a side so that it is checking at most every tile size.

For instance, if your grid is 32*32, and your player is 16w and 64h, you would check the left or right bounding box at y=0, y= +31, and y=+63 (assuming that your origin is at the top). This will prevent blocks half the height of the player from missing the collision check.
 
Last edited:

TheouAegis

Member
A lot of collision boxes in old games were hexagonal, heptagonal, or octagonal, not rectangular like people in GM community think they need to be.

 

Morendral

Member
A lot of collision boxes in old games were hexagonal, heptagonal, or octagonal, not rectangular like people in GM community think they need to be.

Interesting to learn that. What games used this type of system? Im no expert but, it looks to me like it would be much more inefficient to use that method instead of a rectangular collision check. Based on your example, it also looks like it could be unfair to the player with potential hits occurring that don't seem to touch the sprite which is what I've heard to be a big design no-no.
 

TheouAegis

Member
You don't check the whole box, you check the vertices.

For object-to-object collisions like PvE hit detection, you can compare entire boxes, but that's ultimately just fuzzy logic which fails when even just one bounding box is improperly defined. The big problem is how do you make it fair for the player? If the bounding box covers the entire sprite, then air-to-air collisions are fatal. If you make the boxes too small for all objects, then collisions will miss, which is just as unfair. And if the player has a small hitbox to assist in dodging while enemies have large hitboxes to assist in hitting them with attacks, the enemy's larger hitbox offsets the player's smaller box.

With terrain, big hitboxes causes glitches like not being able to jump up even though a collision is only above a shoulder, which has the appearance of no collision at all in practice, or hovering over sloped terrain. So for terrain you should do point-to-point comparisons. Hence the hexagonal hitboxes.
 
J

jakejake_

Guest
For instance, if your grid is 32*32, and your player is 16w and 64h, you would check the left or right bounding box at y=0, y= +31, and y=+63 (assuming that your origin is at the top). This will prevent blocks half the height of the player from missing the collision check.
So I've got my tile collisions stuffed into two scripts. For my player obj (32x32), I'd just call the scripts with a tilesize of 32. If I stuck an enemy with a size of 16x64 like you said in the room, what would I be changing?

Here are my scripts:
Code:
 //script tile_collisions_x
var tilemap_id = argument0;
var tilesize = argument1;


x += hsp;
if (hsp < 0)
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_bottom) & tile_index_mask;
    if (t1 != 0 || t2 != 0)
    {
        x = ((bbox_left + tilesize) & ~(tilesize - 1)) - spr_bbox_left;   
    }
}
if (hsp > 0)
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_bottom) & tile_index_mask;
    if (t1 != 0 || t2 != 0)
    {
        x = ((bbox_right & ~(tilesize - 1)) -1) - spr_bbox_right;   
    }
}

//script tile_collisions_y
var tilemap_id = argument0;
var tilesize = argument1;
y += vsp;
if (vsp < 0) 
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top) & tile_index_mask;
    if (t1 != 0 || t2 != 0)
    {
        y = ((bbox_top + tilesize) & ~(tilesize - 1)) - spr_bbox_top;   
    }
}
if (vsp > 0) 
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_bottom) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_bottom) & tile_index_mask;
    if (t1 != 0 || t2 != 0)
    {
        y = ((bbox_bottom & ~(tilesize - 1)) -1) - spr_bbox_bottom;   
        vsp = 0;
    }
}
 

Morendral

Member
Code:
var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top) & tile_index_mask;
   var t2 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_bottom) & tile_index_mask;

var t3 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top + 32) & tile_index_mask;
I added that third var in there to show you. The Y check on the script is checking halfway in between the top and bottom, assuming it's 64 high. The idea is to have checks like this along each side that are the size of tiles or less so that you can't move through walls if there is a block that won't hit the top or bottom like your script is only checking for currently.

You will have to add more to the next part of the code that checks the vars to include the one we just added.
 

Bentley

Member
a boss or any enemy larger than the 32x32 player in the room? How would you make his sprite comply with the tiles?
I'm not the best to answer this, but I'll try in the chance that it helps:
This would not work:
Code:
// Check if the enemy landed

if ( (tilemap_get_at_pixel(tile_map, bbox_left, bbox_bottom + 1) == GROUND) ||
     (tilemap_get_at_pixel(tile_map, bbox_right, bbox_bottom + 1) == GROUND) )
{
    vspd = 0;
}
Imagine a tile that is by itself. An enemy bigger than the tile could have his bbox_left hanging over the left side of the tile, and his bbox_right hanging over the right side of the tile. Both those collision checks would be false, so he'd go through the tile. You'd need an additional check in the middle.
 
J

jakejake_

Guest
I added your third var in, most likely the wrong way since I'm getting a bunch of nonsense bouncing me up and down and through the walls. You'll have to bare with my noob mistakes, I'm pretty new to game programming.

Here's my code:
Code:
// tile_collisions_x
var tilemap_id = argument0;
var tilesize = argument1;


x += hsp;
if (hsp < 0)
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_bottom) & tile_index_mask;
    var t3 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top + 32) & tile_index_mask;
    if (t1 != 0 || t2 != 0 || t3 != 0)
    {
        x = ((bbox_left + tilesize) & ~(tilesize - 1)) - spr_bbox_left;  
    }
}
if (hsp > 0)
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_bottom) & tile_index_mask;
    var t3 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top + 32) & tile_index_mask;
    if (t1 != 0 || t2 != 0 || t3 != 0)
    {
        x = ((bbox_right & ~(tilesize - 1)) -1) - spr_bbox_right;  
    }
}

//tile_collisions_y
var tilemap_id = argument0;
var tilesize = argument1;


y += vsp;
if (vsp < 0)
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top) & tile_index_mask;
    var t3 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top + 32) & tile_index_mask;
    if (t1 != 0 || t2 != 0 || t3 != 0)
    {
        y = ((bbox_top + tilesize) & ~(tilesize - 1)) - spr_bbox_top;  
    }
}

if (vsp > 0)
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_bottom) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_bottom) & tile_index_mask;
    var t3 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top + 32) & tile_index_mask;
    if (t1 != 0 || t2 != 0 || t3 != 0)
    {
        y = ((bbox_bottom & ~(tilesize - 1)) -1) - spr_bbox_bottom;  
        vsp = 0;
    }
}
Thanks for all the help already, I'm really just trying to get this issue out of the way before I move on to slopes and such.
 

Morendral

Member
Ok let's figure o
ut what's going on.

First, where is the origin of the sprite?
What are the dimensions of the sprite?

The third var check was for the hsp checks as it was checking along a side from top to bottom
hsp check.png
The numbers in the picture correspond to a side of your sprite and the var checks it is doing with tiles. as you can see, doing the same for the vsp checks doesn't work like that.

take out the thrid var check for the vsp ones and then try running it again.

Also, change the check to be + 31 not 32. This is because I am assuming the sprite is 64 high so if your character were standing next to a block that was one high off the ground and your character moved towards it it wouldn't be checked because it is now checking 1 pixel below the bottom of that tile. This is because it starts counting at 0.
check.png

If your sprite is wider than 32 you will need to change your check to this for the vsp collisions:
Code:
var t3 = tilemap_get_at_pixel(tilemap_id, bbox_left+31, bbox_top) & tile_index_mask;
This is now checking along the horizontal on both sides and the middle, assuming the sprite is 64 wide.

I also recommend changing this to be more dynamic as to set up a loop or something that can make these checks every block length along the requested side of the sprite as we have hard coded sizes into these checks and require each object with a different size to have its own unique code now.
 
J

jakejake_

Guest
That's a great system! I've tested it out with a few different numbers (48x48 sprites, changing the check to +23) and it seems to work good. It gets a little wonky with colliding into sides when I make the sprite really big, like 96x96, but I think I can avoid most of those problems with level design.
One thing though, I found that with a 64x64 sprite, the var t3 for the vsp collisions creates a weird effect where when jumping towards a block to the right, I'll hover in the air. When I take out the check, everything seems to work normally.
 

Attachments

Morendral

Member
I'm glad you've got it working well enough now. The system shouldn't have issues though, so there are probably some more things that need to be changed. With the 96px sprites, you are going to need more than 3 vars to check, as it needs to check at least every tile width along a side. That's why i recommend doing a loop.

I will need to see your code to fix the hover thing.
 
J

jakejake_

Guest
Code:
x += hsp;
if (hsp < 0)
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_bottom) & tile_index_mask;
    var t3 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top + 31) & tile_index_mask; // use if sprites are bigger than 32 :: +31 = middle of 64 sprites
    if (t1 != 0 || t2 != 0 || t3 != 0)
    {
        x = ((bbox_left + tilesize) & ~(tilesize - 1)) - spr_bbox_left;   
    }
}
if (hsp > 0)
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_bottom) & tile_index_mask;
    var t3 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top + 31) & tile_index_mask;
    if (t1 != 0 || t2 != 0 || t3 != 0)
    {
        x = ((bbox_right & ~(tilesize - 1)) -1) - spr_bbox_right;   
    }
}

y += vsp;
if (vsp < 0) 
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_top) & tile_index_mask;
    var t3 = tilemap_get_at_pixel(tilemap_id, bbox_left + 31, bbox_top) & tile_index_mask;
    if (t1 != 0 || t2 != 0 || t3 != 0)
    {
        y = ((bbox_top + tilesize) & ~(tilesize - 1)) - spr_bbox_top;   
    }
}

if (vsp > 0) 
{
    var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_bottom) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_bottom) & tile_index_mask;
    var t3 = tilemap_get_at_pixel(tilemap_id, bbox_right + 31, bbox_top) & tile_index_mask;
    if (t1 != 0 || t2 != 0 || t3 != 0)
    {
        y = ((bbox_bottom & ~(tilesize - 1)) -1) - spr_bbox_bottom;   
        vsp = 0;
    }
}
I included a screenshot of the character's size and bounding box. The origin is right inbetween his feet.

With the 96px sprites, you are going to need more than 3 vars to check, as it needs to check at least every tile width along a side. That's why i recommend doing a loop.
How would the loop specifically work? I'm assuming it would be a for loop?
 

Attachments

Morendral

Member
Ok so here is a fix in your last vsp check. One of these things is not like the others, one of these things just isn't the same
Code:
   var t1 = tilemap_get_at_pixel(tilemap_id, bbox_left, bbox_bottom) & tile_index_mask;
   var t2 = tilemap_get_at_pixel(tilemap_id, bbox_right, bbox_bottom) & tile_index_mask;
   var t3 = tilemap_get_at_pixel(tilemap_id, bbox_right + 31, bbox_top) & tile_index_mask
Also your sprite is only 31 px wide from bounding box left to right so it is smaller than a tile width and it's fine with just the 2 checks.

As for the loop, yes a for loop would be good. That's how i did mine.
 

TheouAegis

Member
Tip: instead if "if t1!=0 || t2!=0 || t3!=0", you can lump them all into a bit check:

if t1|t2|t3

Or just addition:

if t1+t2+t3

Or a max check:

if max(t1,t2,t3)


As long as the only value you care about is 0, all of these should work and be a bit faster.

Also, I really, really hate the lump checking method. You perform 3 checks per direction of motion, when most of the time only one or at most two checks are necessary, depending on the structure of the stages.
 
J

jakejake_

Guest
Haha, if I'm being honest I really can't see the change you made to the last vsp check, I don't know if it's really well hidden or if I'm just dumb :confused:
And I feel bad for keeping this thread going on for so long just to say I can't figure out how to build this magic for loop, even though it sounds incredible, so I have a feeling I'll probably end up writing different scripts for different tile sizes. Heh. (unless of course you'd be open to helping out more hey idk up to you)

@TheouAegis I'm assuming "lump checking" basically means sticking everything into combined "or" statements, more or less. If bit checks run faster, cool! Thank you for the tip :)
 

Morendral

Member
I didn't change anything actually, i was just trying to get you to see that the third variable had bbox top instead of bbox bottom like the others.

I'd be happy to help you out more, that's not a problem. My internet will be intermittent for the next month as I'll be travelling but I'll respond as best i can. I can do this over PM too if you don't want to do it in a thread anymore
 
Top