salary 1319059 ml

Jak stworzyć grę internetową w Phaser (1/2)

Wstęp

Witam w kolejnym artykule z serii poświęconej tworzeniu gier internetowych z użyciem frameworku Phaser. W poprzedniej części wyjaśniłem, czym jest Phaser, pokazałem jak przygotować środowisko do pracy z nim oraz uruchomiliśmy pierwszą grę, która po prostu wyświetlała obrazek. W tym artykule pokażę jak stworzyć prawdziwą grę w oparciu o framework Phaser. Będzie ona dość prosta i polegać będzie na chodzeniu małym kosmitą po planszy i zbieraniu spadających gwiazdek.

Obiekt gry

Po tym krótkim wstępie, przejdźmy wreszcie, do czegoś, na co na pewno czekasz, czyli do tworzenia gry. Jednak nim zaczniemy pisać kod, pobierz ten plik. Znajduje się w nim plik index.html oraz folder assets zawierający obrazki, z których będziemy korzystać tworząc naszą grę. Całą zawartość archiwum wstaw do folderu w którym znajdują się pliki hostowane przez Twój serwer, a następnie uruchom go i otwórz w przeglądarce odpowiedni adres. Jeśli nie wiesz jak to zrobić, sprawdź poprzedni artykuł. Na razie przeglądarka wyświetli po prostu białe tło, musimy przecież napisać naszą grę, żeby zostało wyświetlone coś konkretnego.

Cały kod gry będziemy dodawać do bloku script, w miejsce gdzie znajduje się komentarz // Tu dodamy kod. Na początek wstaw tam poniższy fragment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var game = new  Phaser.Game(800, 600, Phaser.AUTO, '', { 
  preload: preload, create: create, update: update 
});

function preload() {
};

function create() {
};

function update() {
}; 

W pierwszej linii tworzymy nowy obiekt game z Phaser.Game. Obiekt ten odpowiada za uruchomienie gry, stworzenie różnych podsystemów, oraz uruchomienie logiki i tak zwanej pętli, którą poznasz później.

Jak widzisz do konstruktora Phaser.Game przekazujemy dodatkowe parametry. 800 i 600 to szerokość i wysokość naszego okienka wyrażona w pikselach. Kolejny parametr odpowiada za wybranie systemu, który będzie renderował grafikę w naszej grze. Parametrem tym może być Phaser.WEBGL, Phaser.CANVAS lub Phaser.AUTO. Dla nas najlepszy jest Phaser.AUTO, który automatycznie dokona wyboru pomiędzy WebGL a Canvas. Wybór ten zależy od urządzenia na którym zostanie uruchomiona gra, dla nowszych przeglądarek i urządzeń, zostanie wybrany bardziej zaawansowany WebGL, natomiast dla urządzeń nie obsługujących tego standardu Canvas.

Kolejny parametr to selektor wskazujący na element DOM do którego ma zostać wstawiona nasza gra. Jeśli przekażemy pusty ciąg znaków tak jak w naszym przypadku, do gra zostanie bezpośrednio dodana do zawartości body.

Ostatni parametr to obiekt zawierający referencje do funkcji odpowiedzialnych za obsługę poszczególnych stanów gry. Przyjrzymy się im dokładnie w dalszej części tego artykułu. Parametr ten jest opcjonalny i Phaser umożliwia nam zastąpienie go grupą oddzielnych obiektów, które będą reprezentowały dane stany. Takie podejście umożliwia lepszy podział kodu na pliki i obiekty, co jest zalecane przy tworzeniu większych projektów. Dla naszej prostej gry obsługa stanów przez zwykłe funkcje jest wystarczająca i umożliwi nam szybsze stworzenie gry.

Ładujemy obrazki

Zajmijmy się teraz pierwszą z funkcji stanu, która na poprzednim przykładzie była jeszcze pusta, czyli preload. Funkcja ta odpowiada za załadowanie zewnętrznych assetów do gry i jest odpalana na początku, podczas uruchamiania gry w przeglądarce. W jej ciele możemy na przykład załadować obrazki, dźwięki oraz pliki xml, json i txt.

Dotychczas pusta funkcja preload powinna teraz mieć następującą zawartość:

1
2
3
4
5
6
function preload() {
  game.load.image('ground', 'assets/platform.png');
  game.load.image('sky', 'assets/sky.png');
  game.load.image('star', 'assets/star.png');
  game.load.spritesheet('dude', 'assets/dude.png', 32, 48);
};

W pierwszych 3 liniach ładujemy zwykłe obrazki metodą game.load.image, zwróć uwagę na to, że game to nasz obiekt gry. Pierwszy parametr metody to klucz, czyli w tym przypadku unikalna nazwa danego obrazka, którą będziemy się później posługiwać odwołując się do niego. Drugi parametr, to jak się pewnie domyślasz ścieżka do pliki, który ma być załadowany. Warto dodać, że zamiast ścieżki możesz też przekazać adres URL danego obrazka.

