Fractional Collision Checking

F

Forestherd

Guest
I have noticed something with GM:S and am not sure if it's a bug or me not being aware of something. Basically, I'm trying to make it so that my collision checking logic works with fractional speeds (0.5, 0.18 etc), however, I have noticed that collision checking functions such as place_meeting & instance_place don't really work with fractional values.

I made a test code to prove this:
Code:
if !collision_rectangle(bbox_left,bbox_top,bbox_right+0.5,bbox_bottom,obj_parent_solid,false,true) {
x += 0.5
}
I spawn my player right next to a wall, so both of their collision boxes are touching, and then run the code - the collision check passes twice, moving the player a total of 1 pixel, into the wall, unable to move out.

So does GM:S collision checking not work with fractional values, or am I missing something here?
 
W

Wayfarer

Guest
I can help with this :D

bbox_left, bbox_top etc... only return integer values, so they're not updating at the same rate your x is. But there are also a few other things to be aware of!

Say the player was 16x16 and you manually code out their bounding box, like so...

Code:
var x1 = x;
var y1 = y;
var x2 = x+16;
var y1 = y+16;
if (collision_rectangle(x1, y1, x2, y2, pWall, false, false) {
  // collision occurs
}
...you need to be aware that a collision would activate even when a wall is directly to the right or below the player (as x+16 and y+16 end up being where the wall starts!).

To get around this, you can do something like (note: this isn't optimised for performance):
Code:
var x1 = x;
var y1 = y;
var x2 = x+16;
var y2 = y+16;
var col = false;
with (pWall) {
  // We check if the player is NOT colliding and then flip the result
  // That way we have less coordinates to check!
  if (!(x2 <= x || x1 >= x+16 || y2 <= y || y1 >= y+16)) {
    col = true;
  }
}

if (col) {
  // collision occurs
}

Slightly off topic, but you should also be aware that place_meeting (and similar functions) seem to have a funny quirk about them. For example, collisions won't register until you're 0.50000763 into something. I'm sure there's a good reason for this, but if you want precise collisions with them... you need to do something like:

Code:
var extra = 0.50000763;
if (place_meeting(x + xSpeed + (sign(xSpeed)*extra), y, pWall)) {
}
With that said, place_meeting functions aren't generally recommended, well especially for larger games (if they're being used to check lots of walls etc), but I think it's important to be aware of this because it can cause strange bugs if you're not aware of it.

The place_meeting thing I only figured out like 6 months ago, and I've been using GM for quite some time!
 
Slightly off topic, but you should also be aware that place_meeting (and similar functions) seem to have a funny quirk about them. For example, collisions won't register until you're 0.50000763 into something. I'm sure there's a good reason for this, but if you want precise collisions with them...
place_meeting is an interesting fellow, isn't he? I've noticed that position_meeting also registers collisions the same as place_meeting does: until you're .5000763 into something. Usually how I go about working around this is I use place_meeting to check for a collision, then relocate the player to the edge of the wall when it does. (I'm assuming you're working with the player here). By relocation we are taking away the fraction and the little space going over/under the collision object. (of course you can floor the value, but I feel that's too inaccurate*.) I made a thread discussing this and I think it might be of good use to you: https://forum.yoyogames.com/index.p...ect-object-based-collision.30739/#post-192598

*Example: If I want my player's movespeed to be 1.5 and x+=floor(movespeed), I'd be moving 1 pixel at a time, not 1.5 like I want.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
This is logical really... The regular collisions in GMS are based off of pixel masks, and you can't check just a fraction of a pixel. Either a pixel is there or it isn't... ;)
 
W

Wayfarer

Guest
place_meeting is an interesting fellow, isn't he? I've noticed that position_meeting also registers collisions the same as place_meeting does: until you're .5000763 into something. Usually how I go about working around this is I use place_meeting to check for a collision, then relocate the player to the edge of the wall when it does. (I'm assuming you're working with the player here). By relocation we are taking away the fraction and the little space going over/under the collision object. (of course you can floor the value, but I feel that's too inaccurate*.) I made a thread discussing this and I think it might be of good use to you: https://forum.yoyogames.com/index.p...ect-object-based-collision.30739/#post-192598

