Js碰撞探测器 - 击中墙壁撞击地板(差异)

时间:2018-05-22 22:49:55

标签: javascript collision-detection

我正在与一个玩家进行一场小型游戏,并建立一个可以构建环境的游戏。我遇到的问题是知道玩家何时击中地面(块的顶部)和撞墙(块的一侧)之间的区别。

到目前为止,玩家可以在地面上行走得很好,但当他遇到一堵墙时,他会立即跳到那个街区的顶部。

这是我的碰撞探测器:



function collisionDetector(){
  if(myPlayer.y + myPlayer.h > c.height){	//Bottom of the canvas
    myPlayer.vy = 0;
    myPlayer.ay = 0;
    myPlayer.y = c.height - myPlayer.h;
    myPlayer.onGround = true;
    console.log(myPlayer.y + myPlayer.h, c.height);
  }
  if(myPlayer.x + myPlayer.w >= c.width){ //right side of canvas
    myPlayer.x = c.width - myPlayer.w;
    myPlayer.vx = 0;
  }
  if(myPlayer.x <= 0){ //Left side of canvas
    myPlayer.x = 0;
    myPlayer.vx = 0;
  }

  function hitTest(a,b){ //hitTest between two objects
    if(a.y + a.h > b.y && a.y < b.y + b.h && a.x + a.w > b.x && a.x < b.x + b.w){
      return true;
    }
  }

  for(var i = 0; i < blocks.length; i++){ //Loop through blocks
    if(hitTest(myPlayer, blocks[i])){ //If it touches a block
        myPlayer.y = blocks[i].y - myPlayer.h;
        myPlayer.onGround = true; //onGround = ready to jump
    }
  }
}
&#13;
&#13;
&#13;

我意识到我将玩家设置为阻止它击中的东西,但我无法找到解决这个问题的方法。任何人都可以帮助我或者至少引导我朝着正确的方向前进吗?谢谢!

(如果您需要更多代码,请告诉我)

PS:玩家只是一个头脑。没有尸体藏在街区后面。

A small taste of what is going on

2 个答案:

答案 0 :(得分:1)

所以基本上,你需要做的是检查播放器中许多点之间的碰撞。

在摘录中,您可以显示播放器中显示的许多点。

  • 底部几乎左侧和几乎右侧(蓝色),检查下面的块。它们不是完全左或右,以防止比赛条件允许玩家墙。在这种情况下,如果玩家正在推墙并跳跃,则对撞机将检测到侧面碰撞和底部碰撞为真,然后玩家将快速移动到顶部,直到没有更多的挡块。
  • 左右点(黑色),检查块的边缘。这只是一个点而不是像底边一样的两个点,因为对于这个特殊情况我们不需要更多。可以轻松添加每侧一个以获得更好的检测。
  • 顶点(红色)检查顶部块。这是在中间,以便让玩家更容易地横切地图。如果不需要这样做,你需要在底部边缘添加一个点(但永远不会到达远边缘,因为这会产生竞争条件)。

总而言之,要想有一个基于点(而不是光线投射)的良好碰撞检测,你需要检测玩家是否有圆角形状,以防止出现奇怪的行为。

您可以通过更改layout变量来播放地图布局。 0是空格,1是棕色块,2是绿色块。

collisionDetector功能有评论可以了解正在发生的事情。

此外,我添加了一个跳转功能,因为我知道你也需要它。

const c = document.getElementById('canvas');
c.width = window.innerWidth;
c.height = window.innerHeight;
const ctx = c.getContext('2d');

// map layout
const layout = 
`000000001
001000001
000000101
100110111
222222222`;

// convert layout to blocks
const blocks = [...layout].reduce((a, c, i) => {
  if (i === 0 || c === "\n") a.push([]);
  if (c === "\n") return a;
  const y = a.length - 1;
  const row = a[y];
  const x = row.length;
  row.push({x: x * 32, y: y * 32, t:c, w:32, h:32});
  return a;
}, []).reduce((a, c) => a.concat(c), []);

// player starting position
const myPlayer = {x: 32*1.5, y: 0, h: 32, w: 16, onGround: true};
const gravity = -1;
let pkl = 0, pkr = 0;
let pvely = 0;

function render() {

  // player logic
  const pvelx = pkr + pkl;
  const speed = 2;
  myPlayer.x += pvelx * speed;
  myPlayer.y -= pvely;
  if (pvely > -2) pvely += gravity;


  const debugColliders = collisionDetector();
  
  ctx.clearRect(0, 0, c.width, c.height);

  // player render
  ctx.fillStyle = '#FFD9B3';
  ctx.fillRect(myPlayer.x, myPlayer.y, myPlayer.w, myPlayer.h);

  renderLayout();

  debugColliders();

  window.requestAnimationFrame(render);
}

function renderLayout() {
  const colors = {'1': '#A3825F', '2': '#7FAC72'}
  
  blocks.forEach(b => {
      if (+b.t > 0) {
        ctx.fillStyle = colors[b.t];
        ctx.fillRect(b.x, b.y, b.w, b.h);
      }
  });
}

window.addEventListener('keydown', e => {
  if (e.key == 'ArrowRight') {
    pkr = 1;
    e.preventDefault();
  } else if (e.key == 'ArrowLeft') {
    pkl = -1;
    e.preventDefault();
  } else if (e.key == 'ArrowUp') {
    if (myPlayer.onGround)
      pvely = 8;
      myPlayer.onGround = false;
      e.preventDefault();
  }

});

window.addEventListener('keyup', e => {
  if (e.key == 'ArrowRight') {
    pkr = 0;
  } else if (e.key == 'ArrowLeft') {
    pkl = 0;
  }
});


function collisionDetector(){
  const p = myPlayer;
  const playerTop = p.y;
  const playerLeft = p.x;
  const playerRight = playerLeft + p.w;
  const playerBottom = playerTop + p.h;
  const playerHalfLeft = playerLeft + p.w * .25;
  const playerHalfRight = playerLeft + p.w * .75;
  const playerHMiddle = playerLeft + p.w * .5;
  const playerVMiddle = playerTop + p.h * .5;

  if(playerBottom > c.height){ //Bottom of the canvas
    p.vy = 0;
    p.ay = 0;
    p.y = c.height - p.h;
    p.onGround = true;
  }
  if(playerRight >= c.width){ //right side of canvas
    p.x = c.width - p.w;
    p.vx = 0;
  }
  if(playerLeft <= 0){ //Left side of canvas
    p.x = 0;
    p.vx = 0;
  }

  blocks.forEach(b => { //Loop through blocks
    if (b.t === "0") return; // If not collidable, do nothing
    const blockTop = b.y;
    const blockLeft = b.x;
    const blockRight = blockLeft + b.w;
    const blockBottom = b.y + b.h;

    // Player bottom against block top
    if ((playerBottom > blockTop && playerBottom < blockBottom) && // If player bottom is going through block top but is above block bottom.
    ((playerHalfLeft > blockLeft && playerHalfLeft < blockRight) || // If player left is inside block horizontal bounds
    (playerHalfRight > blockLeft && playerHalfRight < blockRight))) { // Or if player right is inside block horizontal bounds
      p.y = blockTop - p.h;
      p.onGround = true;
    }

    // Player top against block bottom
    if ((playerTop < blockBottom && playerTop > blockTop) && // If player top is going through block bottom but is below block top.
    ((playerHMiddle > blockLeft && playerHMiddle < blockRight))) { // If player hmiddle is inside block horizontal bounds
      p.y = blockBottom;
      p.onGround = false;
    }

    // Player right against block left, or player left against block right
    if (playerVMiddle > blockTop && playerVMiddle < blockBottom) { // If player vertical-middle is inside block vertical bounds
      if ((playerRight > blockLeft && playerRight < blockRight)) { // If player vmiddle-right goes through block-left
        p.x = blockLeft - p.w;
      } else if ((playerLeft < blockRight && playerRight > blockLeft)) { // If player vmiddle-left goes through block-right
        p.x = blockRight;
      }
    }

  });
  return function debug() {
    ctx.fillStyle = 'black';
    ctx.fillRect(playerLeft, playerVMiddle, 1, 1);
    ctx.fillRect(playerRight, playerVMiddle, 1, 1);
    ctx.fillStyle = 'red';
    ctx.fillRect(playerHMiddle, playerTop, 1, 1);
    ctx.fillStyle = 'blue';
    ctx.fillRect(playerHalfLeft, playerBottom, 1, 1);
    ctx.fillRect(playerHalfRight, playerBottom, 1, 1);
  }
}

window.requestAnimationFrame(render);
html, body{ width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }
canvas { background: #7AC9F9; display: block;  }
<canvas id="canvas"></canvas>

答案 1 :(得分:0)

引入block[i].type属性。例如,如果block[i].type=='floor'然后让玩家留在地板上。如果是另一个实例block[i].type=='wall',那么让它停止穿过墙壁。当block[i].type=='brick'或方块或块或其他什么时,它们是两个的混合物。

要编辑的另一部分是检查碰撞时。如果你只有单向碰撞怎么办?我所说的可能是or代替and这一部分if(a.y + a.h > b.y && a.y < b.y + b.h && a.x + a.w > b.x && a.x < b.x + b.w){

您也可以单独检查每次碰撞,例如

function hitTest(a,b){ //hitTest between two objects
  var collisions = {up: false, down: false, left: false, right: false};
  collisions.up = (a.y + a.h > b.y ) || collisions.up
  collisions.down = (a.y < b.y + b.h ) ||collisions.down
  collisions.right = ( a.x + a.w > b.x) || collisions.right
  collisions.left = (a.x < b.x + b.w) || collisions.left
  return collisions
}

var escapeFrom = {
  down: function(player, block){
     player.y = block.y + block.h;
     player.onGround = true; //onGround = ready to jump
  },
  up: function(player, block){
  // you logic to escape from hitting the ceiling
  },
  // and for the next 2
  left: function(player, block) {},
  right: function(player, block){}
}

// Now here you check whether your player hits blocks
for(var i = 0; i < blocks.length; i++){ //Loop through blocks
    cls = hitTest(myPlayer, blocks[i]) //If it touches a block
    Object.keys(cls).map(function(direction, ind){
         if (cls[direction]){
           // call escape from function to escape collision
            escapeFrom[direction](myPlayer, blocks[i]);
         }
    })
}

这是高度未经优化的,整个代码未被优化,但至少它可以帮助进一步发展。