Обяснен урок за JavaScript Snake

В тази статия ще обясня как да направите игра Snake с помощта на HTML, CSS и JavaScript.

Няма да използваме допълнителни библиотеки; играта ще работи в браузър. Създаването на тази игра е забавно упражнение, което ви помага да разтягате и упражнявате мускулите си за решаване на проблеми.

Схема на проекта

Snake е проста игра, в която насочвате движенията на змия към храна, докато избягвате препятствията. Когато змията стигне до храната, тя я изяжда и става по-дълга. С напредването на играта змията става все по-дълга.

Змията не трябва да се блъска в стени или в себе си. Следователно, с напредването на играта, змията става по-дълга и става все по-трудна за игра.

Целта на този урок за JavaScript Snake е да се изгради играта по-долу:

Кодът на играта е достъпен на моя GitHub. Версия на живо се хоства на Страници на GitHub.

Предпоставки

Ще изградим този проект с помощта на HTML, CSS и JavaScript. Ще пишем само основен HTML и CSS. Основният ни фокус е върху JavaScript. Следователно вече трябва да го разберете, за да следвате заедно с този урок за JavaScript Snake. Ако не, горещо ви препоръчвам да разгледате нашата статия за най-добрите места за изучаване на JavaScript.

Ще ви е необходим и редактор на код, в който да напишете вашия код. В допълнение към това ще ви трябва браузър, който вероятно имате, ако четете това.

Настройка на проекта

Като начало, нека настроим файловете на проекта. В празна папка създайте файл index.html и добавете следното маркиране.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="https://wilku.top/javascript-snake-tutorial-explained/./styles.css" />
    <title>Snake</title>
  </head>
  <body>
    <div id="game-over-screen">
      <h1>Game Over</h1>
    </div>
    <canvas id="canvas" width="420" height="420"> </canvas>
    <script src="./snake.js"></script>
  </body>
</html>

Маркирането по-горе създава основен екран „Играта приключи“. Ще превключваме видимостта на този екран с помощта на JavaScript. Той също така дефинира елемент на платно, върху който ще нарисуваме лабиринта, змията и храната. Маркирането също така свързва таблицата със стилове и JavaScript кода.

След това създайте файл styles.css за стила. Добавете следните стилове към него.

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Courier New', Courier, monospace;
}

body {
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: #00FFFF;
}

#game-over-screen {
    background-color: #FF00FF;
    width: 500px;
    height: 200px;
    border: 5px solid black;
    position: absolute;
    align-items: center;
    justify-content: center;
    display: none;
}

В набора от правила „*“ насочваме всички елементи и нулираме разстоянието. Също така задаваме фамилията шрифтове за всеки елемент и задаваме оразмеряването на елементите на по-предвидим метод за оразмеряване, наречен border-box. За тялото зададохме височината му на пълната височина на прозореца за изглед и подравнихме всички елементи към центъра. Дадохме му и син цвят на фона.

И накрая, стилизирахме екрана „Играта приключи“, за да му дадем височина и ширина съответно от 200 и 500 пиксела. Освен това му дадохме магента цвят на фона и черна граница. Задаваме неговата позиция на абсолютна, така че да е извън нормалния документен поток и да е подравнен към центъра на екрана. След това центрирахме съдържанието му. Зададохме неговото показване на нищо, така че е скрито по подразбиране.

  Вашият AI инструмент за овладяване на срещи

След това създайте файл snake.js, който ще напишем в следващите няколко раздела.

Създаване на глобални променливи

Следващата стъпка в този урок за JavaScript Snake е да дефинираме някои глобални променливи, които ще използваме. Във файла snake.js добавете следните дефиниции на променливи в горната част:

// Creating references to HTML elements
let gameOverScreen = document.getElementById("game-over-screen");
let canvas = document.getElementById("canvas");

// Creating context which will be used to draw on canvas
let ctx = canvas.getContext("2d");

Тези променливи съхраняват препратки към екрана „Играта приключи“ и елементите на платното. След това създадохме контекст, който ще се използва за рисуване върху платното.

След това добавете тези дефиниции на променливи под първия набор.

// Maze definitions
let gridSize = 400;
let unitLength = 10;

Първият определя размера на решетката в пиксели. Вторият определя дължина на единица в играта. Тази единица дължина ще се използва на няколко места. Например, ще го използваме, за да определим колко дебели са стените на лабиринта, колко дебела е змията, височината и ширината на храната и стъпките, в които се движи змията.

След това добавете следните променливи на играта. Тези променливи се използват за проследяване на състоянието на играта.

// Game play variables
let snake = [];
let foodPosition = { x: 0, y: 0 };
let direction = "right";
let collided = false;

Променливата на змията следи позициите, заети в момента от змията. Змията се състои от единици и всяка единица заема позиция върху платното. Позицията, която заема всяка единица, се съхранява в змийския масив. Позицията ще има стойности x и y като свои координати. Първият елемент в масива представлява опашката, докато последният представлява главата.

