Как создать Snake, используя только JavaScript, HTML и CSS

1656584783 kak sozdat snake ispolzuya tolko javascript html i css

Панайотис Николау

1*9xRelIyk3BRGfRoArVU6wA

Привет ?

Добро пожаловать на борт. Сегодня мы отправимся в увлекательное приключение, где мы будем создавать нашу собственную игру о змее. Вы узнаете, как решить проблему, разбив ее на меньшие простые шаги. В конце этого путешествия вы узнаете кое-что новое и будете уверенно исследовать больше самостоятельно.

Если вы новичок в программировании, я рекомендую проверить freeCodeCamp. Это отличное место для обучения… Вы уже догадались… бесплатно. Вот как я начал?

Ладно, хорошо, достаточно возиться – вы готовы начать?

Окончательный код можно найти здесь, а демо-версию здесь.

Начинаем

Давайте начнем с создания файла snake.html, который будет содержать весь наш код.

Поскольку это HTML файл, первое, что нам нужно, это файл <!DOCTYPE> декларация . In snake.html введите следующее:

Прекрасно, а теперь откройте snake.html в желаемом браузере. Вы должны иметь возможность видеть Добро пожаловать в Snake!

1*PFyqYaApjv8H6-ddH79HaQ
snake.html открыт в хром

Мы хорошо начинаем?

Создание холста

Чтобы иметь возможность создать нашу игру, мы должны использовать HTML <canvкак> . Это то, что используется для рисования графики с помощью JavaScript.

Замените поздравительное сообщение snake.html со следующим:

<canvas id="gameCanvas" width="300" height="300">&lt;canvas>

Идентификатор – это то, что идентифицирует полотно, и его всегда следует указывать. Мы используем его для доступа к холсту позже. Ширина и высота являются размерами холста, их также нужно указать. В этом случае 300 x 300 пикселей.

Теперь ваш файл snake.html должен выглядеть следующим образом.

Если вы обновите страницу браузера, где вы раньше открывали snake.html теперь вы увидите пустую страницу. Это потому, что по умолчанию холст пуст и не имеет фона. Давайте поправим это. ?

Придайте полотну цвет фона и кайму

Чтобы сделать наше полотно видимым, мы можем предоставить ему предел, написав некоторый код JavaScript. Для этого нам нужно вставить <script><тег ;/script>s after the куда будет размещен весь код JavaScript.

Если поставить <script> тег передe the &lt;canvas> ваш код не будет работать, поскольку HTML не будет загружен.

Теперь мы можем написать некоторый код JavaScript между вложенными<script><теги ;/script>. Обновите код, как показано ниже.

Сначала мы получаем элемент canvas, используя идентификатор (gameCanvas), который мы указали раньше. Затем мы получаем контекст «2d» полотна, что означает, что мы будем рисовать в 2D-пространстве.

Наконец, мы рисуем белый прямоугольник 300 x 300 с черной рамкой. Это включает все полотно, начиная с верхнего левого угла (0, 0).

Если перезагрузить snake.html В вашем браузере вы должны увидеть белое поле с черной рамкой! Хорошая работа, у нас есть холст, который мы можем использовать для создания нашей игры со змеями! ? До следующего вызова!

Представляя нашу змею

Чтобы наша игра «Змея» работала, нам нужно знать расположение змеи на холсте. Для этого мы можем представить змею посредством массива координат. Таким образом, чтобы создать горизонтальную змейку внутри полотна (150, 150), можно написать следующее:

let snake = [  {x: 150, y: 150},  {x: 140, y: 150},  {x: 130, y: 150},  {x: 120, y: 150},  {x: 110, y: 150},];

Обратите внимание, что координата y для всех частей всегда равна 150. Координата x каждой части равна –10px (слева) от предыдущей части. Первая пара координат в массиве {x: 150, y: 150} представляет голову справа от змеи.

Это станет понятнее, когда мы нарисуем змею в следующей главе.

Создание и рисование нашей змейки

Чтобы отобразить змею на холсте, мы можем написать функцию для рисования прямоугольника для каждого пара координат.

function drawSnakePart(snakePart) {  ctx.fillStyle="lightgreen";  ctx.strokestyle="darkgreen";
  ctx.fillRect(snakePart.x, snakePart.y, 10, 10);  ctx.strokeRect(snakePart.x, snakePart.y, 10, 10);}

Далее мы можем создать другую функцию, печатающую части на холсте.

function drawSnake() {  snake.forEach(drawSnakePart);}

Наши snake.html файл теперь должен выглядеть так:

Если вы обновите страницу браузера сейчас, вы увидите зеленую змею внутри полотна. Прекрасно! ?

1*BLf1mbFS-SCl4M8VQen1Qg

Позволяет змее двигаться горизонтально

