Попытка взять последний объект массива и переместить его вперед для перемещения змеи в игре Vanilla JS / canvas snake - PullRequest
1 голос
/ 20 марта 2020

Я пытался создать игровое приложение-змею без каких-либо обучающих программ, и несколько дней зависал в логике c. Из нижеприведенного кода вы можете видеть, что моя змея движется, но она не движется как веревка, она просто движется как единое целое, состоящее из «блоков» или кусочков.

Я уверен, что большинство из вас может сделать что-то подобное с закрытыми глазами, но мне было интересно, можете ли вы отговорить меня от уступа. Интересно, соединил ли я это таким образом, что мне нужно переосмыслить, как я это выстроил до этого момента и, возможно (в основном) начать заново?

В строках 43 - 47 вы можете видеть, что я попытался '' pop () '' 'вывести последний элемент массива, а затем' '' unshift () '' 'вернуть его вперед и затем нарисуйте новый массив обратно на холст. Могу ли я сделать это с помощью JS и Canvas в моей функции '' 'updateSnake ()' '', а если нет, можете ли вы подтолкнуть меня туда, где мой лог c имеет недостатки?

Спасибо!

https://codepen.io/constequalsexcel/pen/eYNjRER

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Snake Game</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <header id="header">Snake Game</header>
  <div id="score-box">
    <h1>SCORE</h1>
    <h3 id="score">0</h3>
  </div>
  <body>
    <canvas id="gameCanvas" width="500" height="500"></canvas>
    <script src="scripts.js"></script>
  </body>
</html>

CSS

#gameCanvas {
  margin: auto;
  display: block;
}

#header {
  text-align: center;
  margin: 30px 0 100px 0;
  font-size: 35px;
}

#score {
  margin-left: 60px;
}

JS

let canvas = document.getElementById("gameCanvas");
let ctx = canvas.getContext("2d");
let snakeDirection;
document.addEventListener("keydown", moveSnakeKeys, false);
let appleX;
let appleY;
let score = 0;

createApple();

class Snake {
  constructor() {
    this.body = [{ x: 50, y: 200 }, { x: 34, y: 200 }, { x: 18, y: 200 }];
    this.snakeSpeedX = 15;
    this.snakeSpeedY = 15;
    this.color = "green";
    this.snakeSize = 15;
  }

  drawSnake() {
    ctx.fillStyle = this.color;
    for (let i = 0; i < this.body.length; i++) {
      ctx.fillRect(
        this.body[i].x,
        this.body[i].y,
        this.snakeSize,
        this.snakeSize
      );
    }
    ctx.fill();
  }

  // remove 'tail' from snake
  // store 'tail' in a variable
  // check direction of snake
  // place 'tail' infront of snake body

  updateSnake() {
    if (snakeDirection === "right") {
      for (let i = 0; i < this.body.length; i++) {
        this.body[i].x += this.snakeSpeedX;
        // let tail = this.body.pop()
        // console.log(tail)
        // tail.x = this.body[0].x
        // tail.y = this.body[0].y
        // this.body.unshift(tail)
      }
    } else if (snakeDirection === "down") {
      for (let i = 0; i < this.body.length; i++) {
        this.body[i].y += this.snakeSpeedY;
      }
    } else if (snakeDirection === "left") {
      for (let i = 0; i < this.body.length; i++) {
        this.body[i].x -= this.snakeSpeedX;
      }
    } else if (snakeDirection === "up") {
      for (let i = 0; i < this.body.length; i++) {
        this.body[i].y -= this.snakeSpeedY;
      }
    }
    this.drawSnake();
  }
}

const snake = new Snake();

function animate() {
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  snake.updateSnake();
  ctx.fillStyle = "red";
  ctx.fillRect(appleX, appleY, 15, 15);
  ctx.fill();
  updateScore();
  gameOver();
}

setInterval(animate, 200);

function moveSnakeKeys(e) {
  if (e.code === "ArrowRight" && snakeDirection != "left") {
    snakeDirection = "right";
  } else if (e.code === "ArrowLeft" && snakeDirection != "right") {
    snakeDirection = "left";
  } else if (e.code === "ArrowUp" && snakeDirection != "down") {
    snakeDirection = "up";
  } else if (e.code === "ArrowDown" && snakeDirection != "up") {
    snakeDirection = "down";
  }
}

function createApple() {
  appleX = Math.floor(Math.random() * (canvas.width - 15));
  appleY = Math.floor(Math.random() * (canvas.height - 15));
}

function updateScore() {
  if (
    snake.body[0].x < appleX + 15 &&
    snake.body[0].x + 15 > appleX &&
    snake.body[0].y < appleY + 15 &&
    snake.body[0].y + 15 > appleY
  ) {
    ctx.clearRect(appleX, appleY, 15, 15);
    createApple();
    score = score + 1;
    document.getElementById("score").innerText = score;
    growSnake();
  }
}

function growSnake() {
  let xLength = snake.body[snake.body.length - 1].x - 16;
  let yLength = snake.body[snake.body.length - 1].y;
  snake.body.push({ x: xLength, y: yLength });
}

function gameOver() {
  if (
    snake.body[0].x > canvas.width - snake.snakeSpeedX ||
    snake.body[0].x < 0
  ) {
    alert("Game Over!");
  } else if (
    snake.body[0].y > canvas.height - snake.snakeSpeedY ||
    snake.body[0].y < 0
  ) {
    alert("Game Over!");
  }
}

1 Ответ

0 голосов
/ 20 марта 2020

Ваша интуиция хороша. Недостающий элемент фактически обновляет новую головку, чтобы она двигалась в нужном вам направлении.

updateSnake() {
  const dir = {
    up: {x: 0, y: -this.snakeSize},
    down: {x: 0, y: this.snakeSize},
    left: {x: -this.snakeSize, y: 0},
    right: {x: this.snakeSize, y: 0},
  }[snakeDirection] || {x: 0, y: 0};
  this.body.unshift(this.body.pop());
  this.body[0].x = this.body[1].x + dir.x;
  this.body[0].y = this.body[1].y + dir.y;
  this.drawSnake();
}

Условное выражение немного неприглядно, поэтому объект - это простой способ его устранения. Примените его к классу, если вы (по праву) беспокоитесь о выделении его в каждом кадре.

Поскольку snakeDirection не инициализируется в начале скрипта, я использую || {x: 0, y: 0}, чтобы установить dir в значение по умолчанию, если ключ snakeDirection не найден в объекте.

Несколько других указателей:

  • snakeDirection, являющийся глобальным, не нужен, нарушая инкапсуляцию. Передайте его в updateSnake в качестве параметра.
  • Удалите this.drawSnake() из функции updateSnake - лучше разделить проблемы и переместить эти вызовы функций в родительскую функцию.
  • Аналогично, animate не должен вызывать функцию gameOver. Эта функция на самом деле не приводит к окончанию игры, поэтому она несколько ошибочно названа. Он проверяет границы, которые должны быть обязанностью класса игры или змеи. Он также спамит диалоговые окна после окончания игры, но я уверен, что это WIP.
  • Подумайте об использовании постоянного размера сетки, например, 15 или 20 пикселей.
  • Используйте event.preventDefault() для нажатия клавиш, чтобы экран не перемещается, пока пользователь управляет своей змеей.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...