3
\$\begingroup\$

I'm attempting to create a 2D Platformer Game and I'm having difficulty with the physics I have implemented. Each loop my code does the following:

  1. Apply the physics to a temporary X and Y Co-ordinates to find the new position of the Player
  2. Generate points around the sprite to check for collisions and assign them directions (Which allows for collision correction based on the direction of the collision)
  3. Loop through each of the points, find the Tile each point is on and check whether it is solid. If it is, mark that direction (The direction linked to the point, that I created earlier) as solid.
  4. After checking all of the points, only apply the physics to X and/or Y if no collision is detected for that direction.

My code, simplified, is below:

var tileWidth = 8, // Tile Width/Height
    gravity = 0.3, // Map Gravity
    friction = 0.8, // Map Friction
    speed = 10, // Get the Users Speed
    xVelocity = 0, // Get the Players X Velocity
    yVelocity = 0, // Get the Players Y Velocity
    x = this.x, // Players X Value
    y = this.y, // Players Y Value
    origX = x, origY = y, // Store the original X & Y Values for later use
    jumping = this.jumping, // Get whether the Player is currently jumping
    grounded = this.grounded, // Get whether the Player is currently grounded
    scale = 4, // Store the Global Graphics scale for rendering
    spritesheet = this.spritesheet; // Fetch the Players Sprite

if(isPressed(UP_ARROW)) { // If the Up Arrow Key is pressed
    if(!jumping && grounded) { // If the Player is grounded and NOT already Jumping
        jumping = true; // They're now Jumping...
        grounded = false; // And no longer grounded
        yVelocity = -(speed / 2) * 2;
    }
}
if(isPressed(RIGHT_ARROW)) { // If the Right Arrow Key is pressed
    if(xVelocity < speed) {
        xVelocity++;
    }
}
if(isPressed(LEFT_ARROW)) { // If the Left Arrow Key is pressed
    if(xVelocity > -speed) {
        xVelocity--;
    }
}

// Apply Gravity and Friction to the X & Y Velocities
xVelocity *= friction;
yVelocity += gravity;

grounded = false; // Reset the grounded value to allow jumping

var points = [],
    newX = (x + xVelocity),
    newY = (y + yVelocity),
    tile = null,
    dir = null,
    checkedDown = false,
    tilesWidth = Math.ceil(spritesheet.getWidth() / (tileWidth / 2)),
    tilesHeight = Math.ceil(spritesheet.getHeight() / (tileWidth / 2));

for(var h = 0; h <= tilesHeight; h++) {
    points.push([ newX, newY + (h * ((tileWidth / 2) * scale)) + ((h == 0) ? 1 : ((h >= tilesHeight) ? -1 : 0)), 0 ]);
    points.push([ newX + (tilesWidth * ((tileWidth / 2) * scale)), newY + (h * ((tileWidth / 2) * scale)) + ((h == 0) ? 1 : ((h >= tilesHeight) ? -1 : 0)), 1 ]);
}
for(var w = 0; w <= tilesWidth; w++) {
    points.push([ newX + (w * ((tileWidth / 2) * scale)) + ((w == 0) ? 1 : ((w >= tilesWidth) ? -1 : 0)), newY, 2 ]);
    points.push([ newX + (w * ((tileWidth / 2) * scale)) + ((w == 0) ? 1 : ((w >= tilesWidth) ? -1 : 0)), newY + (tilesHeight * ((tileWidth / 2) * scale)), 3 ]);
}

var moveLeft = true,
    moveRight = true,
    moveUp = true,
    moveDown = true,
    solid = false;

for(var p = 0; p < points.length; p++) { // For all the generated checking points...
    dir = points[p][2];
    tile = Engine.manager('map').getActive().findTile(points[p][0], points[p][1]);
    solid = tile.isSolid();

    if(solid) { // If this Tile is Solid...
        switch(dir) { // Switch based on the Tile's direction

            case 0: // Left
                moveLeft = false;
            break;

            case 1: // Right
                moveRight = false;
            break;

            case 2: // Up
                moveUp = false;
            break;

            case 3: // Down
                moveDown = false;
            break;

        }
    }
}

if(!moveDown) {
    yVelocity = 0;
    jumping = false;
    grounded = true;
}
if(!moveLeft || !moveRight) {
    xVelocity = 0;
    jumping = false;
}

