Skip to content

Latest commit

 

History

History
427 lines (274 loc) · 12.8 KB

index.rst

File metadata and controls

427 lines (274 loc) · 12.8 KB

Pong (obj)

python

Klasyczna gra w odbijanie piłeczki zrealizowana z użyciem biblioteki PyGame. Wersja obiektowa. Biblioteka PyGame ułatwia tworzenie aplikacji multimedialnych, w tym gier.

Przygotowanie

Do rozpoczęcia pracy z przykładem pobieramy szczątkowy kod źródłowy:

~/python101$ git checkout -f pong/z1

Okienko gry

Na wstępie w pliku ~/python101/games/pong.py otrzymujemy kod który przygotuje okienko naszej gry:

Kod nr

pong_z1.py

W powyższym kodzie zdefiniowaliśmy klasę Board z dwiema metodami:

  1. konstruktorem __init__, oraz
  2. metodą draw posługującą się biblioteką PyGame do rysowania w oknie.

Na końcu utworzyliśmy instancję klasy Board i wywołaliśmy jej metodę draw na razie bez żadnych elementów wymagających narysowania.

Note

Każdy plik skryptu Python jest uruchamiany w momencie importu — plik/moduł główny jest importowany jako pierwszy.

Deklaracje klas są faktycznie instrukcjami sterującymi mówiącymi by w aktualnym module utworzyć typy zawierające wskazane definicje.

Możemy mieszać deklaracje klas ze zwykłymi instrukcjami sterującymi takimi jak print, czy przypisaniem wartości zmiennej board = Board(800, 400) i następnie wywołaniem metody na obiekcie board.draw().

Nasz program możemy uruchomić komendą:

~/python101$ python games/pong.py

Mrugnęło? Program się wykonał i zakończył działanie :). Żeby zobaczyć efekt na dłużej, możemy na końcu chwilkę uśpić nasz program:

Kod nr
import time
time.sleep(5)

Jednak zamiast tego, dla lepszej kontroli powinniśmy zadeklarować klasę kontrolera gry, usuńmy kod o linii 37 do końca i dodajmy klasę kontrolera:

Kod nr

pong_z2.py

Note

Prócz dodania kontrolera zmieniliśmy także sposób w jaki gra jest uruchamiana — nie mylić z uruchomieniem programu.

Na końcu dodaliśmy instrukcję warunkową if __name__ == "__main__":, w niej sprawdzamy czy nasz moduł jest modułem głównym programu, jeśli nim jest gra zostanie uruchomiona.

Dzięki temu jeśli nasz moduł został zaimportowany gdzieś indziej instrukcją import pong, deklaracje klas zostały by wykonane, ale sama gra nie będzie uruchomiona.

Gotowy kod możemy wyciągnąć komendą:

~/python101$ git checkout -f pong/z2

Piłeczka

Czas dodać piłkę do gry. Piłeczką będzie kolorowe kółko które z każdym przejściem naszej pętli przesuniemy o kilka punktów w osi X i Y, zgodnie wektorem prędkości.

Wcześniej jednak zdefiniujemy wspólną klasę bazową dla obiektów które będziemy rysować w oknie naszej gry:

Kod nr

pong_z3.py

Następnie dodajmy klasę samej piłeczki dziedzicząc z Drawable:

Kod nr

pong_z3.py

W przykładzie powyżej wykonaliśmy dziedziczenie oraz przesłanianie konstruktora, ponieważ rozszerzamy Drawable i chcemy zachować efekt działania konstruktora na początku konstruktora Ball wywołujemy konstruktorr klasy bazowej:

super(Ball, self).__init__(width, height, x, y, color)

Teraz musimy naszą piłeczkę zintegrować z resztą gry:

Kod nr

pong_z3.py

Note

Metoda Board.draw oczekuje wielu opcjonalnych argumentów, chodź na razie przekazujemy tylko jeden. By zwiększyć czytelność potencjalnie dużej listy argumentów — kto wie co jeszcze dodamy :) — podajemy każdy argument w swojej linii zakończonej przecinkiem ,

Python nie traktuje takich osieroconych przecinków jako błąd, jest to ukłon w stronę programistów którzy często zmieniają kod, kopiują i wklejają kawałki.

Dzięki temu możemy wstawiać nowe, i zmieniać kolejność bez zwracania uwagi czy na końcu jest przecinek, czy go brakuje, czy go należy usunąć. Zgodnie z konwencją powinien być tam zawsze.

Gotowy kod możemy wyciągnąć komendą:

~/python101$ git checkout -f pong/z3

Odbijanie piłeczki

Uruchommy naszą "grę" ;)

~/python101$ python games/pong.py

Efekt nie jest powalający, ale mamy już jakiś ruch na planszy. Szkoda, że piłka spada z planszy. Może mogła by się odbijać od krawędzi okienka? Możemy wykorzystać wcześniej przygotowane metody do zmiany kierunku wektora prędkości, musimy tylko wykryć moment w którym piłeczka będzie dotykać krawędzi.

