Napiszmy grę w JavaScript: Daj mi płótno, a namaluję świat

Cześć! To jest fragment książki JavaScript od podstaw, która ma pomóc w nauce programowania od zera.

W tym rozdziale tworzymy płótno (canvas) i rysujemy na nim plansze, piłeczki i liczniki punktów.

Nadszedł czas, aby wykorzystać zdobytą wiedzę w praktyce. JavaScript to bardzo uniwersalny język, więc może być wykorzystany do wielu rzeczy1. Chciałem jednak, by nasz przykład był prosty i wymagał możliwie jak najmniej wiedzy z dziedziny innych języków programowania, a dodatkowo był zabawny. Postanowiłem zatem, że napiszemy razem grę. Niemożliwe? Wręcz przeciwnie, stworzymy odpowiednik gry Pong z 1972 roku. Zasady są proste: dwóch graczy odbija piłeczkę, gdy któryś ją przepuści, piłeczka wraca na środek, a jego przeciwnik zdobywa punkt.

Tak będzie wyglądała nasza gra.

Co ciekawe, posiadasz już większość umiejętności niezbędnych by taką grę napisać. Brakuje nam tylko wiedzy jak wyświetlać elementy widoku oraz jak reagować na naciśnięcie przycisków przez graczy. Pierwszym tematem zajmiemy się w tym rozdziale, a drugim w kolejnym, zatytułowanym: Przejmujemy sterowanie. Reszta to właściwie praktyczne zastosowanie tego, czego nauczyliśmy się w poprzednich działach.

Płótno, czyli HTML canvas

Dla uproszczenia grafika w grach sprowadza się do tego, że na obszarze, na którym jest wyświetlana, rysowane są kolejne obrazki. Podobnie jak w filmach, gdzie sceny są szybko zmieniającymi się kolejnymi klatkami — takie "ruchome obrazki". By odnieść wrażenie postaci w ruchu wyświetlane są kolejno obrazki na następujących po sobie etapach tego ruchu. Do tego dodajemy przesunięcie — jeśli postać porusza się w prawo, to każdy kolejny obrazek powinien być narysowany nieco bardziej na prawo od poprzedniego. Należy też zetrzeć poprzedni obrazek, tak aby nie został po nim ślad. I tym samym odpowiednia ilość szybko wyświetlanych obrazków daje złudzenie płynnego poruszania się postaci.

Bohater gry klatka po klatce.

W naszym przypadku nie zajmiemy się animowaniem postaci, ale zamiast tego wyświetlimy paletki oraz piłeczkę. Gdy nasze przedmioty będą w ruchu, wtedy narysujemy je w nieco innym miejscu.

Zacznijmy od miejsca, które umożliwi nam wyświetlanie poszczególnych elementów. W tym celu wykorzystamy Canvas (po angielsku płótno) z HTML2. Stwórz dokument z końcówką .html o następującej treści:

<!DOCTYPE html>
<html>
<body>

<canvas id="gameCanvas" width="800" height="500"
style="border:1px solid" />

<script>
// Twój kod JavaScript tutaj
</script>

</body>
</html>

To, co dodaliśmy względem pierwotnego kodu przedstawionego w rozdziale Pierwszy program to element typu canvas. Nadaliśmy mu id gameCanvas, szerokość width równą 800 pikseli, oraz wysokość height równą 500 pikseli. Daliśmy mu też ramkę poprzez ustawienie style na wartość border:1px solid3. Na ten moment powyższy kod w zupełności nam wystarczy.

W miejscu // Twój kod JavaScript tutaj będzie znajdował się nasz kod JavaScript. Tak więc następne prezentowane fragmenty kodu powinny zostać umieszczone dokładnie tam.

Pierwszy rysunek

Aby narysować coś na naszym płótnie, potrzebujemy się do niego odnieść. Aby rozpocząć rysunek, najpierw należy odnieść się do samego elementu canvas. Możemy to zrobić po id, dzięki funkcji document.getElementById. Przyjmuje ona id jako argument, a zwraca referencję (odniesienie) do elementu widoku. Używając jej, możemy dany element zmodyfikować albo sprawdzić jego właściwości.

const canvas = document.getElementById("gameCanvas");

Canvas pozwala na różne tryby rysowania, takie jak 2D czy 3D. Określamy je przy pobieraniu obiektu zwanego kontekstem. Zawiera on charakterystyczne metody dla wybranego trybu. Na przykład dla 2D, użyjemy canvas.getContext("2d"), po czym zapiszemy go do zmiennej o nazwie ctx.

const ctx = canvas.getContext("2d");

Przy użyciu tej zmiennej jesteśmy już w stanie rysować potrzebne nam elementy, na przykład:

  • prostokąt, przy użyciu funkcji fillRect;
  • tekst, przy użyciu funkcji fillText;
  • piłeczkę, przy użyciu arc oraz fill.

Wykorzystajmy je kolejno do narysowania paletki, punktacji oraz piłeczki.

Paletka

Najprostszym z potrzebnych nam elementów do namalowania jest paletka. Jest to nic innego jak cienki prostokąt, który możemy namalować przy użyciu metody fillRect. Przyjmuje ona 4 argumenty:

  • współrzędne xy lewego górnego rogu prostokąta w pikselach,
  • szerokość i wysokość prostokąta w pikselach.

Współrzędne na płótnie są liczone od lewego górnego rogu obrazka. x rośnie, gdy przesuwamy się w prawo, a y jak przesuwamy się w dół.

Siatka współrzędnych na płótnie.

Zmieniając więc pierwsze dwie wartości, zmieniamy pozycję prostokąta, a przez pozostałe dwie jego wymiary.