*Example: If I want my player's movespeed to be 1.5 and x+=floor(movespeed), I'd be moving 1 pixel at a time, not 1.5 like I want.
That's similar-ish to what I do (if I use place_meeting(), which I don't really use now), though your method actually isn't completely pixel perfect :p. If something is less than 1 pixel away from a collision, and is moving, it will always collide even if it's only moving at 0.1 due to sign().

What I do is this...
End of Step Event:
Code:
var xCheck = x + xSpeed + (sign(xSpeed)*bExtra);

if (place_meeting(xCheck, y, pWall)) {
  var col = instance_place(xCheck, y, pWall);
  x = col.x + (
    ((xSpeed > 0) * bLeft) +
    ((xSpeed < 0) * (col.sprite_width + bRight))
  );
  xSpeed = 0;
}
x += xSpeed;
Create Event (for the sake of completeness):
Code:
// Physics / Collisions
// --------------------
xSpeed = 0;
ySpeed = 0;

if (mask_index == -1) mask_index = sprite_index;

bExtra = 0.50000763; // <-- used to fix rounding in place_meeting() style functions
bLeft = -sprite_get_xoffset(mask_index);
bTop = -sprite_get_yoffset(mask_index);
bRight = sprite_width + bLeft;
bBottom = sprite_height + bTop;


But even that approach (and your method) has a flaw... though the flaw generally won't occur in most games (it only occurs when blocks aren't confined to a grid), but I'll explain it anyway... if you imagine the player going really fast on the x-axis (say 12px / step) and then colliding with 2 blocks vertically stacked on top of one another. Now, let's say for some strange reason one of the blocks is 4px closer, if they collide with the block that is further away (due to the order of the place_meeting() check) they will actually end up snapping to that block rather than the closer one!

However... there is a solution to that...

End of Step Event:
Code:
var xSpeedBeforeCol = xSpeed;

while (place_meeting(x + xSpeed + (sign(xSpeed)*bExtra), y, pWall)) {
    var col = instance_place(x + xSpeed + (sign(xSpeed)*bExtra), y, pWall);
    x = col.x + (
        ((xSpeedBeforeCol > 0) * bLeft) +
        ((xSpeedBeforeCol < 0) * (col.sprite_width + bRight))
    );
    xSpeed = 0;
}
x += xSpeed;
Important! Even though a while() loop is used, in the case of a collision, place_meeting() would only be checked two times 99.99% of the time (except for that edge case explained above).


After saying all of that...
A 2D array grid system is best :D
 
Last edited:

Yal

