二维正方形到矩形的碰撞检测和动作(物理)

2d Square to rectangle collision detection and action(physics)

所以我用这种方法来检测移动和接触其他方块的方块并做出反应。它使用毕达哥拉斯定理来制作第三个参数,因此当正方形接触时您不会得到两个 true if 语句。我最近一直在尝试在方形到矩形上使用此方法,但似乎无法正常工作。我画了线来帮助可视化代码在做什么。有人对如何使此碰撞正常工作有任何建议吗?

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

let gravity = 1.5;
let friction = 0.9;

//CHARACTER:
class Player {
    constructor(x, y, w, h, vx, vy, c, j) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
        this.color = c;
        this.jumping = j;
    }
    draw() {
      context.fillStyle = this.color;
      context.fillRect(this.x, this.y, this.w, this.h);
    }
    canvasCollision() {
        if (this.x <= 0) this.x = 0;
        if (this.y <= 0) this.y = 0;
        if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
        if (this.y + this.h >= canvas.height) {this.y = canvas.height - this.h; this.vy = 0; this.jumping = false};
    }
    update() {
        this.draw(); 
        this.vy += gravity;
        this.x += this.vx;
        this.y += this.vy;
        this.vx *= friction;
        this.vy *= friction;
        this.canvasCollision() //must be after other updates
    }
}

let player1 = new Player(75, canvas.height/2 + 75, 75, 75, 0, 0, '#8DAA9D', false); 

