繁体   English   中英

二维移动方块碰撞检测

[英]2d moving squares collision detection

有两个方块,它们是用箭头和 asdw 移动的,而且我多年来一直试图降低这种碰撞检测,这让我很生气。 不管怎样,我尝试了很多方法,我用一种方法非常接近,一切都很好,直到我发现当你跳到它旁边时,它并没有完成完整的跳跃,因为其中一个侧面能够通过另一个广场 go。 很长一段时间后,我发现该方法不起作用,因为使用 GetContext.2d,您无法引用正方形的左下角。 还有一个问题是一个角落可以触及两个不同的侧面,这会引发相互矛盾的信息。 我的解决方案是创建第三个条件来指定角接触的哪一侧,天气顶部/底部或左/右。 我做了一个 model 代码的结构: 碰撞检测

我目前的代码如下所示:

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>

为了我的理智,这是我一个月内最后一次尝试解决这个问题。 这是我的最后一击。 我真的很感谢任何努力或建议来解决我的问题,这很难让你理解,可能需要很长时间,thxxx <3

我感觉到你的痛苦。 CD 并不像人们希望的那样容易。 我无法让你的代码正常工作,所以我使用 ES6 类重写了它,因为我喜欢它们。 我也不会将所有内容放入循环 function 而是将控制参数放入单独的函数中并在动画循环中调用这些函数。 希望它不会因您的写作方式而感到困惑。

CD 部分计算块的不同边之间的距离,并确定每个块的哪些边最接近并将发生碰撞。 通过这样做,一次只会调用一个 function。 这可以防止由于块有轻微重叠并导致其他功能也触发而导致的任何不需要的行为。 即在 X 上碰撞但也有 Y 触发器。 我还将它设置为如果积木堆叠只有顶部可以跳跃的位置。

您可以根据自己的需要对其进行定制。

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();

它可能看起来很多,但实际上并非如此,而且效果很好。 前段时间我遇到了同样的问题,并想出了这个用于碰撞 map 因为我的角色会随机朝错误的方向射击,或者跳到一个街区旁边不起作用。 我已经修改了该代码以仅使用 2 个移动块在此处使用。 让我知道这是否适合您。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM