JavaScript Snake Tutorial förklaras

Skapa ett Snake-spel med HTML, CSS och JavaScript

I denna artikel kommer vi att utforska hur man utvecklar ett Snake-spel helt från grunden med hjälp av HTML, CSS och JavaScript. Vi kommer inte att använda några externa bibliotek, utan spelet kommer att köras direkt i webbläsaren. Denna process är inte bara underhållande utan även ett utmärkt sätt att förbättra dina problemlösningsförmågor.

Projektöversikt

Snake är ett klassiskt spel där du styr en orm för att samla mat samtidigt som du undviker kollisioner. När ormen äter mat blir den längre, vilket gradvis ökar spelets svårighetsgrad. Målet är att undvika att ormen krockar med väggarna eller sig själv. Ju längre ormen blir, desto mer utmanande blir spelet.

Målet med denna handledning är att skapa ett fullt fungerande Snake-spel. Du kan hitta koden för spelet på min GitHub. En live-version av spelet finns tillgänglig på GitHub Pages.

Förutsättningar

För att följa med i denna handledning behöver du grundläggande kunskaper i HTML, CSS och JavaScript. Vi kommer att använda enkel HTML-struktur och grundläggande CSS-styling. Huvudfokus kommer att ligga på JavaScript-koden. Om du är nybörjare på JavaScript, rekommenderar jag att du först utforskar resurser för att lära dig grunderna.

Du kommer även att behöva en kodredigerare för att skriva din kod och en webbläsare för att köra spelet.

Projektstruktur

Låt oss börja med att skapa projektfilerna. Inuti en tom mapp, skapa en fil som heter index.html och lägg till följande HTML-kod:

<html lang="sv">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles.css" />
    <title>Snake</title>
  </head>
  <body>
    <div id="game-over-screen">
      <h2>Spelet är slut</h2>
    </div>
    <canvas id="canvas" width="420" height="420"> </canvas>
    <script src="./snake.js"></script>
  </body>
</html>

Denna HTML-kod skapar en enkel ”Game Over”-skärm som vi kommer att visa eller dölja med JavaScript. Den definierar även en canvas där spelet kommer att ritas och länkar CSS- och JavaScript-filerna.

Därefter, skapa en fil som heter styles.css och lägg till följande CSS-koder:

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

Med CSS-koden ställer vi först in grundläggande stilar för alla element, tar bort standardmarginaler och padding, definierar teckensnitt och använder border-box som storleksmodell. För body ställer vi in en bakgrundsfärg, samt centrerar all text på sidan. För ”Game Over”-skärmen ställer vi in en magenta bakgrund, en svart kant, positionering och centrerar dess innehåll. Skärmen är som standard inställd på att vara dold.

Slutligen skapar du en fil med namnet snake.js, där vi kommer att skriva vår JavaScript-kod.

Globala variabler

Nu behöver vi definiera globala variabler som vi kommer att använda genom hela spelet. Lägg till följande definitioner i toppen av filen snake.js:

// Referenser till HTML-element
let gameOverScreen = document.getElementById("game-over-screen");
let canvas = document.getElementById("canvas");

// Canvas kontext
let ctx = canvas.getContext("2d");

Dessa variabler ger oss referenser till ”Game Over”-skärmen och till canvas-elementet. Variabeln ctx används sedan för att rita på duken.

Lägg till följande variabler under de tidigare definitionerna:

// Rutnätsdefinitioner
let gridSize = 400;
let unitLength = 10;
 

Här definierar vi storleken på rutnätet och längden på en enhet i spelet, vilket används för både ormens längd och storleken på matbitarna.

Lägg nu till följande variabler som kommer att hålla koll på spelets tillstånd:

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

snake lagrar positionen av varje del av ormen, där varje enhet har x och y-koordinater. foodPosition lagrar x- och y-koordinaterna för maten. direction håller koll på ormens rörelseriktning och collided är en flagga som indikerar om en kollision har inträffat.

Funktioner

För att göra koden mer strukturerad och lättare att hantera, delar vi upp spelet i funktioner. Här är de funktioner vi kommer att använda:

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

setUp-funktionen ställer in spelet. checkForCollision kontrollerar kollisioner med väggarna eller ormen själv. doesSnakeOccupyPosition kontrollerar om ormen upptar en viss position. move-funktionen flyttar ormen, medan turn ändrar dess riktning. onKeyDown hanterar tangenttryckningar för att ändra riktning och slutligen kör gameLoop spelet genom att uppdatera ormens position och kontrollera kollisioner.

Funktionsdefinitioner

Här definierar vi varje funktion som vi deklarerade tidigare, med en detaljerad förklaring av deras syfte och algoritm.

setUp funktionen

Funktionen setUp ansvarar för att:

  • Rita spelets gränser.
  • Initialisera ormen.
  • Generera matens startposition.

Koden för funktionen ser ut så här:

 // Rita gränser på canvasen
  canvasSideLength = gridSize + unitLength * 2;

  ctx.fillRect(0, 0, canvasSideLength, canvasSideLength);

  ctx.clearRect(unitLength, unitLength, gridSize, gridSize);

  // Initialposition för ormens huvud och svans
  const headPosition = Math.floor(gridSize / 2) + 30;
  const tailPosition = Math.floor(gridSize / 2) - 30;

  for (let i = tailPosition; i <= headPosition; i += unitLength) {
    snake.push({ x: i, y: Math.floor(gridSize / 2) });
    ctx.fillRect(i, Math.floor(gridSize / 2), unitLength, unitLength);
  }

  // Generera mat
  generateFood();
 

doesSnakeOccupyPosition

Den här funktionen tar in en x- och y-koordinat och kontrollerar om ormen befinner sig på den positionen:

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

checkForCollision

Funktionen kontrollerar om ormen kolliderar med väggarna eller sig själv, med följande logik:

 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

generateFood-funktionen letar efter en ledig plats för maten och placerar den där:

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

move

move-funktionen uppdaterar ormens position baserat på den aktuella riktningen och äter mat om det behövs. Här följer dess kod:

function move() {
  // Kopiera positionen för ormens huvud
  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;
  }
  
  // Lägg till det nya huvudet
  snake.push(headPosition);
  ctx.fillRect(headPosition.x, headPosition.y, unitLength, unitLength);

  // Kontrollera om ormen äter
  const isEating =
    foodPosition.x == headPosition.x && foodPosition.y == headPosition.y;

  if (isEating) {
    generateFood();
  } else {
     // Ta bort svansen om den inte äter
    tailPosition = snake.shift();
    ctx.clearRect(tailPosition.x, tailPosition.y, unitLength, unitLength);
  }
}
  

turn

turn-funktionen ändrar ormens riktning, men endast i vinkelräta riktningar:

function turn(newDirection) {
  switch (newDirection) {
    case "left":
    case "right":
      if (direction == "up" || direction == "down") {
        direction = newDirection;
      }
      break;
    case "up":
    case "down":
      if (direction == "left" || direction == "right") {
        direction = newDirection;
      }
      break;
  }
}
  

onKeyDown

onKeyDown-funktionen är en eventlyssnare som hanterar tangenttryckningar och anropar turn med rätt riktning:

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-funktionen anropar move, checkForCollision och avslutar spelet om en kollision har inträffat:

function gameLoop() {
  move();
  checkForCollision();

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

Starta spelet

För att starta spelet lägger vi till följande kod längst ner i filen snake.js:

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

Detta kallar först på setUp, lägger till en eventlyssnare för tangenttryckningar, och startar timern för att köra spelet med 200 ms intervall.

Slutsats

Din JavaScript-fil ska nu vara komplett. Kontrollera med koden på min GitHub om du stöter på problem. Som nästa steg kan du utforska hur man skapar ett bildspel med JavaScript.