简体   繁体   中英

How can I have collision detection on a tilemap in javascript?

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.

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; eg 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?

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>

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
  }
}

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