0

I've been looking for some time now how to detect collisions on a tilemap between my player and the box specified in my table, but all I found are advanced tutorials, I'm trying to do this as simply as possible so that I can understand how it works too. In my table, I therefore seek to detect a collision only if the player walks on a box of value 1 (this would be a wall for example). Then the player will not be able to move on this place of my map.

My code:

// Initi

ctx = null;
var ctx = document.getElementById("canvas").getContext("2d");

// Map

var gameMap = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
  0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  0, 1, 0, 1, 0, 0, 0, 1, 1, 0,
  0, 1, 0, 1, 0, 1, 0, 0, 1, 0,
  0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
  0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];

var tileW = 40,
  tileH = 40;
var mapW = 10,
  mapH = 10;

window.onload = function() {
  requestAnimationFrame(drawGame);
  ctx.font = "bold 10pt sans-serif";
};

// Player

var x = 100;
var y = 100;

var radius = 10;

var upPressed = false;
var downPressed = false;
var leftPressed = false;
var rightPressed = false;

var speed = 1;

function drawPlayer() {
  ctx.fillStyle = "green";
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2)
  ctx.fill();
}

// Inputs

function inputs() {
  if (upPressed) {
    y = y - speed;
  }
  if (downPressed) {
    y = y + speed;
  }
  if (leftPressed) {
    x = x - speed;
  }
  if (rightPressed) {
    x = x + speed;
  }
}

document.body.addEventListener("keydown", keyDown)
document.body.addEventListener("keyup", keyUp)

function keyDown(event) {
  if (event.keyCode == 38) {
    upPressed = true;
  }
  if (event.keyCode == 40) {
    downPressed = true;
  }
  if (event.keyCode == 37) {
    leftPressed = true;
  }
  if (event.keyCode == 39) {
    rightPressed = true;
  }
  if (event.keyCode == 65) {
    speedCodePressed = true;
    speed = 20;
  }
  if (event.keyCode == 32) {
    shootPressed = true;
  }
}

function keyUp(event) {
  if (event.keyCode == 38) {
    upPressed = false;
  }
  if (event.keyCode == 40) {
    downPressed = false;
  }
  if (event.keyCode == 37) {
    leftPressed = false;
  }

  if (event.keyCode == 39) {
    rightPressed = false;
  }
  if (event.keyCode == 32) {
    shootPressed = false;
  }
}

// game map draw function

function drawMap() {
  if (ctx == null) {
    return;
  }

  for (var y = 0; y < mapH; ++y) {
    for (var x = 0; x < mapW; ++x) {
      switch (gameMap[((y * mapW) + x)]) {
        case 0:
          ctx.fillStyle = "#685b48";
          break;
        default:
          ctx.fillStyle = "#5aa457";
      }

      ctx.fillRect(x * tileW, y * tileH, tileW, tileH);
    }
  }

}

// clear screen

