V tomto textu si vysvětlíme, jak naprogramovat hru Had pomocí HTML, CSS a JavaScriptu.
Nebudeme využívat žádné externí knihovny; hra poběží přímo ve webovém prohlížeči. Vytváření této hry je zábavné a zároveň skvělý způsob, jak trénovat vaše dovednosti v řešení problémů.
Struktura projektu
Had je nenáročná hra, kde ovládáte pohyby hada, který se snaží dostat k potravě a zároveň se vyhýbá překážkám. Když had zkonzumuje jídlo, prodlouží se. S postupem hry se had stává delším a hra tak obtížnější.
Had nesmí narazit do okrajů herní plochy ani sám do sebe. S narůstající délkou hada je tedy hra čím dál tím náročnější.
Cílem tohoto tutoriálu o hře Had v JavaScriptu je vytvořit hru, kterou vidíte níže:
Kód hry je k dispozici na mém GitHubu. Živá verze je umístěna na GitHub Pages.
Požadavky
Tento projekt vytvoříme za použití HTML, CSS a JavaScriptu. Budeme psát pouze základní HTML a CSS. Hlavní důraz bude kladen na JavaScript. Proto byste měli mít základní znalosti těchto technologií, abyste mohli s námi postupovat. Pokud ne, doporučujeme se podívat na náš článek o nejlepších místech, kde se naučit JavaScript.
Budete potřebovat také editor kódu pro psaní kódu. Kromě toho budete potřebovat webový prohlížeč, který pravděpodobně už máte, pokud čtete tento článek.
Založení projektu
Nejprve si připravíme soubory projektu. V prázdné složce vytvořte soubor index.html a vložte do něj následující kód.
<!DOCTYPE html> <html lang="cs"> <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>Had</title> </head> <body> <div id="game-over-screen"> <h1>Konec hry</h1> </div> <canvas id="canvas" width="420" height="420"> </canvas> <script src="./snake.js"></script> </body> </html>
Výše uvedený kód vytváří základní obrazovku „Konec hry“. Viditelnost této obrazovky budeme přepínat pomocí JavaScriptu. Dále definuje element plátna, na který budeme kreslit herní plochu, hada a jídlo. Kód také připojuje soubor se styly a JavaScriptový kód.
Následně vytvořte soubor styles.css pro stylování. Vložte do něj tyto 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 elementy a resetujeme jejich okraje. Také jsme nastavili rodinu fontů pro každý element a nastavili velikost elementů na předvídatelnější metodu zvanou border-box. U elementu těla (body) jsme nastavili jeho výšku na celou výšku okna prohlížeče a zarovnali všechny položky na střed. Také jsme mu dali světle modrou barvu pozadí.
Nakonec jsme stylizovali obrazovku „Konec hry“ tak, aby měla výšku 200 a šířku 500 pixelů. Dali jsme jí také fialovou barvu pozadí a černý okraj. Nastavili jsme její pozici na absolutní, takže je mimo běžný tok dokumentu a zarovná se na střed obrazovky. Poté jsme vycentrovali i její obsah. Její zobrazení jsme nastavili na žádné, takže je standardně skryté.
Dále vytvořte soubor snake.js, který napíšeme v následujících sekcích.
Vytvoření globálních proměnných
Dalším krokem v tomto tutoriálu hry Had v JavaScriptu 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:
// Vytvoření odkazů na HTML elementy let gameOverScreen = document.getElementById("game-over-screen"); let canvas = document.getElementById("canvas"); // Vytvoření kontextu, který se bude používat pro kreslení na plátno let ctx = canvas.getContext("2d");
Tyto proměnné uchovávají odkazy na obrazovku ‚Konec hry‘ a na elementy plátna. Dále jsme vytvořili kontext, který bude použit pro kreslení na plátno.
Následně přidejte tyto definice proměnných pod první sadu.
// Definice herní plochy let gridSize = 400; let unitLength = 10;
První proměnná definuje velikost herní plochy v pixelech. Druhá definuje délku jednotky ve hře. Tato délka jednotky bude použita na několika místech. Použijeme ji například k definování, jak silné jsou okraje herní plochy, jak tlustý je had, výška a šířka jídla a kroky, o které se had pohybuje.
Dále přidejte následující herní proměnné. Tyto proměnné slouží pro sledování stavu hry.
// Herní proměnné 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, posunujeme prvky na konec pole. Tím se hlava posune dopředu. Z pole také odstraníme první prvek neboli konec, aby délka zůstala stejná.
Proměnná pozice jídla uchovává aktuální polohu jídla pomocí souřadnic x a y. Proměnná směru uchovává směr, kterým se had pohybuje, zatímco proměnná collided je logická proměnná s hodnotou true, když dojde ke kolizi.
Deklarace funkcí
Celá hra je rozdělena do funkcí, což usnadňuje psaní a správu kódu. V této části deklarujeme tyto funkce a popíšeme jejich účel. V následujících sekcích budou funkce definovány a jejich algoritmy probrány.
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 stěnou nebo sám do sebe. Funkce doesSnakeOccupyPosition vezme pozici definovanou souřadnicemi x a y a zkontroluje, zda je nějaká část hadího těla v této pozici. To bude užitečné při hledání volné pozice pro přidání jídla.
Funkce move pohybuje hadem v libovolném směru, kterým se pohybuje, zatímco funkce turn tento směr mění. Dále bude funkce onKeyDown poslouchat stisky kláves, které se používají ke změně směru. Funkce gameLoop bude pohybovat hadem a kontrolovat kolize.
Definice funkcí
V této sekci definujeme funkce, které jsme deklarovali dříve. Také probereme, jak jednotlivé funkce fungují. Před kódem bude krátký popis funkce a komentáře pro vysvětlení jednotlivých řádků kódu v případě potřeby.
funkce setUp
Funkce setUp provede 3 věci:
Kód tedy bude vypadat takto:
// Kreslení okrajů na plátno // Plátno bude mít velikost herní plochy plus tloušťka dvou okrajů canvasSideLength = gridSize + unitLength * 2; // Nakreslíme černý čtverec, který pokrývá celé plátno ctx.fillRect(0, 0, canvasSideLength, canvasSideLength); // Vymažeme střed černé barvy pro vytvoření herního prostoru // Tím vznikne černý obrys, který představuje okraj ctx.clearRect(unitLength, unitLength, gridSize, gridSize); // Dále uložíme počáteční pozice hlavy a ocasu hada // Počáteční délka hada bude 60px neboli 6 jednotek // Hlava hada bude 30 px neboli 3 jednotky před středem const headPosition = Math.floor(gridSize / 2) + 30; // Ocas hada bude 30 px neboli 3 jednotky za středem const tailPosition = Math.floor(gridSize / 2) - 30; // Smyčka od ocasu k hlavě v krocích délky jednotky for (let i = tailPosition; i <= headPosition; i += unitLength) { // Uložíme pozici hadího těla a nakreslíme jej na plátno snake.push({ x: i, y: Math.floor(gridSize / 2) }); // Nakreslíme obdélník v této pozici o velikosti unitLength * unitLength ctx.fillRect(x, y, unitLength, unitLength); } // Vygenerujeme jídlo generateFood();
doesSnakeOccupyPosition
Tato funkce bere jako vstup souřadnice x a y pozice. Potom zkontroluje, zda taková pozice existuje v těle hada. K nalezení pozice se shodnými souřadnicemi používá metodu find pole v 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í proměnnou collided na true. Nejprve budeme kontrolovat kolize s levými a pravými stěnami, horní a dolní stěnou a poté se samotným hadem.
Pro kontrolu kolizí s levými a pravými stěnami zkontrolujeme, zda je souřadnice x hlavy hada větší než velikost herní plochy nebo menší než 0. Pro kontrolu kolize s horní a dolní stěnou provedeme stejnou kontrolu, ale s y-ovou souřadnicí.
Dále budeme kontrolovat 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]; // Kontrola kolizí s levými a pravými stěnami if (headPosition.x < 0 || headPosition.x >= gridSize - 1) { collided = true; } // Kontrola kolizí s horními a dolními stěnami if (headPosition.y < 0 || headPosition.y >= gridSize - 1) { collided = true; } // Kontrola kolizí se samotným hadem const body = snake.slice(0, -2); if ( body.find( (position) => position.x == headPosition.x && position.y == headPosition.y ) ) { collided = true; } }
generateFood
Funkce generateFood používá cyklus do-while k hledání pozice pro umístění jídla, kterou had neobsazuje. Jakmile je pozice nalezena, uloží se a nakreslí na plátno. Kód funkce 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); }
move
Funkce move začíná vytvořením kopie pozice hlavy hada. Poté, na základě aktuálního směru, zvýší nebo sníží hodnotu souřadnice x nebo y hada. Například zvýšení souřadnice x odpovídá 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 během tohoto pohybu snědl jídlo. To provedeme kontrolou, zda se headPosition rovná foodPosition. Pokud had snědl jídlo, zavoláme funkci createFood.
Pokud had jídlo nesnědl, odstraníme první prvek pole hadů. Tento prvek představuje ocas a jeho odstraněním se zachová délka hada a zároveň se vytvoří iluze pohybu.
function move() { // Vytvoříme kopii objektu reprezentujícího pozici hlavy 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; } // Přidáme novou pozici hlavy do pole snake.push(headPosition); ctx.fillRect(headPosition.x, headPosition.y, unitLength, unitLength); // Zkontrolujeme, zda had žere const isEating = foodPosition.x == headPosition.x && foodPosition.y == headPosition.y; if (isEating) { // Vygenerujeme novou pozici jídla generateFood(); } else { // Odstraníme ocas, pokud had nežere tailPosition = snake.shift(); // Odstraníme ocas z herní plochy ctx.clearRect(tailPosition.x, tailPosition.y, unitLength, unitLength); } }
turn
Poslední hlavní funkcí, kterou si rozebereme, je funkce turn. Tato funkce nabere nový směr a změní proměnnou směru 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ě pohybuje.
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 turn vypadá takto:
function turn(newDirection) { switch (newDirection) { case "left": case "right": // Otočení doleva nebo doprava je povoleno pouze v případě, že se had původně pohyboval nahoru nebo dolů if (direction == "up" || direction == "down") { direction = newDirection; } break; case "up": case "down": // Otočení nahoru nebo dolů je povoleno pouze v případě, že se had původně pohyboval doleva nebo doprava if (direction == "left" || direction == "right") { direction = newDirection; } break; } }
onKeyDown
Funkce onKeyDown je obsluha události, která zavolá funkci turn 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 volána pravidelně, aby hra běžela. Tato funkce volá funkci move a funkci checkForCollision. Také kontroluje, zda je kolize true. Pokud ano, zastaví 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
Pro spuštění hry 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če 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 GitHubu. Pokud něco nefunguje, zkontrolujte kód s repository. Dále se můžete naučit, jak vytvořit posuvník obrázků v JavaScriptu.