简体   繁体   中英

2d moving squares collision detection

There are two squares, they are moved with the arrows and asdw, and I have been trying to get this collision detection down for ages, it's killing me. Anyways, I have tried so many methods, I got really really close with one method, and everything was fine with it, until I found out that when you jumped next to it, it didn't complete the full jump because one of the sides was able to go through the other square. After a long time, I found out that method wouldn't work because with GetContext.2d, you cannot reference the bottom left of a square. There also was the issue that one corner could touch two different sides, which would set off conflicting messages. My solution for that was to make a third condition to specify which side the corner was touching, weather the top/bottom or left/right. I made a model of how the code would be structured: 碰撞检测

The code I have currently looks like this:

const context = document.getElementById("canvasmulti").getContext("2d");

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

//CHARACTER:
const square = {
    height: 75,
    jumping: true,
    width: 75,
    x: canvasmulti.width - 75,
    xVelocity: 0,
    y: canvasmulti.height / 2,
    yVelocity: 0,
    jumpHeight: 30
  };

  const square2 = {
    height: 75,
    jumping: true,
    width: 75,
    x: 0,
    xVelocity: 0,
    y: canvasmulti.height / 2,
    yVelocity: 0,
    jumpHeight: 30
  };

//MOVEMENT:
const controller = {
    left: false,
    right: false,
    up: false,
    keyListener: function (event) {
      let key_state = (event.type == "keydown") ? true : false;
      switch (event.keyCode) {
        case 37: // left arrow
          controller.left = key_state;
          break;
        case 38: // up arrow
          controller.up = key_state;
          break;
        case 39: // right arrow
          controller.right = key_state;
          break;
      }
    }
  };

  const controller2 = {
    left: false,
    right: false,
    up: false,
    keyListener: function (event) {
      let key_state = (event.type == "keydown") ? true : false;
      switch (event.keyCode) {
        case 65: // left arrow
          controller2.left = key_state;
          break;
        case 87: // up arrow
          controller2.up = key_state;
          break;
        case 68: // right arrow
          controller2.right = key_state;
          break;
      }
    }
  };

  const loop = function () {

    //controller one
    if (controller.up && square.jumping == false) {
      square.yVelocity -=square.jumpHeight;
      square.jumping = true;}
    if (controller.left) {
      square.xVelocity -= 0.5;}
    if (controller.right) {
      square.xVelocity += 0.5;}
  
    //controller two
    if (controller2.up && square2.jumping == false) {
        square2.yVelocity -= square2.jumpHeight;
        square2.jumping = true;}
    if (controller2.left) {
        square2.xVelocity -= 0.5;}
    if (controller2.right) {
        square2.xVelocity += 0.5;}

      //controller one
    square.yVelocity += 1.5;// gravity
    square.x += square.xVelocity;
    square.y += square.yVelocity;
    square.xVelocity *= 0.9;// friction
    square.yVelocity *= 0.9;// friction
  
    //controller two
    square2.yVelocity += 1.5;// gravity
    square2.x += square2.xVelocity;
    square2.y += square2.yVelocity;
    square2.xVelocity *= 0.9;// friction
    square2.yVelocity *= 0.9;// friction

    // if square1 is falling below floor line
    if (square.y > canvasmulti.height - 75) {
      square.jumping = false;
      square.y = canvasmulti.height - 75;
      square.yVelocity = 0;
    }

    // if square2 is falling below floor line
    if (square2.y > canvasmulti.height - 75) {
        square2.jumping = false;
        square2.y = canvasmulti.height - 75;
        square2.yVelocity = 0;
    }
  
    // if square1 is going off the left of the screen
    if (square.x < 0) {
      square.x = 0;
    } else if (square.x > canvasmulti.width - 75) {// if square goes past right boundary
      square.x = canvasmulti.width - 75;
    }

    // if square2 is going off the left of the screen
    if (square2.x < 0) {square2.x = 0;}
    else if (square2.x > canvasmulti.width - 75) {// if square goes past right boundary
        square2.x = canvasmulti.width - 75;
    }

    // Creates the backdrop for each frame
    context.fillStyle = "#394129";
    context.fillRect(0, 0, canvasmulti.width, canvasmulti.height); // x, y, width, height

    // Creates and fills square1 for each frame
    context.fillStyle = "#8DAA9D"; // hex for cube color
    context.beginPath();
    context.rect(square.x, square.y, square.width, square.height);
    context.fill();

    // Creates and fills square2 for each frame
    context.fillStyle = "#781818"; // hex for cube color
    context.beginPath();
    context.rect(square2.x, square2.y, square2.width, square2.height);
    context.fill();
    
    // Collision detection of squares

    if (square.x <= square2.x + square2.width && square.x >= square2.x &&
        square.y >= square2.y && square.y <= square2.y + square2.height &&
        square2.y + square2.height >= square.y && square2.y + square2.height <= square.y + square.height)
        {square.y = square2.y - square.height; //set it to a position where they don't overlap
        square.yVelocity = 0;}; //One move down

    if (square.x <= square2.x + square2.width && square.x >= square2.x &&
        square.y >= square2.y && square.y <= square2.y + square2.height &&
        square2.x + square2.width >= square.x && square2.x + square2.width <= square.x + square.width);
        {square.x = square2.x + square2.width; // set it to a position where they don't overlap
        square.xVelocity = 0;}; //One move right

    if (square2.x >= square.x && square2.x <= square.x + square.width && 
        square2.y >= square.y && square2.y <= square.y + square.heights &&
        square.y + square.height >= square2.y && square.y + square.height <= square2.y + square2.height)
        {square.y = square2.y - square.height; // set it to a position where they don't overlap
        square.yVelocity = 0;}; //One move up

     if (square2.x >= square.x && square2.x <= square.x + square.width && 
         square2.y >= square.y && square2.y <= square.y + square.heights &&
         square.x + square.width >= square2.y  && square.x + square.width <= square2.y + square2.height)
         {square.x = square2.x - square.width; // set it to a position where they don't overlap
         square.xVelocity = 0;}; //One move left


    // call update when the browser is ready to draw again
    window.requestAnimationFrame(loop);

  };

  //square1
  window.addEventListener("keydown", controller.keyListener)
  window.addEventListener("keyup", controller.keyListener);
  //square2
  window.addEventListener("keydown", controller2.keyListener)
  window.addEventListener("keyup", controller2.keyListener);

  window.requestAnimationFrame(loop);

<!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>ToneMain</title>
    <style>
        body {
      height:100vh;
      width:100vh;
      margin: 0;
    }
    </style>
</head>
<body>
<canvas id="canvasmulti"></canvas>
<script src="ToneMainMulti.js"></script>

</body>
</html>

For the love of my sanity this was the last time in a month I tried to fix this. This was my last blow. I would really apprestiate any effort or advice to solve my problem, this is tough to wrap your head around and may take a long time, thxxx <3

I feel your pain. CD is not as easy as one would hope. I was unable to get your code working so I rewrote it using ES6 classes because I like them. I also don't put everything into a loop function but rather place controlling parameters into separate functions and call those functions in the animate loop. Hopefully it isn't confusing from how you write.

The CD portion calculates the distance between the different sides of the blocks and determines which sides from each block are the closest and will collide. By doing this only one function will get called at a time. This prevents any of the unwanted behavior you get because of the blocks having a slight overlap and causing other functions to trigger also. ie colliding on the X but having the Y trigger too. I've also set it to where if the blocks are stacked only the top one can jump.

You can tailor this to whatever suits your needs.

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

canvas.width = 500;
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;
    }
    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(0, 0, 0, 0, 'red', false); //must have separate variables like jump/attack etc
let player2 = new Player(canvasmulti.width - 50, 0, 0, 0, 'blue', 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;

        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' }           
        }
        
        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' }           
        }  
    window.addEventListener('keydown', controller1);
    window.addEventListener('keyup', controller1);
    window.addEventListener('keydown', controller2);
    window.addEventListener('keyup', controller2);
    }
}
let controller1 = new Controller();
let controller2 = new Controller();

function collisionDetection(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 d*** 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 to collisionActions
    collisionActions(obj, obj2, val)
}
function collisionActions(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;
        obj.vy = 0;
        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;
        obj.vx = 0;
        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;
        obj2.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 loop() {
    context.clearRect(0, 0, canvas.width, canvas.height); 
    context.fillStyle = 'grey';
    context.fillRect(0, 0, canvas.width, canvas.height);
    controlPlayer1(player1); 
    controlPlayer2(player2);
    collisionDetection(player1, player2) 
    requestAnimationFrame(loop)
}
loop();

It may seem like a lot but it's really not and it works very well. I was having the same issues a while back and came up with this to use on a collision map because my character would randomly shoot of in the wrong direction or jumping next to a block wouldn't work. I have modified that code to be used here with just 2 moving blocks. Let me know if this works for you.

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