function controlPlayer1(obj) {
    //this order matters. If update is before jump then obj won't jump when on top of other block.
    if (controller1.up1 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
    if (controller1.left1) { obj.vx -= 0.5 };
    if (controller1.right1) { obj.vx += 0.5 };
    obj.update();
}

//MOVEMENT:
class Controller {
    constructor() {
        this.left1  = false;
        this.up1    = false;
        this.right1 = false;

        this.down1  = false;

        let controller1 = (e) => {
              if (e.code === 'KeyD')   { this.right1 = e.type === 'keydown' }
            if (e.code === 'KeyA')   { this.left1 = e.type === 'keydown' }
            if (e.code === 'KeyW')   { this.up1 = e.type === 'keydown' }    
            if (e.code === 'KeyS')  { this.down1 = e.type === 'keydown' }       
        }
        
    window.addEventListener('keydown', controller1);
    window.addEventListener('keyup', controller1);

    }
}

let controller1 = new Controller();

//PLATFORM
class Platform {
  constructor(x, y, w, h, yv, c) {
      this.x = x;
      this.y = y;
      this.w = w;
      this.h = h;
      this.yv = yv;
      this.color = c;
  }
  draw(){
    context.fillStyle = this.color;
    context.fillRect(this.x, this.y, this.w, this.h);
  }
  update(){
    this.draw();
    this.y += this.yv;
    this.yv *= 0.9;// friction
  }
}

let platform1 = new Platform(canvas.width/2, canvas.height - 150, 100, 30, 0, '#202020');
let platform2 = new Platform (canvas.width/4, canvas.height/2, 75, 75, 0, '#202020');

//COLLISION DETECTION
function platformDetection(obj, obj2){
 //center point of each side of obj1
 let objLeft = {x: obj.x,  y: obj.y + obj.h/2};
 let objTop = {x: obj.x + obj.w/2, y: obj.y};
 let objRight = {x: obj.x + obj.w, y: obj.y + obj.h/2};
 let objBottom = {x: obj.x + obj.w/2, y: obj.y + obj.h};
 //center point of each side a obj2
 let obj2Left = {x: obj2.x, y: obj2.y + obj2.h/2};
 let obj2Top = {x: obj2.x + obj2.w/2, y: obj2.y};
 let obj2Right = {x: obj2.x + obj2.w, y: obj2.y + obj2.h/2};
 let obj2Bottom = {x: obj2.x + obj2.w/2, y: obj2.y + obj2.h};
 //distance between obj1 and obj2 opposing sides
 let rightDistX = objRight.x - obj2Left.x;
 let rightDistY = objRight.y - obj2Left.y;
 let leftDistX = objLeft.x - obj2Right.x;
 let leftDistY = objLeft.y - obj2Right.y;
 let topDistX =  objTop.x - obj2Bottom.x;
 let topDistY = objTop.y - obj2Bottom.y;
 let bottomDistX = objBottom.x - obj2Top.x;
 let bottomDistY = objBottom.y - obj2Top.y;
 //pythagorean theorem for distance. dRight is from the right side of obj1 to the left of obj2. the rest follow suit.
 let dRight = Math.sqrt(rightDistX*rightDistX + rightDistY*rightDistY);
 let dLeft = Math.sqrt(leftDistX*leftDistX + leftDistY*leftDistY);
 let dTop = Math.sqrt(topDistX*topDistX + topDistY*topDistY);
 let dBottom = Math.sqrt(bottomDistX*bottomDistX + bottomDistY*bottomDistY);
 //Math.min return the smallest value thus variable minimum will be which ever sides are closest together
 let minimum = Math.min(dRight, dLeft, dBottom, dTop);
 let val = 0;
 //compare minimum to pythagorean theorem and set val based on which ever side is closest
 if (dTop == minimum) {
  val = 1;
  //the context stuff can be deleted. It's just here for visual. The if statements can be one line each.
  context.lineWidth = 2;
  context.strokeStyle = 'blue';
  context.beginPath();
  context.moveTo(objTop.x, objTop.y); 
  context.lineTo(obj2Bottom.x, obj2Bottom.y);
  context.stroke();
}
else if (dRight == minimum) {
  val = 2;
  context.strokeStyle = 'orange';
  context.beginPath();
  context.moveTo(objRight.x, objRight.y); 
  context.lineTo(obj2Left.x, obj2Left.y);
  context.stroke();
}
else if (dBottom == minimum) {
  val = 3;
  context.strokeStyle = 'green';
  context.beginPath();
  context.moveTo(objBottom.x, objBottom.y); 
  context.lineTo(obj2Top.x, obj2Top.y);
  context.stroke();
}
else if (dLeft == minimum) {
  val = 4;
  context.strokeStyle = 'pink';
  context.beginPath();
  context.moveTo(objLeft.x, objLeft.y); 
  context.lineTo(obj2Right.x, obj2Right.y);
  context.stroke();
}
 //pass the objects and val 
 platformAction(obj, obj2, val);
}

//ACTION
function platformAction(obj, obj2, val){
//player1 top to player2 bottom
if (obj.y <= obj2.y + obj2.h && obj2.y + obj2.h >= obj.y && val == 1) {
  obj2.y = obj.y - obj2.h; 
  obj.y = obj2.y + obj2.h;
  obj2.vy = 0;
  obj2.jumping = false;
  obj.jumping = true;
}
//player1 right to player2 left
if (obj.x + obj.w >= obj2.x && obj2.x <= obj.x + obj.w && val == 2) {
  obj2.x = obj.x + obj.w;
  obj.x = obj2.x - obj.w - 1;
  obj2.vx = 0;
}
//player1 bottom to player2 top
if (obj.y + obj.h >= obj2.y && obj2.y <= obj.y + obj.h && val == 3) {
  obj.y = obj2.y - obj.h;
  obj2.y = obj.y + obj.h;
  obj.vy = 0;
  obj.jumping = false;
  obj2.jumping = true;
}
//player1 left to player2 right
if (obj.x <= obj2.x + obj2.w && obj2.x + obj2.w >= obj.x && val == 4) {
  obj2.x = obj.x - obj2.w;
  obj.x = obj2.x + obj2.w + 1;
  obj.vx = 0;
  obj2.vx = 0;
}
}

function initObj(obj){
obj.update();
}

function loop() {
  context.clearRect(0, 0, canvas.width, canvas.height); 
  context.fillStyle = 'grey';
  context.fillRect(0, 0, canvas.width, canvas.height);

  //PLATFORM
  initObj(platform1);
  initObj(platform2);

  //PLATFORM DETECTION
  platformDetection(player1, platform1);
  platformDetection(player1, platform2);

  //PLAYER
  controlPlayer1(player1); 

  requestAnimationFrame(loop)
}
loop();
<!DOCTYPE html>
<html>
  <head>

    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi">
    <title>Tone.io</title>
    <style>
    body {
      height:100vh;
      width:100vh;
      margin: 0;
    }
    </style>
  </head>
 
  <body>

    <canvas id="canvas"></canvas>

    <script src = "Js/Tone.js" type = "text/javascript"></script>

  </body>

</html>

@MPdoor2“我做了……”。任何人,当我给你那个代码时,我确实明确地说我已经从我创建的用于 tilemaps 的方法中破解了它。该方法为此目的完美无瑕,尽管代码更多,是的,它是为正方形构建的,因为这就是瓷砖地图的主要内容。

我一直在尝试其他制作 CD 的方法。这是一个较短的方法(到目前为止)似乎运行良好。这种方法仍然确定每边之间的距离,但方式不同。一旦 broadphase 确定发生了碰撞,它就会调用 narrow phase,并且距离最短的一侧就是被穿透的一侧。即,当您与从玩家右​​侧到物体左侧的另一个方块发生碰撞时,我们知道即使是 Y 轴也会穿透(玩家的顶角和底角)。这计算了所有三个之间的距离,并且由于 X 之间的距离为 0,因此它是最短的,并且不会调用用于在 Y 方向上移动播放器的 CD。

试试下面的代码片段,看看是否适合您。

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

canvas.width = 700;
canvas.height = 500;

let gravity = 1.5;
let friction = 0.9;

//CHARACTER:
class Player {
    constructor(x, y, vx, vy, c, j) {
        //each player must have separate values for control purposes i.e. velocity/jump/attack etc.
        this.x = x;
        this.y = y;
        this.w = 50;
        this.h = 50;
        this.vx = vx;
        this.vy = vy;
        this.color = c;
        this.jumping = j;
        this.center = {x: this.x + this.w/2,  y: this.y + this.h/2};
        this.topLeft = {x: this.x, y: this.y};
        this.topRight = {x: this.x + this.w, y: this.y};
        this.bottomRight = {x: this.x + this.w, y: this.y + this.h};
        this.bottomLeft = {x: this.x, y: this.y + this.h};
    }
    draw() {
       context.fillStyle = this.color;
       context.fillRect(this.x, this.y, this.w, this.h);
    }
    canvasCollision() {
        if (this.x <= 0) this.x = 0;
        if (this.y <= 0) this.y = 0;
        if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
        if (this.y + this.h >= canvas.height) {this.y = canvas.height - this.h; this.vy = 0; this.jumping = false};
    }
    update() {
        this.draw(); 
        this.vy += gravity;
        this.x += this.vx;
        this.y += this.vy;
        this.vx *= friction;
        this.vy *= friction;
        this.center = {x: this.x + this.w/2,  y: this.y + this.h/2};
        this.topLeft = {x: this.x, y: this.y};
        this.topRight = {x: this.x + this.w, y: this.y};
        this.bottomRight = {x: this.x + this.w, y: this.y + this.h};
        this.bottomLeft = {x: this.x, y: this.y + this.h};
        this.canvasCollision() //must be after other updates
    }
}
let player = new Player(0, 0, 0, 0, 'red', false); 

function controlPlayer1(obj) {
    if (controller1.up1 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
    if (controller1.left1) { obj.vx -= 0.5 };
    if (controller1.right1) { obj.vx += 0.5 };
    obj.update();
}

class Platform {
    constructor(x,y,w,h) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.center = {x: this.x + this.w/2,  y: this.y + this.h/2};
        this.topLeft = {x: this.x, y: this.y};
        this.topRight = {x: this.x + this.w, y: this.y};
        this.bottomRight = {x: this.x + this.w, y: this.y + this.h};
        this.bottomLeft = {x: this.x, y: this.y + this.h};
    }
    draw() {
        context.fillStyle = 'blue';
        context.fillRect(this.x, this.y, this.w, this.h);
    }
}

let platforms = [];
function createPlatforms() {
    for (let i=0; i<4; i++) {
       platforms.push(new Platform(200 * i, 450 - (i * 75), 100, 25));
    }
}
createPlatforms();

let platform1 = platforms.push(new Platform(300, 400, 50, 100));
let platform2 = platforms.push(new Platform(400, 400, 50, 50));

//MOVEMENT:
class Controller {
    constructor() {
        this.left1  = false;
        this.up1    = false;
        this.right1 = false;
        this.left2  = false;
        this.up2    = false;
        this.right2 = false;

        let controller1 = (e) => {
            if (e.code === 'ArrowRight') { this.right1 = e.type === 'keydown' }
            if (e.code === 'ArrowLeft')  { this.left1 = e.type === 'keydown' }
            if (e.code === 'ArrowUp')    { this.up1 = e.type === 'keydown' }           
        }
    
    window.addEventListener('keydown', controller1);
    window.addEventListener('keyup', controller1);
    }
}
let controller1 = new Controller();

function collisionDetection(obj) {
    if (player.x + player.w < obj.x ||
        player.x > obj.x + obj.w ||
        player.y + player.h < obj.y ||
        player.y > obj.y + obj.h) {
            return
        }
        narrowPhase(obj);
}

function narrowPhase(obj) {
    let playerTop_ObjBottom = Math.abs(player.y - (obj.y + obj.h));
    let playerRight_ObjLeft = Math.abs((player.x + player.w) - obj.x);
    let playerLeft_ObjRight = Math.abs(player.x - (obj.x + obj.w));
    let playerBottom_ObjTop = Math.abs((player.y + player.h) - obj.y);

    if ((player.y <= obj.y + obj.h && player.y + player.h > obj.y + obj.h) && (playerTop_ObjBottom < playerRight_ObjLeft && playerTop_ObjBottom < playerLeft_ObjRight)) {
        player.y = obj.y + obj.h;
        player.vy = 0;
    }
    if ((player.y + player.h >= obj.y && player.y < obj.y) && (playerBottom_ObjTop < playerRight_ObjLeft && playerBottom_ObjTop < playerLeft_ObjRight)) {
        player.y = obj.y - player.h; 
        player.jumping = false;
        player.vy = 0;
    }
    if ((player.x + player.w >= obj.x && player.x < obj.x) && (playerRight_ObjLeft < playerTop_ObjBottom && playerRight_ObjLeft < playerBottom_ObjTop)) {
        player.x = obj.x - player.w;
        player.vx = 0; 
    }
    if ((player.x <= obj.x + obj.w && player.x + player.w > obj.x + obj.w) && (playerLeft_ObjRight < playerTop_ObjBottom && playerLeft_ObjRight < playerBottom_ObjTop)) {
        player.x = obj.x + obj.w;
        player.vx = 0; 
    }
}

function animate() {
    context.clearRect(0, 0, canvas.width, canvas.height); 
    context.fillStyle = 'grey';
    context.fillRect(0, 0, canvas.width, canvas.height);
    controlPlayer1(player); 
    for (let i=0;i<platforms.length;i++) {
        platforms[i].draw();
        collisionDetection(platforms[i])
    };
    requestAnimationFrame(animate)
}
animate();
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height">
    <title>CD</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="squareRectCollision2.js"></script>
</body>
</html>

要检测矩形之间的碰撞,请使用它们边的位置。例如,它们“垂直碰撞”如果:

  • object1 的底部在 object2 的底部下方,object1 的顶部在 object2 的底部上方
  • 对象1的顶部在对象2的顶部之上,对象1的底部在对象2的顶部之下

如果它们同时“垂直”和“水平”碰撞,请考虑它们确实会发生碰撞。

现在,一旦确定了这一点,就需要检测从哪个边缘开始。我在这里使用最近的一个,评估所有“垂直碰撞”或“水平碰撞”的距离。

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

canvas.width = Math.min(window.innerWidth - 3, 800);
canvas.height = Math.min(window.innerHeight - 3, 200);

let gravity = 1.5;
let friction = 0.9;

//CHARACTER:
class Player {
  constructor(x, y, w, h, vx, vy, c, j) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.vx = vx;
    this.vy = vy;
    this.color = c;
    this.jumping = j;
  }
  draw() {
    context.fillStyle = this.color;
    context.fillRect(this.x, this.y, this.w, this.h);
  }
  canvasCollision() {
    if (this.x <= 0) this.x = 0;
    if (this.y <= 0) this.y = 0;
    if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
    if (this.y + this.h >= canvas.height) {
      this.y = canvas.height - this.h;
      this.vy = 0;
      this.jumping = false
    };
  }
  update() {
    this.draw();
    this.vy += gravity;
    this.x += this.vx;
    this.y += this.vy;
    this.vx *= friction;
    this.vy *= friction;
    this.canvasCollision() //must be after other updates
  }
}

