4.1. Pong (str)

Wersja strukturalna klasycznej gry w odbijanie piłeczki zrealizowana z użyciem biblioteki PyGame.

../../_images/pong.png

4.1.1. Pole gry

Tworzymy plik pong_str.py w terminalu lub w wybranym edytorze, zapisujemy na dysku i wprowadzamy poniższy kod:

Kod nr
 1#! /usr/bin/env python2
 2# -*- coding: utf-8 -*-
 3
 4import pygame
 5import sys
 6from pygame.locals import *
 7
 8# inicjacja modułu pygame
 9pygame.init()
10
11# szerokość i wysokość okna gry
12OKNOGRY_SZER = 800
13OKNOGRY_WYS = 400
14# kolor okna gry, składowe RGB zapisane w tupli
15LT_BLUE = (230, 255, 255)
16
17# powierzchnia do rysowania, czyli inicjacja pola gry
18oknogry = pygame.display.set_mode((OKNOGRY_SZER, OKNOGRY_WYS), 0, 32)
19# tytuł okna gry
20pygame.display.set_caption('Prosty Pong')
21
22# pętla główna programu
23while True:
24    # obsługa zdarzeń generowanych przez gracza
25    for event in pygame.event.get():
26        # przechwyć zamknięcie okna
27        if event.type == QUIT:
28            pygame.quit()
29            sys.exit()
30
31    # rysowanie obiektów
32    oknogry.fill(LT_BLUE)  # kolor okna gry
33
34    # zaktualizuj okno i wyświetl
35    pygame.display.update()
36
37# KONIEC

Na początku importujemy wymagane biblioteki i inicjujemy moduł pygame. Dużymi literami zapisujemy nazwy zmiennych określające właściwości pola gry, które inicjalizujemy w instrukcji pygame.display.set_mode(). Tworzy ona powierzchnię o wymiarach 800x400 pikseli i 32 bitowej głębi kolorów, na której umieszczać będziemy pozostałe obiekty. W kolejnej instrukcji ustawiamy tytuł okna gry.

Programy interaktywne, w tym gry, reagujące na działania użytkownika, takie jak ruchy czy kliknięcia myszą, działają w tzw. głównej pętli, której zadaniem jest:

  1. przechwycenie i obsługa działań użytkownika, czyli tzw. zdarzeń (ruchy, kliknięcia myszą, naciśnięcie klawiszy),

  2. aktualizacja stanu gry (np. obliczanie przesunięć elementów) i rysowanie go.

Zadanie z punktu a) realizuje pętla for, która odczytuje kolejne zdarzenia zwracane przez metodę pygame.event.get(). Za pomocą instrukcji warunkowych możemy przechwytywać zdarzenia, które chcemy obsłużyć, np. naciśnięcie przycisku zamknięcia okna: if event.type == QUIT.

Instrukcja oknogry.fill(BLUE) wypełnia okno zdefiniowanym kolorem. Jego wyświetlenie następuje w poleceniu pygame.display.update().

Uruchom aplikację, wydając w terminalu polecenie:

~$ python pong_str.py

4.1.2. Paletka gracza

Planszę gry już mamy, pora umieścić na niej paletkę gracza. Poniższy kod wstawiamy przed pętlą główną programu:

Kod nr
22# paletka gracza #########################################################
23PALETKA_SZER = 100  # szerokość
24PALETKA_WYS = 20  # wysokość
25BLUE = (0, 0, 255)  # kolor wypełnienia
26PALETKA_1_POZ = (350, 360)  # początkowa pozycja zapisana w tupli
27# utworzenie powierzchni paletki, wypełnienie jej kolorem,
28paletka1 = pygame.Surface([PALETKA_SZER, PALETKA_WYS])
29paletka1.fill(BLUE)
30# ustawienie prostokąta zawierającego paletkę w początkowej pozycji
31paletka1_prost = paletka1.get_rect()
32paletka1_prost.x = PALETKA_1_POZ[0]
33paletka1_prost.y = PALETKA_1_POZ[1]

