Условное столкновение объекта работает только с одной стороны

1

Я сделал игру, используя ванильный Javascript и HTML-холст. После добавления объектов драгоценных камней в игровое поле я добавил код, который увеличивает оценку игрока, когда он сталкивается с ними, что является оператором if внутри метода прототипа, отформатированного с использованием синтаксиса класса ES6 (внутри class Gem). (Метод вызывается в отдельном файле JavaScript.)

Кажется, что оператор if который я использую, должен увеличить счет (и создать драгоценный камень в новом случайном месте), когда игрок сталкивается с ним с любой стороны, но он работает только снизу. Что я делаю неправильно?

Здесь оператор if внутри метода прототипа, который содержит его:

update() {
    // Not sure why this if statement only works when player approaches gem from below (a higher y value)
    if ((Math.abs(player.x - this.x) < 30) && (Math.abs(player.y - this.y)) < 30) {
      // Generates new gem of random color and random x and y value from arrays
      this.x = gemX[Math.floor(Math.random() * gemX.length)];
      this.y = gemY[Math.floor(Math.random() * gemY.length)];
      this.sprite = collectibles[Math.floor(Math.random() * 3)];
      score += 5;
      sidebarScore.innerHTML = score;
    }
  }

Живая игра (этот код загружен): https://nataliecardot.com/arcade-game/index.html

Репо, содержащее все файлы: https://github.com/nataliecardot/nataliecardot.github.io/tree/master/arcade-game

Здесь полный код JS файла:

'use strict'; // Enables strict mode to catch common bloopers

const playAgainButton = document.querySelector('.play-again');
const restartButton = document.querySelector('.restart');

// Calls playAgain() function when user clicks reset icon in sidebar
restartButton.addEventListener('click', playAgain);

// Starts lives at 3
let lives = 3;

// 1 of 5 x (horizontal) values: (start at left) 17, 119, 220, 321, 422;
let gemX = [17, 119, 220, 321, 422];

// Selects random index from gemX. Math.random returns a random number between 0 inclusive to 1 exclusive. So if were to produced 0.55, that would be multiplied by 5 (in this case), which equals 2.75, then Math.floor would round it down to the nearest integer (unless it already an integer, which can only be 0), which equals 2, resulting in randomGemX equaling the third index, 220.
let randomGemX = gemX[Math.floor(Math.random() * gemX.length)];

//  1 of 3 y (vertical) values (start at top): 124, 208, 292
let gemY = [124, 208, 290];

let randomGemY = gemY[Math.floor(Math.random() * gemY.length)];

// Used to disable arrow keys while modal opened
let isDead = false;

let sidebarLives = document.querySelector('.lives-left');
sidebarLives.innerHTML = lives;

// Sets an initial player score of 0
let score = 0;

// Sets score shown in sidebar
let sidebarScore = document.querySelector('.score');
sidebarScore.innerHTML = score;

let modalScore = document.querySelector('.modal-score');
modalScore.innerHTML = score;

// Called when user clicks restart button in sidebar or play again button in modal. Sets modal to display: none, resets lives and score
function playAgain() {
  isDead = false;
  // Hides modal if present (if opened by game ending)
  modal.classList.remove('modal-visible');
  lives = 3;
  sidebarLives.innerHTML = lives;
  score = 0;
  sidebarScore.innerHTML = score;
  // Generates new gem of random color and random x and y value from arrays
  gem.x = gemX[Math.floor(Math.random() * gemX.length)];
  gem.y = gemY[Math.floor(Math.random() * gemY.length)];
  gem.sprite = collectibles[Math.floor(Math.random() * 3)];
}

// Note: In a constructor function "this" does not have a value. It is a substitute for the new object. The value of this will become the new object when a new object is created

// Note commas not used to separate methods and properties in a class
class Player {
  // Constructor function, a special function just for initializing new objects, will automatically run when a new object is constructed (with keyword "new") from this class. Contains data needed to create it
  constructor(x, y, speed) {
    this.sprite = 'images/char-boy.png';
    this.x = x;
    this.y = y;
    this.speed = speed;
  }

  // Methods that all objects created from class will inherit. Would exist on prototype in pre-class way of writing it, but effect is the same (the following methods still exist on Player prototype [for example would be Player.prototype.update = function(dt)...])

  // When player reaches water, moves player back to starting position, and increase score by 1
  update() {
    if (this.y === 25) {
      this.x = 200;
      this.y = 400;
      score++;
      sidebarScore.innerHTML = score;
    }
  }

  // Draws player on screen
  render() {
    ctx.drawImage(Resources.get(this.sprite), this.x, this.y)
  }

  // If isDead is false (so it doesn't work when the modal is opened), connects keyboard input to player movement. If statements prevent player movement off screen
  handleInput(allowedKeys) {
    if (isDead) {
      return;
    }

    if (allowedKeys === 'down' && this.y < 425) {
      this.y += 25;
    }

        if (allowedKeys === 'up') {
            this.y -= 25;
        }

        if (allowedKeys === 'left' && this.x > 0) {
            this.x -= 25;
        }

        if (allowedKeys === 'right' && this.x < 400) {
            this.x += 25;
        }
  }
}

class Enemy {
// Sets enemy initial location
  constructor(x, y, speed) {
    this.x = x;
    this.y = y;
    // Sets speed of enemy
    this.speed = speed;
    // The image/sprite for our enemies
    this.sprite = 'images/enemy-bug.png';
  }

