Search code examples
collision-detectiongame-engine

How to react on collision detection in 2d platformers


i wrote an html5 gaming framework and tried to create an 2d platformer. But i stuck on the collision detection part of the game, my framework contains an rect object with the method "intersects". So you can check if the rect intersects with another rect, it works fine but i have no idea how to react on this method. I've tried to set the player position to the last "non colliding" position but the problem with that was the player speed offset, every time there was a gap with the with of the player speed...

I can't think any further myself.


Solution

  • I think this is a game mechanics question in general, not only for HTML5/JS games. I'm not familiar with HTML5 syntax, so you might have to rewrite some details of my code examples. Also, I don't know how exactly you structured your code; I'll assume that both the player and the block have some functions like getX(), getXSpeed(), setX() and so on.

    When you detect collision between the player (or any other object that can move) and an environment block you'll want to update the player's position so it just touches the block without intercepting it. Before you can do this, you'll want to find out how it collides with the block: Did the player enter the block from above, from the left, from the right or from the bottom side?

    First, we need the player's position.

    playerX = player.getX();
    playerY = player.getY();
    

    Depending on the direction in which the player is moving, you'll want to add the player's width and height to get the coordinates of the corner that points in the direction in which the player is moving.

    if(player.getXSpeed() > 0) {
        playerX += player.getWidth();
    }
    if(player.getYSpeed() > 0) {
        playerY += player.getHeight();
    }
    

    Next, you'll want to get the block's sides that are closest to the player's previous position. You can check whether the player's speed is positive or negative to find out from which side he's coming. Together, they represent the coordinates of the corner that points in the direction from which the player is coming.

    blockX = player.getXSpeed() > 0? block.getX() : block.getX() + block.getWidth();
    blockY = player.getYSpeed() > 0? block.getY() : block.getY() + block.getHeight();
    

    Now we have the Y coordinate of the vertical side and the X coordinate of the horizontal side of the block that the player passed through, and in total we have the coordinates of the corner between both sides.

    I created a small diagram in case you don't understand why we are doing this:

    Collision Diagram

    The black box represents the environment block. The orange frames represent the player's previous and current positions. The blue line represents the path the player has moved. We now need to check whether the blue dot, which represents the block's corner, is above or below that line. This tells us whether or not the player bumped against the horizontal or the vertical side of the block.

    If the player hits the vertical side, the inclination of the blue line will be smaller than the inclination from the player's corner to the block's corner. In terms of maths this means:

    (currentPlayerY - previousPlayerY) / (currentPlayerX - previousPlayerX)
    

    is smaller than

    (currentPlayerY - blockY) / (currentPlayerX - blockX)
    

    If this does not apply, the player hits the horizontal side of the block. Your code will look somewhat like this:

    bool verticalSide = abs(player.getYSpeed() / player.getXSpeed()) < abs((playerY / blockY) / (playerX - blockX));
    

    Since we can disregard gravity, we can pretend that the player's movement is linear. We only need one point on this line to tell which direction the player is coming from. This means that (currentPlayerY - previousPlayerY) / (currentPlayerX - previousPlayerX) will always be equal to player.getYSpeed() / player.getXSpeed(), no matter how far away the player has been in the previous game cycle. Note that the player does not necessarily come from the top left, so the inclination we calculated could be negative. We use the math class' abs() function that programming languages usually provide to get the absolute value, i.e. turn all negative values positive.

    Now that we know whether we are dealing with horizontal or vertical collision, we can update the player's position accordingly:

    if(verticalSide) {
        player.setX(player.getXSpeed() > 0? blockX - player.getWidth(): blockX);
        player.setXSpeed(0);
    } else {
        player.setY(player.getYSpeed() > 0? blockY - player.getHeight(): blockY);
        player.setYSpeed(0);
    }
    

    If verticalSide is true, the player is bumping against the side of the block. Now we check whether or not the player's x speed is positive to find out whether he comes from the left or from the right and update his position so he just touches the block. We also set his horizontal speed to 0. However, if verticalSide is not true, the player is bumping against the top or the bottom of the block, so we do the same thing for the y axis. This should be everything you need.

    But I have an additional tip for you: You most likely need information whether or not the player is currently touching the ground to determine whether he can jump when the space bar or whatever key is pressed. To do this, you should create a boolean field grounded in your player class that is set to false every time the player is moved. During collision checking, set it to true again if the player is hitting the top of a block, i.e. verticalSide is false and player.getYSpeed() > 0 is true. If the jump key is pressed, check whether grounded is true before triggering a jump.

    The funny thing is that I had exactly the same problem just yesterday and I figured out this solution, but compressed it to only a few lines of code, so I had to work backwards to remember what exactly I did here. Guess I should comment my code in the future.