【问题标题】:Js collision detector - hit the wall vs. hit the floor (difference)js碰撞检测——撞墙和。撞到地板(差异)
【发布时间】:2018-05-25 04:14:51
【问题描述】:

我正在用一个玩家和构建环境的积木制作一个小游戏。我遇到的问题是知道玩家何时撞到地面(块的顶部)和撞到墙壁(块的侧面)之间的区别。

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

这是我的碰撞检测器:

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
    }
  }
}

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

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

PS:玩家只是一个脑袋。没有尸体躲在积木后面。

【问题讨论】:

  • 不是检查整个矩形,您可以检查玩家的底边是否与方块(如果为真则在地面上),以及玩家的侧边是否与方块(如果为真则为靠墙)。
  • 你用的是什么框架?还是只是普通的 JS?
  • @Jorjon 这是纯 JS
  • @Jorjon 你能告诉我这段代码的样子吗?因为我不明白如果不检查他接触地面时的 x 碰撞,那将如何工作。即使在 x 方向上达不到,这也不会是真的吗?
  • 看我的回答,我已经添加了一个完整的工作示例

标签: javascript collision-detection


【解决方案1】:

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

在 sn-p 中,您可以显示播放器中表示的许多点。

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

因此,总而言之,要基于点(而不是光线投射)进行良好的碰撞检测,您需要检测玩家是否为圆形,以防止出现奇怪的行为。

您可以通过更改layout 变量来调整地图布局。 0 是空白区域,1 是棕色方块,2 是绿色方块。

collisionDetector 函数让 cmets 了解发生了什么。

我还添加了跳转功能,因为我知道您也需要它。

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;  }
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【讨论】:

  • 谢谢,我今天晚些时候试试!我设置了只要我看到它有效并且我理解它就会将它设置为正确的答案。
  • 嘿,我试过你介绍的碰撞检测器,我想我理解这个概念,但它不起作用。我认为我的代码的另一部分可能有问题,因为我的角色突然开始飞越地图并完全出现故障。我有没有可能收到你的邮件或其他东西,所以你可以看看我的代码?我真的很感激!
  • 你能在 Fiddle 上提供一个mcve 吗?或者,可以尝试使用我的代码作为基础,然后开始逐个检查每个步骤,这样您就可以轻松检测出问题所在。
  • 我怀疑这与myPlayer.vymyPlayer.ay有关
  • 其实,我想我应该把你的答案设置为正确答案,因为我几乎可以在角色开始行动之前测试碰撞。但是当涉及到我的问题时,似乎有我稍微随机的«定时器»激活了故障。真的很难形容。有没有办法向您展示整个代码?
【解决方案2】:

引入block[i].type 属性。例如,如果block[i].type=='floor' 则让玩家留在地板上。如果对于另一个实例block[i].type=='wall' 然后让它停止穿过墙壁。当block[i].type=='brick' 或正方形或块状或其他任何东西时,它们是两者的混合。

要编辑的另一部分是检查碰撞时。如果只有单向碰撞怎么办?我的意思是可能在这部分使用or 而不是and if(a.y + a.h &gt; b.y &amp;&amp; a.y &lt; b.y + b.h &amp;&amp; a.x + a.w &gt; b.x &amp;&amp; a.x &lt; 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]);
         }
    })
}

这是高度未优化的,您的整个代码都未优化,但至少它可以帮助更进一步。

【讨论】:

  • 嗯,谢谢,但正如您在图片上看到的那样,有些墙壁既是墙壁又是地板,所以我认为这行不通。
  • 谢谢,我试试这个
  • 它没有用。我试过了,现在他一落地就停不下来了。
  • @Benjamhw 我编辑了我的评论。试试。另外,你能不能选择我的答案,只要它是正确的。谢谢
  • 我会测试的,谢谢
猜你喜欢
  • 2013-01-31
  • 1970-01-01
  • 2011-09-05
  • 2015-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多