Search code examples
javascripthtmlhtml5-canvasjavascript-objectscollision-detection

How to create an advanced 2d object collision? JavaScript Canvas


How do you create a rectangle-to-rectangle collision system that stops the player from moving in that direction? e.g. making the player not able to move up if an object is in the way. I know how to make the player not able to go off the edge, but I don't know how to make a collision for a solid object. e.g. A box or cube that you can't go through and stops player movement on that side of the cube.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cWidth = 400;
var cHeight = 250;
var keyboardKeys = [];
var friction = 0.8;

var solidObjs = [];

class Player {
  constructor(x, y, width, height, color) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.color = color;
    this.velX = 0;
    this.velY = 0;
    this.speed = 5;
  }
}

function createObj(x, y, width, height, color) {
  solidObjs.push({
    x: x,
    y: y,
    width: width,
    height: height,
    color: color
  });
}

var player1 = new Player(cWidth / 2, cHeight / 2, 25, 25, "#0080ff");

canvas.width = cWidth;
canvas.height = cHeight;

createObj(0, 0, 10, cHeight, "#808080");
createObj(cWidth - 10, 0, 10, cHeight, "#808080");
createObj(0, 0, cWidth, 10, "#808080");
createObj(0, cHeight - 10, cWidth, 10, "#808080");
createObj(50, 50, 100, 100, "#808080");

function drawFrame() {
  ctx.clearRect(0, 0, cWidth, cHeight);
  ctx.fillStyle = "#000000";
  ctx.fillRect(0, 0, cWidth, cHeight);

  if (keyboardKeys["w"] || keyboardKeys["ArrowUp"]) {
    if (player1.velY > player1.speed * -1) {
      player1.velY--;
    }
  }

  if (keyboardKeys["s"] || keyboardKeys["ArrowDown"]) {
    if (player1.velY < player1.speed) {
      player1.velY++;
    }
  }

  if (keyboardKeys["a"] || keyboardKeys["ArrowLeft"]) {
    if (player1.velX > player1.speed * -1) {
      player1.velX--;
    }
  }

  if (keyboardKeys["d"] || keyboardKeys["ArrowRight"]) {
    if (player1.velX < player1.speed) {
      player1.velX++;
    }
  }

  player1.velX *= friction;
  player1.velY *= friction;

  player1.x += player1.velX;
  player1.y += player1.velY;

  ctx.fillStyle = player1.color;
  ctx.fillRect(player1.x, player1.y, player1.width, player1.height);

  for (i = 0; i < solidObjs.length; i++) {
    ctx.fillStyle = solidObjs[i].color;
    ctx.fillRect(solidObjs[i].x, solidObjs[i].y, solidObjs[i].width, solidObjs[i].height);

    advancedCollisionCheck(player1, solidObjs[i]);
  }

  ctx.fillStyle = "white";
  ctx.font = "16px arial";
  ctx.fillText("Add collision to this box.", 50, 66);
}

function simpleCollisionCheck(obj1, obj2) {
  if (obj1.x < obj2.x + obj2.width && obj1.x + obj1.width > obj2.x &&
    obj1.y < obj2.y + obj2.height && obj1.y + obj1.height > obj2.y) {
    return true;
  }
}

function advancedCollisionCheck(obj1, obj2) {
    //looking for an answer for this
}

setInterval(() => {
  drawFrame();
}, 1000 / 60);

document.body.addEventListener("keydown", (e) => {
  keyboardKeys[e.key] = true;
});

document.body.addEventListener("keyup", (e) => {
  keyboardKeys[e.key] = false;
});
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    * {
      padding: 0px 0px;
      margin: 0px 0px;
      box-sizing: border-box;
    }
  </style>
  <title>Document</title>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script src="pg2d.js"></script>
</body>

</html>

I've tried but it seems to disable the player from moving even if they aren't touching any objects.