  update(dt) {
    // Multiplies enemy movement by time delta to ensure game runs at same speed for all computers
    this.x += this.speed * dt;
    // Once enemy finished moving across screen, moves it back so it can cross screen again and randomizes its speed
    if (this.x > 500) {
      this.x = -75;
      // Math.random() function returns random number between 0 (inclusive) and 1 (exclusive). Math.floor() returns the largest integer less than or equal to a given number
      this.speed = 70 + Math.floor(Math.random() * 450);
    }

    // When collision occurs, subtracts a life, updates lives displayed in sidebar, and updates score that will be displayed in modal if no lives remaining
    if ((player.x < (this.x + 70)) && ((player.x + 17) > this.x) && (player.y < (this.y + 45)) && ((30 + player.y) > this.y)) {
        player.x = 200;
        player.y = 400;
      lives--;
      sidebarLives.innerHTML = lives;
      modalScore.innerHTML = score;
      if (lives === 0) {
        isDead = true;
        // Calls function that adds class that sets modal to display: block
        showModal();
      }
    }
  }

  // Draws enemy on screen
  render() {
    ctx.drawImage(Resources.get(this.sprite), this.x, this.y);
  }
};

class Gem {
  constructor(x, y) {
    // Math.random() function returns random number between 0 (inclusive) and 1 (exclusive). Math.floor() returns the largest integer less than or equal to a given number. Since collectibles is an array, starts at 0, so we want index 0, 1, or 2. (If Math.random were 0.99, it would would become 2.99 after being multiplied by 3, then Math.floor would make it 2)
    this.sprite = collectibles[Math.floor(Math.random() * 3)];
    this.x = randomGemX;
    this.y = randomGemY;
  }

  // Draws gem on screen
  render() {
    ctx.drawImage(Resources.get(this.sprite), this.x, this.y)
  }

  update() {
    // Not sure why this if statement only works when player approaches gem from below (a higher y value)
    if ((Math.abs(player.x - this.x) < 30) && (Math.abs(player.y - this.y)) < 30) {
      // Generates new gem of random color and random x and y value from arrays
      this.x = gemX[Math.floor(Math.random() * gemX.length)];
      this.y = gemY[Math.floor(Math.random() * gemY.length)];
      this.sprite = collectibles[Math.floor(Math.random() * 3)];
      score += 5;
      sidebarScore.innerHTML = score;
    }
  }
}

let collectibles = [
  'images/Gem Blue Sm.png',
  'images/Gem Orange Sm.png',
  'images/Gem Green Sm.png',
];

// ENEMY/PLAYER/GEM OBJECT INSTANTIATION

let gem = new Gem(randomGemX, randomGemY);

let enemyPosition = [60, 140, 220];

let allEnemies = [];

let player = new Player(200, 400, 50);

enemyPosition.forEach(function(posY) {
  // x position of 0, y of whatever is passed in, and random speed
  let enemy = new Enemy(0, posY, 70 + Math.floor(Math.random() * 450));
  allEnemies.push(enemy);
});

// MODAL

const modal = document.getElementById('myModal');
const closeIcon = document.querySelector('.close');

// When called, adds class that sets modal to display: block when player reaches water
function showModal() {
  modal.classList.add('modal-visible');

  // Calls playAgain() function when user clicks play again button in modal
  playAgainButton.addEventListener('click', playAgain);

  // If esc is pressed, closes modal and restarts game (note: keydown used instead of keypress because keypress only works for keys that produce a character value)
  document.addEventListener('keydown', function(e) {
    let keyCode = e.keyCode;
    if (keyCode === 27) {
      modal.classList.remove('modal-visible');
      playAgain();
    }
  });

  // If enter is pressed, closes modal and restarts game
  document.addEventListener('keydown', function(e) {
    let keyCode = e.keyCode;
    if (keyCode === 13) {
      modal.classList.remove('modal-visible');
      playAgain()
    }
  });

  // If user clicks modal close icon, closes modal and restarts game
  closeIcon.addEventListener('click', function() {
    modal.classList.remove('modal-visible');
    playAgain();
  });
}

// Listens for keydown event (fired when a key is pressed down [regardless of whether it produces a character, unlike keypress]) and sends the keys to Player.handleInput() method
document.addEventListener('keydown', function(e) {
  let allowedKeys = {
    37: 'left',
    38: 'up',
    39: 'right',
    40: 'down'
  };

  // "player" needs to be lowercase because an instance of the class is needed
  player.handleInput(allowedKeys[e.keyCode]);
});
  • 0
    Кажется, для меня это работает со всех сторон, точка столкновения - это всего лишь кончик ее головы, так что вам нужно глубоко проникнуть сверху (или снизу вниз), чтобы она зарегистрировалась.
Теги:
html5-canvas

1 ответ

1
Лучший ответ

Я вижу, что у вас есть пользовательский код для столкновений с драгоценными камнями и врагами...

Из класса Gem:

if ( ((Math.abs( (player.imgWidth + player.x) - (this.x + this.imgWidth) ) < 55)) && ...

Из класса Enemy:

if ((player.x < (this.x + 50)) && ((player.x + 17) > this.x) && ...

Я настоятельно рекомендую вам стандартизировать все, что...
Вот два момента, которые вы сравниваете:

Изображение 174551

Чтобы визуализировать это, я разветкил ваше репо на GitHub и сделал некоторые изменения, чтобы точно увидеть, какие точки вы использовали для обнаружения столкновения, и используете левый верхний край спрайта: https://raw.githack.com/heldersepu/nataliecardot.github. IO/ведущая/аркадные игры /index.html

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

  • 0
    @Natalie Я надеюсь, что это толчок в правильном направлении ... Я мог бы исправить ваш код, но это не лучший способ обучения, я рекомендую вам пройти этот курс: coursera.org/learn/interactive-python-1

Ещё вопросы

Сообщество Overcoder
Наверх
Меню