Gra w Pythonie – PyGame – Przeszkody – #5
W poprzednim odcinku nasz dinozaur już skakał, a jego tło poruszało się – dokładnie tak, jak w oryginale. Nadszedł czas na implementację najfajniejszej części tej minigry, czyli kaktusów :). Do dzieła!
Klasa “Kaktus”?
Zacznijmy od samych obrazków. Z właściwej aplikacji wyłapałam cztery rodzaje kaktusów:
Oczywiście w grze są one obracane (a czasem chyba też zmniejszane/zwiększane), ale póki co, możemy oprzeć naszych “przeciwników” na tych czterech obrazkach, bez dodatkowych utrudnień.
Kaktusy muszą respawnować się w losowych miejscach – często w grupach, nie większych niż cztery rośliny. Większej grupy oczywiście nie dałoby się przeskoczyć.
Napiszmy więc klasę kaktus, na podstawie której, wraz z przebiegiem gry, stworzymy poszczególne obiekty:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Cactus(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) cactusImg = [] cactusImg.append(pygame.image.load('kaktus1.png')) cactusImg.append(pygame.image.load('kaktus2.png')) cactusImg.append(pygame.image.load('kaktus3.png')) cactusImg.append(pygame.image.load('kaktus4.png')) rand = random.randint(0,3) self.image = cactusImg[rand] self.rect = self.image.get_rect() self.x = 800 self.y = 200 self.rect.move_ip(self.x, self.y) self.dx = -10 def update(self): self.rect = self.rect.move(self.dx, 0) |
Żeby uporządkować obrazy, które mamy do wyboru, zdecydowałam się na użycie tablicy z poszczególnymi ilustracjami. Możemy teraz wylosować numer od 0-3, aby pojawił się jeden z kaktusów.
To, czym wyróżnia się ta klasa od innych, które do tej pory stworzyliśmy, jest niepozorne parę słów w nawiasie tuż za nazwą klasy – pygame.sprite.Sprite. Oznaczają one, że instacje tej klasy będą tak zwanymi “sprajtami”, czyli ruchomymi elementami (postaciami) naszej gry. W przypadku sprite’ów ważne jest, żeby zdefiniować pole self.rect i pobrać kształt prostokąta z obrazka. Dzięki temu będziemy mogli później tworzyć kolizję oraz poruszać postaci. Jak widzisz, w kodzie pojawiają się również nowe funkcje – self.rect.move_ip i self.rect.move – pierwsza z nich przemieszcza sprite’a względem planszy, a druga względem jego własnej pozycji.
W metodzie update(), dokładnie tak jak w przypadku tła, definiujemy ruch sprite’a. Mimo że kod wygląda nieco inaczej, to działać będzie dokładnie tak samo.
Całość nie wygląda zbyt skomplikowanie – przejdźmy więc do samego respawnu i interakcji z naszym T-Rexem.
Respawn przeciwnika
Dzięki temu, że zdefiniowaliśmy kaktusy jako sprite’y, możemy utworzyć ich grupę. Dzięki temu respawny staną się bardzo proste.
Najpierw dodajmy trochę kodu do __init__ klasy Game():
1 2 3 4 5 6 7 8 9 10 |
class Game: def __init__(self): self._running = True self.dino = Dino() self.background = Background() self.counter = Counter() self.FPS = 60 self.fpsClock = pygame.time.Clock() self.cactuses = pygame.sprite.Group() self.respawn = 100 |
Interesują nas szczególnie dwie ostatnie. W pierwszej tworzymy wspomnianą już grupę, którą upychamy w zmienną self.cactuses, a pod self.respawn podstawiamy 100 – po takim czasie pojawi się pierwszy kaktus. Później będziemy losować tę wartość.
Do pętli while dodajmy inne potrzebne skrypty:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
while( self._running ): self.respawn -= 1 if self.respawn == 0: self.cactuses.add(Cactus()) self.respawn = random.randint(60, 200) for cactus in self.cactuses: is_collided = pygame.sprite.collide_rect(self.dino, cactus) if (is_collided): print("KOLIZJA!!!") self._running = False self.cactuses.update() self.cactuses.draw(self._display_surface) pygame.display.update() |
Z każdym odświeżeniem ekranu self.respawn zmniejsza się o 1 – gdy dojdzie do 0, tworzymy nowego kaktusa (dodając go do grupy) i losując nową wartość respawnu. Liczby między 60 a 200 powodują rzadkie respawny, ale jak tylko dopracujemy kod, by wyświetlać przeszkody również grupami, powinno działać dobrze.
Kolejny blok kodu to wykrywanie kolizji. Dla każdego kaktusa – gdy zetknie się z naszym dinozaurem, is_collided dostaje wartość 1 i gra się zatrzymuje (printując kolizję). Wygląda prosto i tak też działa – opieramy się na kształcie rectangli wybudowanych wokół postaci.
Na koniec update’ujemy wszystkie kaktusy, aby przemieszczały się razem z tłem, i blitujemy je do powierzchni. Z racji, że stanowią grupę sprite’ów możemy skorzystać z metody draw(surface).
Spróbujmy uruchomić grę!
Pojawia się piękny error…
Jak już wspominałam, kolizje oparte są na kształcie rectangli zbudowanych wokół postaci. Zapomnieliśmy zdefiniować recta dla T-Rexa.
Dopracujmy dinozaura
1 2 3 4 5 6 7 8 9 10 |
class Dino(): def __init__(self): self.x = 10 self.y = 200 self.dy = 0 self.leg_flag = 0 self.time = 0 self.dino_Img = pygame.image.load('dino1a1.png') self.rect = self.dino_Img.get_rect() self.jumping = False |
Interesuje nas przedostatni wers – identyczny, jak dla kaktusów.
Aby kolizje mogły działać, musimy jeszcze sprawić, że ruch dinozaura będzie identyczny na rectanglu, co na samym obrazku. Uda nam się to dzięki umieszczeniu kodu:
1 |
self.rect.topleft = self.x, self.y |
na samym dole metody update(). Czas ponownie włączyć grę!
I w końcu jakoś to wygląda :) Jedyny błąd, jaki widać to położenie najmniejszego kaktusa – dodajmy warunek, który w przypadku wylosowania jego numeru, umiejscowi obrazek nieco niżej:
1 2 3 4 5 |
if (rand == 2): self.y = 218 else: self.y = 200 self.rect.move_ip(self.x, self.y) |
Teraz prezentuje się lepiej.
Grając w naszą wersję gry, możemy spowodować kolizję nawet skacząc w odpowiednim momencie. Dlaczego? Sprawdziłam wysokość skoku w obu wersjach apki, i jak można zauważyć na obrazku:
W oryginale (po lewej) skacze zdecydowanie wyżej. Dopracowanie tego zostawimy jednak na następny raz i wtedy też zajmiemy się grupowaniem kaktusów i ładnym ekranem GAME OVER. See you! :)