let player1 = new Player(75, canvas.height / 2 + 75, 75, 75, 0, 0, '#8DAA9D', false);

function controlPlayer1(obj) {
  //this order matters. If update is before jump then obj won't jump when on top of other block.
  if (controller1.up1 && !obj.jumping) {
    obj.vy -= 25;
    obj.jumping = true
  };
  if (controller1.left1) {
    obj.vx -= 0.5
  };
  if (controller1.right1) {
    obj.vx += 0.5
  };
  obj.update();
}

//MOVEMENT:
class Controller {
  constructor() {
    this.left1 = false;
    this.up1 = false;
    this.right1 = false;

    this.down1 = false;

    let controller1 = (e) => {
      if (e.code === 'KeyD') {
        this.right1 = e.type === 'keydown'
      }
      if (e.code === 'KeyA') {
        this.left1 = e.type === 'keydown'
      }
      if (e.code === 'KeyW') {
        this.up1 = e.type === 'keydown'
      }
      if (e.code === 'KeyS') {
        this.down1 = e.type === 'keydown'
      }
    }

    window.addEventListener('keydown', controller1);
    window.addEventListener('keyup', controller1);

  }
}

let controller1 = new Controller();

//PLATFORM
class Platform {
  constructor(x, y, w, h, yv, c) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.yv = yv;
    this.color = c;
  }
  draw() {
    context.fillStyle = this.color;
    context.fillRect(this.x, this.y, this.w, this.h);
  }
  update() {
    this.draw();
    this.y += this.yv;
    this.yv *= 0.9; // friction
  }
}