ctx.fillRect(100, 100, 150, 100);
ctx.fillRect(400, 200, 200, 100);

Przykłady narysowanych prostokątów — dla każdego podane są współrzędne lewego górnego rogu, wysokość oraz szerokość.

Nasza paletka będzie miała standardowo: szerokość 20 i wysokość 100 pikseli. Poniżej przedstawiam cały kod wystarczający, by ją zobaczyć.

<!DOCTYPE html>
<html>
<body>

<canvas id="gameCanvas" width="800" height="500"
style="border:1px solid" />

<script>
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
ctx.fillRect(10, 10, 20, 100);
</script>

</body>
</html>

Skoro będziemy potrzebowali wielokrotnie rysować takie paletki, wydzielmy funkcję, która się tym zajmie. Nazwijmy ją drawPaddle. Paletki mogą się przemieszczać w górę i w dół, więc funkcja musi przyjmować jako argument wartość y, informującą o aktualnym położeniu obiektu. Potrzebujemy dwóch paletek, a więc będą się znajdowały pod różnymi wartościami x, tą zmienną również przekażemy jako argument. Wymiary paletki (wysokość i szerokość) pozostaną niezmienione.

function drawPaddle(x, y) {
  ctx.fillRect(x, y, 20, 100);
}

drawPaddle(770, 100);
drawPaddle(10, 300);

Aktualny stan w CodePen.

Tekst

Następną rzeczą, której potrzebujemy, jest wyświetlenie tekstu. Przy jego pomocy pokażemy liczbę punktów każdego z graczy. Możemy go wyświetlić przy pomocy funkcji fillText o 3 argumentach:

  • tekście do wyświetlenia,
  • współrzędnych x i y lewego górnego rogu obszaru, na którym wypisywany jest tekst.

Przykładowo moglibyśmy wyświetlić tekst następująco:

ctx.fillText("Hello World", 10, 50);

Tak wyświetlony tekst będzie domyślnie bardzo mały.

Aby ustawić wielkość tekstu oraz czcionkę, powinniśmy ustawić właściwość font w obiekcie ctx. Wystarczy zrobić to raz, na początku działania programu. Dopóki nie zmienimy tej właściwości, ustawienie to zapisze się jako domyślne. Ustawmy tą wartość na "30px Arial" (Arial to nazwa czcionki, a 30px to wielkość 30 pikseli).

ctx.font = "30px Arial";

Po wydzieleniu funkcja do wyświetlania tekstu powinna wyglądać następująco:

ctx.font = "30px Arial";

function drawText(text, x, y) {
  ctx.fillText(text, x, y);
}

// Przykładowe użycie
drawText("3", 300, 50);
drawText("6", 500, 50);

Aktualny kod w HTML REPL.

Piłeczka

Wyświetlenie koła jest największym wyzwaniem, ponieważ nie ma do tego dedykowanej funkcji. Aby nie wnikać w zawiłości tej technologii, uwierz mi na słowo, że nasza funkcja do wyświetlania koła będzie wyglądała następująco:

function drawCircle(x, y, r) {
  ctx.beginPath();
  ctx.arc(x, y, r, 0, Math.PI * 2, true);
  ctx.closePath();
  ctx.fill();
}

Gdzie:

  • xy to współrzędne środka koła
  • r to promień koła, czyli odległość od środka do boku, determinująca wielkość naszej piłeczki.

Niestety w programowaniu nawet doświadczeni programiści często nie wiedzą jak coś zrobić i szukają gotowych rozwiązań w internecie. Za dobrą praktykę przyjmuje się zrozumienie znalezionego rozwiązania przed jego zastosowaniem. Poniżej przedstawiłem gotowe rozwiązanie, a Twoim zadaniem jest poszukanie w internecie, jak ono działa4.

Przykładowe koła w CodePen.

Czyszczenie płótna

Brakuje nam już tylko jednej funkcji do rysowania na płótnie. Po narysowaniu elementów w jednym miejscu, musimy je zmazać zanim będziemy mogli wyświetlić je gdzie indziej. Bez tego nasze płótno wypełni się nieaktualnymi rysunkami. Do wyczyszczenia płótna użyjemy funkcji clearRect, która podobnie do funkcji fillRect przyjmuje:

  • współrzędne lewego górnego punktu powierzchni, dla której chcemy wyczyścić płótno,
  • wysokość i szerokość tej powierzchni.

Aby wyczyścić całe płótno, potrzebujemy zacząć od punktu (0, 0) i użyć szerokości i wysokości płótna. Te natomiast możemy pobrać z referencji tego elementu przy użyciu odpowiednio canvas.widthcanvas.height.

function clearCanvas() {
  context.clearRect(0, 0, canvas.width, canvas.height);
}

Kod, z którym powinieneś skończyć po tym rozdziale, znajdziesz pod linkiem:

https://github.com/MarcinMoskala/pong/blob/master/1_canvas.html

Zamiast linku, możesz użyć tego kodu QR.

Aktualny stan gry w CodePen.

Po tej sekcji powinniśmy mieć już płótno, na którym możemy malować oraz funkcje do rysowania trzech koniecznych dla nas elementów. Brzmi jak dobry początek.

1:

Patrz rozdział Co jeszcze można robić w JavaScript.

2:

HTML to język opisujący jak ma wyglądać strona. Jest to jednak temat na osobną książkę, więc po prostu przeprowadzę Cię przez ten jeden krok, który w tym języku musimy zrobić.

3:

Aby nauczyć się HTML, polecam szukać w Google pod hasłem "best free html course".

4:

Wpisz w Google "js canvas arc".