V tomto článku vysvětlím, jak vytvořit hru Snake pomocí HTML, CSS a JavaScriptu.
Nebudeme používat další knihovny; hra poběží v prohlížeči. Vytvoření této hry je zábavné cvičení, které vám pomůže protáhnout a procvičit svaly, které řeší problémy.
Table of Contents
Osnova projektu
Snake je jednoduchá hra, kde řídíte pohyby hada směrem k jídlu a zároveň uhýbáte překážkám. Když had dosáhne potravy, sežere ji a poroste déle. Jak hra postupuje, had je stále delší.
Had by neměl narazit do zdí nebo do sebe. Proto, jak hra postupuje, had se prodlužuje a je stále těžší hrát.
Cílem tohoto JavaScript Snake Tutoriálu je vytvořit níže uvedenou hru:
Kód ke hře je k dispozici na mém GitHub. Živá verze je hostována na Stránky GitHub.
Předpoklady
Tento projekt vytvoříme pomocí HTML, CSS a JavaScriptu. Budeme psát pouze základní HTML a CSS. Naším hlavním zaměřením je JavaScript. Proto byste již měli porozumět tomu, abyste jej následovali spolu s tímto výukovým programem hada JavaScript. Pokud ne, vřele doporučuji, abyste se podívali na náš článek o nejlepších místech, kde se můžete naučit JavaScript.
Budete také potřebovat editor kódu, do kterého zapíšete svůj kód. Kromě toho budete potřebovat prohlížeč, který pravděpodobně máte, pokud toto čtete.
Nastavení projektu
Pro začátek nastavíme soubory projektu. V prázdné složce vytvořte soubor index.html a přidejte následující označení.
<!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>
Výše uvedené označení vytváří základní obrazovku „Game Over“. Viditelnost této obrazovky přepneme pomocí JavaScriptu. Definuje také prvek plátna, na který nakreslíme bludiště, hada a jídlo. Označení také propojuje šablonu stylů a kód JavaScript.
Dále vytvořte soubor styles.css pro styl. Přidejte k tomu následující styly.
* { 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; }
V sadě pravidel ‚*‘ cílíme na všechny prvky a resetujeme mezery. Nastavili jsme také rodinu písem pro každý prvek a nastavili velikost prvků na předvídatelnější metodu velikosti zvanou border-box. U těla jsme nastavili jeho výšku na celou výšku výřezu a všechny položky zarovnali na střed. Dali jsme mu také modrou barvu pozadí.
Nakonec jsme stylizovali obrazovku „Game Over“ tak, aby měla výšku a šířku 200 a 500 pixelů. Dali jsme mu také purpurovou barvu pozadí a černý okraj. Nastavíme jeho polohu na absolutní, takže je mimo normální tok dokumentu a zarovná se na střed obrazovky. Poté jsme jeho obsah vycentrovali. Jeho zobrazení jsme nastavili na žádné, takže je ve výchozím nastavení skryté.
Dále vytvořte soubor snake.js, který napíšeme v několika následujících částech.
Vytváření globálních proměnných
Dalším krokem v tomto tutoriálu JavaScript Snake je definování některých globálních proměnných, které budeme používat. V souboru snake.js přidejte na začátek následující definice proměnných:
// 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");
Tyto proměnné ukládají odkazy na obrazovku ‚Game Over‘ a prvky plátna. Dále jsme vytvořili kontext, který bude použit pro kreslení na plátno.
Dále přidejte tyto definice proměnných pod první sadu.
// Maze definitions let gridSize = 400; let unitLength = 10;
První definuje velikost mřížky v pixelech. Druhý definuje délku jednotky ve hře. Tato délka jednotky bude použita na několika místech. Použijeme ho například k definování, jak tlusté jsou stěny bludiště, jak tlustý je had, výšku a šířku jídla a přírůstky, ve kterých se had pohybuje.
Dále přidejte následující herní proměnné. Tyto proměnné slouží ke sledování stavu hry.
// Game play variables let snake = []; let foodPosition = { x: 0, y: 0 }; let direction = "right"; let collided = false;
Proměnná had sleduje pozice aktuálně obsazené hadem. Had se skládá z jednotek a každá jednotka zaujímá pozici na plátně. Pozice, kterou každá jednotka zaujímá, je uložena v poli hadů. Pozice bude mít hodnoty x a y jako své souřadnice. První prvek v poli představuje ocas, zatímco poslední představuje hlavu.
Jak se had pohybuje, posuneme prvky na konec pole. Tím se hlava posune dopředu. Z pole také odstraníme první prvek nebo konec, aby délka zůstala stejná.
Proměnná pozice jídla ukládá aktuální polohu jídla pomocí souřadnic x a y. Proměnná směru ukládá směr, kterým se had pohybuje, zatímco kolidovaná proměnná je booleovská proměnná označená jako true, když byla detekována kolize.
Deklarace funkcí
Celá hra je rozdělena do funkcí, což usnadňuje psaní a správu. V této části deklarujeme tyto funkce a jejich účely. V následujících částech budou definovány funkce a probrány jejich algoritmy.
function setUp() {} function doesSnakeOccupyPosition(x, y) {} function checkForCollision() {} function generateFood() {} function move() {} function turn(newDirection) {} function onKeyDown(e) {} function gameLoop() {}
Stručně řečeno, funkce setUp nastaví hru. Funkce checkForCollision zkontroluje, zda se had srazil se zdí nebo do sebe. Funkce dosSnakeOccupyPosition zaujme polohu definovanou souřadnicemi x a y a zkontroluje, zda je nějaká část hadího těla v této poloze. To bude užitečné při hledání volné pozice pro přidání jídla.
Funkce pohybu pohybuje hadem v jakémkoli směru, na který ukazuje, zatímco funkce otáčení tento směr mění. Dále bude funkce onKeyDown naslouchat stisknutí kláves, které se používají ke změně směru. Funkce gameLoop bude pohybovat hadem a kontrolovat kolize.
Definování funkcí
V této části definujeme funkce, které jsme deklarovali dříve. Probereme také, jak jednotlivé funkce fungují. Před kódem bude stručný popis funkce a komentáře pro vysvětlení řádek po řádku v případě potřeby.
funkce nastavení
Funkce nastavení provede 3 věci:
Proto bude kód vypadat takto:
// 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();
děláSnakeOccupyPosition
Tato funkce bere souřadnice x a y jako polohu. Poté zkontroluje, zda taková pozice v těle hada existuje. K nalezení pozice se shodnými souřadnicemi používá metodu hledání pole JavaScriptu.
function doesSnakeOccupyPosition(x, y) { return !!snake.find((position) => { return position.x == x && y == foodPosition.y; }); }
checkForCollision
Tato funkce zkontroluje, zda se had s něčím nesrazil, a nastaví kolidovanou proměnnou na hodnotu true. Začneme tím, že zkontrolujeme kolize s levou a pravou stěnou, horní a spodní stěnou a poté s hadem samotným.
Abychom zkontrolovali kolize s levou a pravou stěnou, zkontrolujeme, zda je souřadnice x hlavy hada větší než velikost mřížky nebo menší než 0. Pro kontrolu kolize s horní a spodní stěnou provedeme stejnou kontrolu, ale s y-ové souřadnice.
Dále zkontrolujeme kolize se samotným hadem; zkontrolujeme, zda nějaká jiná část jeho těla zaujímá pozici, kterou aktuálně zaujímá hlava. Když to vše zkombinujeme, tělo funkce checkForCllision by mělo vypadat takto:
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; } }
generovat jídlo
Funkce createFood využívá smyčku do-while k vyhledání pozice pro umístění jídla, které had neobsazuje. Po nalezení je poloha jídla zaznamenána a nakreslena na plátno. Kód pro funkci createFood by měl vypadat takto:
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); }
hýbat se
Funkce přesunu začíná vytvořením kopie pozice hlavy hada. Poté na základě aktuálního směru zvyšuje nebo snižuje hodnotu souřadnice x nebo y hada. Například zvětšení x souřadnice je ekvivalentní pohybu doprava.
Jakmile to uděláme, posuneme novou pozici headPosition do pole hadů. Na plátno také nakreslíme novou pozici hlavy.
Dále zkontrolujeme, zda had při tomto pohybu snědl potravu. To provedeme kontrolou, zda se headPosition rovná foodPosition. Pokud had snědl potravu, zavoláme funkci createFood.
Pokud had nesnědl jídlo, odstraníme první prvek pole hadů. Tento prvek představuje ocas a jeho odstraněním zůstane délka hada stejná a zároveň vznikne iluze pohybu.
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); } }
otočit se
Poslední hlavní funkcí, kterou se budeme zabývat, je funkce otočení. Tato funkce nabere nový směr a změní směrovou proměnnou na tento nový směr. Had se však může otočit pouze ve směru kolmém k tomu, kterým se právě pohybují.
Proto se had může otočit doleva nebo doprava pouze při pohybu nahoru nebo dolů. Naopak se může otočit nahoru nebo dolů pouze při pohybu doleva nebo doprava. S ohledem na tato omezení funkce otočení vypadá takto:
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
Funkce onKeyDown je obsluha události, která zavolá funkci otočení ve směru odpovídajícím stisknuté šipce. Funkce tedy vypadá takto:
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
Funkce gameLoop bude pravidelně volána, aby hra běžela. Tato funkce zavolá funkci přesunu a funkci checkForCollision. Také kontroluje, zda je kolize pravdivá. Pokud ano, zastaví se intervalový časovač, který používáme ke spuštění hry, a zobrazí se obrazovka ‚konec hry‘. Funkce bude vypadat takto:
function gameLoop() { move(); checkForCollision(); if (collided) { clearInterval(timer); gameOverScreen.style.display = "flex"; } }
Spuštění hry
Chcete-li spustit hru, přidejte následující řádky kódu:
setUp(); document.addEventListener("keydown", onKeyDown); let timer = setInterval(gameLoop, 200);
Nejprve zavoláme funkci setUp. Dále přidáme posluchač události ‚keydown‘. Nakonec použijeme funkci setInterval ke spuštění časovače.
Závěr
V tomto okamžiku by váš soubor JavaScript měl vypadat jako ten na mém GitHub. V případě, že něco nefunguje, znovu to zkontrolujte u repo. Dále se možná budete chtít naučit, jak vytvořit jezdec obrázku v JavaScriptu.