簡體   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