function clearScreen() {
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

// game loop

function drawGame() {
  requestAnimationFrame(drawGame);
  clearScreen();
  drawMap();
  drawPlayer();
  inputs();
}
<canvas id="canvas"></canvas>

I won't go into too much detail, as I think it's pretty straightforward, but I'm a beginner and really have no idea.

disinfor
  • 10,865
  • 2
  • 33
  • 44
Karloux
  • 11
  • 4

3 Answers3

1

See the changes below...

  • I added canvas.height = tileH * mapH same for width to match the real size of the game.
  • Created a new object var player = { x: 100, y: 100 , radius: 10, speed: 1 } you should keep everything related to the player in that object
  • I'm using Path2D to create the structure that we draw (walls) and a path that we use for the collisions
  • The collisions are detected with isPointInPath read more here: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath
  • I also changed the gameMap to a 2 dimensional array its makes everything easier now that we are using the Path2D, not really required but I like it better that way.

var canvas = document.getElementById("canvas")
var tileW = 40
var tileH = 40
var mapW = 10
var mapH = 10
var ctx = canvas.getContext("2d");

var upPressed = false;
var downPressed = false;
var leftPressed = false;
var rightPressed = false

var player = { x: 100, y: 100, radius: 10, speed: 1 }
var gameMap = [
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
  [0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
  [0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
  [0, 1, 0, 1, 0, 0, 0, 1, 1, 0],
  [0, 1, 0, 1, 0, 1, 0, 0, 1, 0],
  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
  [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
  [0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];

var path = new Path2D()
var walls = new Path2D()
window.onload = function() {
  canvas.height = tileH * mapH
  canvas.width = tileW * mapW
  for (var y = 0; y < mapH; ++y) {
    for (var x = 0; x < mapW; ++x) {
      if (gameMap[y][x]) {
        path.rect(x * tileW- player.radius, y * tileH- player.radius, tileW + player.radius*2, tileH + player.radius*2)
        walls.rect(x * tileW, y * tileH, tileW, tileH)
      }
    }
  }
  requestAnimationFrame(drawGame);
};

function drawPlayer() {
  ctx.fillStyle = "green";
  ctx.beginPath();
  ctx.arc(player.x, player.y, player.radius, 0, Math.PI * 2)
  ctx.fill();
}

function inputs() {
  var newx = player.x
  var newy = player.y
  if (upPressed) newy = player.y - player.speed;
  if (downPressed) newy = player.y + player.speed;
  if (leftPressed) newx = player.x - player.speed;
  if (rightPressed) newx = player.x + player.speed;
  if (!ctx.isPointInPath(path, newx, newy)) {
    player.x = newx;
    player.y = newy;
  }
}

document.body.addEventListener("keydown", keyDown)
document.body.addEventListener("keyup", keyUp)

function keyDown(event) {
  if (event.keyCode == 38) upPressed = true;
  if (event.keyCode == 40) downPressed = true;
  if (event.keyCode == 37) leftPressed = true;
  if (event.keyCode == 39) rightPressed = true;
}

function keyUp(event) {
  if (event.keyCode == 38) upPressed = false;
  if (event.keyCode == 40) downPressed = false;
  if (event.keyCode == 37) leftPressed = false;
  if (event.keyCode == 39) rightPressed = false;
}

function drawGame() {
  ctx.fillStyle = "#685b48"
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#5aa457"
  ctx.fill(walls)
  //ctx.stroke(path);
  drawPlayer();
  inputs();
  requestAnimationFrame(drawGame);
}
<canvas id="canvas"></canvas>
Helder Sepulveda
  • 15,500
  • 4
  • 29
  • 56
0

Solution:

Check if the new position is not 1 in the game map.

If it's 1 do nothing.

If it's not 1 assign position

Calculating position:

Math.floor(y / tileH) // y
Math.floor(x / tileW) // x

Actual code:

function inputs() {
  let newX = x
  let newY = y
  if(upPressed) {
    newY -= speed
  }
  if(downPressed) {
    newY += speed
  }
  if(leftPressed) {
    newX -= speed
  }
  if(rightPressed) {
    newX += speed
  }

  if (gameMap[Math.floor(newY / tileH)][Math.floor(newX / tileW)] !== 1) {
    x = newX
    y = newY
  }
}
Konrad
  • 21,590
  • 4
  • 28
  • 64
0

One way to solve this is by changing your game map from a 1 dimensional array to a 2 dimensional array.

So instead of:

    var gameMap = [
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
        0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
        0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
        0, 1, 0, 1, 0, 0, 0, 1, 1, 0,
        0, 1, 0, 1, 0, 1, 0, 0, 1, 0,
        0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
        0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
        0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    ];

Make it:

    let gameMap = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
        [0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
        [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
        [0, 1, 0, 1, 0, 0, 0, 1, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 0, 1, 0],
        [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
        [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
        [0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ];

Or however you want to structure your game's map.

Then once you have this 2D array, keep track of the row and column index of where your player is currently located.

    let player_index_x = 3;
    let player_index_y = 5;

Update this index whenever the player changes locations; e.g if the player moves up 1, then you subtract 1 from the y index. If the player moves right 1, add one to the x index.

Then collision detection becomes a lot more straightforward because before moving left, right, up, or down, you can check something like:

    if(left_pressed)
    {
        // make sure that it is indeed possible to move left
        if(player_index_x > 1)
        {
            if(gameMap[player_index_x - 1][player_index_y] == 1)
            {
                // collision detected, do not move, return if in function
            }
            else
            {
                // move player
                player_index_x -= 1;
            }
        }
    }
    

My Recommendations:

  1. Be sure to check first whether or not the move is a valid one, so the player does not fall off the map
  2. If a collision occurs, what should happen? If a collision doesn't occur, what should happen? I recommend writing down a list of your assumptions while coding and checking them as you go. Especially in collision detection, it can be very easy to have unintended bugs from unchecked assumptions.

Resources for Learning to do this:

How can I create a two dimensional array in JavaScript?

Kelsey
  • 174
  • 14
  • didn't work for me – Karloux Sep 22 '22 at 16:34
  • What didn't work? – Kelsey Sep 22 '22 at 16:43
  • When I try to implement this code to mine keeping a 1d array with player_index_x and y it doesn't work, I can't move left. – Karloux Sep 22 '22 at 17:10
  • Does it work if you use a 2D array? – Kelsey Sep 22 '22 at 19:19
  • 1
    you can't just change the array to a 2D array and expect the drawing of the map to work, the logic to draw has to change too – Helder Sepulveda Sep 22 '22 at 20:39
  • I agree, I'm wondering if OP changes both the array to 2D and follows the general logic I posted if it would work – Kelsey Sep 22 '22 at 21:02
  • Yes, but suddenly, I have to rethink the method to draw the map, I would have preferred in 1d. – Karloux Sep 23 '22 at 14:57
  • If you kept the array in 1D, then you'd need an algorithm to be able to calculate the tile that is directly above and below yours so you can move to it when the player moves. And this algorithm would change every time your map changes - which could be risky. Or you could go the easy route and use 2D array, and keep track of the x and y indexes. – Kelsey Sep 23 '22 at 15:07
  • 1
    Ok, okay and how can I draw the map in 2d? because my drawMap function does not work – Karloux Sep 23 '22 at 15:31
  • You can keep the two for loops and then check the value of gameMap[x][y]. So like switch(gameMap[x][y]) – Kelsey Sep 23 '22 at 15:43