Js детектор столкновений - удар по стене и удар по полу (разница) - PullRequest
0 голосов
/ 23 мая 2018

Я делаю небольшую игру с одним игроком и блоками, которая создает окружающую среду.У меня проблема в том, что я знаю разницу между моментом, когда игрок падает на землю (верх блока), и ударом в стену (сторона блока).

Пока игрок может идти дальшеземля просто отлично, но когда он встречает стену, он сразу же прыгает на вершину этого блока.

Это мой детектор столкновений:

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

Я понял, что ставлю игроков на вершине того, что он блокирует, но я не могу найти решение этой проблемы.Кто-нибудь может мне помочь или хотя бы привести меня в правильном направлении?Спасибо!

(Дайте мне знать, если вам нужно больше кода)

PS: игрок - просто голова.Ни одно тело не прячется за блоками.

A small taste of what is going on

Ответы [ 2 ]

0 голосов
/ 24 мая 2018

Итак, в общем, вам нужно проверить столкновение между многими точками в игроке.

В фрагменте вы можете показать много точек, представленных в проигрывателе.

  • Снизу почти слева и почти справа (синим цветом), проверьте против блоков ниже.Они не полностью влево или вправо, чтобы предотвратить состояние гонки, которое позволит игроку взобраться на стены.В этом случае, если игрок толкает стену и прыгает, коллайдер обнаружит как боковое, так и нижнее столкновение как истинное, тогда игрок будет быстро двигаться к вершине, пока не останется больше блоков.
  • Левая и правая точки (в черном цвете), проверьте по краям блоков.Это просто точка вместо двух, как нижний край, потому что нам не нужно больше для этого конкретного случая.Еще один для каждой стороны может быть легко добавлен для лучшего обнаружения.
  • Верхняя точка (в красном) проверяет верхние блоки.Это посередине, чтобы позволить игроку более простой способ пересечь карту.Если это не нужно, вам нужно добавить еще одну точку, как в нижнем крае (но никогда не достигать дальнего края, потому что это создаст состояние гонки).

Итак, в итоге, кчтобы иметь хорошее обнаружение столкновений по точкам (вместо лучевых трансляций), вам нужно определять игрока, как если бы он находился в округлой форме, чтобы избежать странного поведения.

Вы можете перемещаться с макетом карты с помощьюизменение переменной 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>
0 голосов
/ 23 мая 2018

Введите атрибут 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]);
         }
    })
}

Это крайне неоптимизировано,весь ваш код неоптимизирован, но, по крайней мере, он может помочь двигаться дальше.

...