W ostatniej linii funkcji preload znajduje się wywołanie metody game.load.spritesheet. Zobaczmy jak wygląda obrazek dude.png, który ładuje ta metoda.

dude

Jak widzisz zawiera on klatki z animacji danego bohatera. Taki obrazek jest właśnie nazywany spritesheet, czyli tłumacząc na język polski arkusz spiritów lub arkusz duszków, gdzie duszek oznacza właśnie danego bohatera ze świata gry.

Wiesz już czym jest jest spritesheet wróćmy więc do wywołania metody game.load.spritesheet. Jej pierwsze dwa parametry to jak się pewnie domyślasz klucz oraz ścieżka do pliku. Jak wiesz spritesheet jest arkuszem, który zawiera kilka klatek z danej postaci, musimy więc jakoś przekazać Phaserowi jaki wymiar ma dana klatka. Ten wymiar to właśnie trzeci i czwarty parametr, który w naszym przypadku wynosi 32x48px.

Dodajemy tło

Napisaliśmy już trochę kodu, jednak jeśli sprawdzisz co na tym etapie wyświetla przeglądarka, to zobaczysz tylko czarne tło. Aby to zmienić zajmiemy się teraz funkcją create, która do tej pory była pusta. Funkcja ta jest uruchamiana tylko raz, od razu po załadowaniu assetów przez preload. To właśnie w ciele create będziemy tworzyć świat gry.

Na początek dodamy tło naszej gry. Aby to zrobić wstaw poniższą linię do ciała funkcji create

1
game.add.sprite(0, 0, 'sky');

Jeśli teraz sprawdzisz w przeglądarce jak wygląda gra, powinieneś zobaczyć obrazek sky.png, czyli niebieski niebo. Jak widzisz metoda game.add.sprite służy do dodawania obiektów do świata gry. Przyjmuje ona 3 parametry, pierwsze dwa to współrzędne X i Y, określające miejsce umieszczenia obrazka, a trzeci parametr, to klucz, czyli nazwa jaką nadaliśmy danemu obiektowi w funkcji preload. Jeśli chcesz się przekonać jak działają współrzędne, zmień pierwsze dwa parametry, np. na 50 i 100.

Warto w tym miejscu jeszcze dodać, że świat gry nie ma sztywnie określonej powierzchni, potrafi się praktycznie dowolnie rozciągać. Wymiar 800x600, który określiliśmy tworząc obiekt game jest tylko wymiarem okienka, w którym widzimy grę. Jeśli świat będzie większy, to używając odpowiednich funkcji możemy się po nim przemieszczać.

Tworzymy świat gry

Dodaliśmy już tło, jednak przed nami jeszcze sporo pracy, przejdźmy więc do stworzenia reszty świata naszej gry.

Zastąp dotychczasową funkcję create poniższym kodem

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var platforms;

function create() {
  game.physics.startSystem(Phaser.Physics.ARCADE);

  game.add.sprite(0, 0, 'sky');

  platforms = game.add.group();
  platforms.enableBody = true;

  var ground = platforms.create(0, game.world.height - 32, 'ground');
  ground.body.immovable = true;
  ground = platforms.create(400, game.world.height - 32, 'ground');
  ground.body.immovable = true;

  var platform1 = platforms.create(400, 400, 'ground');
  platform1.body.immovable = true;
  var platform2 = platforms.create(-150, 250, 'ground');
  platform2.body.immovable = true;
};

Przyjrzyjmy się teraz dokładnie temu co się w nim znajduje. W pierwszej linii tworzymy na razie pustą zmienną platforms, w zmiennej tej będziemy przechowywać grupę obiektów reprezentujących platformy w naszej grze. Tworzymy ją poza funkcją create, po to, aby mieć do niej dostęp również w pozostałych miejscach kodu naszej gry, właściwie platforms jest w tym przypadku zmienną globalną.

Dalej w 4 linii znajduje się włączenie systemu fizyki ARCADE w naszej grze. System fizyki jest zbiorem mechanizmów odpowiedzialnych za odwzorowanie praw fizyki oddziałujących na obiekty znajdujące się w grze. Następnie w linii 8 widzisz stworzenie grupy platforms. Kolejna instrukcja platforms.enableBody = true odpowiada za włączenie praw fizyki dla obiektów z grupy platforms. W dalszych liniach widzisz dodanie obiektów reprezentujących grunt oraz dwie platformy. Ustawienie właściwości body.immovable na true powoduje, to że dany element nie będzie się przemieszczał, gdy system fizyki wykryje kolizję pomiędzy obiektami.