let platform1 = new Platform(canvas.width / 2, canvas.height - 150, 100, 30, 0, '#202020');
let platform2 = new Platform(canvas.width / 4, canvas.height / 2, 75, 75, 0, '#202020');

//COLLISION DETECTION
function platformDetection(obj, obj2) {

  let ol1 = obj.x;
  let ot1 = obj.y;
  let or1 = ol1 + obj.w;
  let ob1 = ot1 + obj.h;
  let ol2 = obj2.x;
  let ot2 = obj2.y;
  let or2 = ol2 + obj2.w;
  let ob2 = ot2 + obj2.h;

  let [collideTop, collideBottom, collideRight, collideLeft] = [-1, -1, -1, -1];

  if (ob1 >= ob2) {
    if (ot1 <= ob2) {
      collideTop = ob2 - ot1;
    }
  }
  if (ol1 <= ol2) {
    if (or1 >= ol2) {
      collideRight = or1 - ol2;
    }
  }
  if (ot1 <= ot2) {
    if (ob1 >= ot2) {
      collideBottom = ob1 - ot2;
    }
  }
  if (or1 >= or2) {
    if (ol1 <= or2) {
      collideLeft = or2 - ol1;
    }
  }
  let min = minOfVals(collideTop, collideRight, collideBottom, collideLeft);
  let val = 0;
  if (min >= 0) {
    if (min == collideTop && (collideLeft >= 0 || collideRight >= 0)) {
      val = 1;
    } else if (min == collideRight && (collideTop >= 0 || collideBottom >= 0)) {
      val = 2;
    } else if (min == collideBottom && (collideLeft >= 0 || collideRight >= 0)) {
      val = 3;
    } else if (min == collideLeft && (collideTop >= 0 || collideBottom >= 0)) {
      val = 4;
    }
  }
  if (val) console.log(val, min, collideTop, collideRight, collideBottom, collideLeft);
  //pass the objects and val 
  platformAction(obj, obj2, val);
}

