简体   繁体   中英

HTML Canvas game: 2D collision detection

I need to program a very simple 2D HTML canvas game with a character and a few walls. The map (top view) is a multidimensional array ( 1=walls )

map = [
  [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
  [1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
  [1,0,0,1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,1,0,0,0,0,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,1],
  [1,0,0,1,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,1],
  [1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1,1,0,1,1],
  [1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,1,1,0,1,1],
  [0,0,0,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,1,0,1,1],
  [1,1,1,0,1,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,1,0,1,1],
  [1,1,1,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,0,0,0,0,1,1],
  [1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,1,0,0,1,1],
  [1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1],
  [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
];

The character shouldn't be able to walk over the walls... so he should only walk on the "0"s. I already got the map rendering and the walking part of the character working just fine, but I can't quite figure out how to check for collisions yet.

A very simple version can be found on JSBin . You can either use the arrow keys or WASD to move around (black square).

I already tried to do a very simple collision detection by using something like this:

function checkCollision( x, y ) {
  if ( map[ Math.round( x ) ][ Math.round( y ) ] !== 0 ) {
    return true; // Collision
  }
  return false;
}

But this doesn't quite work (see JSBin ). With Math.round the character and wall overlap... if I use Math.ceil or Math.floor it's even worse.

Is there any way that I can improve this "collision detection", so that the character can't walk over the red walls?

There are a few problems:

First you should avoid using 0.1 as coordinate step because it's a "bad number" in floating point (it's periodic when expressed in binary). Much better is 0.125 (1/8). Adding/substracting 1/8 will guarantee your numbers will always remain exact multiples of 1/8 , not accumulating any error.

You should define an "ok(x, y)" function checking if the (possibly fractional) point (x, y) is valid or inside a wall... a simple implementation could be:

return map[y|0][x|0] == 0; //  <expr>|0 is used to convert to integer

Finally you should compute new_charX and new_charY and only accept moving from charX , charY to the new position if all four points:

(new_charX+s, new_charY+s)
(new_charX+1-s, new_charY+s)
(new_charX+s, new_charY+1-s)
(new_charX+1-s, new_charY+1-s)

are valid with s = 1/16 (ie half of the moving step).

Example: http://jsbin.com/wipimidije/edit?js,output

My answer is a little different. Instead of having a 2D array I'd use a simple array and calculate the rows (if you even need them). Now you only have to check the map array at the one index that is the position of the actor:

var map = [0,0,0,1,1,0,0,1,1,0,0,...];

//heres how to calculate the x/y if you need it for something else
var pos_x = position % NUM_OF_ROWS;
var pos_y = Math.floor(position / NUM_OF_ROWS);

//for collisions now you just check the value of the array at that index
leftKey.addEventListener('keypress', function() { 
    test(position - 1);
});
rightKey.addEventListener('keypress', function() { 
    test(position + 1);
});
upKey.addEventListener('keypress', function() { 
    test(position + NUM_OF_ROWS);
});
downKey.addEventListener('keypress', function() { 
    test(position - NUM_OF_ROWS);
});

function test(n) {
    if (map[n] === 0) {
        //if there's no collision, update the position.
        position = n;
    } else {
        console.log('Collided!');
    }
}

You need to consider two aspects: the first is collision detection, the second is response. Let's start with the detection. You are checking a single point, but in reality you have a tile size, so there is thickness which you must consider. The coordinate of the character, and the coordinate of your tiles is the top-left corner. It is not sufficient to compare the top-left corners, you must also check the other corners. The right-hand side of the player's square for example is at charX + config.tileSize .

The second aspect is the collision response. The simplest mechanism you can use here is to check the next position of the character for collisions, and only move the character if there are none. You should preferably check the two axes separately to allow the character to "slide" along walls (otherwise it till get stuck in walls in moving diagonally into the wall).

Your player can potentially be overlapping four squares in the map at any time. So you need to check for collision in all four squares (corresponding to the top-left, top-right, bottom-left, bottom-right corners of the character). To allow it to 'squeeze' through corridors (given that the character is the same size as a tile) you may also need to adjust this by one or two pixels (hence the 1 / config.tileSize in the following).

function checkCollision( x, y ) {
  var x1 = Math.floor(x + 1 / config.tileSize), 
      y1 = Math.floor(y + 1 / config.tileSize),
      x2 = Math.floor(x + 1 - 1 / config.tileSize), 
      y2 = Math.floor(y + 1 - 1 / config.tileSize);
  if (map[y1][x1] !== 0 || map[y2][x1] !== 0 || map[y1][x2] !== 0 || 
      map[y2][x2] !== 0) {
    return true; // Collision
  }
  return false;
}

See this version of the JSBin

First of all I would change the tiles "value" if you change the character to walk in 1's but in 0's you can check if a tile is walkable by typing If(tile[x][y])... Then, I would, first calculate the next position and then make the move if the player is able to...

Var nextpos = new position;

If(KEYLEFT){
    Nextpos.x = currpos - 1;
}

If(nextpos > 0 && nextpos < mapsize && tile[nextpos.x][nextpos.y])
    Player.pos = nextpos;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM