W ostatnim poście napisałem, że muszę znaleźć sposób na to aby odłączyć obiekt bohatera od stanu gry reprezentującego poziom. Myślałem trochę nad tym problemem i postanowiłem ugryźć go z trochę innej strony.
Zamiast odłączać postać gracza od stanu, odłączę dane samego poziomu. Chcę w ten sposób w jednym stanie wczytywać aktualne etapy gry. Na przykład, gdy gracz ukończy jeden etap, zamiast zmieniać stan, gra wczyta nowe dane do aktualnego.
Gdy poszperałem w internecie, szybko okazało się, że mój pomysł wcale nie jest taki oryginalny 🙂 Wielu ludzi wpadło już na takie rozwiązanie przede mną. Mało tego, jest nawet narzędzie, które pozwala w łatwy sposób generować takie dane poziomu. I żeby tego było mało, wygenerowane dane są w pełni kompatybilne z phaserem!
Narzędzie, o którym piszę to Tiled. Jest to graficzne środowisko, w którym można z ogromną łatwością przygotować dwuwymiarową mapę na podstawie dowolnie dobranego zestawu kafelków.
Mapę wygenerowaną w Tiled zapisać można w formacie JSON i taki format można z łatwością wykorzystać w projekcie opartym na Phaser. Nie będę tu opisywał dokładnie jak korzystać z tego narzędzia. Według mnie jest dość intuicyjne, ale jeżeli ktoś chciałbym abym poświęcił Tiled osobny post, niech da znać w komentarzu. Postaram się coś przygotować 😉
Teraz skupię się na zmianach w samej grze. Wygenerowałem testową mapę i dodałem ją do mojego projektu. Efekt można zobaczyć klikając na obrazek powyżej.
Pierwsze co musiałem zrobić to dodać utworzoną mapę do projektu. Robię to oczywiście w stanie init. Wystarczy dodać te dwie proste linijki kodu:
1 2 |
game.load.image('tileset', 'GFX/tileset.png'); game.load.tilemap('forest', 'forest.json', null, Phaser.Tilemap.TILED_JSON); |
Pierwsza linijka to standardowe dołączenie obrazka do projektu. Jest to zestaw kafelków, który wykorzystywany jest w nowej mapie. W moim wypadku nie jest on specjalnie skomplikowany, jest to 9 kafelków zawierających drzewka (wygląda tak). Dzieli się on na dokładnie dziewięć kwadratów o bokach równych 32 piksele. Każdy z tych kwadratów, będzie kafelkiem, który mogę dowolnie rozmieścić na mapie.
Druga linijka kodu, dodaje do projektu dane zawierające szczegóły mapy gry. Znajdują się one w pliku forest.json. Warto przyjrzeć się treści tego pliku. Nie będę tu wklejał całości, można ją obejrzeć pod tym linkiem. Znajdują się tam informacje o pliku z kafelkami, wielkości mapy czy wielkości kafelków.
Ważną właściwością jest pole layers. Zawiera ono tablicę z warstwami obiektów mapy. Każdą z tych warstw można zaprogramować inaczej. W moim przypadku jest tylko jedna warstwa, zawiera ona drzewa, które będą blokowały graczowi przejście. Nic jednak stoi na przeszkodzie aby dodać do mapy więcej warstw. Mogę one zawierać obiekty, które mają inne działanie. Na razie skupię się jednak na moim, prostym przykładzie.
Gdy dodatkowe pliki są już załadowane, czas skorzystać z nich w stanie gry. Najpierw w metodzie create muszę stworzyć nową mapę. Oto odpowiedni kod:
1 2 3 |
this.map = game.add.tilemap('forest'); this.map.addTilesetImage('tileset'); this.map.setCollisionBetween(1,9); |
Do nowego obiektu map przypisuję tilemap, który odnajduję po kluczu, ustalonym w stanie inicjalizacji. Następnie do obiektu mapy dodaję odpowiedni zestaw kafelków. Na koniec ustawiam kolizje na wybranych z zestawu kafelkach. Metoda obiektu mapy setCollisionBetween przyjmuje dwa argumenty, są to numery kafelków z zestawu. Te kafelki i wszystkie pomiędzy nimi będą generowały kolizje. W moim przypadku będą to wszystkie kafelki z zestawu. Wszystkie te metody są dostępne wewnątrz phasera i można je wywoływać na obiektach, stworzonych na podstawie JSONa z tiled 🙂 Mam nadzieję, że jest to w miarę jasne.
Stworzenie mapy to nie wszystko. Póki co gra wie jedynie jak wygląda mapa i które kafelki mają reagować na kolizje. Kolejny krok to stworzenie odpowiedniej warstwy i zdefiniowanie zachowań w razie kolizji. Tak jak wspomniałem wcześniej, moja mapa zawiera tylko jedną warstwę. Dodaję ją do gry w taki sposób:
1 2 |
this.forest = this.map.createLayer('Tile Layer 1'); this.forest.resizeWorld(); |
W pierwszej linijce tworzę nowy obiekt warstwy mapy i przypisuję go do zmiennej forest. Odpowiednią warstwę znajduję po kluczu, który można zobaczyć w pliku JSON, w miejscu na które wcześniej zwracałem uwagę.
Gdy warstwa jest już gotowa wywołuję jej metodę resizeWorld. Ta potężna metoda powoduje, że świat gry, nie jest już ograniczony krawędziami płótna gry. Teraz jest on tak duży jak wczytana warstwa, W moim wypadku ma ona taką samą szerokość jak element canvas, ale jest 3 razy dłuższa.
Dlatego, na koniec metody create, muszę dodać jeszcze jedną linijkę kodu:
1 |
game.camera.follow(this.hero); |
Metoda follow pola camera obiektu gry powoduje, że kamera podążać będzie za obiektem przekazanym w argumencie. Co to znaczy, że kamera będzie za czymś podążać? Po prostu płótno gry będzie przesuwać się po świecie gry tak aby postać gracza była zawsze na środku. A wszystko to osiągnięte dwoma linijkami kodu 🙂 .
Już prawie gotowe. Jedyne co mi pozostało to zdefiniowanie zachowania nowych kafelków w grze. Muszę zadeklarować to w metodzie update stanu gry. Wygląda to tak:
1 |
game.physics.arcade.collide(this.hero, this.forest); |
I znów tylko jedna linijka kodu 🙂 Dzięki niej, gracz będzie blokowany przez obiekty z warstwy forest. Które dokładnie obiekty? Te przedstawione przez wskazane wcześniej (podczas tworzenia mapy) kafelki.
I tak to wygląda na tę chwilę. Póki co sprawa wygląda obiecująco, co z tego wyjdzie zobaczymy 🙂 Trzymajcie kciuki.
Jeżeli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem 🙂 .
Jest jakiś problem z atakowaniem na skos. Zwykle jak jestem ustawiony w kierunki NE lub NW to zazwyczaj atak w ogóle nie działa. Zdarza się, że będąc skierowanym w kierunku NE zaczyna sam się obracać i atakować w kierunku NW. Czasami atakuje normalnie (zgodnie z obranym kierunkiem), ale tylko czasami