Elementy graficzne tworzymy za pomocą polecenia pygame.Surface((szerokosc, wysokosc), flagi, głębia). Utworzony obiekt możemy wypełnić kolorem: .fill(kolor). Położenie obiektu określimy pobierając na początku prostokątny obszar (Rect), który go reprezentuje, metodą get_rect(). Następnie podajemy współrzędne x i y wyznaczające położenie w poziomie i pionie.

Informacja

  • Początek układu współrzędnych w Pygame to lewy górny róg okna głównego.

  • Położenie obiektu można ustawić również podając nazwane argumenty: obiekt_prost = obiekt.get_rect(x = 350, y =350).

  • Położenie obiektów klasy Rect (prostokątów) możemy odczytwyać wykorzystując właściwości, takie jak: .x, .y, .centerx, .right, .left, .top, .bottom.

Omówiony kod utworzy obiekt reprezentujący paletkę gracza, ale trzeba ją jeszcze umieścić na planszy gry. W tym celu użyjemy metody .blit(), która służy rysowaniu jednego obrazka na drugim. Poniższy kod musimy wstawić w pętli głównej przed instrukcją wyświetlającą okno.

Kod nr
47    # narysuj w oknie gry paletki
48    oknogry.blit(paletka1, paletka1_prost)

Pozostaje uruchomienie kodu.

4.1.3. Ruch paletki

W pętli przechwytującej zdarzenia dopisujemy zaznaczony poniżej kod:

Kod nr
35# pętla główna programu
36while True:
37    # obsługa zdarzeń generowanych przez gracza
38    for event in pygame.event.get():
39        # przechwyć zamknięcie okna
40        if event.type == QUIT:
41            pygame.quit()
42            sys.exit()
43
44        # przechwyć ruch myszy
45        if event.type == MOUSEMOTION:
46            myszaX, myszaY = event.pos  # współrzędne x, y kursora myszy
47
48            # oblicz przesunięcie paletki gracza
49            przesuniecie = myszaX - (PALETKA_SZER / 2)
50
51            # jeżeli wykraczamy poza okno gry w prawo
52            if przesuniecie > OKNOGRY_SZER - PALETKA_SZER:
53                przesuniecie = OKNOGRY_SZER - PALETKA_SZER
54            # jeżeli wykraczamy poza okno gry w lewo
55            if przesuniecie < 0:
56                przesuniecie = 0
57            # zaktualizuj położenie paletki w poziomie
58            paletka1_prost.x = przesuniecie
59
60    # rysowanie obiektów
61    oknogry.fill(LT_BLUE)  # kolor okna gry
62
63    # narysuj w oknie gry paletki
64    oknogry.blit(paletka1, paletka1_prost)
65
66    # zaktualizuj okno i wyświetl
67    pygame.display.update()

Chcemy sterować paletką za pomocą myszy. Zadaniem powyższego kodu jest przechwycenie jej ruchu (MOUSEMOTION), odczytanie współrzędnych kursora z tupli event.pos i obliczenie przesunięcia określającego nowe położenie paletki. Kolejne instrukcje warunkowe korygują nową pozycję paletki, jeśli wykraczamy poza granice pola gry.

Przetestuj kod.

4.1.4. Piłka w grze

Piłkę tworzymy podobnie jak paletkę. Przed pętlą główną programu wstawiamy poniższy kod:

Kod nr
35# piłka #################################################################
36P_SZER = 20  # szerokość
37P_WYS = 20  # wysokość
38P_PREDKOSC_X = 6  # prędkość pozioma x
39P_PREDKOSC_Y = 6  # prędkość pionowa y
40GREEN = (0, 255, 0)  # kolor piłki
41# utworzenie powierzchni piłki, narysowanie piłki i wypełnienie kolorem
42pilka = pygame.Surface([P_SZER, P_WYS], pygame.SRCALPHA, 32).convert_alpha()
43pygame.draw.ellipse(pilka, GREEN, [0, 0, P_SZER, P_WYS])
44# ustawienie prostokąta zawierającego piłkę w początkowej pozycji
45pilka_prost = pilka.get_rect()
46pilka_prost.x = OKNOGRY_SZER / 2
47pilka_prost.y = OKNOGRY_WYS / 2
48
49# ustawienia animacji ###################################################
50FPS = 30  # liczba klatek na sekundę
51fpsClock = pygame.time.Clock()  # zegar śledzący czas

Przy tworzeniu powierzchni dla piłki używamy flagi SRCALPHA, co oznacza, że obiekt graficzny będzie zawierał przezroczyste piksele. Samą piłkę rysujemy za pomocą instrukcji pygame.draw.ellipse(powierzchnia, kolor, prostokąt). Ostatni argument to lista zawierająca współrzędne lewego górnego i prawego dolnego rogu prostokąta, w który wpisujemy piłkę.

Ruch piłki, aby był płynny, wymaga użycia animacji. Ustawiamy więc liczbę generowanych klatek na sekundę (FPS = 30) i przygotowujemy obiekt zegara, który będzie kontrolował czas.

Teraz pod pętlą (nie w pętli!) for, która przechwytuje zdarzenia, umieszczamy kod:

Kod nr
 78    # ruch piłki ########################################################
 79    # przesuń piłkę po obsłużeniu zdarzeń
 80    pilka_prost.move_ip(P_PREDKOSC_X, P_PREDKOSC_Y)
 81
 82    # jeżeli piłka wykracza poza pole gry
 83    # z lewej/prawej – odwracamy kierunek ruchu poziomego piłki
 84    if pilka_prost.right >= OKNOGRY_SZER:
 85        P_PREDKOSC_X *= -1
 86    if pilka_prost.left <= 0:
 87        P_PREDKOSC_X *= -1
 88
 89    if pilka_prost.top <= 0:  # piłka uciekła górą
 90        P_PREDKOSC_Y *= -1  # odwracamy kierunek ruchu pionowego piłki
 91
 92    if pilka_prost.bottom >= OKNOGRY_WYS:  # piłka uciekła dołem
 93        pilka_prost.x = OKNOGRY_SZER / 2  # więc startuję ze środka
 94        pilka_prost.y = OKNOGRY_WYS / 2
 95
 96    # jeżeli piłka dotknie paletki gracza, skieruj ją w przeciwną stronę
 97    if pilka_prost.colliderect(paletka1_prost):
 98        P_PREDKOSC_Y *= -1
 99        # zapobiegaj przysłanianiu paletki przez piłkę
100        pilka_prost.bottom = paletka1_prost.top

Na uwagę zasługuje metoda .move_ip(offset, offset), która przesuwa prostokąt zawierający piłkę o podane jako offset wartości. Dalej decydujemy, co ma się dziać, kiedy piłka wyjdzie poza pole gry. Metoda .colliderect(prostokąt) pozwala sprawdzić, czy dwa obiekty nachodzą na siebie. Dzięki temu możemy odwrócić bieg piłeczki po jej zetknięciu się z paletką gracza.

Piłkę trzeba umieścić na polu gry. Podaną niżej instrukcję umieszczamy poniżej polecenia rysującego paletkę gracza:

Kod nr
108    # narysuj w oknie piłkę
109    oknogry.blit(pilka, pilka_prost)

Na koniec ograniczamy prędkość animacji wywołując metodę .tick(fps), która wstrzymuje wykonywanie programu na podaną jako argument liczbę klatek na sekundę. Podany niżej kod trzeba dopisać na końcu w pętli głównej:

Kod nr
114    # zaktualizuj zegar po narysowaniu obiektów
115    fpsClock.tick(FPS)

Teraz możesz już zagrać sam ze sobą! Przetestuj działanie programu.

4.1.5. AI – przeciwnik

Dodamy do gry przeciwnika AI (ang. artificial inteligence), czyli paletkę sterowaną programowo.

Przed główną pętlą programu dopisujemy kod tworzący paletkę AI:

Kod nr
53# paletka ai ############################################################
54RED = (255, 0, 0)
55PALETKA_AI_POZ = (350, 20)  # początkowa pozycja zapisana w tupli
56# utworzenie powierzchni paletki, wypełnienie jej kolorem,
57paletkaAI = pygame.Surface([PALETKA_SZER, PALETKA_WYS])
58paletkaAI.fill(RED)
59# ustawienie prostokąta zawierającego paletkę w początkowej pozycji
60paletkaAI_prost = paletkaAI.get_rect()
61paletkaAI_prost.x = PALETKA_AI_POZ[0]
62paletkaAI_prost.y = PALETKA_AI_POZ[1]
63# szybkość paletki AI
64PREDKOSC_AI = 5