Solution

  • So I figured this out on my own now, and it is simpler than I thought.

    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    var cWidth = 400;
    var cHeight = 250;
    var keyboardKeys = [];
    var friction = 0.8;
    var fps = 60;
    var gameLoop;
    
    var solidObjs = [];
    
    class Player {
      constructor(x, y, width, height, color) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.color = color;
        this.velX = 0;
        this.velY = 0;
        this.speed = 5;
      }
    }
    
    function createObj(x, y, width, height, color) {
      solidObjs.push({
        x: x,
        y: y,
        width: width,
        height: height,
        color: color
      });
    }
    
    var player1 = new Player(cWidth / 2, cHeight / 2, 25, 25, "#0080ff");
    
    canvas.width = cWidth;
    canvas.height = cHeight;
    
    createObj(0, 0, 10, cHeight, "#808080");
    createObj(cWidth - 10, 0, 10, cHeight, "#808080");
    createObj(0, 0, cWidth, 10, "#808080");
    createObj(0, cHeight - 10, cWidth, 10, "#808080");
    createObj(50, 50, 100, 100, "#808080");
    
    function drawFrame() {
      ctx.clearRect(0, 0, cWidth, cHeight);
      ctx.fillStyle = "#000000";
      ctx.fillRect(0, 0, cWidth, cHeight);
    
      if (keyboardKeys["w"] || keyboardKeys["ArrowUp"]) {
        if (player1.velY > player1.speed * -1) {
          player1.velY--;
        }
      }
    
      if (keyboardKeys["s"] || keyboardKeys["ArrowDown"]) {
        if (player1.velY < player1.speed) {
          player1.velY++;
        }
      }
    
      if (keyboardKeys["a"] || keyboardKeys["ArrowLeft"]) {
        if (player1.velX > player1.speed * -1) {
          player1.velX--;
        }
      }
    
      if (keyboardKeys["d"] || keyboardKeys["ArrowRight"]) {
        if (player1.velX < player1.speed) {
          player1.velX++;
        }
      }
    
      player1.velX *= friction;
      player1.velY *= friction;
    
      player1.x += player1.velX;
      player1.y += player1.velY;
    
      for (i = 0; i < solidObjs.length; i++) {
        ctx.fillStyle = solidObjs[i].color;
        ctx.fillRect(solidObjs[i].x, solidObjs[i].y, solidObjs[i].width, solidObjs[i].height);
    
        if (simpleCollisionCheck(player1, solidObjs[i])) {
          advancedCollisionCheck(player1, solidObjs[i]);
        }
      }
    
      ctx.fillStyle = player1.color;
      ctx.fillRect(player1.x, player1.y, player1.width, player1.height);
    }
    
    function simpleCollisionCheck(obj1, obj2) {
      if (obj1.x < obj2.x + obj2.width && obj1.x + obj1.width > obj2.x &&
        obj1.y < obj2.y + obj2.height && obj1.y + obj1.height > obj2.y) {
        return true;
      }
    }
    
    function advancedCollisionCheck(obj1, obj2) {
      var vx = (obj1.x + obj1.width / 2) - (obj2.x + obj2.width / 2);
      var vy = (obj1.y + obj1.height / 2) - (obj2.y + obj2.height / 2);
    
      if (Math.abs(vx / obj2.width) > Math.abs(vy / obj2.height)) {
        if (vx < 0) {
          obj1.x = obj2.x - obj1.width;
        } else {
          obj1.x = obj2.x + obj2.width;
        }
      } else {
        if (vy < 0) {
          obj1.y = obj2.y - obj1.height;
        } else {
          obj1.y = obj2.y + obj2.height;
        }
      }
    }
    
    window.onload = function() {
      gameLoop = setInterval(() => {
        drawFrame();
      }, 1000 / fps);
    }
    
    document.body.addEventListener("keydown", (e) => {
      keyboardKeys[e.key] = true;
    });
    
    document.body.addEventListener("keyup", (e) => {
      keyboardKeys[e.key] = false;
    });
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
        * {
          padding: 0px 0px;
          margin: 0px 0px;
          box-sizing: border-box;
        }
      </style>
      <title>Document</title>
    </head>
    
    <body>
      <canvas id="canvas"></canvas>
    </body>
    
    </html>