Објашњено упутство за ЈаваСцрипт змију

У овом чланку ћу објаснити како да направите игру Снаке користећи ХТМЛ, ЦСС и ЈаваСцрипт.

Нећемо користити додатне библиотеке; игра ће се покренути у претраживачу. Прављење ове игре је забавна вежба која вам помаже да истегнете и вежбате мишиће за решавање проблема.

Пројецт Оутлине

Снаке је једноставна игра у којој водите покрете змије ка храни док избегавате препреке. Када змија дође до хране, она је поједе и расте дуже. Како игра напредује, змија постаје све дужа.

Змија не би требало да налети на зидове или на себе. Због тога, како игра напредује, змија постаје дужа и постаје све теже играти.

Циљ овог упутства за ЈаваСцрипт Снаке је да направите игру испод:

Код за игру је доступан на мом ГитХуб. Верзија уживо је хостована ГитХуб странице.

Предуслови

Изградићемо овај пројекат користећи ХТМЛ, ЦСС и ЈаваСцрипт. Ми ћемо писати само основни ХТМЛ и ЦСС. Наш примарни фокус је на ЈаваСцрипт-у. Због тога би већ требало да разумете да бисте пратили овај водич за ЈаваСцрипт змију. Ако не, топло препоручујем да погледате наш чланак о најбољим местима за учење ЈаваСцрипт-а.

Такође ће вам требати уређивач кода да упишете свој код. Поред тога, биће вам потребан претраживач, који вероватно имате ако ово читате.

Постављање пројекта

За почетак, поставимо датотеке пројекта. У празној фасцикли направите датотеку индек.хтмл и додајте следеће ознаке.

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

Горе наведене ознаке стварају основни екран „Гаме Овер“. Ми ћемо променити видљивост овог екрана користећи ЈаваСцрипт. Такође дефинише елемент платна на коме ћемо нацртати лавиринт, змију и храну. Означавање такође повезује листу стилова и ЈаваСцрипт код.

Затим направите датотеку стилес.цсс за стил. Додајте му следеће стилове.

* {
    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;
}

У скупу правила ‘*’ циљамо све елементе и ресетујемо размак. Такође смо поставили фамилију фонтова за сваки елемент и поставили величину елемената на предвидљивији метод одређивања величине који се зове бордер-бок. За тело смо поставили његову висину на пуну висину прозора за приказ и поравнали све ставке у центар. Такође смо му дали плаву боју позадине.

На крају, стилизовали смо екран ‘Гаме Овер’ тако да му дамо висину и ширину од 200 и 500 пиксела, респективно. Такође смо му дали магента боју позадине и црну ивицу. Поставили смо њену позицију на апсолутну тако да је изван нормалног тока документа и поравната са центром екрана. Затим смо центрирали његов садржај. Његов приказ смо поставили на ништа, тако да је подразумевано скривен.

Затим креирајте датотеку снаке.јс, коју ћемо писати у наредних неколико одељака.

Креирање глобалних променљивих

Следећи корак у овом водичу за ЈаваСцрипт Снаке је да дефинишемо неке глобалне променљиве које ћемо користити. У датотеци снаке.јс додајте следеће дефиниције променљивих на врх:

// 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;

Променљива змија прати позиције које змија тренутно заузима. Змија се састоји од јединица, а свака јединица заузима позицију на платну. Позиција коју свака јединица заузима чува се у низу змија. Положај ће имати вредности к и и као координате. Први елемент у низу представља реп, док последњи представља главу.

Како се змија креће, гураћемо елементе до краја низа. Ово ће померити главу напред. Такође ћемо уклонити први елемент или реп из низа тако да дужина остане иста.

Варијабла позиције хране чува тренутну локацију хране користећи к и и координате. Променљива смера чува правац у коме се змија креће, док је сударена променљива логичка променљива означена са тачно када је судар откривен.

Декларисање функција

Цела игра је подељена на функције, што олакшава писање и управљање. У овом одељку ћемо декларисати те функције и њихову сврху. Следећи одељци ће дефинисати функције и размотрити њихове алгоритме.

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

Укратко, функција подешавања поставља игру. Функција цхецкФорЦоллисион проверава да ли се змија сударила са зидом или саму себе. Функција доСнакеОццупиПоситион заузима позицију, дефинисану координатама к и и, и проверава да ли је било који део тела змије у том положају. Ово ће бити корисно када тражите слободну позицију за додавање хране.

Функција померања помера змију у ком год смеру показује, док функција окретања мења тај смер. Затим, функција онКеиДовн ће слушати притискања тастера који се користе за промену смера. Функција гамеЛооп ће померити змију и проверити да ли има судара.

Дефинисање функција

У овом одељку ћемо дефинисати функције које смо раније декларисали. Такође ћемо разговарати о томе како свака функција функционише. Биће кратак опис функције пре кода и коментари који ће објаснити ред по ред где је то потребно.

функција подешавања

Функција подешавања ће урадити 3 ствари:

  • Нацртајте границе лавиринта на платну.
  • Подесите змију тако што ћете додати њене позиције променљивој змија и нацртати је на платну.
  • Генеришите почетну позицију хране.
  • Према томе, код за то ће изгледати овако:

      // 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();

    доесСнакеОццупиПоситион

    Ова функција узима координате к и и као позицију. Затим проверава да ли постоји таква позиција у телу змије. Користи метод за проналажење ЈаваСцрипт низа да пронађе позицију са одговарајућим координатама.

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

    цхецкФорЦоллисион

    Ова функција проверава да ли се змија сударила са нечим и поставља сударну променљиву на труе. Почећемо тако што ћемо проверити да ли се судара са левим и десним зидом, горњим и доњим зидом, а затим и са самом змијом.

    Да бисмо проверили да ли има судара са левим и десним зидом, проверавамо да ли је к координата змијске главе већа од величине мреже или мања од 0. Да бисмо проверили колизије на горњи и доњи зид, извршићемо исту проверу, али са и-координате.

    Затим ћемо проверити да ли има судара са самом змијом; проверићемо да ли неки други део његовог тела заузима положај који тренутно заузима глава. Комбинујући све ово, тело функције цхецкФорЦллисион би требало да изгледа овако:

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

    генератеФоод

    ГенерирајФоод функција користи до-вхиле петљу да тражи позицију за постављање хране коју змија не заузима. Када се пронађе, позиција хране се снима и црта на платну. Код за функцију генератеФоод би требао изгледати овако:

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

    потез

    Функција померања почиње креирањем копије положаја главе змије. Затим, на основу тренутног правца, повећава или смањује вредност к или и координате змије. На пример, повећање координате к је еквивалентно померању удесно.

    Када је то урађено, гурамо нови хеадПоситион у низ змија. Такође цртамо нову позицију главе на платну.

    Затим проверавамо да ли је змија појела храну у том потезу. Ово радимо тако што проверавамо да ли је хеадПоситион једнака фоодПоситион. Ако је змија појела храну, зовемо функцију генерисФоод.

    Ако змија није појела храну, бришемо први елемент низа змија. Овај елемент представља реп, а његово уклањање ће задржати дужину змије истом, а дајући илузију кретања.

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

    ред

    Последња главна функција коју ћемо покрити је функција окретања. Ова функција ће узети нови правац и променити променљиву смера у тај нови правац. Међутим, змија се може окренути само у правцу који је окомит на онај у коме се тренутно креће.

    Дакле, змија може да се окрене само лево или десно ако се креће нагоре или надоле. Насупрот томе, може се окренути само нагоре или надоле ако се креће лево или десно. Имајући у виду та ограничења, функција окретања изгледа овако:

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

    онКеиДовн

    Функција онКеиДовн је руковалац догађаја који ће позвати функцију окретања са смером који одговара тастеру са стрелицом који је притиснут. Функција, дакле, изгледа овако:

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

    гамеЛооп

    Функција гамеЛооп ће се редовно позивати да би се игра наставила. Ова функција ће позвати функцију покретања и функцију цхецкФорЦоллисион. Такође проверава да ли је колизија истинита. Ако је тако, зауставља интервални тајмер који користимо за покретање игре и приказује екран ‘гаме овер’. Функција ће изгледати овако:

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

    Покретање игре

    Да бисте започели игру, додајте следеће линије кода:

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

    Прво, позивамо функцију сетУп. Затим додајемо слушалац догађаја ‘кеидовн’. На крају, користимо функцију сетИнтервал да покренемо тајмер.

    Закључак

    У овом тренутку, ваша ЈаваСцрипт датотека треба да изгледа као она на мојој ГитХуб. У случају да нешто не ради, проверите поново са репо. Затим ћете можда желети да научите како да направите клизач слике у ЈаваСцрипт-у.