Jeśli zastanawiasz się dlaczego dwa razy dodałem obiekt reprezentujący grunt, to już wyjaśniam. Nasze okienko gry jest dwa razy szersze niż obrazek platform.png, dlatego aby pokryć gruntem całą dolną część okienka dodałem obiekt dwa razy.

Jeśli teraz sprawdzisz jak wygląda nasza gra powinieneś już zobaczyć poniższy widok:

SCREEN_1

Dodajemy bohatera

Dodajmy teraz postać, którą będziemy poruszać grając w naszą grę. Dodaj poniższy kod na koniec ciała funkcji create

1
2
3
4
5
6
7
8
player = game.add.sprite(32, game.world.height - 150, 'dude');
game.physics.arcade.enable(player);

player.body.gravity.y = 350;
player.body.collideWorldBounds = true;

player.animations.add('left', [0, 1, 2, 3], 10, true);
player.animations.add('right', [5, 6, 7, 8], 10, true);

Przeanalizujmy teraz dokładnie powyższy kod. W pierwszej linii dodajemy nowy obiekt i przypisujemy go do globalnej zmiennej player. Następnie włączamy wykrywanie dodanego obiektu przez system fizyki.

Przypisanie odpowiedniej liczby do gravity.y powoduje dodanie pionowej grawitacji. W naszym przykładzie przypisaliśmy temu atrybutowi liczbę 350. Nie jest to jakaś wielkość fizyczna z odpowiednimi jednostkami, po prostu im większa będzie ta liczba, tym szybciej obiekt będzie spadał w dół. Przypisanie atrybutowi collideWorldBounds wartości true spowoduje, że nasz bohater nie będzie mógł wyjść poza krawędź świata gry.

W ostatnich dwóch liniach znajduje się dodanie animacji, które będą uruchamiane podczas przemieszczania się bohatera. Aby dokładnie je wyjaśnić przypomnijmy, że obiekt posiadający klucz dude jest spritesheet’em, czyli arkuszem zawierającym kilka wersji danego obiektu. Wygląda on następująco.

dude

Jak widzisz znajduje się na nim 9 wersji bohatera, każda z nich to osobna klatka animacji. Pierwsze cztery odpowiadają za animację ruchu w lewo, a ostatnie cztery za ruch w prawo.

Wróćmy teraz jednak do dodawania animacji. Metoda player.animations.add przyjmuje cztery parametry, pierwszy to nazwa animacji, drugi to tablica zawierająca numery klatek, które mają zostać wyświetlone podczas danej animacji, trzeci argument to szybkość wyrażona w liczbie klatek na sekundę, natomiast ostatni parametr określa, czy animacja ma zostać zapętlona.

Jeśli teraz uruchomisz grę w przeglądarce powinieneś zobaczyć:

SCREEN_2

Jak widzisz w grze znajduje się nasz bohater, ale po uruchomieniu spada on w dół dopóki nie dotknie dolnej krawędzi okienka. Dodamy teraz kod, który spowoduje, że bohater zatrzyma się na glebie i nie spadnie poniżej.

Aby to zrobić musimy zająć się funkcją update, która do tej pory pozostała pusta. Funkcja ta jest uruchamiana w pętli, jej wywołanie odbywa się przed wyrenderowaniem każdej klatki. Żeby nasz bohater nie spadł na obszar zajmowany przez glebę musimy wykryć kolizję pomiędzy tymi dwoma obiektami. Aby to zrobić zastąp pustą funkcję update poniższym kodem:

1
2
3
function update() {
  game.physics.arcade.collide(player, platforms);
};

Metoda game.physics.arcade.collide przyjmuje jako parametr dwa obiekty lub grupy obiektów, następnie pilnuje aby dwa z nich na siebie nie nachodziły. Zauważ, że przekazaliśmy do tej metody obiekt gracza player oraz grupę platforms. Phaser wie, że obiekt gleby należy do grupy platforms i dlatego nie dopuszcza do kolizji pomiędzy nim a graczem.

Po dokonanych zmianach, uruchamiając grę w przeglądarce zobaczysz:

SCREEN_3

Podsumowanie

W tym artykule pokazałem jak zacząć tworzyć swoją własną grę. Już teraz możesz przejść do kolejnej części, w której dokończymy nasze dzieło.

Ikony użyte podczas tworzenia gry oraz pomysł na grę pochodzą z oficjalnego tutoriala Phaser udostępnionego na licencji MIT na stronie https://github.com/photonstorm/phaser

Kurs wideo

Jeśli spodobał Ci się ten artykuł i chciałbyś dokładniej poznać narzędzie Phaser, serdecznie zapraszamy do naszego nowego kursu wideo Tworzenie gier w JavaScript. Tworzymy w nim klona popularnej gry Pacman, przy pomocy frameworku Phaser. Jeśli jeszcze nie masz konta, zarejestruj się za darmo.