Tu nie ma nic nowego, więc od razu przed instrukcją wykrywającą kolizję piłki z paletką gracza (if pilka_prost.colliderect(paletka1_prost)) dopisujemy kod sterujący ruchem paletki AI:

Kod nr
111    # AI (jak gra komputer) #############################################
112    # jeżeli piłka ucieka na prawo, przesuń za nią paletkę
113    if pilka_prost.centerx > paletkaAI_prost.centerx:
114        paletkaAI_prost.x += PREDKOSC_AI
115    # w przeciwnym wypadku przesuń w lewo
116    elif pilka_prost.centerx < paletkaAI_prost.centerx:
117        paletkaAI_prost.x -= PREDKOSC_AI
118
119    # jeżeli piłka dotknie paletki AI, skieruj ją w przeciwną stronę
120    if pilka_prost.colliderect(paletkaAI_prost):
121        P_PREDKOSC_Y *= -1
122        # uwzględnij nachodzenie paletki na piłkę (przysłonięcie)
123        pilka_prost.top = paletkaAI_prost.bottom

Samą paletkę AI trzeba umieścić na planszy, po instrukcji rysującej paletkę gracza dopisujemy więc:

Kod nr

134    # narysuj w oknie gry paletki
135    oknogry.blit(paletka1, paletka1_prost)
136    oknogry.blit(paletkaAI, paletkaAI_prost)

Pozostaje zmienić kod odpowiedzialny za odbijanie piłki od górnej krawędzi planszy (if pilka_prost.top <= 0), żeby przeciwnik AI mógł przegrywać. W tym celu dokonujemy zmian wg poniższego kodu:

Kod nr
102    if pilka_prost.top <= 0:  # piłka uciekła górą
103        #  P_PREDKOSC_Y *= -1  # odwracamy kierunek ruchu pionowego piłki
104        pilka_prost.x = OKNOGRY_SZER / 2  # więc startuję ze środka
105        pilka_prost.y = OKNOGRY_WYS / 2

Teraz można już zagrać z komputerem :-).

4.1.6. Liczymy punkty

Co to za gra, w której nie wiadomo, kto wygrywa… Dodamy kod zliczający i wyświetlający punkty. Przed główną pętlą programu wstawiamy poniższy kod:

Kod nr
66# komunikaty tekstowe ###################################################
67# zmienne przechowujące punkty i funkcje wyświetlające punkty
68PKT_1 = '0'
69PKT_AI = '0'
70fontObj = pygame.font.Font('freesansbold.ttf', 64)  # czcionka komunikatów
71
72
73def drukuj_punkty1():
74    tekst1 = fontObj.render(PKT_1, True, (0, 0, 0))
75    tekst_prost1 = tekst1.get_rect()
76    tekst_prost1.center = (OKNOGRY_SZER / 2, OKNOGRY_WYS * 0.75)
77    oknogry.blit(tekst1, tekst_prost1)
78
79
80def drukuj_punktyAI():
81    tekstAI = fontObj.render(PKT_AI, True, (0, 0, 0))
82    tekst_prostAI = tekstAI.get_rect()
83    tekst_prostAI.center = (OKNOGRY_SZER / 2, OKNOGRY_WYS / 4)
84    oknogry.blit(tekstAI, tekst_prostAI)

Po zdefiniowaniu zmiennych przechowujących punkty graczy, tworzymy obiekt czcionki z podanego pliku (pygame.font.Font()). Następnie definiujemy funkcje, których zadaniem jest rysowanie punktacji graczy. Na początku tworzą one nowe obrazki z punktacją gracza (.render()), pobierają ich prostokąty (.get_rect()), pozycjonują je (.center()) i rysują na głównej powierzchni gry (.blit()).

Informacja

