Legacy GM Collisions seem to be rounded?

W

Wayfarer

Guest
I've noticed just now that collisions seem to only activate when an object is overlapping more than 50% of one pixel into the collision event object.

To explain that more clearly:
1. Say you have a player object moving downwards at 0.2 pixels a step. Directly below that you have a wall. Like, literally, right below it, so there's no gap.
2. Now assume there is a collision event for the player with the wall.
3. You might expect that the player would collide with the wall upon moving down 0.2 pixels the first step. However, this isn't the case! It's not until the third step, once the player is over 50% into the first pixel, that the collision event occurs.

So, in other words, the player doesn't collide with the wall until they're more than 0.5 of a pixel into it. I can't quite understand how I've only noticed this now.

I managed to figure a quick workaround by ceiling the y axis at the end of the step event, and then setting it back to it's correct value in the draw event. But I'm curious though, is this the best way to go about this? Is there a more logical way?
 
W

Wayfarer

Guest
@Pixelated_Pope:
Hmm, interesting. When using GMS 1.4, the example steps I gave above only cause a collision to occur on the third step. Wouldn't that be rounding?

Here's the example stated more generically:
So you have Object A (which moves down 0.2 pixels each step) and Object B (the object that Object A will collide with). Object A is placed directly above Object B.

How it plays out...
Step 1: Object A has moved down 0.2
Step 2: Object A has moved down 0.4
Step 3: Object A has moved down 0.6 (and this is the exact point where the collision seems to activate)

Am I missing something?
 

TheouAegis

Member
Anyone that writes their own collision check script tends to floor coordinates themselves. The Game Maker manual itself, however, explicitly states in the entry on place_meeting() that values are rounded.
Note that the given x/y coordinates will be rounded to the nearest integer before the check is performed, so if this is not what you require (or you have been using a legacy GameMaker product), you should floor the coordinates in the check: instance_place(floor(x), floor(y), obj_Enemy).
 
W

Wayfarer

Guest
@TheouAegis:
In my manual (and the one online) it states for place_meeting...
Note that the given x/y coordinates will be floored to the nearest integer before the check is performed.
On instance_place, though, it does mention the quote you said.

Regardless, I'm actually talking about the collision events and these do seem to be rounded. Usually I would use my own collision system so I'd know the exact criteria for a collision, but I'm working on a engine where having a single script in the collision event works really neatly. There is a single parent collision object that handles all different types of collisions.

So I guess if I'm using the collision events I have to compensate by "rounding up" both the x and y in the direction of velocity, and then re-assign their correct values in the draw event (or probably better in End Step if that occurs after Collisions).
 

TheouAegis

Member
Oh, I did use instance_place! Haha, I just typed place_meeting out instead of copy-pasting like I had meant to. :oops:

Well @Nocturne the manual is wrong again! Hahah! You need to copy the entry from instance_place into the entries for place_free, place_empty and place_meeting.


The functions use the bounding box, by the looks of it. The bounding box coordinates are rounded every step.
 
W

Wayfarer

Guest
@TheouAegis
At least something good came out of this then!

The functions use the bounding box, by the looks of it. The bounding box coordinates are rounded every step.
That makes sense.

Hmm, I might try moving the collisions to the step event and use a with statement. Using a with isn't ideal, but I'd need something like that to determine if the player is colliding with multiple objects at once.

Something like:
Code:
// 1. "Round up" x and y in velocity's direction
xActual = x;
yActual = y;
x = xVelocity > 0 ? ceil(x); floor(x);
y = yVelocity > 0 ? ceil(y); floor(y);

// 2. Check for collision
with (pCollision) {
    if (place_meeting(x, y, other) {
        // collision here
    }
}

// 3. Re-assign correct values to x and y
x = xActual;
y = yActual;
Maybe not the best idea? Doesn't seem like it would be very efficient.

Edit:
Yep, that gives the desired effect (after changing the ternary operators to if statements). Still not sure if it'd be wise doing it like this. The engine does combine wall objects together (to form rectangle collision areas) so it'd probably only be iterating 20-40 instances, but I'm still not sure.

Or would I be better off using place_meeting in a while loop that momentarily moves a collision object say -1000px and keeps doing this until there's no collisions left and then move all the collided objects back to their original positions. This might be more efficient, as in 99% of the cases, there'd only be one or two collisions on a single step. Will have to see if this is more efficient. If anyone knows from experience, though, that would help!!

Edit2:
Woah, the while loop approach ends up being 2x faster when testing on 40,000 wall objects with 6 player-like objects that can collide with walls. This is the code:
Code:
// 1. Some variables
collisionObjects[0] = noone;
var collisionObjectsTotal = -1;

// 2. Loop through all colliding walls
while (place_meeting(x, y, pWall)) {
    collisionObjectsTotal += 1; 

    collisionID = instance_place(x, y, pWall);
    collisionObjects[collisionObjectsTotal] = collisionID;
 
    collisionID.x -= 1000;
}

// 3. Move walls back to their original position
for (i = 0; i <= collisionObjectsTotal; i += 1) {
    collisionObjects[i].x += 1000;
}
 
Last edited:
Top