function minOfVals(...vals) {
  let min = -1;

  function isBelowMin(v) {
    return v >= 0 && (min < 0 || v < min);
  }
  for (v of vals) {
    if (isBelowMin(v)) min = v;
  }
  return min;
}

//ACTION
function platformAction(obj, obj2, val) {
  //player1 top to player2 bottom
  if (val == 1) {
    console.log("colliding from top");
    obj2.y = obj.y - obj2.h;
    //obj.y = obj2.y + obj2.h; //useless
    obj2.vy = 0;
    obj2.jumping = false;
    obj.jumping = true;
  }
  //player1 right to player2 left
  if (val == 2) {
    console.log("colliding from right");
    obj2.x = obj.x + obj.w;
    obj.x = obj2.x - obj.w - 1;
    obj2.vx = 0;
  }
  //player1 bottom to player2 top
  if (val == 3) {
    console.log("colliding from bottom");
    obj.y = obj2.y - obj.h;
    //obj2.y = obj.y + obj.h; //useless
    obj.vy = 0;
    obj.jumping = false;
    obj2.jumping = true;
  }
  //player1 left to player2 right
  if (val == 4) {
    console.log("colliding from left");
    obj2.x = obj.x - obj2.w;
    obj.x = obj2.x + obj2.w + 1;
    obj.vx = 0;
    obj2.vx = 0;
  }
}

