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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#! /usr/bin/env python2
# -*- coding: utf-8 -*-

import pygame
import sys
from pygame.locals import *

# inicjacja modułu pygame
pygame.init()

# szerokość i wysokość okna gry
OKNOGRY_SZER = 800
OKNOGRY_WYS = 400
# kolor okna gry, składowe RGB zapisane w tupli
LT_BLUE = (230, 255, 255)

# powierzchnia do rysowania, czyli inicjacja pola gry
oknogry = pygame.display.set_mode((OKNOGRY_SZER, OKNOGRY_WYS), 0, 32)
# tytuł okna gry
pygame.display.set_caption('Prosty Pong')

# pętla główna programu
while True:
    # obsługa zdarzeń generowanych przez gracza
    for event in pygame.event.get():
        # przechwyć zamknięcie okna
        if event.type == QUIT:
            pygame.quit()
            sys.exit()

    # rysowanie obiektów
    oknogry.fill(LT_BLUE)  # kolor okna gry

    # zaktualizuj okno i wyświetl
    pygame.display.update()

# 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
23
24
25
26
27
28
29
30
31
32
33
# paletka gracza #########################################################
PALETKA_SZER = 100  # szerokość
PALETKA_WYS = 20  # wysokość
BLUE = (0, 0, 255)  # kolor wypełnienia
PALETKA_1_POZ = (350, 360)  # początkowa pozycja zapisana w tupli
# utworzenie powierzchni paletki, wypełnienie jej kolorem,
paletka1 = pygame.Surface([PALETKA_SZER, PALETKA_WYS])
paletka1.fill(BLUE)
# ustawienie prostokąta zawierającego paletkę w początkowej pozycji
paletka1_prost = paletka1.get_rect()
paletka1_prost.x = PALETKA_1_POZ[0]
paletka1_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
48
    # narysuj w oknie gry paletki
    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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# pętla główna programu
while True:
    # obsługa zdarzeń generowanych przez gracza
    for event in pygame.event.get():
        # przechwyć zamknięcie okna
        if event.type == QUIT:
            pygame.quit()
            sys.exit()

        # przechwyć ruch myszy
        if event.type == MOUSEMOTION:
            myszaX, myszaY = event.pos  # współrzędne x, y kursora myszy

            # oblicz przesunięcie paletki gracza
            przesuniecie = myszaX - (PALETKA_SZER / 2)

            # jeżeli wykraczamy poza okno gry w prawo
            if przesuniecie > OKNOGRY_SZER - PALETKA_SZER:
                przesuniecie = OKNOGRY_SZER - PALETKA_SZER
            # jeżeli wykraczamy poza okno gry w lewo
            if przesuniecie < 0:
                przesuniecie = 0
            # zaktualizuj położenie paletki w poziomie
            paletka1_prost.x = przesuniecie

    # rysowanie obiektów
    oknogry.fill(LT_BLUE)  # kolor okna gry

    # narysuj w oknie gry paletki
    oknogry.blit(paletka1, paletka1_prost)

    # zaktualizuj okno i wyświetl
    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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# piłka #################################################################
P_SZER = 20  # szerokość
P_WYS = 20  # wysokość
P_PREDKOSC_X = 6  # prędkość pozioma x
P_PREDKOSC_Y = 6  # prędkość pionowa y
GREEN = (0, 255, 0)  # kolor piłki
# utworzenie powierzchni piłki, narysowanie piłki i wypełnienie kolorem
pilka = pygame.Surface([P_SZER, P_WYS], pygame.SRCALPHA, 32).convert_alpha()
pygame.draw.ellipse(pilka, GREEN, [0, 0, P_SZER, P_WYS])
# ustawienie prostokąta zawierającego piłkę w początkowej pozycji
pilka_prost = pilka.get_rect()
pilka_prost.x = OKNOGRY_SZER / 2
pilka_prost.y = OKNOGRY_WYS / 2

# ustawienia animacji ###################################################
FPS = 30  # liczba klatek na sekundę
fpsClock = 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
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
    # ruch piłki ########################################################
    # przesuń piłkę po obsłużeniu zdarzeń
    pilka_prost.move_ip(P_PREDKOSC_X, P_PREDKOSC_Y)

    # jeżeli piłka wykracza poza pole gry
    # z lewej/prawej – odwracamy kierunek ruchu poziomego piłki
    if pilka_prost.right >= OKNOGRY_SZER:
        P_PREDKOSC_X *= -1
    if pilka_prost.left <= 0:
        P_PREDKOSC_X *= -1

    if pilka_prost.top <= 0:  # piłka uciekła górą
        P_PREDKOSC_Y *= -1  # odwracamy kierunek ruchu pionowego piłki

    if pilka_prost.bottom >= OKNOGRY_WYS:  # piłka uciekła dołem
        pilka_prost.x = OKNOGRY_SZER / 2  # więc startuję ze środka
        pilka_prost.y = OKNOGRY_WYS / 2

    # jeżeli piłka dotknie paletki gracza, skieruj ją w przeciwną stronę
    if pilka_prost.colliderect(paletka1_prost):
        P_PREDKOSC_Y *= -1
        # zapobiegaj przysłanianiu paletki przez piłkę
        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