Докато змията се движи, ние ще избутваме елементи до края на масива. Това ще премести главата напред. Също така ще премахнем първия елемент или опашката от масива, така че дължината да остане същата.

Променливата за позицията на храната съхранява текущото местоположение на храната, използвайки координатите x и y. Променливата за посока съхранява посоката, в която се движи змията, докато променливата за сблъсък е булева променлива, маркирана като истина, когато бъде открит сблъсък.

Деклариране на функции

Цялата игра е разделена на функции, което улеснява писането и управлението. В този раздел ще декларираме тези функции и техните цели. Следващите раздели ще дефинират функциите и ще обсъдят техните алгоритми.

function setUp() {}
function doesSnakeOccupyPosition(x, y) {}
function checkForCollision() {}
function generateFood() {}
function move() {}
function turn(newDirection) {}
function onKeyDown(e) {}
function gameLoop() {}

Накратко, функцията SetUp настройва играта. Функцията checkForCollision проверява дали змията се е сблъскала със стена или самата себе си. Функцията doesSnakeOccupyPosition заема позиция, дефинирана от координатите x и y, и проверява дали някоя част от тялото на змията е в тази позиция. Това ще бъде полезно, когато търсите свободна позиция за добавяне на храна.

Функцията за движение премества змията в каквато и посока да сочи, докато функцията за завъртане променя тази посока. След това функцията onKeyDown ще слуша за натискания на клавиши, които се използват за промяна на посоката. Функцията gameLoop ще премести змията и ще провери за сблъсъци.

Дефиниране на функциите

В този раздел ще дефинираме функциите, които декларирахме по-рано. Ще обсъдим също как работи всяка функция. Ще има кратко описание на функцията преди кода и коментари за обяснение ред по ред, където е необходимо.

функция за настройка

Функцията за настройка ще направи 3 неща:

  • Начертайте границите на лабиринта върху платното.
  • Настройте змията, като добавите нейните позиции към променливата на змията и я начертаете върху платното.
  • Генерирайте първоначалната позиция на храната.
  •   Какво означава „IDGI“ и как го използвате?

    Следователно кодът за това ще изглежда така:

      // Drawing borders on canvas
      // The canvas will be the size of the grid plus thickness of the two side border
      canvasSideLength = gridSize + unitLength * 2;
    
      // We draw a black square that covers the entire canvas
      ctx.fillRect(0, 0, canvasSideLength, canvasSideLength);
    
      // We erase the center of the black to create the game space
      // This leaves a black outline for the that represents the border
      ctx.clearRect(unitLength, unitLength, gridSize, gridSize);
    
      // Next, we will store the initial positions of the snake's head and tail
      // The initial length of the snake will be 60px or 6 units
    
      // The head of the snake will be 30 px or 3 units ahead of the midpoint
      const headPosition = Math.floor(gridSize / 2) + 30;
    
      // The tail of the snake will be 30 px or 3 units behind the midpoint
      const tailPosition = Math.floor(gridSize / 2) - 30;
    
      // Loop from tail to head in unitLength increments
      for (let i = tailPosition; i <= headPosition; i += unitLength) {
    
        // Store the position of the snake's body and drawing on the canvas
        snake.push({ x: i, y: Math.floor(gridSize / 2) });
    
        // Draw a rectangle at that position of unitLength * unitLength
        ctx.fillRect(x, y, unitLength, unitLength);
      }
    
      // Generate food
      generateFood();

    doesSnakeOccupyPosition

    Тази функция приема координатите x и y като позиция. След това проверява дали съществува такава позиция в тялото на змията. Той използва метода за намиране на масив на JavaScript, за да намери позиция със съвпадащи координати.

    function doesSnakeOccupyPosition(x, y) {
      return !!snake.find((position) => {
        return position.x == x && y == foodPosition.y;
      });
    }

    checkForCollision

    Тази функция проверява дали змията се е сблъскала с нещо и задава променливата за сблъсък на true. Ще започнем с проверка за сблъсъци с лявата и дясната стена, горната и долната стена и след това със самата змия.

    За да проверим за сблъсъци срещу лявата и дясната стена, проверяваме дали координатата x на главата на змията е по-голяма от gridSize или по-малка от 0. За да проверим за сблъсъци срещу горната и долната стена, ще извършим същата проверка, но с y-координати.

    След това ще проверим за сблъсъци със самата змия; ще проверим дали някоя друга част от тялото му заема позицията, която в момента заема главата. Комбинирайки всичко това, тялото на функцията checkForCllision трябва да изглежда така:

     function checkForCollision() {
      const headPosition = snake.slice(-1)[0];
      // Check for collisions against left and right walls
      if (headPosition.x < 0 || headPosition.x >= gridSize - 1) {
        collided = true;
      }
    
      // Check for collisions against top and bottom walls
      if (headPosition.y < 0 || headPosition.y >= gridSize - 1) {
        collided = true;
      }
    
      // Check for collisions against the snake itself
      const body = snake.slice(0, -2);
      if (
        body.find(
          (position) => position.x == headPosition.x && position.y == headPosition.y
        )
      ) {
        collided = true;
      }
    }

    генерирам храна

    Функцията generateFood използва do-while цикъл, за да търси позиция за поставяне на храна, която не е заета от змията. Веднъж намерена, позицията на храната се записва и начертава върху платното. Кодът за функцията generateFood трябва да изглежда така:

    function generateFood() {
      let x = 0,
        y = 0;
      do {
        x = Math.floor((Math.random() * gridSize) / 10) * 10;
        y = Math.floor((Math.random() * gridSize) / 10) * 10;
      } while (doesSnakeOccupyPosition(x, y));
    
      foodPosition = { x, y };
      ctx.fillRect(x, y, unitLength, unitLength);
    }

    ход

    Функцията за преместване започва със създаване на копие на позицията на главата на змията. След това, въз основа на текущата посока, той увеличава или намалява стойността на x или y координатата на змията. Например увеличаването на координатата x е еквивалентно на преместване надясно.

      5 начина да спечелите повече пари в „Stardew Valley“

    След като това е направено, притискаме новата headPosition към змийския масив. Освен това начертаваме новата HeadPosition към платното.

    След това проверяваме дали змията е изяла храна в този ход. Правим това, като проверяваме дали headPosition е равна на foodPosition. Ако змията е яла храна, ние извикваме функцията generateFood.

    Ако змията не е яла храна, изтриваме първия елемент от змийския масив. Този елемент представлява опашката и премахването й ще запази дължината на змията същата, като същевременно създава илюзията за движение.

    function move() {
      // Create a copy of the object representing the position of the head
      const headPosition = Object.assign({}, snake.slice(-1)[0]);
    
      switch (direction) {
        case "left":
          headPosition.x -= unitLength;
          break;
        case "right":
          headPosition.x += unitLength;
          break;
        case "up":
          headPosition.y -= unitLength;
          break;
        case "down":
          headPosition.y += unitLength;
      }
    
      // Add the new headPosition to the array
      snake.push(headPosition);
    
      ctx.fillRect(headPosition.x, headPosition.y, unitLength, unitLength);
    
      // Check if snake is eating
      const isEating =
        foodPosition.x == headPosition.x && foodPosition.y == headPosition.y;
    
      if (isEating) {
        // Generate new food position
        generateFood();
      } else {
        // Remove the tail if the snake is not eating
        tailPosition = snake.shift();
    
        // Remove tail from grid
        ctx.clearRect(tailPosition.x, tailPosition.y, unitLength, unitLength);
      }
    }

    завой

    Последната основна функция, която ще разгледаме, е функцията за завъртане. Тази функция ще вземе нова посока и ще промени променливата за посока към тази нова посока. Въпреки това, змията може да се обърне само в посока, перпендикулярна на тази, в която се движи в момента.

    Следователно змията може да се обърне наляво или надясно само ако се движи нагоре или надолу. Обратно, той може да се обърне нагоре или надолу само ако се движи наляво или надясно. Имайки предвид тези ограничения, функцията turn изглежда така:

    function turn(newDirection) {
      switch (newDirection) {
        case "left":
        case "right":
          // Only allow turning left or right if they were originally moving up or down
          if (direction == "up" || direction == "down") {
            direction = newDirection;
          }
          break;
        case "up":
        case "down":
          // Only allow turning up or down if they were originally moving left or right
          if (direction == "left" || direction == "right") {
            direction = newDirection;
          }
          break;
      }
    }

    onKeyDown

    Функцията onKeyDown е манипулатор на събития, който ще извика функцията turn с посока, съответстваща на натиснатия клавиш със стрелка. Следователно функцията изглежда така:

    function onKeyDown(e) {
      switch (e.key) {
        case "ArrowDown":
          turn("down");
          break;
        case "ArrowUp":
          turn("up");
          break;
        case "ArrowLeft":
          turn("left");
          break;
        case "ArrowRight":
          turn("right");
          break;
      }
    }

    gameLoop

    Функцията gameLoop ще се извиква редовно, за да продължи играта да работи. Тази функция ще извика функцията за преместване и функцията checkForCollision. Той също така проверява дали сблъсъкът е верен. Ако е така, той спира интервалния таймер, който използваме за стартиране на играта, и показва екрана „играта приключи“. Функцията ще изглежда така:

    function gameLoop() {
      move();
      checkForCollision();
    
      if (collided) {
        clearInterval(timer);
        gameOverScreen.style.display = "flex";
      }
    }

    Стартиране на играта

    За да започнете играта, добавете следните редове код:

    setUp();
    document.addEventListener("keydown", onKeyDown);
    let timer = setInterval(gameLoop, 200);

    Първо извикваме функцията setUp. След това добавяме слушателя на събития „keydown“. И накрая, използваме функцията setInterval, за да стартираме таймера.

    Заключение

    В този момент вашият JavaScript файл трябва да изглежда като този на моя GitHub. В случай, че нещо не работи, проверете отново с репото. След това може да искате да научите как да създадете плъзгач за изображение в JavaScript.