function initObj(obj) {
  obj.update();
}

function loop() {
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.fillStyle = 'grey';
  context.fillRect(0, 0, canvas.width, canvas.height);

  //PLATFORM
  initObj(platform1);
  initObj(platform2);

  //PLATFORM DETECTION
  platformDetection(player1, platform1);
  platformDetection(player1, platform2);

  //PLAYER
  controlPlayer1(player1);

  requestAnimationFrame(loop)
}
loop();
<!DOCTYPE html>
<html>

<head>

  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi">
  <title>Tone.io</title>
  <style>
    html {
      margin: 0;
      padding: 0;
    }
    body {
      height: 100vh;
      width: 100vh;
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>

  <canvas id="canvas"></canvas>

  <script src="Js/Tone.js" type="text/javascript"></script>

</body>

</html>

当你控制的玩家来自左侧或右侧时,仍然存在不同的行为:推动速度不一样。但这不是由于碰撞,所以我让你弄清楚这个(也许也是故意的)。

哟,谢谢贾斯汀的解决方案。我采用了该解决方案并将其添加到我之前问过的一个问题中,即如何对两个有运动的方块(带有箭头和 wasd)进行碰撞检测和操作。我是这样添加的:

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

let gravity = 1.5;
let friction = 0.9;

//CHARACTER:
class Player {
   constructor(x, y, w, h, vx, vy, c, j) {
       this.x = x;
       this.y = y;
       this.w = w;
       this.h = h;
       this.vx = vx;
       this.vy = vy;
       this.color = c;
       this.jumping = j;

       this.center = {x: this.x + this.w/2,  y: this.y + this.h/2};
       this.topLeft = {x: this.x, y: this.y};
       this.topRight = {x: this.x + this.w, y: this.y};
       this.bottomRight = {x: this.x + this.w, y: this.y + this.h};
       this.bottomLeft = {x: this.x, y: this.y + this.h};
   }
   draw() {
     context.fillStyle = this.color;
     context.fillRect(this.x, this.y, this.w, this.h);
   }
   canvasCollision() {
       if (this.x <= 0) this.x = 0;
       if (this.y <= 0) this.y = 0;
       if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
       if (this.y + this.h >= canvas.height) {this.y = canvas.height - this.h; this.vy = 0; this.jumping = false};
   }
   update() {
       this.draw(); 
       this.vy += gravity;
       this.x += this.vx;
       this.y += this.vy;
       this.vx *= friction;
       this.vy *= friction;
       this.canvasCollision() //must be after other updates
   }
}

let player1 = new Player(canvas.width/2, canvas.height/2 + 75, 75, 75, 0, 0, 'darkgrey', false); 
let player2 = new Player(75, canvas.height/2 + 75, 75, 75, 0, 0, '#8DAA9D', false); 


function controlPlayer1(obj) {
   //this order matters. If update is before jump then obj won't jump when on top of other block.
   if (controller1.up1 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
   if (controller1.left1) { obj.vx -= 0.5 };
   if (controller1.right1) { obj.vx += 0.5 };
   obj.update();
}

function controlPlayer2(obj) {
   if (controller2.up2 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
   if (controller2.right2) { obj.vx += 0.5 };
   if (controller2.left2) { obj.vx -= 0.5 };
   obj.update();
}

//MOVEMENT:
class Controller {
   constructor() {
       this.left1  = false;
       this.up1    = false;
       this.right1 = false;

       this.left2  = false;
       this.up2    = false;
       this.right2 = false;

       this.down  = false;

       let controller1 = (e) => {
           if (e.code === 'ArrowRight') { this.right1 = e.type === 'keydown' }
           if (e.code === 'ArrowLeft')  { this.left1 = e.type === 'keydown' }
           if (e.code === 'ArrowUp')    { this.up1 = e.type === 'keydown' }   
           if (e.code === 'ArrowDown')      { this.down = e.type === 'keydown' }          
       }
       
       let controller2 = (e) => {
           if (e.code === 'KeyD')   { this.right2 = e.type === 'keydown' }
           if (e.code === 'KeyA')   { this.left2 = e.type === 'keydown' }
           if (e.code === 'KeyW')   { this.up2 = e.type === 'keydown' }    
           if (e.code === 'KeyS')  { this.down = e.type === 'keydown' }         
       }  

   window.addEventListener('keydown', controller1);
   window.addEventListener('keyup', controller1);
   window.addEventListener('keydown', controller2);
   window.addEventListener('keyup', controller2);
   }
}

let controller1 = new Controller();
let controller2 = new Controller();

//COLLISION DETECTION
function collisionDetection(obj, obj2){
 if (obj.x + obj.w <= obj2.x ||
   obj.x >= obj2.x + obj2.w ||
   obj.y + obj.h < obj2.y ||
   obj.y > obj2.y + obj2.h)
   return
collisionAction(obj, obj2);
}

//ACTION 
function collisionAction(obj, obj2){
   let playerTop_ObjBottom = Math.abs(obj.y - (obj2.y + obj2.h));
   let playerRight_ObjLeft = Math.abs((obj.x + obj.w) - obj2.x);
   let playerLeft_ObjRight = Math.abs(obj.x - (obj2.x + obj2.w));
   let playerBottom_ObjTop = Math.abs((obj.y + obj.h) - obj2.y);
   
   //virtical obj 1 top 
   if ((obj.y <= obj2.y + obj2.h && obj.y + obj.h > obj2.y + obj2.h) && (playerTop_ObjBottom < playerRight_ObjLeft && playerTop_ObjBottom < playerLeft_ObjRight)) {
       obj2.y = obj.y - obj2.h;
       obj2.jumping = false;
       obj2.vy = 0;}
       
   //virtical obj 1 bottom 
   if ((obj.y + obj.h >= obj2.y && obj.y < obj2.y) && (playerBottom_ObjTop < playerRight_ObjLeft && playerBottom_ObjTop < playerLeft_ObjRight)) {
       obj.y = obj2.y - obj.h; 
       obj.jumping = false;
       obj.vy = 0;}

   //horizontal obj 1 right 
   if ((obj.x + obj.w >= obj2.x && obj.x < obj2.x) && (playerRight_ObjLeft < playerTop_ObjBottom && playerRight_ObjLeft < playerBottom_ObjTop)) {
     obj2.x = obj.x + obj.w;
     obj.x = obj2.x - obj.w;
     obj.vx = 0;
     obj2.vx = 0;}

   //horizontal obj1 left 
   if ((obj.x <= obj2.x + obj2.w && obj.x + obj.w > obj2.x + obj2.w) && (playerLeft_ObjRight < playerTop_ObjBottom && playerLeft_ObjRight < playerBottom_ObjTop)) {
     obj2.x = obj.x - obj2.w;
     obj.x = obj2.x + obj2.w;
     obj.vx = 0;
     obj2.vx = 0;}
}

function loop() {
 context.clearRect(0, 0, canvas.width, canvas.height); 
 context.fillStyle = 'grey';
 context.fillRect(0, 0, canvas.width, canvas.height);

 //PLAYER
 controlPlayer1(player1); 
 controlPlayer2(player2);
 
 collisionDetection(player1, player2)

 requestAnimationFrame(loop)
}
loop();