if(grounded) yVelocity = 0;

// Add the X & Y Velocity to the X & Y values
x += xVelocity;
y += yVelocity;

// Round off our X & Y Value to stop Graphical glitches
x = Math.ceil(x);
y = Math.ceil(y);

(Copied from the Entity Update Loop, the scale value is for scaling up graphics)

The Collision points generation would generate points like this:

Collision Point Generation

The issue I'm having is that when a player falls and hits the floor, the collision detection for Left and Right is triggered. This is because it checks for collision on Left, Right, Up and Down with the new co-ordinates, and even if they collide with the ground it triggers the Left/Right because they were also in a solid tile at the time. What I want to know is; how should I go about this? I've been trying to think of a different way to approach this but I've had no luck, I'm not great when it comes to physics (Hence the super-simple Friction and Gravity)

\$\endgroup\$

2 Answers 2

4
+50
\$\begingroup\$

Once you know that your rectangles collided, you can use the square of the distance between them to know from which side, the collision took place.

var pRect; // Player rectangle
var oRect; // Other rectangle

if (intersects(pRect, oRect))
{
    // Calculate the vertical and horizontal
    // length between the centres of rectangles

    var hd = abs((pRect.centerX * pRect.centerX) + (oRect.centerX * oRect.centerX));
    var vd = abs((pRect.centerY * pRect.centerY) + (oRect.centerY * oRect.centerY));

    // Now compare them to know the side of collision

    if (hd < vd)
    {
        if (pRect.centerX < oRect.centerX)
            // Collision on right side of player
        else
            // Collision on left side of player
    } 
    else if (vd < hd)
    {
        if (pRect.centerY < oRect.centerY)
            // Collision on bottom side of player
        else
            // Collision on top side of player
    }
}

Hope this helps.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I know this is late but I have a question. Why do you have to square the distances of the two rectangles? \$\endgroup\$ Nov 13, 2016 at 11:36
  • \$\begingroup\$ @SeanFrancisN.Ballais To get the distance between two points in math, you need a square-root operation. Because both sides in comparison require a square-root, we simply do not perform the square-root. That is the reason I mentioned it as the square of the distances. \$\endgroup\$ Oct 5, 2019 at 19:15
0
\$\begingroup\$

The "left" and "right" collision points should start "below" the top collision point and "above" the low collision point so they won't be triggered when standing on the floor or hitting your head against a ceiling.

Also, it might make it easier to do collision checking before moving your player, rather than moving, and then collision checking.

An easy way to achieve this, is to remove the topmost and lowermost "side" collision checking points.

\$\endgroup\$
4
  • \$\begingroup\$ I do exactly this - if you look at the points generated on the Player Sprite, the Left and Right points start and stop one pixel above and below the top and bottom ones. That isn't the issue at all; when the player is falling, the newX and newY values are both in a solid tile (The floor), so both the Left/Right and Down collision detection is triggered. \$\endgroup\$
    – oyed
    May 5, 2014 at 11:51
  • \$\begingroup\$ Exactly, and this will happen whenever the player hits a tile at a speed of more than 1 pixel per step on the Y axis. The easy solution is to move them further up. The harder solution is to do fall checking (top / bottom collision detection) first, then if the player turned out to be falling and hitting a tile at the bottom, stop y velocity, move player into "contact position" with the tile, and then check for left/right collisions \$\endgroup\$ May 5, 2014 at 11:55
  • \$\begingroup\$ Moving them further up isn't really a viable solution, as if the Y Velocity is larger then the gap (And the gap can't be larger than 1 Tiles length) then this problem would still occur. Your other solution is something I've contemplated - but I don't know enough about Game Dev to know if this was the correct thing to do. Would it not mess up anything like falling and hitting a wall? \$\endgroup\$
    – oyed
    May 5, 2014 at 12:00
  • \$\begingroup\$ This depends on the order in which you handle the specific kinds of collisions. it's a viable solution, but it requires you to detect and handle vertical collisions before handling the horizontal ones. If the Y velocity does indeed get that high, you will want to look at predicting collision rather than detecting it when it occurs. \$\endgroup\$ May 6, 2014 at 10:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .