4 edycja
warsztatów zakończona

Pygame - biblioteka do tworzenia prostych gier w Pythonie, cz.1

Published: Fri 20 February 2015
tags: pygame python

W ciągu ostatnich kilkudzięsięciu lat gry nabrały bardzo dużego znaczenia. Świadczy o tym między innymi wielkość tego rynku, powstanie tak zwanego e-sportu czy też np. granie zawodowe. Szacuje się, że wartość rynku gier PC w 2014 roku wyniesie łącznie około 25 mld dolarów. W niniejszym artykule dam wskazówki jak wykorzystać bibliotekę pygame i samemu napisać prostą grę.

Dlaczego "Space Invaders"? Rozpoczynając zabawę z tworzeniem gier, najłatwiej jest zacząć od projektów, które znamy, w których mechanika nie jest zbyt skomplikowana, a pozwoli nam zaznajomić się z biblioteką. “Space Invaders” jest prostą grą typu arcade, w której musimy zdobywać punkty strzelając do obcych statków kosmicznych. Dzięki swojej prostocie i grywalności była bardzo popularna oraz stała się inspiracją do powstawania kolejnych produkcji.

Co potrzebujemy?

Na początku zdefiniujmy, jakie w naszej grze panują zasady. Mechanika jest na tyle prosta, że łatwo ją rozpisać. A dokładnie:

  1. Jako gracz:
    • Możemy poruszać się w prawo
    • Możemy poruszać się w lewo
    • Możemy strzelać do przeciwnika
  2. Przeciwnik:
    • Porusza się w prawo
    • Porusza się w lewo
    • Porusza się w dół
    • Strzela do gracza
  3. Świat:
    • Gdy przeciwnik dotrze do gracza, koniec gry - inwazja się powiodła
    • Gdy gracz straci wszystkie życia, koniec gry
    • Plansza odnawia się w nieskończoność, dopóki gracz nie straci życia

Tych kilka prostych zasad wyznacza nam ilość mechaniki, której potrzebujemy, aby powstała grywalna wersja i pozwoliła nam na rozbudowę gry o rzeczy, których nie ma w pierwowzorze.

Do wykonania tej gry potrzebujemy następujące elementy:

  • Plansza (świat gry)
  • Statki przeciwników
  • Statek gracza

Początkowo nasze statki mogą być przedstawione jako różnokolorowe kwadraty, choć oczywiście, jeśli chcemy, możemy stworzyć grafikę własnoręcznie lub też poszukać jej w Internecie. W sieci istnieje dużo serwisów, które w swoich zasobach posiadają darmowe assety, np. http://opengameart.org. Oprócz statków musimy jeszcze zdecydować, jakiego rodzaju będzie tło i ewentualne menu gry. Do naszego projektu wykorzystamy czarne tło, bez dodatkowych efektów. Nie będziemy też potrzebować menu gry, wystarczy nam przycisk, który wystartuje planszę.

Gameplay

Aby wykonać gameplay ustalimy wyznaczniki tego, w jaki sposób będzie przebiegała rozgrywka. Podzielimy je na 4 grupy.

Gracz

Gracz (jak już wcześniej wspomniano) porusza się w lewo bądź w prawo. Na starcie znajduje się u dołu ekranu, po środku planszy. Porusza się maksymalnie do krawędzi planszy. Na początku posiada 3 punkty życia, po trafieniu przez przeciwnika traci 1 punkt. Gdy przeciwnik dotrze do gracza, gracz traci wszystkie punkty życia. Strzela w odstępach czasowych co 0,5 sekundy. Strzał pojawia się na środku statku gracza.

Przeciwnik

Armia przeciwników składa się z 5 rzędów po 11 statków w każdym rzędzie. Przeciwnicy różnią się pomiędzy sobą kolorem i punktacją. Dwa pierwsze rzędy będą najmniej punktowane (po 10 punktów), kolejne dwa rzędy będę średnio punktowane (po 20 punktów), natomiast ostatni rząd będzie najwyżej punktowany (po 30 punktów). Łączna liczba punktów powinna wynosić 990 na jedną planszę. Przeciwnicy poruszają się “wężykiem” z prawej do lewej, następnie w dół, potem z lewej do prawej, następnie znów w dół. Ruch ten powtarzany jest aż do momentu zetknięcia się z graczem. Wrogie statki co 5 sekund poruszają się coraz szybciej. Nie starają się trafić gracza, strzelają losowo. Strzał mogą oddać jedynie przeciwnicy najbliżej gracza, nie mogą strzelać zza pleców przeciwnika. Strzał przeciwnika pojawia się na jego środku. Każdy przeciwnik ginie od jednego strzału.

HUD

Na nasze potrzeby wystarczy bardzo prosty HUD. Jedna linia tekstu z informacją o ilości życia i punktacją. Oryginalnie ilość żyć i ilość kredytów była na dole, natomiast wynik znajdował się u góry. Z racji, iż nie mamy licznika wrzuconych kredytów, wystarczy nam jedna linia na dole ekranu z informacją.

Sterowanie

Do gry wystarczy nam obsługa klawiatury. Posłużymy się klawiszem enter jako przyciskiem startu gry. Natomiast w samej grze poruszanie statkiem będzie odbywać się za pomocą strzałek w lewo i w prawo, a do strzelania użyjemy spacji. Do wyjścia z gry użyjemy przycisku escape.

Przechodzenie między kolejnymi etapami gry odbywa się tak, że po zniszczeniu wszystkich przeciwników plansza powinna wczytać się na nowo z zapamiętanym wynikiem i ilością pozostałego życia. Drugą rzeczą jest dodawanie wyników do listy i jej wyświetlenie. Listę poprzednich wyników będziemy trzymać w pliku.

Ten prosty opis pozwala nam stworzyć grę w dowolnie wybranym języku, natomiast my wykorzystamy Pythona i bibliotekę pygame (http://www.pygame.org). Wszystkie konieczne elementy, takie jak obsługa klawiatury, wyświetlanie grafiki, kolizje, są już dostępne w tej bibliotece. Na głównej stronie znajdziemy dokumentację, instrukcję instalacji oraz potrzebne wersje dla danego systemu operacyjnego. Dodatkowo znajdziemy też proste tutoriale do nauki (jeden z nich jest w języku polskim).

A więc startujemy! Zakładam, iż macie już zainstalowaną bibliotekę pygame, odpowiednią do systemu operacyjnego i wersji Pythona. Pierwszą rzeczą, którą musimy przygotować jest główny plik i pierwsze okienko wyświetlające zaproszenie do gry. Sama struktura kodu gry nie będzie dostosowywana do różnych istniejących metod tworzenia gier. Nie będziemy optymalizować gry pod względem wydajności czy zużycia danych, gdyż gra jest tylko wstępem do poznania biblioteki.

Kod potrzebny do zdefiniowania okna:

from sys import exit

import pygame
from pygame.locals import *

SCREEN_SIZE = (800, 600)

class SpaceInvadersGame(object):
    def __init__(self):
        pygame.init()
        flag = DOUBLEBUF
        self.surface = pygame.display.set_mode(SCREEN_SIZE, flag)
        self.gamestate = 1
        self.loop()

    def game_exit(self):
        """ funkcja przerywa dzialanie gry i wychodzi do systemu"""
        exit()

    def loop(self):
        """ glowna petla gry """
        while self.gamestate == 1:
           for event in pygame.event.get():
               if (event.type == QUIT or
                   (event.type == KEYDOWN and event.key == K_ESCAPE)
               ):
                   self.gamestate = 0
        self.game_exit()

if __name__ == '__main__':
   SpaceInvadersGame()

Listing 1. Zdefiniowanie szkieletu gry

Na pierwszym listingu widzimy importowanie głównej biblioteki oraz zmiennych i funkcji globalnych, którę będę nam potrzebne do obsługi przycisków - jest to from pygame.locals import *, linijka ta jest opcjonalna. W głównej klasie dodaliśmy 3 metody:

  • __init__ - metoda odpowiedzialna za inicjalizację biblioteki pygame oraz ustawienie i wyświetlenie okna
  • game_exit - metoda, która jest odpowiedzialna za przerwanie działania gry i wyjście do głównego systemu
  • loop - główna pętla gry, obsługująca zdarzenia wyłączania okna oraz klawisza escape, tutaj również będzie znajdowała się obsługa naszej gry i zdarzeń

Po uruchomieniu powyższego kodu powinniśmy zobaczyć wyrenderowane okno.

Teraz dodamy tekst powitalny i obsługę przycisku enter. Do metody __init__ dodajemy kod z poniższego listingu:

self.surface.fill((0, 0, 0))

myfont = pygame.font.Font(None, 15)
label = myfont.render(
    "Press ENTER to start game", 1, (255, 255, 0)
)
self.surface.blit(label, (100, 100))
pygame.display.flip()
while True:
    for event in pygame.event.get():
        if event.type == KEYDOWN and event.key == K_RETURN:
            self.gamestate = 1
            self.loop()
        if (event.type == QUIT or 
            (event.type == KEYDOWN and event.key == K_ESCAPE)
        ):
            exit()

Listing 2. Dodanie tekstu powitalnego oraz wystartowanie głównej pętli po naciśnięciu klawisza enter

Pierwsza linijka w listingu 2 czyści ekran i ustawia kolor tła na czarny. Następnie ustawiamy font, wywołujemy ją z wartością None (w tym momencie pygame załaduje domyślny font używany w pygame) oraz ustawiamy jego rozmiar na 15. Kolejny etap to renderowanie fontu wraz z napisem. Ustawiamy dodatkowo dwa parametry - oprócz treści dodajemy informacje, czy font ma być wygładzony oraz jaki jest jego kolor. Po ustawieniu odpowiedniego napisu musimy jeszcze go dodać do wyświetlenia. Dzięki prostemu poleceniu pygame.display.flip() wysyłamy sygnał, aby zaktualizować wyświetlany obraz. Dodatkowo ustawimy pętlę, aby nasłuchiwać wciśnięcia przycisku enter i wystartować grę oraz obsłużyć wyłączenie okna i klawisza escape.

Kolejnym etapem jest dodanie gracza. Ustalmy, że rozmiar gracza wynosi 50x50 px (proponuję, aby pobrać grafikę statku właśnie w tym rozmiarze, gdyż będzie łatwiej następnie policzyć jego pozycję oraz strzały), jego prędkość oraz początkową pozycję. Dodamy metodę dla statku gracza, która wygląda następująco:

def draw_player(self):
    self.player = pygame.image.load("ship.png")
    self.speed = 1.2
    self.player_x = SCREEN_SIZE[0]/2 - 25
    self.player_y = SCREEN_SIZE[1] - 75

Listing 3. Metoda ustawiająca postać gracza

Na powyższym listingu ustawiamy gracza, wczytujemy do niego odpowiedni obrazek oraz ustawiamy prędkość, a także pozycję startową. Aby statek gracza pokazał się nam na środku ekranu wyliczamy jego pozycję. W zmiennej SCREEN_SIZE posiadamy rozmiar okna, którym posłużymy się do obliczenia pozycji. Pozycję na osi x obliczymy poprzez podzielenie szerokości okna przez 2 i odjęcia 25 px, aby środek statku znajdował się na środku ekranu. Natomiast dla osi y wystarczy nam, abyśmy odjęli od wysokości okna 75 px (50 px wysokości statku oraz dodatkowe 25 px). Dodatkowe 25 px u dołu ekranu wykorzystamy do wyświetlenia informacji o pozostałym życiu oraz wyniki gracza. Aby zainicjować statek gracza, musimy dodać wywołanie metody draw_player do metody __init__, przed linię aktualizującą wyświetlanie na ekranie (to znaczy przed linijką pygame.display.flip()). W tym momencie przy wczytywaniu pierwszego ekranu gry, będziemy mieli już załadowaną grafikę oraz pozycję gracza.

Aby nasz gracz mógł się poruszać musimy dodać odpowiednią metodę. Początkowo nie będziemy sprawdzać kolizji z bokami ekranu. Do tego celu wykorzystamy poniższy kod.

def move(self, dirx, diry):
    self.player_x = self.player_x + (dirx * self.speed)
    self.player_y = self.player_y + (diry * self.speed)

Listing 4. Metoda odpowiedzialna za aktualizację pozycji gracza

Przedstawiona na powyższym listingu metoda, oblicza pozycję gracza w świecie. Przyjmuje ona dwa parametry, czyli kierunek ruchu po osi x (prawo, lewo) oraz po osi y (góra, dół). Do naszej gry wykorzystamy ruch tylko po osi x. Gdy mamy już zdefiniowaną metodę do poruszania się, należy teraz dodać kod odpowiedzialny za poruszania się. W tym celu wykorzystamy:

keys = pygame.key.get_pressed()

if keys[K_RIGHT]:
    self.move(1, 0)

if keys[K_LEFT]:
    self.move(-1, 0)

self.surface.fill((0, 0, 0))
self.surface.blit(self.player, (self.player_x, self.player_y))

pygame.display.flip()

Listing 5. Kod odpowiedzialny za obsługę poruszania się gracza

Aby się poruszać, potrzebujemy obsługiwać dwa przyciski, klawisz strzałki w prawo oraz w lewo. Wykorzystamy gotową obsługę klawiatury, czyli metodę z biblioteki pygame - pygame.key.get_pressed(). Dzięki niej pobieramy wciśnięte przyciski i możemy dla nich dodać obsługę zdarzenia, jakie ma zostać wykonane. Przy wykryciu wciśniętego klawisza w prawo lub w lewo wywołujemy metodę move() z dwoma parametrami. Jako pierwszy parametr przekazujemy w jakim kierunku ma się poruszać po osi x, w drugim parametrze w jakim kierunku ma się poruszać po osi y. Zgodnie z ustaleniami dodajemy możliwość poruszania się tylko i wyłącznie po osi x. Dlatego przy wciśniętym przycisku w prawo wywołujemy metodę move() z pierwszym parametrem ustawionym na 1, a drugim na 0. Analogicznie dla przycisku w lewo ustawiamy pierwszy parametr na -1, a drugi również na 0. Dzięki temu nasz statek będzie poruszał się po wyznaczonej przez nas osi. Następnie musimy zaktualizować pozycję gracza, którą wykonujemy poprzez wysłanie na ekran danych i ich aktualizację, do tego wykorzystujemy dwie metody blit() oraz flip(). W metodzie blit() jako pierwszy parametr podajemy obiekt gracza, a następnie jego zaktualizowaną pozycję. Metoda flip() wysyła zaktualizowane dane na ekran.

W tym momencie mamy możliwość poruszania się statkiem w prawo i w lewo. Przy ustawieniu prędkości na 1.2 nasz statek będzie się poruszał powoli, możemy oczywiście dowolnie modyfikować jego prędkość, musimy jedynie pamiętać, aby dobrze ją zbalansować. Statek, którym poruszamy w tej chwili może wyjechać poza ekran, gdyż nie ustawiliśmy ograniczenia. Naszym ograniczeniem będzie sprawdzanie w głównej pętli gry, czy pozycja gracza nie wykracza poza obszar ekranu. Do tego celu musimy zmodyfikować warunek odpowiedzialny za poruszanie się.

    if keys[K_RIGHT] and self.player_x < SCREEN_SIZE[0] - 50:
        self.move(1,0)

    if keys[K_LEFT] and self.player_x > 0:
        self.move(-1, 0)

Listing 6. Zmodyfikowane warunki dla poruszania się statku gracza

Dla klawisza w prawo dodajemy warunek sprawdzający, czy pozycja gracza nie jest większa niż rozmiar ekranu. Robimy to odejmując od niej szerokość naszego statku - inaczej nasz statek schowa się za krawędź ekranu i zatrzyma dopiero, gdy początek naszego dotrze do wartości okienka. W przypadku poruszania się w lewo nie mamy tego problemu i możemy dodać tylko warunek sprawdzający, czy pozycja gracza nie jest mniejsza od 0. Te dwa warunki pozwalają nam na wprowadzenie prostego ograniczenia poruszania się statku.

Podsumowanie

W części pierwszej rozpisaliśmy mechanikę gry, wraz ze stworzeniem postaci gracza i wprawieniem jej w ruch, oraz wymieniliśmy niezbędne czynności, które przygotowują projekt do dalszej rozbudowy. W kolejnym artykule przedstawię, jak dojść do wersji grywalnej, czyli dodać możliwość strzelania, przeciwników, wyświetlanie oraz zliczanie wyniku punktów.

Łukasz Jagodziński Łukasz Jagodziński

Na co dzień zajmuje się tworzeniem aplikacji webowych w Pythonie. Od kilku lat współpracuje z STX Next w Poznaniu. Po godzinach interesuję się tworzeniem gier od A do Z czyli od etapu projektowania, poprzez kodowanie, aż do gotowego produktu na sprzedaż.

comments powered by Disqus
Współpraca: programista