109
    # narysuj w oknie piłkę
    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
115
    # zaktualizuj zegar po narysowaniu obiektów
    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
54
55
56
57
58
59
60
61
62
63
64
# paletka ai ############################################################
RED = (255, 0, 0)
PALETKA_AI_POZ = (350, 20)  # początkowa pozycja zapisana w tupli
# utworzenie powierzchni paletki, wypełnienie jej kolorem,
paletkaAI = pygame.Surface([PALETKA_SZER, PALETKA_WYS])
paletkaAI.fill(RED)
# ustawienie prostokąta zawierającego paletkę w początkowej pozycji
paletkaAI_prost = paletkaAI.get_rect()
paletkaAI_prost.x = PALETKA_AI_POZ[0]
paletkaAI_prost.y = PALETKA_AI_POZ[1]
# szybkość paletki AI
PREDKOSC_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
112
113
114
115
116
117
118
119
120
121
122
123
    # AI (jak gra komputer) #############################################
    # jeżeli piłka ucieka na prawo, przesuń za nią paletkę
    if pilka_prost.centerx > paletkaAI_prost.centerx:
        paletkaAI_prost.x += PREDKOSC_AI
    # w przeciwnym wypadku przesuń w lewo
    elif pilka_prost.centerx < paletkaAI_prost.centerx:
        paletkaAI_prost.x -= PREDKOSC_AI

    # jeżeli piłka dotknie paletki AI, skieruj ją w przeciwną stronę
    if pilka_prost.colliderect(paletkaAI_prost):
        P_PREDKOSC_Y *= -1
        # uwzględnij nachodzenie paletki na piłkę (przysłonięcie)
        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
135
136
    # narysuj w oknie gry paletki
    oknogry.blit(paletka1, paletka1_prost)
    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
103
104
105
    if pilka_prost.top <= 0:  # piłka uciekła górą
        #  P_PREDKOSC_Y *= -1  # odwracamy kierunek ruchu pionowego piłki
        pilka_prost.x = OKNOGRY_SZER / 2  # więc startuję ze środka
        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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# komunikaty tekstowe ###################################################
# zmienne przechowujące punkty i funkcje wyświetlające punkty
PKT_1 = '0'
PKT_AI = '0'
fontObj = pygame.font.Font('freesansbold.ttf', 64)  # czcionka komunikatów


def drukuj_punkty1():
    tekst1 = fontObj.render(PKT_1, True, (0, 0, 0))
    tekst_prost1 = tekst1.get_rect()
    tekst_prost1.center = (OKNOGRY_SZER / 2, OKNOGRY_WYS * 0.75)
    oknogry.blit(tekst1, tekst_prost1)


def drukuj_punktyAI():
    tekstAI = fontObj.render(PKT_AI, True, (0, 0, 0))
    tekst_prostAI = tekstAI.get_rect()
    tekst_prostAI.center = (OKNOGRY_SZER / 2, OKNOGRY_WYS / 4)
    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
123
124
125
126
127
128
129
130
131
    if pilka_prost.top <= 0:  # piłka uciekła górą
        # P_PREDKOSC_Y *= -1  # odwracamy kierunek ruchu pionowego piłki
        pilka_prost.x = OKNOGRY_SZER / 2  # więc startuję ze środka
        pilka_prost.y = OKNOGRY_WYS / 2
        PKT_1 = str(int(PKT_1) + 1)

    if pilka_prost.bottom >= OKNOGRY_WYS:  # piłka uciekła dołem
        pilka_prost.x = OKNOGRY_SZER / 2  # więc startuję ze środka
        pilka_prost.y = OKNOGRY_WYS / 2
        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
154
155
156
157
    # rysowanie obiektów ################################################
    oknogry.fill(LT_BLUE)  # wypełnienie okna gry kolorem

    drukuj_punkty1()  # wyświetl punkty gracza
    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
115
116
117
118
119
120
121
122
123
        # przechwyć naciśnięcia klawiszy kursora
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                paletka1_prost.x -= 5
                if paletka1_prost.x < 0:
                    paletka1_prost.x = 0
            if event.key == pygame.K_RIGHT:
                paletka1_prost.x += 5
                if paletka1_prost.x > OKNOGRY_SZER - PALETKA_SZER:
                    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
87
# powtarzalność klawiszy (delay, interval)
pygame.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:2022-05-22 o 19:52 w Sphinx 1.5.3
Autorzy:Patrz plik “Autorzy”