Дальше мы хотим предоставить змее возможность двигаться. Но как это сделать? ?

Ну, чтобы змея двигалась на шаг (10 пикселей) вправо, мы можем увеличить x-координату каждый часть змеи на 10px (dx=+10px). Чтобы змея двигалась влево, мы можем уменьшить координату x каждый часть змеи на 10px (dx=-10).

dx – горизонтальная скорость змеи.

Создание змеи, которая переместилась на 10 пикселей вправо, должно выглядеть так

1*zytaPha9jcM6N45xrOnyTw

Создайте функцию под названием advanceSnake который мы будем использовать для обновления змей.

function advanceSnake() {  const head = {x: snake[0].x + dx, y: snake[0].y};
  snake.unshift(head);
  snake.pop();}

Сначала создаем новую голову для змеи. Затем добавляем новую голову в начало змея используя unshift и удалите последний элемент змея используя поп. Таким образом все остальные части змейки смещаются на свои места, как показано выше.

Бум, ты употребляешься в этом.

Позволяет змее двигаться вертикально

Чтобы перемещать нашу змею вверх и вниз, мы не можем изменить все координаты y на 10 пикселей. Это сдвинуло бы всю змею вверх и вниз.

1*39OySfjontvvtgrLQhyt3A

Вместо этого мы можем изменить координату у головы. Убавьте его на 10 пикселей, чтобы переместить змею вниз, и увеличить на 10 пикселей, чтобы переместить змею вверх. Это заставит змею двигаться правильно.

К счастью, из-за того, как мы написали advanceSnake функция это очень легко сделать. Внутри advanceSnakeобновите голову, чтобы также увеличить координату y головы на dy.

const head = {x: snake[0].x + dx, y: snake[0].y + dy};

Чтобы проверить, как наши advanceSnake функция работает, мы можем временно вызвать ее перед drawSnake функция.

// Move on step to the rightadvanceSnake()
// Change vertical velocity to 0dx = 0;// Change horizontal velocity to 10dy = -10;
// Move one step upadvanceSnake();
// Draw snake on the canvasdrawSnake();

Вот так наши snake.html файл выглядит пока.

Обновив браузер, мы видим, что наша змея переместилась. Удачи!

1*Qnmxp7sFC4TREwVFYqh2vg

Рефакторинг нашего кода

Прежде чем мы перейдем дальше, давайте сделаем некоторый рефакторинг и переместим код, рисующий полотно внутри функции. Это поможет нам в следующей главе.

«Рефакторинг кода это процесс реструктуризации существующего компьютера код, не изменяя его внешнего поведения». — Википедия

function clearCanvas() {  ctx.fillStyle = "white";  ctx.strokeStyle = "black";
  ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height);  ctx.strokeRect(0, 0, gameCanvas.width, gameCanvas.height);}

Мы достигаем больших успехов! ?

Заставить нашу змею двигаться автоматически

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

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

Таким образом, если бы мы хотели заставить змею двигаться на пять шагов вправо, мы бы призвали advanceSnake() пять раз подряд.

clearCanvas();advanceSnake();advanceSnake();advanceSnake();advanceSnake();advanceSnake();drawSnake();

Но, вызывая его пять раз подряд, как показано выше, змея прыгнет на 50 пикселей вперед.

1*8MMmK5I75eaA_fxHFUiiwQ

Мы хотим сделать так, чтобы змея шаг за шагом двигалась вперед.

Для этого мы можем добавить маленькую задержку меж каждым вызовом, используя setTimeout. Нам также нужно обязательно позвонить drawSnake каждый раз, когда мы звоним advanceSnake. Если мы этого не сделаем, мы не сможем увидеть промежуточные шаги, которые показывают, как движется змея.

setTimeout(function onTick() {  clearCanvas();  advanceSnake();  drawSnake();}, 100);
setTimeout(function onTick() {  clearCanvas();  advanceSnake();  drawSnake();}, 100);
...
drawSnake();

Обратите внимание, как мы также называем clearCanvas() внутри каждого setTimeout. Это требуется для удаления всех предыдущих позиций змеи, которые бы оставляли след за собой.

1*q59cpBnigigbLPslBUkiLw

Хотя с приведенным выше кодом есть проблема. Здесь нет ничего, чтобы сказать программе, что она должна ждать setTimeout перед тем, как перейти к следующему setTimeout. Это означает, что змея будет до сих пор перейти на 50 пикселей вперед, но после a небольшая задержка.

Чтобы это исправить, мы должны завернуть наш код внутри функций, вызывая одну функцию за раз.