🐧 *penguin noises*
GMC Elder
I've had another interesting (aka ****ing annoying) bug in my current project where a pixel between two wall objects would not register a collision with either, randomly breaking code and making no sense whatsoever (you'd think a point on the exact border between two objects would belong to either of them, since, y'know, they're whole pixels). (I don't remember the details, but I think I was using collision_line() to check for collisions around the ground). I ended up finding a workaround by having the affected objects' sprites extend so they were overlapping, at least. (for reasons, I couldn't use collision_rectangle - this was a thing about sloped terrain using angle rotation and stuff)
 
W

Wayfarer

Guest
I've had another interesting (aka ****ing annoying) bug in my current project where a pixel between two wall objects would not register a collision with either, randomly breaking code and making no sense whatsoever (you'd think a point on the exact border between two objects would belong to either of them, since, y'know, they're whole pixels). (I don't remember the details, but I think I was using collision_line() to check for collisions around the ground). I ended up finding a workaround by having the affected objects' sprites extend so they were overlapping, at least. (for reasons, I couldn't use collision_rectangle - this was a thing about sloped terrain using angle rotation and stuff)
It'd be worth setting up a test project just to figure out exactly how collision_line() works. Surely there's a neater way than scaling the sprite :p
For some reason, if I can't understand why something is working - it bothers me - even if it works!

And once I finally worked out exactly what was going on with place_meeting() all of a sudden a lot of strange past bugs made more sense. In recent times, I do all collisions manually, with the exception of "sometimes" using position_meeting() at room start up (mainly for preparation things like auto-tiling etc).
 

Yal

🐧 *penguin noises*
GMC Elder
Surely there's a neater way than scaling the sprite :p
I'm not scaling the sprite (that'd ALSO mess with the collision mask - these are pixel-perfect slopes and curves, basically, so stretching them would just be opening another can of worms), I made the actual sprites 2 pixels wider so they extend into the walls a bit :p
 
W

Wayfarer

Guest
Hopefully this helps someone... I made some functions to help with subpixel accurate collisions:

Functions:
  • motionMeeting(x motion, y motion, instance), subpixel version of place_meeting().
    Code:
    /// motionMeeting(x motion, y motion, instance);
    
    // Arguments
    var x1Motion = argument[0];
    var y1Motion = argument[1];
    var o = argument[2];
    
    // Preparation
    var mask = mask_index;
    if (mask == -1) mask = sprite_index;
    
    var bLeft = -sprite_get_xoffset(mask);
    var bTop = -sprite_get_yoffset(mask);
    var bRight = sprite_get_width(mask) + bLeft;
    var bBottom = sprite_get_height(mask) + bTop;
    
    // Function
    if (!(
        (x + bRight + x1Motion) <= (o.x) ||
        (x + bLeft + x1Motion) >= (o.x + o.sprite_width) ||
        (y + bBottom + y1Motion) <= (o.y) ||
        (y + bTop + y1Motion) >= (o.y + o.sprite_height)
    )) {
        return true;
    }
    
    return false;
  • motionSnapX(x speed, instance to snap to), snap X to an instance edge
    Code:
    var dir = sign(argument[0]);
    var o = argument[1];
    
    var mask = mask_index;
    if (mask == -1) mask = sprite_index;
    var bLeft = -sprite_get_xoffset(mask);
    var bRight = sprite_get_width(mask) + bLeft;
    
    switch (dir) {
        case 1: return (o.x - bRight); break;
        case -1: return (o.x + o.sprite_width - bLeft); break;
    }
  • motionSnapY(y speed, instance to snap to), snap Y to an instance edge
    Code:
    var dir = sign(argument[0]);
    var o = argument[1];
    
    var mask = mask_index;
    if (mask == -1) mask = sprite_index;
    var bTop = -sprite_get_yoffset(mask);
    var bBottom = sprite_get_height(mask) + bTop;
    
    switch (dir) {
        case 1: return (o.y - bBottom); break;
        case -1: return (o.y + o.sprite_height - bTop); break;
    }

You can see these functions simplify code, too:
Code:
// A basic horizontal collision check
x += xSpeed;

with (pWall) { with (other) {
    if (motionMeeting(0, 0, other)) {
        x = motionSnapX(xSpeed, other);
        xSpeed = 0;
    }
}}
Notes:
  • At least for now, when using these functions to detect walls etc, the wall must have an origin of "0, 0" and be a solid square sprite.
  • While you could build an entire game purely from these functions, it's recommended you use a 2D array / tile grid approach and use these functions for things like moving platforms, or dynamic platforms.
  • Quick tip: motionMeeting(0, 0, other) is like using place_meeting(x, y, other), and motionMeeting(4, 0, other) is like using place_meeting(x + 4, y, other).


I've made an example that shows gravity based moving platforms (that bounce around colliding off one another etc) but I would like to polish it up a bit before showing it.
 
Last edited:
B

basement ape

Guest
@Wayfarer
Those functions work fantastic with square blocks, great job and thanks a lot for sharing! :) It's been puzzling me for the longest time why I couldn't get 100% accurate platform collisions with place_meeting. I thought it was something in my code. Guess not.
 
Top