0
\$\begingroup\$

I'm in the process of creating a game using electron and HTML5. It's going to be a pretty basic story-based platformer, but I'm programming the game engine myself. So far it's going well, but I encountered some difficulty when trying to implement solid objects, things the player can't go through.

In the first revision of my engine, the algorithm is as follows:

//when the player moves left
var moveby = -player.speed; //player.x - player.speed => player moves left
//for each solid object
for (var i = 0; i < Game.currentmap.solids.length; i++) {
  var solid = Game.currentmap.solids[i];
  //if player isn't going to touch the solid object when it moves, continue loop
  if (!Game.check_collision(x - speed, y - speed, w + (speed * 2), h + (speed * 2), solid.x, solid.y, solid.w, solid.h)) {
    continue;
  }
  //a hitbox on the side of the solid object
  var hitbox = {
    x: ogbox[0] + ogbox[2],
    y: ogbox[1],
    w: speed,
    h: ogbox[3]
  }
  if (Game.check_collision(x, y, w, h, hitbox.x, hitbox.y, hitbox.w, hitbox.h)) {
    //move player left until they hit it, then break loop
    moveby = hitbox.x - x;
    break;
  }
}
return moveby;

Essentially, this code is copied for each direction the player can move (up, down, left, and right). The x, y, w, and h variables are the x, y, width, and height of the player's hitbox. The game will check if the player will collide with the solid object when it moves. If it won't, the loop continues. If it will, the function will calculate how far the player moves before it will hit the solid object, then moves the player that far.

Clearly, this isn't very efficient, and the code looks terrible. But I can't think of a better way to go about this. Could somebody please give me a better recommendation?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Firstly, I wouldn't recommend writing a separate piece of code for each direction. Instead, try to formulate a general solution. Also, if you need more complex collision checking and handling, you might want to use a physics engine like Box2D. But if you want to write the code yourself, here is my explanation:

I assume you are using a rectangle as your player's collision box and that you don't need continuous collision detection (needed for very fast objects). I also assume that going in positive y-direction is going 'up' and going in positive x-direction is going 'right'. I don't know electron, but will try to use obvious variable and function names. Further down I defined some utility functions. The minimal approach I can think of works as follows.

Separate your code into two parts:

  1. Calculating your player's speed and setting new position
  2. Detecting and handling collisions

Calculating player's speed and setting new position

  • Calculate the player's speed as a 2D vector with x- and y- components (this may involve walking, jumping, falling)

      // Set horizontal speed depending on user's input
      if (key_is_pressed(LEFT) && !keyIsPressed(RIGHT)) {
          speed.x = -walk_speed;
      } else if (key_is_pressed(RIGHT) && !keyIsPressed(LEFT)) {
          speed.x = walk_speed;
      } else {
          speed.x = 0;
      }
    
      // Calculate vertical speed (jumping, gravity)
      if (isGrounded) {
          key_is_pressed(UP) {  // JUMP
              isGrounded = false;
              speed.y = jump_speed;
          }
      } else {
          speed.y -= gravity;  // FALL - accelerate downwards
      }
    
      // Move player to new position
      x = x + speed.x;
      y = y + speed.y;
    

Detecting and handling collisions

  • Check whether the player is colliding with a solid object
  • If there is a collision, move the player out of the solid object either horizontally or vertically depending on his/her position relative to the solid object and set the player's respective speed to 0

This pseudocode is a more detailed description of the last step:

// FOR LOOP START
if (check_collision_rectangle(x, y, w, h, solid.x, solid.y, solid.w, solid.h)) {
    deltaX = get_x_overlap(x, y, w, h, solid.x, solid.y, solid.w, solid.h);
    deltaY = get_y_overlap(x, y, w, h, solid.x, solid.y, solid.w, solid.h);
    // Move player out of solid object horizontally or vertically,
    // whichever is shorter, and set respective speed to 0
    if (Math.abs(deltaX) <= Math.abs(deltaY)) {
        x = x + deltaX;
    } else {
        if (deltaY < 0) {  // if player just landed
            isGrounded = true;
        }
        y = y + deltaY;
    }
}
// FOR LOOP END

Moving in steps of limited size

Should you face the problem, that the player is penetrating solid objects or behaving weirdly, it might be due to a too big jump from one frame to the next. In this case you could moving the player at steps of a limited size at a time. Do this by moving the player by the step size into the desired direction. This can be done by first normalizing the speed-vector. Then multiply it by step_size and then executing the code inside a loop until the player was moved by the full amount.

let magnitude = Math.pow(speed.x, 2) + Math.pow(speed.y, 2);
let speedNormalizedX = speed.x / magnitude;
let speedNormalizedY = speed.y / magnitude;
let stepSize = 5;

while(magnitude > 0) {
    if magnitude >= stepSize {
        x = x + speedNormalizedX * stepSize;
        y = y + speedNormalizedY * stepSize;
    } else {
        x = x + speedNormalizedX * magnitude;
        y = y + speedNormalizedY * magnitude;
    }
    magnitude = magnitude - stepSize;

    // DO COLLISION CHECKING AND HANDLING HERE
}

Note that this is not very performant, if the step size is small. But it shouldn't cause problems for a basic platformer.

Utility functions

function get_x_overlap(x1, y1, w1, h1, x2, y2, w2, h2) {
    leftOverlap = x2 - (x1 + w1);
    rightOverlap = x1 - (x2 + w2);
    // if not overlapping return 0
    if (leftOverlap <= 0 || rightOverlap <= 0) {
        return 0;
    }
    if (leftOverlap < rightOverlap) {
        return -leftOverlap;
    } else {
        return rightOverlap;
    }
}

function get_y_overlap(x1, y1, w1, h1, x2, y2, w2, h2) {
    bottomOverlap = y2 - (y1 + h1);
    topOverlap = y1 - (y2 + h2);
    // if not overlapping return 0
    if (bottomOverlap <= 0 || topOverlap  <= 0) {
        return 0;
    }
    if (bottomOverlap < topOverlap ) {
        return -bottomOverlap;
    } else {
        return topOverlap;
    }
}

function check_collision_rectangle(x1, y1, w1, h1, x2, y2, w2, h2) {
    // if rect1 is above, below, left or right of rect2, return false
    if (y1 > y2 + h2) {  // above
        return false;
    }
    if (y1 + h1 < y2) {  // below
        return false;
    }
    if (x1 + w1 < x2) {  // left
        return false;
    }
    if (x1 > x2 + w2) {  // right
        return false;
    }

    // else return true
    return true;
}
\$\endgroup\$

You must log in to answer this question.

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