Plik wykorzystywany do wyświetlania tekstu (freesansbold.ttf) musi znaleźć się w katalogu ze skryptem.

W pętli głównej programu musimy umieścić wyrażenia zliczające punkty. Jeżeli piłka ucieknie górą, punkty dostaje gracz, w przeciwnym wypadku AI. Dopisz podświetlone instrukcje:

Kod nr
122    if pilka_prost.top <= 0:  # piłka uciekła górą
123        # P_PREDKOSC_Y *= -1  # odwracamy kierunek ruchu pionowego piłki
124        pilka_prost.x = OKNOGRY_SZER / 2  # więc startuję ze środka
125        pilka_prost.y = OKNOGRY_WYS / 2
126        PKT_1 = str(int(PKT_1) + 1)
127
128    if pilka_prost.bottom >= OKNOGRY_WYS:  # piłka uciekła dołem
129        pilka_prost.x = OKNOGRY_SZER / 2  # więc startuję ze środka
130        pilka_prost.y = OKNOGRY_WYS / 2
131        PKT_AI = str(int(PKT_AI) + 1)

Obie funkcje wyświetlające punkty również trzeba wywołać z pętli głównej, a więc po instrukcji wypełniającej okno gry kolorem (oknogry.fill(LT_BLUE)) dopisujemy:

Kod nr
153    # rysowanie obiektów ################################################
154    oknogry.fill(LT_BLUE)  # wypełnienie okna gry kolorem
155
156    drukuj_punkty1()  # wyświetl punkty gracza
157    drukuj_punktyAI()  # wyświetl punkty AI

4.1.7. Sterowanie klawiszami

Skoro możemy przechwytywać ruch myszy, nic nie stoi na przeszkodzie, aby umożliwić poruszanie paletką za pomocą klawiszy. W pętli for odczytującej zdarzenia dopisujemy:

Kod nr
114        # przechwyć naciśnięcia klawiszy kursora
115        if event.type == pygame.KEYDOWN:
116            if event.key == pygame.K_LEFT:
117                paletka1_prost.x -= 5
118                if paletka1_prost.x < 0:
119                    paletka1_prost.x = 0
120            if event.key == pygame.K_RIGHT:
121                paletka1_prost.x += 5
122                if paletka1_prost.x > OKNOGRY_SZER - PALETKA_SZER:
123                    paletka1_prost.x = OKNOGRY_SZER - PALETKA_SZER

Naciśnięcie klawisza generuje zdarzenie pygame.KEYDOWN. Dalej w instrukcji warunkowej sprawdzamy, czy naciśnięto klawisz kursora lewy lub prawy i przesuwamy paletkę o 5 pikseli.

Wskazówka

Kody klawiszy możemy sprawdzić w dokumentacji Pygame.

Uruchom program i sprawdź, jak działa. Szybko zauważysz, że wciśnięcie strzałki porusza paletką, ale żeby poruszyła się znowu, trzeba naciskanie powtarzać. To niewygodne, paletka powinna ruszać się dopóki klawisz jest wciśnięty. Przed pętlą główną dodamy więc poniższy kod:

Kod nr
86# powtarzalność klawiszy (delay, interval)
87pygame.key.set_repeat(50, 25)

Dzięki tej instrukcji włączyliśmy powtarzalność wciśnięć klawiszy. Przetestuj, czy działa.

4.1.8. Zadania dodatkowe

  • Zmodyfikuj właściwości obiektów (paletek, piłki) takie jak rozmiar, kolor, początkowa pozycja.

  • Zmień położenie paletek tak, aby znalazły się przy lewej i prawej krawędzi okna, wprowadź potrzebne zmiany w kodzie, aby poruszały się w pionie.

  • Dodaj trzecią paletkę, która co jakiś czas będzie „przelatywać” przez środek planszy i zmieniać w przypadku kolizji tor i kolor piłki.

4.1.9. Materiały

Źródła:


Licencja Creative Commons Materiały Python 101 udostępniane przez Centrum Edukacji Obywatelskiej na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.

Utworzony:

2025-04-12 o 10:21 w Sphinx 7.3.7

Autorzy:

Patrz plik „Autorzy”