W tym celu piłeczka musi być świadoma istnienia planszy i pozycji krawędzi, dlatego zmodyfikujemy metodę Ball.move tak by przyjmowała board jako argument i na jego podstawie sprawdzimy czy piłeczka powinna się odbijać:

Kod nr
def move(self, board):
    """
    Przesuwa piłeczkę o wektor prędkości
    """
    self.rect.x += self.x_speed
    self.rect.y += self.y_speed

    if self.rect.x < 0 or self.rect.x > board.surface.get_width():
        self.bounce_x()

    if self.rect.y < 0 or self.rect.y > board.surface.get_height():
        self.bounce_y()

Jeszcze zmodyfikujmy wywołanie metody move w naszej pętli głównej:

Kod nr
def run(self):
    """
    Główna pętla programu
    """
    while not self.handle_events():
        self.ball.move(self.board)
        self.board.draw(
            self.ball,
        )
        self.fps_clock.tick(30)

Warning

Powyższe przykłady mają o jedno wcięcie za mało. Poprawnie wcięte przykłady straciłyby kolorowanie w tej formie materiałów. Ze względu na czytelność kodu zdecydowaliśmy się na taki drobny błąd. Kod po ewentualnym wklejeniu należy poprawić dodając jedno wcięcie (4 spacje).

Sprawdzamy piłka się odbija, uruchamiamy nasz program:

~/python101$ python games/pong.py

Gotowy kod możemy wyciągnąć komendą:

~/python101$ git checkout -f pong/z4

Odbijamy piłeczkę rakietką

Dodajmy "rakietkę" od przy pomocy której będziemy mogli odbijać piłeczkę. Dodajmy zwykły prostokąt, który będziemy przesuwać przy pomocy myszki.

Kod nr

pong_z5.py

Note

W tym przykładzie zastosowaliśmy operator warunkowy, za jego pomocą ograniczamy prędkość poruszania się rakietki:

delta = self.max_speed if delta > 0 else -self.max_speed

Zmienna delta otrzyma wartość max_speed ze znakiem + lub - w zależności od znaku jaki ma aktualnie.

Następnie "pokażemy" rakietkę piłeczce, tak by mogła się od niej odbijać. Wiemy że rakietek będzie więcej dlatego od razu tak zmodyfikujemy metodę Ball.move by przyjmowała kolekcję rakietek:

Kod nr
def move(self, board, *args):
    """
    Przesuwa piłeczkę o wektor prędkości
    """
    self.rect.x += self.x_speed
    self.rect.y += self.y_speed

    if self.rect.x < 0 or self.rect.x > board.surface.get_width():
        self.bounce_x()

    if self.rect.y < 0 or self.rect.y > board.surface.get_height():
        self.bounce_y()

    for racket in args:
        if self.rect.colliderect(racket.rect):
            self.bounce_y()

Tak jak w przypadku dodawania piłeczki, rakietkę też trzeba dodać do "gry", dodatkowo musimy ją pokazać piłeczce:

Kod nr

pong_z5.py

Gotowy kod możemy wyciągnąć komendą:

~/python101$ git checkout -f pong/z5

Note

W tym miejscu można się pobawić naszą grą, zmodyfikuj ją według uznania i pochwal się rezultatem z innymi. Jeśli kod przestanie działać, można szybko porzucić zmiany poniższą komendą.

~/python101$ git reset --hard

Gramy przeciwko komputerowi

Dodajemy przeciwnika, nasz przeciwnik będzie mistrzem, będzie dokładnie śledził piłeczkę i zawsze starał się utrzymać rakietkę gotową do odbicia piłeczki.

Kod nr

pong_z6.py

Tak jak w przypadku piłeczki i rakietki dodajemy nasze Ai do gry, a wraz nią wraz dodajemy drugą rakietkę. Dwie rakietki ustawiamy na przeciwległych brzegach planszy.

Trzeba pamiętać by pokazać drugą rakietkę piłeczce, tak by mogła się od niej odbijać.

Kod nr

pong_z6.py

Pokazujemy punkty

Dodajmy klasę sędziego, który patrząc na poszczególne elementy gry będzie decydował czy graczom należą się punkty i będzie ustawiał piłkę w początkowym położeniu.

Kod nr

pong_z7.py

Tradycyjnie dodajemy instancję nowej klasy do gry:

Kod nr

pong_z7.py

Zadania dodatkowe

  1. Piłeczka "odbija się" po zewnętrznej prawej i dolnej krawędzi. Można to poprawić.
  2. Metoda Ball.move otrzymuje w argumentach planszę i rakietki. Te elementy można piłeczce przekazać tylko raz w konstruktorze.
  3. Komputer nie odbija piłeczkę rogiem rakietki.
  4. Rakietka gracza rusza się tylko gdy gracz rusza myszką, ruch w stronę myszki powinen być kontynuowany także gdy myszka jest bezczynna.
  5. Gdy piłeczka odbija się od boków rakietki powinna odbijać się w osi X.
  6. Gra dwuosobowa z użyciem komunikacji po sieci.