stepOne();    function stepOne() {  setTimeout(function onTick() {    clearCanvas();    advanceSnake();    drawSnake();   // Call the second function   stepTwo();  }, 100)}
function stepTwo() {  setTimeout(function onTick() {    clearCanvas();    advanceSnake();    drawSnake();    // Call the third function    stepThree();  }, 100)}
...

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

function main() {  setTimeout(function onTick() {    clearCanvas();    advanceSnake();    drawSnake();
    // Call main again    main();  }, 100)}

Вуаль! Теперь у нас есть змея, которая будет двигаться вправо. Хотя, достигнув конца полотна, он продолжает свое бесконечное путешествие в неизвестное». Мы исправим это в свое время, терпение, юный падаван. ?.

1*qr0kvF_aQ2E_n6QcxfkyQA

Изменение направления движения змеи

Наша следующая задача – изменить направление змеи при нажатии одной из клавиш со стрелками. Добавьте следующий код после drawSnakePart функция.

function changeDirection(event) {  const LEFT_KEY = 37;  const RIGHT_KEY = 39;  const UP_KEY = 38;  const DOWN_KEY = 40;
  const keyPressed = event.keyCode;  const goingUp = dy === -10;  const goingDown = dy === 10;  const goingRight = dx === 10;  const goingLeft = dx === -10;
  if (keyPressed === LEFT_KEY && !goingRight) {    dx = -10;    dy = 0;  }
  if (keyPressed === UP_KEY && !goingDown) {    dx = 0;    dy = -10;  }
  if (keyPressed === RIGHT_KEY && !goingLeft) {    dx = 10;    dy = 0;  }
  if (keyPressed === DOWN_KEY && !goingDown) {    dx = 0;    dy = 10;  }}

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

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

1*PxB5A6SCbJlwye4PUj_Bdw
Змея задним ходом

Соединить changeDirection в нашей игре мы можем использовать addEventListener в документе, чтобы прослушивать нажатие клавиши. Тогда можем позвонить changeDirection с нажатием клавиши. Добавьте следующий код после main функция.

document.addEventListener("keydown", changeDirection)

Теперь вы можете изменить направление змеи с помощью четырех клавиш со стрелками. Прекрасная работа, ты горишь?!

Дальше посмотрим, как мы можем производить пищу и выращивать нашу змею.

Создание пищи для змеи

Для нашей пищи для змей мы должны создать случайный набор координат. Мы можем использовать вспомогательную функцию randomTen чтобы получить два числа. Один для координаты x и один для координаты y.

Мы также должны убедиться, что еда не находится там, где находится змея. Если это так, мы должны создать новое местоположение пищи.

function randomTen(min, max) {  return Math.round((Math.random() * (max-min) + min) / 10) * 10;}
function createFood() {  foodX = randomTen(0, gameCanvas.width - 10);  foodY = randomTen(0, gameCanvas.height - 10);
  snake.forEach(function isFoodOnSnake(part) {    const foodIsOnSnake = part.x == foodX && part.y == foodY    if (foodIsOnSnake)      createFood();  });}

Затем мы должны создать функцию для рисования пищи на холсте.

function drawFood() { ctx.fillStyle="red"; ctx.strokestyle="darkred"; ctx.fillRect(foodX, foodY, 10, 10); ctx.strokeRect(foodX, foodY, 10, 10);}

Наконец-то мы можем позвонить createFood перед звонком main. Не забудьте также обновить main использовать drawFood функция.

function main() {  setTimeout(function onTick() {    clearCanvas();    drawFood()    advanceSnake();    drawSnake();
    main();  }, 100)}

Выращивание змеи

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

function advanceSnake() {  const head = {x: snake[0].x + dx, y: snake[0].y};
  snake.unshift(head);
  const didEatFood = snake[0].x === foodX && snake[0].y === foodY;  if (didEatFood) {    createFood();  } else {    snake.pop();  }}

Отслеживание счета

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

Создайте новую переменную оценку и установите ее на 0 после объявления snake.

let score = 0;

Затем добавьте новый div с идентификатором «оценка» перед полотном. Мы можем использовать это для отображения счета.

<div id="score">0</div><canvas id="gameCanvas" width="300" height="300"></canvas>

Наконец-то обновите advanceSnake чтобы увеличить и отразить оценку, когда змея ест пищу.

function advanceSnake() {  ...
  if (didEatFood) {    score += 10;    document.getElementById('score').innerHTML = score;
    createFood();  } else {    ...  }}

Фу, это было довольно много, но мы прошли долгий путь?

Завершите игру

Осталась одна последняя часть, и это закончить игру? Для этого мы можем создать функцию didGameEnd что возвращает tрута когда игра закончилась или fтакже иначе.

function didGameEnd() {  for (let i = 4; i < snake.length; i++) {    const didCollide = snake[i].x === snake[0].x &&      snake[i].y === snake[0].y
    if (didCollide) return true  }
  const hitLeftWall = snake[0].x < 0;  const hitRightWall = snake[0].x > gameCanvas.width - 10;  const hitToptWall = snake[0].y &lt; 0;  const hitBottomWall = snake[0].y > gameCanvas.height - 10;
  return hitLeftWall ||          hitRightWall ||          hitToptWall ||         hitBottomWall}

Сначала проверяем, касается голова змеи другой части змеи, и возвращаемся правда если это так.

Обратите внимание, что мы начинаем наш цикл с индекса 4. Это две причины. Первое это то didCollide будет немедленно оценена как истина, если индекс равен 0, так что игра закончится. Вторая состоит в том, что первые три части не могут касаться друг друга.

Далее мы проверяем, не ударилась ли змея в любую из стен полотна, и возвращаемся правда если да, иначе мы вернемся ошибочный.

Теперь мы можем вернуться раньше main функция если didEndGame возвращает true, тем самым завершая игру.

function main() {  if (didGameEnd()) return;
  ...}

Теперь наш snake.html должен выглядеть так:

Теперь у вас есть действующая игра змейка, в которую вы можете играть и делиться с друзьями. Но перед празднованием рассмотрим последнюю проблему. Это будет последний, я обещаю.

Коварные ошибки?

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

Когда возникает ошибка, лучший способ ее решить – сначала иметь надежный способ ее воспроизведения. То есть придумайте точные шаги, которые приведут к неожиданному поведению. Тогда вам нужно понять, почему они вызывают неожиданное поведение, а затем найти решение.

Воспроизведение ошибки

В нашем случае, шаги, предпринятые для воспроизведения ошибки, таковы:

  • Змея двигается влево
  • Игрок нажимает клавишу со стрелкой вниз
  • Игрок немедленно нажимает клавишу со стрелкой вправо (до истечения 100 мс)
  • Игра заканчивается
1*rMOAsWJxt8uD2p3dILRHnw

Понимание ошибки

Давайте шаг за шагом разберем происходящее.

Змея двигается слева

  • Горизонтальная скорость, dx равна -10
  • main функция вызывается
  • advanceSnake называется, который перемещает змею на -10px слева.

Игрок нажимает клавишу со стрелкой вниз

  • changeDirection это называется
  • keyPressed === DOWN_KEY && dy !goingUp оценивает как истину
  • dx меняется на 0
  • dy меняется на +10

Игрок немедленно нажимает стрелку вправо (до истечения 100 мс)

  • changeDirection это называется
  • keyPressed === RIGHT_KEY && !goingLeft оценивает как истину
  • dx меняется на +10
  • dy меняется на 0

Игра заканчивается

  • main функция вызывается через 100 мс.
  • advanceSnake называется продвигающий змею на 10 пикселей вправо.
  • const didCollide = snake[i].x === snake[0].x && snake[i].y === snake[0].y оценивает как истину
  • didGameEnd возвращает true
  • main функция возвращается раньше
  • Игра заканчивается

Исправление ошибки

Изучив случившееся, мы узнаем, что игра закончилась из-за того, что змея перевернулась.

Это потому, что когда игрок нажал стрелку вниз, dx был установлен на 0. Таким образом keyPressed === RIGHT_KEY && !goingLeft оценено как истина, а dx изменено на 10.

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

Чтобы исправить нашу ошибку, мы должны убедиться, что мы сможем изменить направление только после этого main и advanceSnake были вызваны. Мы можем создать переменную изменяет направление. Это будет установлено как true, когда changeDirection вызывается, а к false – когда advanceSnake это называется.

Внутри нашего changeDirection функции, мы можем вернуться раньше, если изменяет направление правда.

function changeDirection(event) {  const LEFT_KEY = 37;  const RIGHT_KEY = 39;  const UP_KEY = 38;  const DOWN_KEY = 40;
  if (changingDirection) return;
  changingDirection = true;
  ...}
function main() {  setTimeout(function onTick() {    changingDirection = false;        ...
  }, 100)}

Вот наша окончательная версия snake.html

Обратите внимание, что я тоже добавил некоторые стили? между &lt;style><теги ;/style>. То есть, чтобы полотно и отметка появились в середине экрана.

Вывод

Поздравляю! ??

Мы подошли к концу нашей поездки. Надеюсь, вам понравилось учиться вместе со мной, и теперь вы можете уверенно продлить свое следующее приключение.

Но это не должно заканчиваться этим. Моя следующая статья будет сосредоточена на том, чтобы помочь вам начать работу с очень увлекательный мир открытый источник.

Открытый код – это отличный способ учиться много новых вещей и знакомства с удивительными людьми. Это очень полезно, но сначала может быть страшно?

Чтобы получать уведомления, когда выйдет моя следующая статья, вы можете подписаться на меня! ?

Было приятно быть с вами в этой поездке.

В следующий раз. ✨

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *