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.