🔩 Nowy Rekrut
0 / 34 misji

ASTROPYTHON

Zbuduj Grę z Asteroidami

Kurs Pygame dla Pasjonatów Gier

ROK 2189
Galaktyka została zaatakowana przez sztuczną inteligencję VOID. Większość floty została zniszczona.
Ostatnią nadzieją jest statek SPACE SURVIVAL. Ale statek jest uszkodzony: silniki nie działają, radar jest offline, system bojowy został zniszczony, komputer pokładowy ledwo żyje.

Tylko jedna osoba może uratować statek.
ASTRO. Czyli TY.
Technologie
Python 3 Pygame Bez OOP Język polski
Czego się nauczysz
zmienne i typy pętle for i while funkcje i return listy i filtrowanie if / elif / else losowość animacja kolizje timery debugowanie
📋

Sprawdź wiedzę — gotowość do AstroPython

📋 Test gotowości — Python + Pygame przed kursem AstroPython
24 pytania w 6 kategoriach — sprawdź, czy znasz Python i Pygame potrzebne do AstroPython.
Próg: ≥70% per kategoria. Poniżej progu → link do sekcji w kurs-python-basic lub kurs-snake.
🅐 Surface i rysowanie 🅑 Rect i kolizje 🅒 Game loop i zdarzenia 🅓 Listy obiektów 🅔 Wektory i ruch 2D 🅕 Funkcje i math
🚀 AstroPython v2 — Buduj grę od pierwszej lekcji
🎮 START

Co zbudujesz

Zbudujesz prawdziwą grę kosmiczną w Pythonie — własny kod, lekcja po lekcji. Nie gotowiec ani szablon: każdą linijkę napiszesz sam i zrozumiesz. Po drodze poznasz pętle, kolizje, stan gry i obsługę zdarzeń — te same mechanizmy napędzają aplikacje, których używasz codziennie.
Finalna gra Asteroidy: statek strzelający laserem, asteroidy, wrogowie i HUD z wynikiem
🎯 Oto Twój cel — finalna gra, którą zbudujesz krok po kroku. To Twój kod na końcu kursu, nie obrazek z internetu.
Etap po etapie dodasz:
statek sterowany strzałkami, lecący przez kosmos
asteroidy spadające z góry, które trzeba omijać
laser wystrzeliwany spacją, którym niszczysz trafione asteroidy
wrogów z własnym AI oraz całe fale przeciwników
wynik, poziomy trudności i wielki finał — kompletną grę do pokazania znajomym

Kurs prowadzi Cię lekcja po lekcji, a każda dokłada jeden mały kawałek do tego samego pliku gra.py — od pustego okna po kompletną grę. Po drodze przeplatasz lekcje projektami „zrób sam" (własna asteroida, wróg, fala przeciwników), a co kilka etapów zdajesz egzamin na kolejną rangę pilota; krótkie quizy sprawdzają, czy naprawdę rozumiesz, a nie tylko przepisujesz. To prawdziwy Python i Pygame — ten sam język i biblioteka, których game-deweloperzy używają zawodowo, nie bloczki ani zabawka. Wystarczą podstawy Pythona (zmienne, if, pętle, listy, funkcje); jeśli nie masz pewności, zrób krótki test gotowości albo zacznij od „Podstaw Pythona". Najpierw przygotujesz środowisko (następna lekcja), a potem zaczynasz budować.
🚀 I nie zniechęcaj się rozbiegówką — pierwsze lekcje to fundament (okno, współrzędne), ale już za parę kroków zobaczysz swój statek na ekranie i polecisz nim strzałkami. Gra ożywa szybciej, niż myślisz.
⚙️ SETUP

Przygotowanie środowiska

Zanim napiszesz pierwsze okno gry, potrzebujesz Pythona 3.12, Pygame i edytora VS Code. Przygotujesz to raz — naszą instrukcją krok po kroku, ze zrzutem ekranu do każdego kliknięcia.
🛠️ Pełna instrukcja instalacji — krok po kroku
Python 3.12 + Pygame + VS Code (po polsku), każdy krok ze zrzutem ekranu. ~10 minut, bez administratora. Zrób ją raz i wróć tutaj.
👉 Otwórz instrukcję: devostro.pl/start →
Masz już środowisko? To sprawdź w terminalu (w VS Code skrótem Ctrl+`) te dwie rzeczy, zanim ruszysz dalej:
python --version
python -m pip install pygame
> python --version
Python 3.12.10
> python -m pip install pygame
Successfully installed pygame-2.6.1
Coś nie działa? 'python' is not recognized → przejdź instrukcję devostro.pl/start od początku i pamiętaj o zaznaczeniu „Add python.exe to PATH". ModuleNotFoundError: No module named 'pygame' → VS Code odpala inny Python niż ten z Pygame: Ctrl+Shift+P → „Python: Select Interpreter" → wybierz tego, w którym instalowałeś Pygame.
Ostatni krok — plik gry. W folderze swojego projektu (np. devostro) stwórz pusty plik gra.py — to w nim, lekcja po lekcji, powstanie cała gra. Na razie zostaw go pusty; napiszemy go razem w Lekcji 1.
✔️ Zanim ruszysz dalej — sprawdź:
  • python --version pokazuje 3.12.x
  • python -m pip install pygame kończy się „Successfully installed pygame-2.6.1"
  • Masz pusty plik gra.py w folderze projektu
Lekcja 1

Pierwsze okno

Po tej lekcji uruchomisz swoje pierwsze prawdziwe okno gry — ciemnogranatowy prostokąt kosmosu o wymiarach 800 na 600 pikseli, z własnym tytułem na pasku. To jeszcze nie gra, tylko płótno, na którym w kolejnych lekcjach pojawi się statek i asteroidy. Jedno uprzedzenie: tego okna na razie NIE zamkniesz krzyżykiem — i to jest celowe.
Pygame to gotowy silnik do gier w Pythonie — sam rysuje okna, słucha klawiszy i maluje grafikę, więc nie musisz pisać tego od zera. Zanim jednak czegokolwiek użyjemy, trzeba ten silnik wczytać do programu — bo bez tego Python nie zna ani jednego polecenia Pygame. Robi to jedna linijka na samej górze pliku:
import pygame
Silnik jest już w programie, ale trzeba go jeszcze włączyć — to budzi do życia wszystkie wewnętrzne części Pygame. Ta linijka zawsze stoi tuż pod importem:
pygame.init()
Teraz zastanówmy się, jak duże ma być nasze okno. Szerokość i wysokość zapiszemy w dwóch zmiennych — dzięki temu rozmiar zmienisz później w jednym miejscu, a nie w kilku. Niech będzie 800 pikseli w poziomie i 600 w pionie (to zwykłe zmienne liczbowe, jak w kursie podstawowym, moduł M01):
SZEROKOSC = 800
WYSOKOSC = 600
AstroPython — Lekcja 1 × kosmos (0, 0, 30) SZEROKOSC = 800 WYSOKOSC = 600
Uwaga — te dwie zmienne to na razie tylko liczby; okno jeszcze nie istnieje. Tworzy je dopiero osobne polecenie set_mode. Jego wynik zapisujemy w zmiennej ekran — to ona od teraz reprezentuje powierzchnię, na której wszystko będziemy malować. Zwróć uwagę na podwójne nawiasy: rozmiar podajemy jako jedną parę liczb (szerokość, wysokość) — taką parę w Pythonie nazywamy krotką.
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
ekran to nasze okno gry — to właśnie na nim w kolejnych lekcjach pojawi się statek, a potem asteroidy. Wypada jeszcze nadać oknu nazwę: napis na jego pasku tytułu. To czysta dekoracja, ale miło zobaczyć tytuł własnej gry zamiast „pygame window":
pygame.display.set_caption("AstroPython - Lekcja 1: Pierwsze okno!")
Zostało serce każdej gry — pętla. Gra to nie jeden obrazek, lecz ten sam kod powtarzany w kółko, dziesiątki razy na sekundę; każde powtórzenie to jedna klatka. while True znaczy „rób to bez końca" (znasz je z kursu podstawowego, moduł M04). W środku dzieją się dwie rzeczy: ekran.fill((0, 0, 30)) maluje całe tło na ciemny granat (trójka liczb to kolor RGB), a pygame.display.flip() pokazuje świeżo namalowaną klatkę. Bez flip malowalibyśmy w ukryciu, a okno zostałoby czarne:
while True:
    ekran.fill((0, 0, 30))
    pygame.display.flip()
Teraz złóż wszystko w jeden plik i uruchom — to Twój pierwszy program gry:
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("AstroPython - Lekcja 1: Pierwsze okno!")

while True:
    ekran.fill((0, 0, 30))
    pygame.display.flip()
🎮 Co zobaczysz w oknie gry:
Jedno okno 800×600 z tytułem „AstroPython — Lekcja 1: Pierwsze okno!" na pasku. Całe wnętrze jest jednolicie ciemnogranatowe — to nasz kosmos. Okno po prostu stoi i czeka. Uwaga: krzyżyk w rogu na nic nie reaguje — to normalne, naprawimy to w lekcji 2.
Dlaczego okno nie chce się zamknąć? Bo nasza pętla robi tylko dwie rzeczy — maluje tło i pokazuje klatkę — a w ogóle nie sprawdza, czy kliknąłeś krzyżyk. Pygame zauważa kliknięcie i wysyła sygnał, ale nikt go jeszcze nie odbiera, więc okno go ignoruje. Odbieranie takich sygnałów to temat lekcji 2. Jak więc teraz zamknąć to uparte okno? Krzyżyk możesz klikać kilka razy — czasem system w końcu ubije okno sam — ale najpewniej zatrzymasz program tam, skąd go uruchomiłeś: w edytorze (przycisk Stop) albo zamykając okno konsoli. To niewygodne i właśnie dlatego w następnej lekcji nauczymy program reagować na krzyżyk.

Skoro pętla jest tak ważna, zatrzymajmy się przy niej. Bez while True dwie linijki ze środka wykonałyby się tylko raz, w ułamku sekundy, i program natychmiast by się skończył. Pętla sprawia, że okno trwa: maluje tę samą klatkę w kółko, dzięki czemu masz co oglądać. To właśnie dlatego każda gra ma w sobie taką nieskończoną pętlę.
✔️ Sprawdź, że działa
  • Po uruchomieniu widzisz jedno ciemnogranatowe okno, a nie czarny ekran ani błąd w konsoli.
  • Na pasku okna czytasz tytuł, który wpisałeś w set_caption.
  • Okno NIE zamyka się po kliknięciu krzyżyka — i wiesz, że to celowe (naprawimy w lekcji 2).
  • Potrafisz wskazać w kodzie linię ustawiającą rozmiar okna oraz tę, która maluje tło.
Zanim pozmieniasz kolory — skąd właściwie biorą się w grze? Kolor to przepis ze światła. Wyobraź sobie trzy reflektory świecące na to samo miejsce: czerwony, zielony i niebieski. Trójka liczb (R, G, B) mówi, jak mocno świeci każdy z nich — od 0 (zgaszony) do 255 (pełna moc) — a ich mieszanka daje końcowy kolor:
R = red (czerwony)  ·  G = green (zielony)  ·  B = blue (niebieski)
(255, 0, 0) czerwień  ·  (0, 255, 0) zielony  ·  (0, 0, 255) niebieski
(0, 0, 0) czarny (wszystko zgaszone)  ·  (255, 255, 255) biały (wszystko na full)
Nasze tło (0, 0, 30) to odrobina niebieskiego na czerni — stąd ciemny granat kosmosu. Teraz pobaw się tym oknem: najszybciej zrozumiesz kod, zmieniając liczby i patrząc, co się stanie. Najpierw zgadnij wynik, dopiero potem uruchom grę.
🧪 Eksperyment — zmień jedną liczbę, obserwuj okno
1. Zamień kolor tła (0, 0, 30) na (30, 0, 0) — na jaki kolor zmieni się kosmos?
2. Teraz spróbuj (0, 100, 0) — a teraz?
3. Ustaw SZEROKOSC = 400 i WYSOKOSC = 400 — jak zmieni się okno?
4. Wpisz duże liczby, np. 1200 na 900 — co się stanie?
Uruchom grę po każdej zmianie. Odpowiedzi nie podajemy — zobaczysz je na ekranie.
🧩 Zagadka — co się stanie, jeśli USUNIESZ pętlę while?
Wyobraź sobie, że kasujesz linię while True: i zostawiasz tylko ekran.fill oraz pygame.display.flip(). Najpierw zgadnij: okno zostanie otwarte, zniknie, a może pojawi się błąd?
Pokaż odpowiedź
Okno mignie i natychmiast zniknie. Bez pętli program wykonuje dwie linijki raz, w ułamku sekundy dobiega do końca pliku i się kończy — a gdy program się kończy, okno znika razem z nim. Pętla while True jest tym, co utrzymuje okno przy życiu; to nie ozdoba, lecz serce każdej gry.
🎯 Zadanie dla Ciebie — to Twoje pierwsze okno gry
1. Ustaw rozmiar okna na 640×480 i sprawdź, że tytuł nadal się wyświetla.
2. Dobierz kolor tła, który kojarzy Ci się z kosmosem — głęboki fiolet? prawie czarny? Wpisuj różne trójki RGB, aż trafisz w swój klimat.
3. Zmień tytuł w set_caption na własną nazwę gry.

To pierwsze okno jest całe Twoje 🚀 — to na nim za parę lekcji wyląduje statek, którym polecisz przez asteroidy. Zapamiętaj ten moment: właśnie odpaliłeś silnik swojej gry.
🚀 W lekcji 2 nauczymy Pygame słuchać Ciebie — dołożymy obsługę zdarzeń i wreszcie zamkniemy okno krzyżykiem, tak jak każdy normalny program. To krótka zmiana, a robi ogromną różnicę: od tego momentu Twoja gra zacznie reagować na gracza.
Lekcja 2

Pętla gry i zdarzenia

Pamiętasz okno z lekcji 1, które nie chciało się zamknąć krzyżykiem? Dziś to naprawiamy. Po tej lekcji Twoje okno zachowa się jak każdy normalny program: klikasz X w rogu i okno znika. Sekret to tak zwana pętla zdarzeń — fragment, dzięki któremu gra wreszcie zaczyna słuchać gracza.
Skąd gra wie, że coś robisz? Pygame nieustannie obserwuje gracza — ruch myszy, naciśnięcie klawisza, kliknięcie krzyżyka — i każde takie wydarzenie wrzuca do kolejki, jak listy do skrzynki. Te wydarzenia nazywamy zdarzeniami. Samo wrzucenie do skrzynki nic nie da: ktoś musi ją regularnie opróżniać. W lekcji 1 nikt tego nie robił, bo nasza pętla w ogóle nie zaglądała do kolejki — i właśnie dlatego okno nie reagowało na nic.

Naszą skrzynkę opróżnia jedno polecenie. Zwraca ono listę wszystkich zdarzeń, które uzbierały się od poprzedniej klatki:
pygame.event.get()
Po tej liście przechodzimy pętlą for — tą samą, którą znasz z kursu podstawowego (moduł M04). Bierze ona po kolei każde zdarzenie z kolejki i pozwala nam na nie zareagować:
for zdarzenie in pygame.event.get():
    ...   # tu zareagujemy na pojedyncze zdarzenie
To dokładnie ten sam ruch, co chodzenie po każdej liście w Pythonie — for wyjmuje z niej element po elemencie.
🔁 Przypomnij pętlę for po liście (kurs podstawowy, M04)
Pętla for przechodzi po liście element po elemencie. Ten kod:
ekwipunek = ["miecz", "tarcza"]
for przedmiot in ekwipunek:
    print(przedmiot)
wypisze w konsoli kolejno:
miecz
tarcza
— pętla wzięła elementy po kolei: najpierw „miecz", potem „tarcza". W naszej grze zamiast ekwipunku mamy zdarzenia, a listę dostarcza pygame.event.get().
Jak Pygame obsługuje krzyżyk gracz klika krzyżyk × kolejka zdarzeń QUIT pętla for czyta je QUIT? zamknij
Mając zdarzenie w ręku, sprawdzamy, czego dotyczy. Robi to if — warunek znany z kursu podstawowego (moduł M02). Zapis if zdarzenie.type == pygame.QUIT znaczy „jeśli to akurat zdarzenie zamknięcia okna". pygame.QUIT to właśnie sygnał, który Pygame wysyła po kliknięciu krzyżyka. Gdy warunek jest prawdziwy, wykonujemy dwie linijki: pygame.quit() wyłącza silnik gry, a exit() kończy cały program. Razem sprzątają po grze i zamykają okno:
if zdarzenie.type == pygame.QUIT:
    pygame.quit()
    exit()
Teraz złóżmy wszystko w jeden plik. Pętla zdarzeń trafia na samą górę pętli gry — jeszcze przed malowaniem tła, bo najpierw chcemy sprawdzić, czy gracz nie chce wyjść:
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("AstroPython - Lekcja 2: Pierwsze zdarzenia!")

while True:

    for zdarzenie in pygame.event.get():        # <<< NOWE - czytamy kolejke zdarzen
        if zdarzenie.type == pygame.QUIT:        # <<< NOWE - czy klikniety krzyzyk?
            pygame.quit()                        # <<< NOWE - wylaczamy Pygame
            exit()                               # <<< NOWE - konczymy program

    ekran.fill((0, 0, 30))
    pygame.display.flip()
🎮 Co zobaczysz w oknie gry:
Znane ciemnogranatowe okno — wygląda identycznie jak w lekcji 1. Różnica kryje się w zachowaniu: teraz klikasz krzyżyk i okno natychmiast się zamyka, a program kończy się czysto, bez zawieszania. Wreszcie działa tak, jak powinno!
Zwróć uwagę, gdzie stoi pętla zdarzeń — na samej górze pętli gry, jeszcze przed malowaniem tła. To celowe, bo najpierw chcemy sprawdzić, czy gracz czegoś nie chce (na przykład wyjść), a dopiero potem rysujemy kolejną klatkę. Cała ta obsługa dzieje się dziesiątki razy na sekundę, więc reakcja na krzyżyk jest natychmiastowa. Dwie ostatnie linijki, ekran.fill i pygame.display.flip(), znasz już z lekcji 1 — robią dokładnie to samo, co wcześniej.
✔️ Sprawdź, że działa
  • Po uruchomieniu okno wygląda jak w lekcji 1, ale teraz zamyka się po kliknięciu krzyżyka.
  • Program kończy się sam, bez zatrzymywania go w edytorze.
  • Potrafisz wskazać w kodzie pętlę for, która czyta zdarzenia.
  • Rozumiesz, że pygame.QUIT to sygnał wysyłany po kliknięciu krzyżyka.
Zajrzyjmy do środka kolejki zdarzeń — najłatwiej zobaczyć ją w działaniu, wypisując coś w konsoli za każdym razem, gdy coś się dzieje.
🧪 Eksperyment — podejrzyj kolejkę zdarzeń
Dorzuć jedną linię print jako pierwszą wewnątrz pętli for — reszta pętli zostaje bez zmian. Dokładnie tak:
    for zdarzenie in pygame.event.get():
        print("klik")                       # <<< dodaj te jedna linie
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()
1. Uruchom grę i poruszaj myszą nad oknem — patrz na konsolę.
2. Poklikaj myszą w oknie i ponaciskaj klawisze — jak często pojawia się napis?
3. Teraz zamień samą linię print("klik") na print(zdarzenie) — co teraz wypisuje konsola przy ruchu myszą i klawiszach?
Uruchom po każdej zmianie i obserwuj konsolę obok okna gry. Odpowiedzi nie podajemy — zobaczysz je sam.
🧩 Zagadka — co się stanie, jeśli POMINIESZ sprawdzanie QUIT?
Wyobraź sobie, że zostawiasz pętlę for zdarzenie in pygame.event.get(), ale usuwasz z niej całe if zdarzenie.type == pygame.QUIT wraz z pygame.quit() i exit(). Zgadnij: okno zamknie się krzyżykiem czy nie?
Pokaż odpowiedź
Nie zamknie się — wrócisz dokładnie do problemu z lekcji 1. Pygame nadal wkłada zdarzenie QUIT do kolejki, a pętla je nawet odczytuje, ale skoro nic z nim nie robimy, kliknięcie krzyżyka zostaje bez odpowiedzi. Samo czytanie kolejki nie wystarczy — trzeba jeszcze zareagować na właściwe zdarzenie.
❌ ZAPAMIĘTAJ — samo pygame.event.get() nie zamyka okna. Dopiero if zdarzenie.type == pygame.QUIT z pygame.quit() i exit() sprawia, że krzyżyk działa. Bez tej reakcji okno jest „głuche" jak w lekcji 1.
🎯 Zadanie dla Ciebie — okno, które słucha
1. Uruchom grę i zamknij ją krzyżykiem trzy razy z rzędu — upewnij się, że za każdym razem kończy się czysto.
2. Dodaj print("Zamykam gre!") tuż przed pygame.quit() i sprawdź w konsoli, że napis pojawia się dokładnie w chwili kliknięcia krzyżyka.
3. Zmień tytuł okna na własny i kliknij krzyżyk — przekonaj się, że tytuł nie ma wpływu na zamykanie.

Od teraz Twoja gra naprawdę słucha 🎮 — to ta sama pętla zdarzeń, w której później dołożysz strzałki sterujące statkiem. Właśnie zbudowałeś jej fundament.
🚀 Twoja gra reaguje na gracza, ale pędzi na pełnej prędkości — pętla kręci się tak szybko, jak pozwala komputer, więc na słabszym i mocniejszym sprzęcie zachowa się inaczej. W lekcji 3 założymy jej hamulec: zegar, który ustali stałe, płynne tempo gry.
Lekcja 3

Czas i FPS

Twoja gra z lekcji 2 działa, ale ma wadę, której jeszcze nie widać: pędzi tak szybko, jak tylko pozwala procesor. Na szybkim komputerze ruch byłby błyskawiczny, na wolnym — ślamazarny, a ta sama gra zachowywałaby się u każdego inaczej. Po tej lekcji ustalisz grze stałe, równe tempo, niezależne od sprzętu. To jedna z najważniejszych linijek w całej grze.
Gra rysuje obraz nie raz, lecz wiele razy na sekundę. Każdy pojedynczy obraz to klatka, a liczbę klatek pokazywanych w ciągu sekundy nazywamy FPS — od angielskiego frames per second, czyli „klatki na sekundę". To dokładnie ta sama zasada co w filmie albo kreskówce: pokaż dużo nieruchomych obrazków szybko po sobie, a oko zobaczy płynny ruch. 60 klatek na sekundę to standard, przy którym ruch wygląda gładko.

Problem w tym, że pętla while True z poprzednich lekcji kręci się najszybciej, jak potrafi komputer — czasem setki, a nawet tysiące razy na sekundę. To marnuje moc procesora i sprawia, że szybkość gry zależy od sprzętu. Potrzebujemy hamulca, który powie: „nie częściej niż 60 razy na sekundę".
60 klatek / sekundę 1 sekunda 1 30 60 1 klatka = 1/60 s (równe odstępy) czas → (zegar.tick(60) pilnuje równego tempa)
Tym hamulcem jest zegar. Tworzymy go raz, przed pętlą, linią zegar = pygame.time.Clock() — to nasz licznik czasu. Potem na samym końcu pętli, po pokazaniu klatki, wołamy zegar.tick(60). To polecenie sprawdza, ile czasu zajęła klatka, i jeśli była za szybka, wstrzymuje grę na resztę jednej sześćdziesiątej sekundy. Dzięki temu pętla nigdy nie wykona się częściej niż 60 razy na sekundę. Liczba 60 to zwykła liczba całkowita, jak te z kursu podstawowego (moduł M01) — możesz ją podmienić i od razu poczujesz różnicę. Zwróć uwagę na miejsce: Clock() tworzymy raz przed pętlą, a tick wołamy w każdej klatce, na jej końcu.
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy - Lekcja 3: Czas i FPS!")

zegar = pygame.time.Clock()      # <<< NOWE - tworzymy zegar gry (raz, przed petla)

while True:

    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    ekran.fill((0, 0, 30))
    pygame.display.flip()

    zegar.tick(60)               # <<< NOWE - najwyzej 60 klatek na sekunde
🎮 Co zobaczysz w oknie gry:
Okno wygląda tak samo jak w lekcji 2 — ciemny kosmos, zamykany krzyżykiem. Na pierwszy rzut oka nic się nie zmieniło, bo nic się jeszcze nie porusza. Różnica jest pod spodem: pętla kręci się teraz spokojnie, równo 60 razy na sekundę, a procesor przestał pracować na pełnych obrotach.
Skoro na ekranie nic się nie rusza, po co od razu zegar? Bo gdy w kolejnych lekcjach statek i asteroidy zaczną się przesuwać, to właśnie liczba klatek zdecyduje o ich szybkości. Ustawiając tempo teraz, sprawiamy, że ruch dodany później będzie u każdego gracza taki sam. Zegar to też dobry nawyk: praktycznie każda gra w Pygame ma tick na końcu pętli, bo bez niego marnuje moc komputera bez żadnego zysku.
✔️ Sprawdź, że działa
  • Gra uruchamia się i zamyka krzyżykiem dokładnie jak w lekcji 2.
  • W kodzie widzisz zegar = pygame.time.Clock() PRZED pętlą.
  • Widzisz zegar.tick(60) na samym końcu pętli, po pygame.display.flip().
  • Rozumiesz, że 60 oznacza najwyżej 60 klatek na sekundę.
Liczba w nawiasie tick to limit klatek na sekundę — i najlepiej poczujesz, co robi, gdy nią pokręcisz. Najpierw zgadnij efekt, potem uruchom.
🧪 Eksperyment — pokręć tempem gry
1. Zmień zegar.tick(60) na zegar.tick(5), uruchom i poruszaj myszą nad oknem oraz spróbuj zamknąć krzyżyk — jak „ciężko" reaguje okno?
2. Ustaw zegar.tick(120) — czujesz różnicę względem 60?
3. Wróć do zegar.tick(60) i tak zostaw — to wartość, na której zbudujemy resztę gry.
Uruchom po każdej zmianie. Efekt zobaczysz wyraźniej, gdy w kolejnych lekcjach coś zacznie się ruszać.
🧩 Zagadka — co się stanie, jeśli USUNIESZ zegar.tick?
Skasuj linię zegar.tick(60) i uruchom grę. Zgadnij: zobaczysz różnicę na ekranie czy nie?
Pokaż odpowiedź
Na oko okno wygląda tak samo (przecież nic się nie rusza), ale pod spodem dzieje się coś niedobrego — pętla kręci się tak szybko, jak potrafi komputer, często tysiące razy na sekundę. Obciążenie procesora skacze ku 100%, wentylator może przyspieszyć, a gdy później dodasz ruch, obiekty będą śmigać jak szalone i z różną prędkością na różnych komputerach. tick to hamulec, który temu zapobiega — dlatego zostaje na końcu pętli na stałe.
🎯 Zadanie dla Ciebie — poczuj tempo
1. Ustaw kolejno zegar.tick(30) i zegar.tick(60); klikając krzyżyk, oceń, przy którym oknie zamyka się odrobinę szybciej.
2. Sprawdź, co się dzieje przy zegar.tick(1) — ile razy na sekundę odświeża się wtedy gra?
3. Wróć do 60 i zostaw — to będzie tempo Twojej gry do końca kursu.

Właśnie nadałeś swojej grze rytm ⏱️ — 60 uderzeń serca na sekundę, w którym za chwilę poruszy się wszystko, co narysujesz. To Twój zegar i Twoje tempo.
🚀 Masz już okno, które słucha gracza i bije w równym rytmie. Brakuje jednej rzeczy, zanim cokolwiek narysujemy: trzeba wiedzieć, GDZIE na ekranie to umieścić. W lekcji 4 poznasz mapę ekranu — układ współrzędnych, dzięki któremu wskażesz Pygame dokładny punkt dla statku.
Lekcja 4

Układ współrzędnych

Po tej lekcji będziesz czytać ekran jak mapę. Nauczysz się wskazywać każdemu punktowi w oknie dokładny adres złożony z dwóch liczb — i dzięki temu w następnej lekcji postawisz swój pierwszy statek dokładnie tam, gdzie zechcesz, a nie „gdzieś tam". Bez nowego pliku, bez uruchamiania — dziś trenujemy głowę, nie palce.
Masz już okno (lekcja 1), które słucha gracza (lekcja 2) i bije w równym rytmie (lekcja 3). Zanim w następnej lekcji narysujemy w nim statek, musimy umówić się co do jednej rzeczy: jak wskazać Pygame, GDZIE dokładnie coś umieścić. Komputer nie rozumie „na dole po lewej" — potrzebuje liczb. Dlatego każdy punkt okna ma swój adres złożony z dwóch liczb: poziomej x i pionowej y. Ten system adresów nazywamy układem współrzędnych.
(0, 0) x → (w prawo) y ↓ (w dół) (800, 0) (0, 600) (800, 600)
Mapa ekranu rządzi się trzema prostymi zasadami. Po pierwsze, początek, czyli punkt (0, 0), leży w lewym górnym rogu okna — nie pośrodku, jak na lekcjach matematyki. Po drugie, współrzędna x rośnie w prawo: im większy x, tym dalej od lewej krawędzi. Po trzecie, współrzędna y rośnie w dół: im większy y, tym niżej. W naszym oknie 800×600 prawy dolny róg ma więc adres (800, 600). To po prostu reguła ekranu — wszystkie programy graficzne liczą tak samo, więc warto ją zapamiętać i mieć z głowy.
(400, 300) — środek (0, 600) — lewy dół (800, 0) — prawy góra
Spójrz na kilka adresów na rysunku. Środek okna to (400, 300) — połowa z 800 i połowa z 600. Lewy dolny róg to (0, 600): x przy samej lewej krawędzi, y na samym dole. Prawy górny to (800, 0). Gdy złapiesz tę zasadę, ustawienie czegokolwiek w oknie staje się proste — bo wystarczy, że wskażesz dwie liczby i już wiesz, gdzie wylądują. W kodzie taki adres to po prostu para liczb (x, y):
# Kazde miejsce na ekranie to para liczb (x, y):
lewy_gorny  = (0, 0)        # rog lewy gorny
prawy_gorny = (800, 0)      # x rosnie w prawo
lewy_dolny  = (0, 600)      # y rosnie w dol
srodek      = (400, 300)    # srodek okna 800x600
W każdej takiej parze pierwsza liczba mówi, jak daleko w prawo (to x), a druga — jak daleko w dół (to y). I to wszystko, czego dziś potrzebujesz: umieć wskazać dowolne miejsce w oknie dwiema liczbami. W następnej lekcji użyjemy takiego punktu, żeby postawić w nim swój statek.
✔️ Sprawdź, że rozumiesz
  • Wiesz, że punkt (0, 0) jest w lewym górnym rogu okna, nie na środku.
  • Potrafisz powiedzieć, w którą stronę rośnie x (w prawo) i y (w dół).
  • Wskażesz adres środka okna 800×600: (400, 300).
  • Dla punktu (x, y) wiesz, że pierwsza liczba to odległość w prawo, a druga w dół.
Najlepszy sposób, żeby to utrwalić, to poćwiczyć w głowie — bez komputera. Dla każdego punktu poniżej wyobraź sobie, gdzie wyląduje, zanim sprawdzisz odpowiedź.
🧪 Ćwiczenie na kartce — bez uruchamiania
Okno ma 800 pikseli szerokości i 600 wysokości. Powiedz, gdzie mniej więcej wyląduje każdy punkt:
1. (400, 300) — w której części okna?
2. (0, 599) — przy której krawędzi?
3. (800, 0) — w którym rogu?
4. (790, 590) — blisko którego rogu?
Sprawdź się
1. dokładnie na środku (połowa z 800 i połowa z 600). 2. tuż przy dolnej krawędzi, z lewej strony (x=0 to lewy brzeg, y=599 prawie na samym dole). 3. prawy górny róg (x na maksimum, y=0 to góra). 4. tuż przy prawym dolnym rogu.
🧩 Częsty błąd — kolejność liczb ma znaczenie
Te dwa punkty wyglądają podobnie, ale wskazują dwa różne miejsca. Zgadnij, czym różni się (100, 50) od (50, 100):
Pokaż odpowiedź
Pierwsza liczba to zawsze x (w prawo), druga to y (w dół) — więc kolejność nie jest dowolna. Punkt (100, 50) leży 100 w prawo i 50 w dół; (50, 100) — 50 w prawo i 100 w dół, czyli niżej i bardziej z lewej. Zamiana x z y nie wywoła błędu — miejsce po prostu wypadnie gdzie indziej, niż chciałeś. Zapamiętaj: najpierw poziom (x), potem pion (y).
🎯 Zadanie dla Ciebie — rozstaw punkty po rogach
Nic nie uruchamiasz — tylko podaj współrzędne (x, y). Okno ma 800×600. Wskaż punkt, który leży:
1. w lewym górnym rogu
2. w prawym górnym rogu
3. w lewym dolnym rogu
4. w prawym dolnym rogu
5. dokładnie na środku okna
Sprawdź odpowiedzi
1. (0, 0)  |  2. (800, 0)  |  3. (0, 600)  |  4. (800, 600)  |  5. (400, 300) — połowa z 800 i połowa z 600.


Zapamiętaj te cztery rogi 🧭 — w następnej lekcji w jednym z tych punktów postawisz swój pierwszy statek. Te same dwie liczby (x, y) podasz wtedy poleceniu draw.rect, którym go narysujesz.
🚀 Znasz już mapę ekranu: (0, 0) to lewy górny róg, x rośnie w prawo, a y w dół. W lekcji 5 użyjesz tej wiedzy do czegoś konkretnego — narysujesz swój pierwszy statek i ustawisz go dokładnie tam, gdzie chcesz. Czas wlecieć w grę!
Lekcja 5

Pierwszy statek

Po tej lekcji zobaczysz swój statek — niebieski prostokąt stojący u dołu ekranu na tle ciemnego kosmosu. Jeszcze nie ruszy się ani o piksel, bo sterowanie strzałkami dokładamy dopiero w lekcji 7. Dziś liczy się jedno: postawić statek dokładnie tam, gdzie chcemy, i zrozumieć, jak Pygame odczytuje jego pozycję.
Statek rysujemy jednym poleceniem: pygame.draw.rect. Samo „narysuj prostokąt" to dla Pygame za mało — musi wiedzieć trzy rzeczy i podajemy mu je zawsze w tej samej kolejności: na czym rysujemy, jakim kolorem, oraz gdzie i jak duży ma być prostokąt. Najpierw zobacz samo wywołanie z opisem każdej części:
pygame.draw.rect(
    ekran,                  # 1. NA CZYM rysujemy - nasze okno z lekcji 1
    (0, 200, 255),          # 2. JAKIM kolorem - trojka RGB (tu niebieski)
    (statek_x, statek_y,    # 3. GDZIE  - lewy gorny rog prostokata
     statek_szerokosc, statek_wysokosc))   #    JAK DUZY - szerokosc i wysokosc
Rozłóżmy te trzy informacje po kolei. Na czym — pierwszy argument to ekran, czyli powierzchnia okna stworzona w lekcji 1; to na niej maluje się wszystko, co widać.

Jakim kolorem — drugi argument to trójka liczb RGB od 0 do 255, mówiących, ile w kolorze czerwieni, zieleni i błękitu; (0, 200, 255) daje jasny niebieski. Gdzie i jak duży — trzeci argument to czwórka liczb: dwie pierwsze, (x, y), wskazują lewy górny róg prostokąta (a nie jego środek!), a dwie kolejne to szerokość w prawo i wysokość w dół.
🎨 Co to RGB? (kliknij, jeśli nie pamiętasz)
Kolor to trójka liczb (R, G, B), każda od 0 do 255 (poznałeś to w lekcji 1):
R = red (czerwony)  ·  G = green (zielony)  ·  B = blue (niebieski)
(255, 0, 0) czerwień  ·  (0, 255, 0) zielony  ·  (0, 0, 255) niebieski
(0, 0, 0) czarny  ·  (255, 255, 255) biały
Nasz statek (0, 200, 255) to dużo błękitu z domieszką zieleni — jasny niebieski.
A skąd Pygame wie, gdzie jest ten róg? Z układu współrzędnych, który poznałeś w lekcji 4: punkt (0, 0) leży w lewym górnym rogu okna, x rośnie w prawo, a y rośnie w dół. Spójrz na rysunek, gdzie ląduje statek:
(0, 0) x → y ↓ (statek_x, statek_y) szerokość wysokość
Żółta kropka w lewym górnym rogu statku to nasz punkt (x, y) — od niej Pygame odmierza szerokość w prawo i wysokość w dół. Dlatego, żeby przesunąć statek niżej, zwiększamy y, a nie zmniejszamy — to częsta pułapka, gdy pamięta się osie z matematyki, gdzie góra to większe liczby.

Skąd Pygame bierze te cztery liczby? Z czterech zmiennych, które sam ustawiasz na górze pliku, zanim zacznie się pętla gry:
statek_x = 350          # pozioma pozycja (od lewej)
statek_y = 500          # pionowa pozycja (od gory)
statek_szerokosc = 40   # szerokosc prostokata
statek_wysokosc = 50    # wysokosc prostokata
Dwie pierwsze mówią, gdzie statek jest, dwie kolejne — jak duży. Definiujemy je na górze i pod własnymi nazwami z dwóch powodów. Po pierwsze, statek_x czyta się o wiele jaśniej niż samo 350 zgubione wśród innych liczb w środku pętli. Po drugie, gdy zechcesz przesunąć statek, zmieniasz jedną liczbę w jednym, łatwym do znalezienia miejscu, a nie szukasz jej w gąszczu kodu. To zwykłe zmienne liczbowe — dokładnie takie same jak w kursie podstawowym (moduł M01).

Czas złożyć to w całość. Otwórz swój plik gry z poprzednich lekcji (okno, pętla gry i zegar) i dodaj do niego dwie rzeczy: cztery zmienne statku przed pętlą oraz jedno polecenie pygame.draw.rect w środku pętli. Tylko te nowe linie dopisujesz — poznasz je po komentarzu <<< NOWE; reszta została z lekcji 3.
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy - Lekcja 5: Pierwszy statek!")

zegar = pygame.time.Clock()

statek_x = 350          # <<< NOWE - pozycja pozioma (od lewej)
statek_y = 500          # <<< NOWE - pozycja pionowa (od gory)
statek_szerokosc = 40   # <<< NOWE - szerokosc statku w pikselach
statek_wysokosc = 50    # <<< NOWE - wysokosc statku w pikselach

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    ekran.fill((0, 0, 30))

    # <<< NOWE - rysujemy statek: niebieski prostokat
    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    pygame.display.flip()
    zegar.tick(60)
🎮 Co zobaczysz w oknie gry:
Ciemnogranatowe okno kosmosu, a u jego dołu jeden niebieski prostokąt — twój statek. Stoi nieruchomo: nie reaguje jeszcze na klawisze. Zamknij okno krzyżykiem, by zakończyć program.
Co dokładnie dorzuciłeś? Najpierw cztery liczby przed pętlą, bo statek wystarczy opisać raz — to jego dane startowe. Potem, już w pętli, jedno pygame.draw.rect tuż po ekran.fill, żeby statek malował się na świeżo wyczyszczonym tle w każdej klatce. Kolejność tych dwóch linii ma ogromne znaczenie i za chwilę zobaczysz, dlaczego.
✔️ Sprawdź, że działa
  • Po uruchomieniu widzisz niebieski prostokąt u dołu okna, a nie pusty ekran.
  • Potrafisz wskazać w kodzie, która liczba to statek_x, a która statek_y.
  • Gdy zwiększysz statek_y, statek przesuwa się w dół (nie w górę) — i wiesz, dlaczego.
  • Komentarz NOWE wskazuje dokładnie te linie, których nie było w lekcji 3.
Teraz najlepsza część — pomajstruj przy statku samodzielnie. Te cztery liczby zrozumiesz naprawdę dopiero wtedy, gdy sam je pozmieniasz i zobaczysz skutek na ekranie. Przy każdej próbie najpierw postaw hipotezę („co się stanie?"), dopiero potem uruchom grę i sprawdź, czy miałeś rację.
🧪 Eksperyment — zmień jedną liczbę, obserwuj statek
1. Zamień kolor (0, 200, 255) na (255, 0, 0) — jakiego koloru będzie statek?
2. Zwiększ statek_y z 500 na 550 — w którą stronę przesunie się statek?
3. Ustaw statek_szerokosc = 120 — co stanie się z kształtem statku?
4. Spróbuj statek_x = 0 i statek_y = 0 — w którym rogu wyląduje?
Uruchom grę po każdej zmianie i porównaj wynik ze swoją hipotezą. Tu odpowiedzi nie podajemy — sam je zobaczysz na ekranie.
🧩 Zagadka — a co, jeśli narysujesz statek POZA ekranem?
Ustaw statek_x = 900 (okno ma tylko 800 pikseli szerokości) i uruchom. Najpierw zgadnij — program się wykrzaczy? Statek owinie się na drugą stronę? Postaw hipotezę, dopiero potem rozwiń odpowiedź.
Pokaż odpowiedź
Ani jedno, ani drugie — Pygame nie zgłasza żadnego błędu. Rysuje statek dokładnie tam, gdzie kazałeś, tyle że to miejsce leży poza widocznym oknem, więc nie widać nic. Statek istnieje, po prostu jest poza kadrem.

Dlaczego? pygame.draw.rect maluje po współrzędnych i nie sprawdza, czy mieszczą się w oknie — okno pokazuje tylko wycinek od 0 do 800 w poziomie i od 0 do 600 w pionie. To się przyda przy asteroidach, które będą startować nad ekranem (z ujemnym y), zanim wlecą w kadr.
Pygame nie ostrzeże Cię też w częstszej sytuacji — gdy pomylisz kolejność rysowania w pętli. Sprawdźmy to tą samą metodą: najpierw zgadnij, potem uruchom.
🧩 Druga zagadka — co zobaczysz po uruchomieniu tego kodu?
Statek rysowany, a zaraz po nim czyszczenie tła. Zgadnij, zanim uruchomisz — pojawi się statek czy pusty ekran?
pygame.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, 40, 50))
ekran.fill((0, 0, 30))     # uwaga na kolejnosc!
Pokaż odpowiedź
Pusty, ciemny ekran — statku nie widać! ekran.fill wykonuje się PO pygame.draw.rect, więc w każdej klatce zamalowuje świeżo narysowanego statka. Rysujesz go i natychmiast przykrywasz tłem.
❌ ZAPAMIĘTAJ — w pętli gry zawsze najpierw ekran.fill, POTEM rysowanie. Nigdy odwrotnie!
🎯 Zadanie dla Ciebie — zaparkuj statek (policz!)
Okno ma 800×600, statek 40×50, a (statek_x, statek_y) to jego lewy górny róg. Dobierz statek_x i statek_y tak, żeby CAŁY statek dotykał:
1. lewego górnego rogu okna
2. prawego górnego rogu
3. lewego dolnego rogu
4. prawego dolnego rogu

A teraz zależność — tu trzeba wyprowadzić wzór, nie zgadnąć:
5. ustaw statek IDEALNIE na środku okna — jaki wzór na statek_x i statek_y zadziała nawet, gdyby okno albo statek miały inny rozmiar?
Sprawdź odpowiedzi
1. (0, 0)  |  2. (760, 0) — bo 800−40  |  3. (0, 550) — bo 600−50  |  4. (760, 550).
5. środek: statek_x = SZEROKOSC//2 - statek_szerokosc//2 (=380), statek_y = WYSOKOSC//2 - statek_wysokosc//2 (=275). Reguła: do prawej/dołu odejmujesz rozmiar statku od rozmiaru okna; na środek — połowa okna minus połowa statku.


Na koniec coś tylko dla Ciebie — to już Twój statek 🚀. Nadaj mu charakter: ustaw go w wybranym rogu i dobierz własny kolor (pamiętasz RGB?). Nie ma jednej dobrej odpowiedzi — to Twoja decyzja, a od następnej lekcji to właśnie tym statkiem polecisz dalej.
🚀 Już w lekcji 6 dołożymy HUD — napis na ekranie pokazujący pozycję statku na żywo. Zobaczysz, jak liczby x i y z kodu zamieniają się w tekst widoczny w oknie gry. To pierwszy krok w stronę tablicy wyniku, którą zbudujemy później.
Lekcja 6

Tekst na ekranie (HUD)

Po tej lekcji w rogu okna pojawi się napis, który na żywo pokazuje pozycję Twojego statku — coś w stylu Pozycja statku: X=380 Y=520. To pierwszy element HUD, czyli tablicy z informacjami, którą gracz widzi podczas gry: później dołożymy na niej liczbę żyć i wynik. Statek wciąż stoi w miejscu, bo sterowanie strzałkami dokładamy dopiero w lekcji 7 — dziś uczymy okno mówić tekstem.
Masz już statek z lekcji 5 i umiesz wskazać każdy punkt okna z lekcji 4. Problem w tym, że pygame.draw.rect potrafi malować tylko prostokąty — nie zna się na literach. Tekst w Pygame powstaje w trzech krokach, zawsze w tej samej kolejności: najpierw wybierasz czcionkę, potem zamieniasz słowa na gotowy obrazek-napis, a na końcu naklejasz ten obrazek na ekran w wybranym miejscu. Zobacz, gdzie wyląduje napis, zanim rozłożymy każdy krok osobno.
X=380 Y=520 napis na ekranie (HUD) statek
Krok pierwszy to czcionka. Tworzymy ją raz, na górze pliku, poleceniem pygame.font.SysFont, któremu podajemy dwie rzeczy: nazwę kroju (np. "Arial") i wielkość liter w pikselach (np. 24). Wynik zapisujemy w zmiennej font — to nasza maszyna do pisania, której będziemy używać przez całą grę. Robimy to raz, bo tworzenie czcionki jest dość kosztowne i nie ma sensu powtarzać tego w każdej klatce.

Krok drugi to zamiana tekstu na obrazek. Polecenie font.render bierze trzy informacje i zwraca gotowy obrazek z napisem: co napisać (tekst), czy wygładzać krawędzie liter (True = ładniej) oraz jakim kolorem (trójka RGB, tak jak przy statku). Dlaczego od razu obrazek, a nie po prostu „tekst"? Bo ekran potrafi wyświetlać tylko obrazki — litery muszą najpierw zamienić się w pikselki.
font = pygame.font.SysFont("Arial", 24)         # 1. maszyna do pisania
napis = font.render("Witaj!", True, (255, 255, 255))  # 2. tekst -> obrazek (True=wygladz litery, (255,255,255)=bialy)
ekran.blit(napis, (10, 10))                     # 3. naklej obrazek w punkcie (10, 10)
Krok trzeci to ekran.blit. Słowo blit znaczy tyle co „naklej" — bierzemy gotowy obrazek-napis i przyklejamy go na ekran w punkcie (x, y), który (jak prostokąt z lekcji 4) wskazuje jego lewy górny róg. Punkt (10, 10) to mały odstęp od lewego górnego rogu okna, żeby napis nie kleił się do samej krawędzi.

Zostaje jedno: skąd biorą się liczby w napisie. Piszemy literę f tuż przed cudzysłowem i wstawiamy zmienne prosto w nawiasy klamrowe: f"Pozycja statku: X={statek_x} Y={statek_y}" — Python sam podmienia {statek_x} i {statek_y} na aktualne wartości zmiennych. To ten sam mechanizm f-stringów, który poznajesz w kursie podstawowym (moduł M01.4, formatowanie i łączenie tekstu). Przy okazji ustawiamy statek na środku w poziomie wzorem SZEROKOSC // 2 - statek_szerokosc // 2; podwójny ukośnik // to dzielenie całkowite, dające równą liczbę pikseli bez ułamka (kurs podstawowy, moduł M01).
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy - Lekcja 6: Tekst na ekranie")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2   # <<< NOWE - srodek w poziomie
statek_y = WYSOKOSC - 80                             # <<< NOWE - blisko dolu ekranu

font = pygame.font.SysFont("Arial", 24)             # <<< NOWE - czcionka napisow

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    # <<< NOWE - zamieniamy liczby na tekst i naklejamy napis na ekran
    tekst = f"Pozycja statku: X={statek_x} Y={statek_y}"
    napis = font.render(tekst, True, (255, 255, 255))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)
🎮 Co zobaczysz w oknie gry:
Ten sam ciemny kosmos co w lekcji 5, statek wyśrodkowany u dołu okna, a w lewym górnym rogu biały napis Pozycja statku: X=380 Y=520. Napis się nie zmienia, bo statek jeszcze nie rusza — ale jest już gotów pokazywać jego ruch, gdy tylko damy mu sterowanie.
Zwróć uwagę, co stoi gdzie. Czcionkę font tworzymy raz, przed pętlą, bo wystarczy ją przygotować jeden raz. Za to font.render i ekran.blit wołamy w pętli, tuż po ekran.fill i po narysowaniu statku — i to nie przypadek. Skoro ekran.fill w każdej klatce zamalowuje całe okno na nowo, to napis też trzeba namalować od nowa za każdym razem, inaczej zniknąłby razem z poprzednią klatką. Dlatego rysowanie napisu, podobnie jak statku, musi siedzieć wewnątrz pętli gry.
✔️ Sprawdź, że działa
  • W lewym górnym rogu okna widzisz biały napis z literami X i Y, a nie sam statek.
  • Liczby w napisie zgadzają się z pozycją statku (X=380, Y=520 przy ustawieniach z kodu).
  • Potrafisz wskazać w kodzie trzy kroki tekstu: SysFont, render i blit.
  • Czcionka font jest tworzona przed pętlą, a render i blit — w środku pętli.
Teraz pomajstruj przy napisie samodzielnie. Przy każdej próbie zmieniasz jedną rzecz i uruchamiasz grę — tu nie podajemy odpowiedzi, bo wszystko zobaczysz na ekranie szybciej, niż przeczytasz.
🧪 Eksperyment — pobaw się napisem
1. Zmień treść w cudzysłowie na własną, np. "Mój statek bojowy".
2. Zamień kolor (255, 255, 255) na (255, 255, 0) — jaki kolor liter teraz?
3. Przesuń napis: zamiast (10, 10) wpisz (10, 560).
4. Powiększ litery: w SysFont daj 48 zamiast 24.
🧩 Częsty błąd — co podać do render?
Ktoś chce pokazać samą liczbę i wpisuje do render wprost zmienną liczbową. Zgadnij, czy to zadziała:
napis = font.render(statek_x, True, (255, 255, 255))   # statek_x to LICZBA
Pokaż odpowiedź
Program się wykrzaczy z błędem typu (TypeError) — render chce dostać tekst, a nie samą liczbę. Liczbę trzeba najpierw zamienić w tekst: albo wstawić ją do napisu f-stringiem f"X={statek_x}", albo owinąć w str(statek_x). Dlatego w naszym kodzie liczba zawsze trafia do napisu przez f-string i nawiasy klamrowe {}.
❌ ZAPAMIĘTAJ — font.render wyświetla wyłącznie TEKST. Liczbę najpierw zamień w tekst (przez .format albo str()), inaczej dostaniesz błąd.
🎯 Zadanie dla Ciebie — zaprojektuj swój HUD
Dołóż w pętli (tuż pod istniejącym napisem) drugi napis, używając kolejnej pary render + blit:
1. niech pokazuje rozmiar okna, np. "Ekran: 800 x 600"
2. ustaw go o linijkę niżej niż pierwszy — jaki punkt (x, y) wybierzesz, żeby nie nachodził na napis z pozycją?
3. nadaj mu inny kolor niż biały
Sprawdź się
Pierwszy napis stoi w (10, 10), a litery mają 24 piksele wysokości — więc drugi zmieści się od (10, 40) w dół (10 + około 30). Pamiętaj o trzech rzeczach: nowy tekst, nowy render, nowy blit w innym punkcie.


To już Twój HUD 🛰️ — tablica, którą gracz ma przed oczami przez całą grę. Dobierz kolory i ułożenie tak, żeby pasowały do Twojego statku; w kolejnych lekcjach dopniemy do niej liczby, które naprawdę będą się zmieniać.
🚀 Napis już działa, ale pokazuje wciąż te same liczby, bo statek tkwi w miejscu. W lekcji 7 to się zmienia — bierzesz statek pod własne palce: strzałki na klawiaturze, płynny ruch w cztery strony i HUD, który na żywo liczy, gdzie właśnie jesteś. Czas wreszcie polecieć!
Lekcja 7

Ruch statku

To lekcja, na którą czekałeś od początku: statek zaczyna latać. Naciśniesz strzałkę i niebieski prostokąt posłusznie ruszy w tę stronę — lewo, prawo, góra, dół — a HUD z lekcji 6 będzie na żywo liczył, gdzie się znajdujesz. Od tej chwili to już nie obrazek, tylko gra, którą naprawdę sterujesz palcami. Dorobimy też niewidzialne ściany, żeby statek nie uciekł poza okno.
Pamiętasz zdarzenia z lekcji 2? Kliknięcie krzyżyka (QUIT) trafiało do kolejki, którą czytaliśmy pętlą po każdej klatce:
for zdarzenie in pygame.event.get():
    if zdarzenie.type == pygame.QUIT:
        pygame.quit()
        exit()
Naciśnięcie klawisza to też zdarzenie i też trafia do tej kolejki. Ale do płynnego ruchu wygodniej zapytać wprost: „które klawisze gracz właśnie trzyma?" — i sprawdzać to w każdej klatce (nie pojedyncze naciśnięcie, lecz to, co jest trzymane teraz). Robi to pygame.key.get_pressed(); stan wszystkich klawiszy zapisujemy w zmiennej klawisze. Skoro pętla kręci się 60 razy na sekundę (zegar z lekcji 3), drobne przesunięcia zlewają się w płynny lot.
klawisze = pygame.key.get_pressed()   # co jest TERAZ trzymane
A teraz sedno: ruch to zmiana współrzędnych statku. Z lekcji 4 pamiętasz mapę ekranu — statek_x rośnie w prawo, statek_y w dół. Każda strzałka zmienia jedną z tych liczb o predkosc. Zobacz to na czterech obrazkach, po jednym na kierunek:
PRAWO →
statek_x += predkosc
← LEWO
statek_x -= predkosc
DÓŁ ↓
statek_y += predkosc
GÓRA ↑
statek_y -= predkosc
Wzór czytamy tak: statek_x += predkosc znaczy „weź obecne statek_x i dodaj do niego predkosc" (skrót += i jego brat -= znasz z kursu podstawowego, moduł M01). predkosc to liczba pikseli na klatkę — im większa, tym szybszy statek. Każdą strzałkę sprawdzamy osobnym if (moduł M02), a nie elif — dzięki temu możesz trzymać dwie naraz (np. prawo + dół) i lecieć na ukos.
import pygame

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("AstroPython - Lekcja 7: Ruch statku")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5                # <<< NOWE - ile pikseli statek pokonuje na klatke

font = pygame.font.SysFont("Arial", 24)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()   # <<< NOWE - ktore klawisze sa teraz wcisniete

    if klawisze[pygame.K_LEFT]:           # <<< NOWE - strzalka w lewo
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:          # <<< NOWE - strzalka w prawo
        statek_x += predkosc
    if klawisze[pygame.K_UP]:             # <<< NOWE - strzalka w gore
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:           # <<< NOWE - strzalka w dol
        statek_y += predkosc

    # <<< NOWE - niewidzialne sciany: statek nie wyleci poza okno
    if statek_x < 0:
        statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc:
        statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0:
        statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc:
        statek_y = WYSOKOSC - statek_wysokosc

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    tekst = f"Steruj strzalkami | X={statek_x} Y={statek_y}"
    napis = font.render(tekst, True, (200, 200, 200))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)
🎮 Co zobaczysz w oknie gry:
Statek, który nareszcie słucha! Naciśnij strzałkę — prostokąt płynnie sunie w tę stronę, a liczby X i Y w HUD zmieniają się na żywo. Dolecisz do krawędzi okna i statek się o nią zatrzyma, jakby trafił na niewidzialną ścianę. Trzymaj dwie strzałki naraz, a poleci na ukos.
Po co te cztery if z granicami na końcu? Bez nich strzałka trzymana zbyt długo wypchnęłaby statek poza okno — a z lekcji 5 wiesz, że Pygame rysuje też poza kadrem i wtedy statek po prostu znika z oczu. Każda z tych linii pilnuje jednej krawędzi: jeśli statek_x spadłby poniżej zera (za lewą krawędź), wciskamy go z powrotem do zera; jeśli przekroczyłby SZEROKOSC - statek_szerokosc (czyli CAŁY statek dotknąłby prawej krawędzi), zatrzymujemy go tam. Tak samo w pionie. To te niewidzialne ściany, które trzymają statek w grze.
✔️ Sprawdź, że działa
  • Strzałki w cztery strony przesuwają statek, a liczby X i Y w HUD reagują na żywo.
  • Statek zatrzymuje się przy każdej z czterech krawędzi i nie znika za oknem.
  • Trzymając dwie strzałki naraz (np. prawo + dół), lecisz na ukos.
  • Wiesz, że statek_y += predkosc przesuwa w dół, bo y rośnie w dół.
Najlepiej poczujesz ten kod, gdy sam pokręcisz pokrętłami. Zmieniaj po jednej rzeczy i od razu lataj — odpowiedzi nie zdradzamy, bo statek pokaże Ci je sam.
🧪 Eksperyment — popilotuj ustawieniami
1. Ustaw predkosc = 12 i polataj — jak zmienia się sterowanie?
2. Teraz predkosc = 1 — jak teraz lata statek?
3. Usuń (zakomentuj #) cztery if z granicami i dolecień do krawędzi — co się stanie ze statkiem?
4. Zamień statek_x -= predkosc na += przy strzałce w lewo — w którą stronę poleci teraz po wciśnięciu lewej?
🧩 Częsty błąd — jeden znak, a statek nie chce jechać
Ktoś zamiast += wpisuje zwykłe = przy strzałce w prawo. Zgadnij, jak teraz zachowa się statek:
if klawisze[pygame.K_RIGHT]:
    statek_x = predkosc      # mialo byc +=, jest samo =
Pokaż odpowiedź
Po wciśnięciu prawej statek nie pojedzie w prawo — przeskoczy na pozycję x = 5 (czyli przy lewej krawędzi) i tam utknie. Bo = nie dodaje, tylko nadpisuje: ustawia statek_x na samą wartość predkosc, gubiąc to, gdzie statek był wcześniej. += dodaje do obecnej pozycji, a = ją zastępuje — to dwie zupełnie różne rzeczy.
❌ ZAPAMIĘTAJ — += dodaje do obecnej wartości, a samo = ją nadpisuje. Do ruchu zawsze używaj += lub -=, nigdy samego =. Pod spodem += to po prostu skrót:
statek_x = statek_x + 1     # weź obecny x i dodaj 1
statek_x += 1               # dokładnie to samo, tylko krócej
🎯 Zadanie dla Ciebie — dorób sterowanie WSAD
Wielu graczy woli klawisze WSAD od strzałek. Dołóż je obok strzałek (nie zamiast):
1. dodaj if klawisze[pygame.K_a]: statek_x -= predkosc (A w lewo)
2. dorób analogicznie K_d w prawo, K_w w górę, K_s w dół
3. sprawdź, czy statek słucha teraz i strzałek, i WSAD
Sprawdź się
Każdy nowy klawisz to osobny if z tym samym przesunięciem co odpowiadająca mu strzałka — np. K_w robi to samo co K_UP (statek_y -= predkosc). Granice na końcu działają dalej bez zmian, bo pilnują samych liczb statek_x i statek_y, nieważne, który klawisz je zmienił.


Od teraz to Ty pilotujesz 🕹️ — dobierz prędkość i klawisze tak, jak najwygodniej lata się właśnie Tobie. To Twój statek i Twoje sterowanie.
🚀 Umiesz już latać, ale po pustym kosmosie. Czas na Twój pierwszy samodzielny projekt — stwórz sobie towarzystwo, asteroidę! Tym razem bez gotowca na start: spróbujesz sam, korzystając z tego, co już umiesz. Do dzieła!
🚀 Projekt 1

Stwórz asteroidę — sam!

Latasz już statkiem po kosmosie — ale zupełnie sam. Czas na Twój pierwszy samodzielny projekt: stwórz sobie towarzystwo — asteroidę. Tym razem NIE dostajesz gotowego kodu na start; spróbujesz sam. Spokojnie — wszystko, czego trzeba, już umiesz z lekcji 4-7: współrzędne, pygame.draw.rect, zmienne, a nawet ruch. Podpowiedzi i pełne rozwiązanie czekają niżej, ale zajrzyj tam dopiero po własnej próbie.
Mamy do Ciebie prośbę 🪨. Do swojej gry z lekcji 7 dorzuć asteroidę — na razie zwykły szary kwadrat. Czego oczekujemy:
• trzy nowe zmienne na górze, obok zmiennych statku: asteroida_x, asteroida_y, asteroida_rozmiar (np. 40)
• kolor szary: (160, 160, 160)
• niech pojawi się u góry okna (małe asteroida_y, np. 50) i gdzieś w poziomie (np. asteroida_x = 200)
• narysuj ją w pętli, tak samo jak statek — tym samym poleceniem pygame.draw.rect

A jeśli poczujesz się pewnie — bonus 🚀: spraw, żeby asteroida spadała sama, bez dotykania klawiszy. Tu jest haczyk do przemyślenia — statek ruszał się tylko WTEDY, gdy wcisnąłeś strzałkę (zmiana siedziała w if). Asteroida ma lecieć cały czas, sama z siebie. Spróbuj sam wykombinować, co zrobić z asteroida_y w każdym obrocie pętli i gdzie to wstawić. A jeśli nie wpadniesz — żaden problem: ruchu asteroidy nauczymy się razem, krok po kroku, w lekcji 9. To bonus, nie musisz go teraz dopiąć.
📦 Twój ekwipunek — co masz pod ręką (same rzeczy z poprzednich lekcji):
narysuj prostokąt (lekcja 5) → pygame.draw.rect(ekran, kolor, (x, y, szerokosc, wysokosc))
kolor szary jako RGB (lekcja 1) → (160, 160, 160)
ruch przez dodawanie (lekcja 7) → zmienna += ile, np. asteroida_y += 3
gdzie rysować → w pętli, PO ekran.fill (inaczej tło zamaluje obiekt — pamiętasz zagadkę z lekcji 5?)
🧪 Twoja kolej — najpierw spróbuj sam
Otwórz swój plik z lekcji 7 i dodaj asteroidę. Utknąłeś? Rozwiń podpowiedzi — ale dopiero, gdy naprawdę utkniesz:
💡 Podpowiedź 1 — jak narysować
Asteroida to prostokąt, więc rysujesz ją tym samym poleceniem co statek — tylko z jej zmiennymi i szarym kolorem:
pygame.draw.rect(ekran, (160, 160, 160),
                 (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))
Wstaw ją w pętli, tuż po narysowaniu statku.
💡 Podpowiedź 2 — ruch (bonus)
Ha, z Ciebie ciekawski! 😏 Akurat ruchu tu nie zdradzimy — szkoda psuć zabawę. Mała wskazówka: pamiętasz += z lekcji 7? Spróbuj z nim pokombinować. A jeśli nie wyjdzie — spokojnie, ruchu asteroidy nauczymy się razem w lekcji 9. 🚀
🎮 Co powinieneś zobaczyć:
Twój niebieski statek (którym dalej sterujesz strzałkami) ORAZ szary kwadrat u góry okna. Jeśli zrobiłeś bonus — asteroida powoli zjeżdża w dół. To Twój pierwszy obiekt dodany całkiem samodzielnie!
✅ Rozwiązanie — zajrzyj dopiero po własnej próbie
Pokaż rozwiązanie
Dodajesz do gry z lekcji 7 dokładnie trzy rzeczy — żadnej nowej magii, same rzeczy z lekcji 5 i 7:
# 1) NA GORZE, obok zmiennych statku:
asteroida_x = 200
asteroida_y = 50
asteroida_rozmiar = 40

# 2) W PETLI, po ekran.fill i po narysowaniu statku:
pygame.draw.rect(ekran, (160, 160, 160),
                 (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

# 3) BONUS (ruch): dodasz go w lekcji 9 - tam zrobimy to razem.

Udało się? 🎉 Właśnie samodzielnie rozbudowałeś swoją grę. To Twoja asteroida — za chwilę stanie się częścią rozgrywki.
🚀 Masz własną asteroidę! Jest jednak jeden problem: ląduje zawsze w tym samym miejscu, więc gra jest przewidywalna. W lekcji 8 nauczymy ją pojawiać się losowo — za każdym razem gdzie indziej. Twoja asteroida staje się oficjalnym wrogiem gry!
Lekcja 8

Losowa asteroida

W projekcie dorzuciłeś do gry własną asteroidę — ale ląduje zawsze w tym samym miejscu, więc rozgrywka jest przewidywalna. Po tej lekcji nauczymy ją pojawiać się za każdym uruchomieniem gdzie indziej. To zasługa losowości: sztuczki, dzięki której gra nigdy nie wygląda dwa razy tak samo. Asteroida na razie wisi w miejscu; spadać zacznie w lekcji 9.
Gdybyśmy wpisali asteroidzie sztywną pozycję, lądowałaby zawsze w tym samym punkcie — nudno i przewidywalnie. Dlatego ją wylosujemy. Python ma do tego gotowy moduł random; dopisujemy na górze pliku import random (to jak wyjęcie kostki z szuflady, zanim nią rzucisz; moduły znasz z kursu podstawowego, moduł M01). Najprościej widać to na jednej liczbie — wylosujmy coś od 1 do 10:
import random

liczba = random.randint(1, 10)   # wylosuje 1, 2, 3 ... albo 10
print(liczba)                    # za kazdym uruchomieniem inna
random.randint(a, b) losuje liczbę całkowitą od a do b włącznie — jak rzut kostką, której sami ustalamy liczbę ścianek. A teraz najlepsze: zamiast byle liczby wylosujemy pozycję asteroidy. Potrzebujemy trzech losowań — pozioma pozycja (w szerokości okna), pionowa i rozmiar — każdemu damy własny przedział:
asteroida_x = random.randint(0, SZEROKOSC - 50)   # gdzies miedzy lewa a prawa krawedzia
asteroida_y = random.randint(50, 250)             # gdzies w gornej czesci okna
asteroida_rozmiar = random.randint(20, 55)        # raz mala, raz spora
🧩 Zagadka — czemu SZEROKOSC - 50, a nie samo SZEROKOSC?
Gdybyśmy losowali asteroida_x aż do SZEROKOSC (800), co poszłoby nie tak? Zgadnij, zanim rozwiniesz.
Pokaż odpowiedź
Asteroida miałaby lewy róg aż przy x = 800, a sama ma jeszcze swoje ~50 pikseli szerokości — więc wystawałaby poza prawą krawędź (pamiętasz z lekcji 5: Pygame rysuje też poza oknem, bez błędu). Odejmując jej rozmiar (SZEROKOSC - 50) pilnujemy, żeby CAŁA zmieściła się na ekranie.
losowe miejsce (random) — za każdym razem inne to uruchomienie duchy = inne możliwe miejsca statek
Spójrz na rysunek: pełna szara asteroida to miejsce z jednego uruchomienia, a przerywane kontury pokazują inne punkty, w których równie dobrze mogłaby wylądować następnym razem. Pozycję losujemy raz, na górze pliku przed pętlą — bo asteroida ma stać w jednym miejscu przez całe to uruchomienie, a nie skakać co klatkę. Samą asteroidę rysujemy tym samym poleceniem co statek, pygame.draw.rect, tylko w kolorze szarym (160, 160, 160) i w wylosowanych współrzędnych.
import pygame
import random          # <<< NOWE - modul do losowania liczb

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy - Lekcja 8: Losowa asteroida")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)   # <<< NOWE - losowa pozycja X
asteroida_y = random.randint(50, 250)             # <<< NOWE - losowa pozycja Y
asteroida_rozmiar = random.randint(20, 55)        # <<< NOWE - losowy rozmiar

font = pygame.font.SysFont("Arial", 24)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0:
        statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc:
        statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0:
        statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc:
        statek_y = WYSOKOSC - statek_wysokosc

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    # <<< NOWE - rysujemy asteroide (szary kwadrat) w wylosowanym miejscu
    pygame.draw.rect(ekran, (160, 160, 160),
                     (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    tekst = f"Asteroida: X={asteroida_x} Y={asteroida_y} rozmiar={asteroida_rozmiar}"
    napis = font.render(tekst, True, (200, 200, 200))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)
🎮 Co zobaczysz w oknie gry:
Twój statek (sterowany jak w lekcji 7) i nowa szara asteroida wisząca w górnej części okna. W HUD widać jej wylosowane współrzędne i rozmiar. Zamknij grę i uruchom ponownie — asteroida pojawi się w innym miejscu i może mieć inny rozmiar.
✔️ Sprawdź, że działa
  • W górnej części okna widać szarą asteroidę obok Twojego sterowanego statku.
  • HUD pokazuje wylosowane X, Y i rozmiar asteroidy.
  • Po ponownym uruchomieniu asteroida ląduje w innym miejscu (a czasem ma inny rozmiar).
  • Na górze pliku masz import random, a losowanie stoi przed pętlą.
Teraz poeksperymentuj z losowaniem. Zmieniaj zakresy i uruchamiaj kilka razy — dopiero seria uruchomień pokazuje, co naprawdę robi randint. Odpowiedzi nie podajemy; zobaczysz je na ekranie.
🧪 Eksperyment — pokręć kostką losowości
1. Uruchom grę 4–5 razy z rzędu i obserwuj, gdzie ląduje asteroida.
2. Zwęź zakres: asteroida_x = random.randint(350, 450) i uruchom kilka razy.
3. Zmień rozmiar na zawsze duży: random.randint(60, 90).
4. Zmień kolor asteroidy z (160, 160, 160) na własny.
🧩 Zagadka — dlaczego asteroida co chwilę gdzie indziej?
Nie ruszasz kodu, tylko uruchamiasz tę samą grę dwa razy — i asteroida ląduje w dwóch różnych miejscach. Zgadnij, dlaczego, zanim rozwiniesz odpowiedź.
Pokaż odpowiedź
Bo w kodzie nie ma żadnej konkretnej liczby pozycji — jest tylko polecenie „wylosuj coś z tego przedziału". random.randint przy każdym starcie programu sięga po nową losową wartość, więc asteroida_x i asteroida_y rodzą się na nowo dopiero w chwili uruchomienia. Komputer nie pamięta poprzedniego losowania — dlatego ta sama gra potrafi za każdym razem wyglądać inaczej.
🧩 Częsty błąd — zapomniany import
Ktoś używa random.randint, ale zapomniał dopisać na górze import random. Zgadnij, co zrobi program:
asteroida_x = random.randint(0, 750)   # a na gorze pliku BRAK: import random
Pokaż odpowiedź
Program przerwie się z błędem NameError: name 'random' is not defined. Python nie zna słowa random, bo nikt mu nie powiedział, gdzie go szukać. Każdy moduł trzeba najpierw wyciągnąć z biblioteki poleceniem import — dopiero wtedy jego narzędzia stają się dostępne.
❌ ZAPAMIĘTAJ — zanim użyjesz narzędzia z modułu (np. random.randint), dopisz na górze pliku import random. Bez tego dostaniesz NameError.
🎯 Zadanie dla Ciebie — pole asteroid
Jedna asteroida to dopiero początek. Dorób drugą, niezależną:
1. dodaj na górze trzy nowe zmienne: asteroida2_x, asteroida2_y, asteroida2_rozmiar, każdą wylosowaną przez random.randint
2. dorysuj ją drugim pygame.draw.rect w innym kolorze
3. uruchom kilka razy — obie powinny lądować niezależnie od siebie
Sprawdź się
Druga asteroida to po prostu komplet własnych zmiennych (z osobnymi nazwami, żeby nie nadpisać pierwszej) plus drugie pygame.draw.rect z tymi nowymi nazwami. Skoro każda ma swoje random.randint, ich pozycje są od siebie niezależne.


To Twoje pole asteroid ☄️. Ale zatrzymaj się i pomyśl: gdybyś chciał 20 asteroid, kopiowałbyś te linijki dwadzieścia razy — 60 zmiennych i 20 rysowań? Tak się nie da na dłuższą metę. I dobrze, że to czujesz — w dalszej części kursu poznasz listy, dzięki którym nawet setka asteroid mieści się w kilku linijkach. Na razie wystarczy, że umiesz dorobić jedną.
🚀 Asteroida już jest, ale wisi nieruchomo — trudno nazwać ją zagrożeniem, skoro grzecznie czeka. W lekcji 9 wprawimy ją w ruch: zacznie spadać z góry na dół, a gdy zniknie pod ekranem, wróci na górę w nowym miejscu. Z lekcji 7 pamiętasz już ruch statku, więc animacja asteroidy pójdzie Ci znajomą drogą.
Lekcja 9

Spadająca asteroida

Pora, żeby asteroida przestała wisieć i ruszyła do ataku. Po tej lekcji będzie spadać z góry na dół, a kiedy zniknie pod dolną krawędzią okna, wróci na samą górę w nowym, wylosowanym miejscu — i tak bez końca. To pierwszy prawdziwy ruch przeciwnika i zarazem Twoje pierwsze spotkanie z animacją. Statkiem dalej uciekasz strzałkami.
Animacja w grze nie jest żadną magią — to ten sam trik, który widziałeś przy ruchu statku w lekcji 7. Pętla gry powtarza się około 60 razy na sekundę (zegar z lekcji 3), a my w każdym obrocie odrobinę zmieniamy pozycję asteroidy. Pojedynczy krok jest malutki, ale 60 takich kroków na sekundę oko widzi jako płynne spadanie. Mówiąc krótko: animacja to zmiana pozycji obiektu w każdej klatce.
start: y = -50 (nad ekranem) klatka 1 klatka 2 klatka 3 spada co klatkę (y += predkosc)
Robimy to jedną linijką: asteroida_y += asteroida_predkosc. To znów znajomy skrót += z lekcji 7 — „dodaj do obecnego asteroida_y" — tyle że teraz dzieje się sam z siebie w każdej klatce, bez naciskania klawisza. Zmienna asteroida_predkosc mówi, ile pikseli asteroida pokonuje na klatkę: większa liczba to szybszy, trudniejszy do ominięcia spadek. Zwiększamy y, a nie zmniejszamy, bo — jak wiesz z mapy ekranu z lekcji 4 — y rośnie w dół.

Jest jeszcze jeden problem do rozwiązania. Gdyby asteroida tylko spadała, po chwili zniknęłaby pod ekranem i już nigdy nie wróciła — gra opustoszałaby po kilku sekundach. Dlatego sprawdzamy warunkiem if, czy asteroida zjechała poniżej dolnej krawędzi (asteroida_y > WYSOKOSC), i jeśli tak — teleportujemy ją z powrotem ponad górną krawędź oraz losujemy świeżą pozycję poziomą i nowy rozmiar. Dzięki temu jedna asteroida w kółko nadlatuje od nowa, za każdym razem trochę inaczej (warunki if poznajesz w kursie podstawowym, moduł M04). Startuje zresztą spoza kadru, od y = -50 — z lekcji 5 wiesz, że rysowanie poza oknem jest dozwolone, więc asteroida wpływa w widok płynnie od góry.
import pygame
import random

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy - Lekcja 9: Spadajaca asteroida")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50                              # <<< NOWE - start ponad ekranem
asteroida_rozmiar = random.randint(25, 55)
asteroida_predkosc = 3                         # <<< NOWE - ile pikseli spada na klatke

font = pygame.font.SysFont("Arial", 24)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0:
        statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc:
        statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0:
        statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc:
        statek_y = WYSOKOSC - statek_wysokosc

    asteroida_y += asteroida_predkosc          # <<< NOWE - przesuwamy asteroide w dol

    # <<< NOWE - gdy zjedzie pod ekran, wraca na gore w nowym miejscu
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        asteroida_rozmiar = random.randint(25, 55)

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    pygame.draw.rect(ekran, (160, 160, 160),
                     (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    tekst = f"Unikaj asteroidy! Predkosc spadania = {asteroida_predkosc}"
    napis = font.render(tekst, True, (255, 255, 100))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)
🎮 Co zobaczysz w oknie gry:
Szara asteroida płynnie zjeżdża z góry okna w dół. Gdy zniknie pod dolną krawędzią, po chwili pojawia się znów u góry — w innym miejscu i czasem w innym rozmiarze. Statkiem (strzałki) możesz przed nią uciekać w bok. Asteroida nadlatuje bez końca.
Zauważ, że spadanie i reset dopisaliśmy w pętli, ale przed rysowaniem — najpierw liczymy nową pozycję asteroidy, a dopiero potem malujemy ją w tym świeżym miejscu. Gdyby reset stał gdzie indziej albo go zabrakło, asteroida spadłaby raz i przepadła. To dlatego ten jeden if jest tak ważny: zamienia jednorazowy spadek w nieskończony strumień nadlatujących przeszkód.
✔️ Sprawdź, że działa
  • Asteroida płynnie spada z góry na dół, a nie stoi w miejscu.
  • Po zniknięciu pod ekranem wraca na górę w nowej, wylosowanej pozycji.
  • Statkiem da się odjechać strzałkami w bok od toru asteroidy.
  • Rozumiesz, że animacja to asteroida_y += predkosc powtarzane co klatkę.
Teraz pokręć prędkością spadania i zobacz, jak zmienia się trudność. Zmieniaj jedną liczbę i uruchamiaj — sam poczujesz różnicę szybciej, niż ją opiszemy.
🧪 Eksperyment — ustaw trudność spadania
1. Zmień asteroida_predkosc = 3 na 9 — jak teraz spada?
2. Ustaw asteroida_predkosc = 1 — jak teraz?
3. Zmień start z asteroida_y = -50 na asteroida_y = 300 — skąd zacznie pierwszy lot?
4. W resecie zmień zakres losowania rozmiaru na random.randint(60, 100).
🧩 Częsty błąd — co, gdy zapomnisz o resecie?
Wyobraź sobie, że dopisałeś spadanie, ale pominąłeś warunek resetu. Zgadnij, jak zachowa się asteroida:
asteroida_y += asteroida_predkosc
# ...i to wszystko - BRAK: if asteroida_y > WYSOKOSC: ...
Pokaż odpowiedź
Asteroida spadnie tylko raz: zjedzie w dół, zniknie pod dolną krawędzią i już nigdy nie wróci. asteroida_y będzie sobie dalej rosnąć (600, 700, 1000...), ale to wszystko poza widokiem — okno pozostanie puste. Bez resetu masz jednorazowy spadek zamiast nieskończonego strumienia przeszkód. Dlatego warunek „jeśli zjechałaś za nisko, wróć na górę" jest sercem tej animacji.
🎯 Zadanie dla Ciebie — coraz szybszy kosmos
Spraw, żeby gra robiła się z czasem trudniejsza:
1. w warunku resetu (po teleporcie na górę) dopisz asteroida_predkosc += 1
2. uruchom i poczekaj, aż asteroida kilka razy okrąży ekran — co dzieje się z prędkością?
3. zastanów się: czy taka gra da się wygrać w nieskończoność?
Sprawdź się
Po każdym powrocie na górę asteroida będzie spadać o piksel szybciej, więc z czasem zrobi się prawie nie do ominięcia — klasyczne „im dłużej grasz, tym trudniej". To Twój pierwszy mechanizm rosnącej trudności; wartość początkową i krok przyspieszenia dobierasz sam.


To Twój rytm gry ⏱️ — Ty ustalasz, jak groźny jest kosmos i jak szybko rośnie wyzwanie. Lekki spadek czy zabójcze tempo? Decyzja należy do Ciebie.
🚀 Asteroida spada, Ty jej unikasz — ale na razie przelatuje przez statek bez żadnego efektu, jakby byli duchami. W lekcji 10 to się zmienia: nauczysz grę wykrywać, kiedy dwa prostokąty naprawdę się nakładają. To moment, w którym uniki zaczną mieć znaczenie!
Lekcja 10

Wykrywanie kolizji

Do tej pory asteroida przelatywała przez statek jak przez ducha. Po tej lekcji gra wreszcie zauważy zderzenie: gdy szary kwadrat dotknie niebieskiego, na ekranie podskoczy licznik trafień. To moment, w którym uniki z lekcji 9 zaczynają mieć znaczenie — bo komputer w końcu wie, kiedy się stykacie.
Cała sztuczka polega na jednym pytaniu: czy dwa prostokąty na siebie nachodzą? Człowiek widzi to od razu, ale komputer potrzebuje konkretu. Pygame ma na to gotowe narzędzie. Najpierw zamykamy każdy obiekt w specjalnym prostokącie pygame.Rect — podajemy mu te same cztery liczby co przy rysowaniu (pozycja x, y oraz szerokość i wysokość), a on zapamiętuje, jaki kawałek ekranu obiekt zajmuje. Rect to nie obrazek; to niewidzialna ramka do liczenia, czysta matematyka pod spodem.
statek_rect asteroida_rect colliderect → prostokąty nakładają się
Mając dwie takie ramki, wystarczy je o siebie zapytać poleceniem statek_rect.colliderect(asteroida_rect). To metoda prostokąta — sprawdza, czy podany drugi prostokąt na niego nachodzi, i odpowiada True (tak, stykają się) albo False (nie). Na rysunku widać dokładnie ten przypadek True: żółte pole to wspólny kawałek, który zajmują oba prostokąty naraz. Wynik wsadzamy wprost do if (kurs podstawowy, moduł M04): „jeśli prostokąty się nakładają, zalicz trafienie".

Jeśli grałeś już w Snake, zauważ różnicę: tam kolizja to porównanie kratek (czy głowa wpadła na zajęte pole siatki). Tu ruch jest płynny — pikselowy, nie skokowy — więc zamiast adresów kratek pytamy dwa prostokąty, czy się nakładają. Dwa style gry, jedno pytanie: „czy się zderzyły?".

Ważny szczegół: prostokąty Rect budujemy w pętli, w każdej klatce na nowo, z aktualnych statek_x, statek_y, asteroida_x i asteroida_y. Dlaczego co klatkę? Bo statek i asteroida wciąż się ruszają — ramka policzona raz, przed pętlą, opisywałaby ich stare położenie i kolizja nigdy nie zgadzałaby się z tym, co widać na ekranie. Po wykryciu trafienia dorzucamy trafienia += 1 i odsyłamy asteroidę nad ekran, żeby to samo zderzenie nie naliczyło się wiele razy z rzędu.
import pygame
import random

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy - Lekcja 10: Wykrywanie kolizji")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

trafienia = 0              # <<< NOWE - licznik dotkniec asteroidy

font = pygame.font.SysFont("Arial", 26)

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0:
        statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc:
        statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0:
        statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc:
        statek_y = WYSOKOSC - statek_wysokosc

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        asteroida_rozmiar = random.randint(30, 55)

    # <<< NOWE - zamykamy statek i asteroide w niewidzialnych ramkach Rect
    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)

    # <<< NOWE - czy ramki na siebie nachodza?
    if statek_rect.colliderect(asteroida_rect):
        trafienia += 1
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    ekran.fill((0, 0, 30))

    pygame.draw.rect(ekran, (0, 200, 255),
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))
    pygame.draw.rect(ekran, (160, 160, 160),
                     (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    tekst = f"Trafienia: {trafienia}"
    napis = font.render(tekst, True, (255, 255, 100))
    ekran.blit(napis, (10, 10))

    pygame.display.flip()
    zegar.tick(60)
🎮 Co zobaczysz w oknie gry:
Najedź statkiem prosto na spadającą asteroidę — w chwili zetknięcia licznik Trafienia u góry skacze o jeden, a asteroida natychmiast wraca na górę okna. Jeśli zgrabnie ją wyminiesz, licznik stoi w miejscu. Gra wreszcie reaguje na to, gdzie jest Twój statek.
Po co w ogóle Rect, skoro współrzędne mamy już w zwykłych zmiennych? Bo samodzielne porównywanie „czy prawa krawędź jednego jest na lewo od lewej krawędzi drugiego..." to cztery zawiłe warunki, w których łatwo o pomyłkę. colliderect robi tę całą matematykę za nas jednym poleceniem i zwraca prostą odpowiedź True albo False. Dlatego opłaca się zamknąć obiekty w ramkach — to mniej kodu i mniej okazji do błędu.
✔️ Sprawdź, że działa
  • Najechanie statkiem na asteroidę zwiększa licznik Trafienia o jeden.
  • Po trafieniu asteroida od razu wraca na górę okna.
  • Zgrabny unik (bez dotknięcia) zostawia licznik bez zmian.
  • Widzisz w kodzie, że statek_rect i asteroida_rect powstają wewnątrz pętli.
Teraz sprawdź na własnej skórze, jak czuła jest kolizja. Zmieniaj ustawienia i graj — trafienia poczujesz palcami szybciej, niż je opiszemy.
🧪 Eksperyment — przetestuj zderzenia
1. Wleć statkiem prosto pod asteroidę i policz, jak rośnie licznik.
2. Spróbuj minąć ją o włos — jak blisko da się przelecieć bez trafienia?
3. Zwiększ asteroida_predkosc do 9 — łatwiej czy trudniej trafić?
4. Powiększ statek (statek_szerokosc = 90) — jak to zmienia liczbę trafień?
🧩 Częsty błąd — ramka policzona za wcześnie
Ktoś dla „oszczędności" tworzy statek_rect tylko raz, na górze pliku przed pętlą. Zgadnij, jak zachowa się wtedy kolizja:
statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
while True:
    # ...statek lata strzalkami, ale statek_rect juz sie NIE zmienia...
    if statek_rect.colliderect(asteroida_rect):
        trafienia += 1
Pokaż odpowiedź
Kolizja będzie sprawdzana wobec startowej pozycji statku, a nie tej, którą widzisz na ekranie. Statek poleci sobie strzałkami w bok, ale jego niewidzialna ramka zostanie tam, gdzie powstała — więc trafienia naliczą się „w pustce", obok widocznego statku. Dlatego Rect musi powstawać w pętli, co klatkę, z aktualnych współrzędnych.
❌ ZAPAMIĘTAJ — Rect obiektu, który się rusza, buduj wewnątrz pętli (co klatkę). Ramka policzona raz przed pętlą opisuje starą pozycję i kolizja przestaje pasować do obrazu.
🎯 Zadanie dla Ciebie — kara za zderzenie
Licznik trafień rośnie, ale zderzenie nic nie kosztuje. Dołóż prostą karę:
1. dodaj zmienną punkty = 100 na górze pliku
2. w bloku kolizji (obok trafienia += 1) dopisz punkty -= 10
3. pokaż punkty w HUD drugim napisem (pamiętasz render i blit z lekcji 6?)
Sprawdź się
Każde trafienie odejmie 10 od punkty (start 100 → 90 → 80...). Drugi napis to nowy tekst = f"Punkty: {punkty}", własny render i blit ustawiony pod pierwszym, np. w (10, 45).


To Twój system punktów 🎯 — sam zdecydujesz, ile kosztuje błąd i od ilu punktów się zaczyna. W kolejnej lekcji zamienimy te trafienia w coś dużo bardziej dramatycznego.
🚀 Gra wie już, kiedy obrywasz — ale obrywać możesz w nieskończoność, bez żadnych konsekwencji. W lekcji 11, ostatniej z darmowych, dołożymy system żyć, efekt trafienia i prawdziwy ekran KONIEC GRY. Z luźnego latania zrobi się gra, którą da się przegrać — i właśnie dlatego warto w nią grać.
Lekcja 11

System żyć + KONIEC GRY

To ostatnia darmowa lekcja — i ta, w której wszystko składa się w prawdziwą grę. Dasz statkowi trzy życia, każde trafienie odbierze jedno z efektownym czerwonym błyskiem i napisem BUM!, a gdy życia się skończą — pojawi się ekran KONIEC GRY. Od tej chwili da się przegrać, a więc i wygrywać. Masz pełny grywalny hak: statek, uniki i życia.
Życia to po prostu zmienna licznikowa: zaczynamy od zycia = 3 i przy każdym trafieniu robimy zycia -= 1. Brzmi prosto, ale czai się tu pułapka. Pamiętasz z lekcji 9 i 10, że pętla kręci się około 60 razy na sekundę? Zderzenie statku z asteroidą nie trwa jednej klatki — ich ramki nachodzą na siebie przez wiele klatek z rzędu. Gdybyśmy odejmowali życie w każdej z nich, trzy życia wyparowałyby w ułamku sekundy z jednego dotknięcia, bo pętla naliczyłaby ten sam cios dziesiątki razy.

Rozwiązaniem jest flaga — zmienna typu prawda/fałsz, która pamięta „czy właśnie obrywamy" (wartości True/False poznajesz w kursie podstawowym, moduł M02). Zakładamy trafiony = False. Życie odejmujemy tylko wtedy, gdy jest kolizja i flaga jeszcze nie jest podniesiona (and not trafiony); w tej samej chwili ustawiamy trafiony = True, żeby kolejne klatki tego samego zderzenia już nic nie odejmowały. Flagę opuszczamy z powrotem po krótkiej chwili niewrażliwości.

Tę „chwilę" odmierzamy zegarem pygame.time.get_ticks(), który podaje, ile milisekund minęło od startu gry (1000 ms = 1 sekunda). W momencie trafienia zapisujemy ten czas w czas_trafienia, a w każdej klatce sprawdzamy, czy od tamtej chwili minęło więcej niż 600 ms — jeśli tak, gasimy flagę i statek znów może oberwać. Po trafieniu masz więc ułamek sekundy oddechu, ponieważ flaga blokuje kolejne ciosy, zanim następna asteroida zdąży Cię dosięgnąć.

Reszta to już czysta frajda dla oka. Dopóki flaga trafiony jest podniesiona, malujemy tło na czerwono, statek na czerwono i wypisujemy wielkie BUM! — stąd druga, większa czcionka font_duzy. Robimy to po to, żeby trafienie było widać gołym okiem nawet w ferworze gry. Życia pokazujemy w HUD jako rządek serduszek. A kiedy zycia <= 0, zamiast normalnej klatki rysujemy czarny ekran z napisem KONIEC GRY, czekamy chwilę i zamykamy grę. Tak wygląda przegrana.
import pygame
import random

pygame.init()

SZEROKOSC = 800
WYSOKOSC = 600
ekran = pygame.display.set_mode((SZEROKOSC, WYSOKOSC))
pygame.display.set_caption("Asteroidy - Lekcja 11: System zyc i KONIEC GRY")

zegar = pygame.time.Clock()

statek_szerokosc = 40
statek_wysokosc = 50
statek_x = SZEROKOSC // 2 - statek_szerokosc // 2
statek_y = WYSOKOSC - 80
predkosc = 5

asteroida_x = random.randint(0, SZEROKOSC - 50)
asteroida_y = -50
asteroida_rozmiar = random.randint(30, 55)
asteroida_predkosc = 3

zycia = 3                 # <<< NOWE - ile razy mozemy oberwac
trafiony = False          # <<< NOWE - flaga: czy wlasnie dostalismy cios
czas_trafienia = 0        # <<< NOWE - kiedy (w ms) dostalismy cios

font = pygame.font.SysFont("Arial", 26)
font_duzy = pygame.font.SysFont("Arial", 64)   # <<< NOWE - wielka czcionka na napisy

while True:
    for zdarzenie in pygame.event.get():
        if zdarzenie.type == pygame.QUIT:
            pygame.quit()
            exit()

    klawisze = pygame.key.get_pressed()
    if klawisze[pygame.K_LEFT]:
        statek_x -= predkosc
    if klawisze[pygame.K_RIGHT]:
        statek_x += predkosc
    if klawisze[pygame.K_UP]:
        statek_y -= predkosc
    if klawisze[pygame.K_DOWN]:
        statek_y += predkosc

    if statek_x < 0:
        statek_x = 0
    if statek_x > SZEROKOSC - statek_szerokosc:
        statek_x = SZEROKOSC - statek_szerokosc
    if statek_y < 0:
        statek_y = 0
    if statek_y > WYSOKOSC - statek_wysokosc:
        statek_y = WYSOKOSC - statek_wysokosc

    asteroida_y += asteroida_predkosc
    if asteroida_y > WYSOKOSC:
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)
        asteroida_rozmiar = random.randint(30, 55)

    statek_rect = pygame.Rect(statek_x, statek_y, statek_szerokosc, statek_wysokosc)
    asteroida_rect = pygame.Rect(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar)

    # <<< NOWE - kolizja zabiera zycie tylko RAZ (dzieki fladze trafiony)
    if statek_rect.colliderect(asteroida_rect) and not trafiony:
        zycia -= 1
        trafiony = True
        czas_trafienia = pygame.time.get_ticks()
        asteroida_y = -asteroida_rozmiar
        asteroida_x = random.randint(0, SZEROKOSC - asteroida_rozmiar)

    # <<< NOWE - blysk trafienia gasnie po 600 ms
    if trafiony and pygame.time.get_ticks() - czas_trafienia > 600:
        trafiony = False

    # <<< NOWE - tlo czerwienieje na chwile po trafieniu
    if trafiony:
        ekran.fill((80, 0, 0))
    else:
        ekran.fill((0, 0, 30))

    # <<< NOWE - statek robi sie czerwony, gdy oberwal
    if trafiony:
        kolor_statku = (255, 80, 80)
    else:
        kolor_statku = (0, 200, 255)
    pygame.draw.rect(ekran, kolor_statku,
                     (statek_x, statek_y, statek_szerokosc, statek_wysokosc))

    pygame.draw.rect(ekran, (160, 160, 160),
                     (asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))

    # <<< NOWE - pasek zyc jako serduszka
    tekst_zyc = "Zycia: " + ("♥ " * zycia)
    ekran.blit(font.render(tekst_zyc, True, (255, 80, 80)), (10, 10))

    # <<< NOWE - wielkie BUM! w chwili trafienia
    if trafiony:
        bum = font_duzy.render("BUM!", True, (255, 255, 0))
        ekran.blit(bum, (SZEROKOSC // 2 - 70, WYSOKOSC // 2 - 50))

    # <<< NOWE - ekran KONIEC GRY, gdy skoncza sie zycia
    if zycia <= 0:
        ekran.fill((0, 0, 0))
        koniec = font_duzy.render("KONIEC GRY!", True, (255, 50, 50))
        ekran.blit(koniec, (SZEROKOSC // 2 - 180, WYSOKOSC // 2 - 50))
        pygame.display.flip()
        pygame.time.wait(3000)
        pygame.quit()
        exit()

    pygame.display.flip()
    zegar.tick(60)
🎮 Co zobaczysz w oknie gry:
U góry rządek serduszek — Twoje trzy życia. Gdy asteroida Cię trafi, ekran błyska czerwienią, statek czerwienieje, wyskakuje wielkie żółte BUM! i znika jedno serduszko. Po trzecim trafieniu ekran gaśnie do czerni z napisem KONIEC GRY, a po chwili gra się zamyka. To już pełnoprawna gra zręcznościowa.
Zauważ, jak elegancko współpracują tu flaga i zegar. Flaga trafiony pilnuje, żeby jedno zderzenie odebrało dokładnie jedno życie, a zegar get_ticks() sam ją opuszcza po 600 ms — bez tej pary albo straciłbyś wszystkie życia naraz, albo po pierwszym ciosie stałbyś się nieśmiertelny. To częsty wzorzec w grach: zdarzenie liczy się raz, a potem następuje krótki czas ochrony.
✔️ Sprawdź, że działa
  • U góry widać trzy serduszka, a każde trafienie zabiera dokładnie jedno (nie wszystkie naraz).
  • Po trafieniu ekran i statek błyskają czerwienią, a na środku pojawia się BUM!.
  • Po krótkiej chwili błysk gaśnie i statek znów może oberwać.
  • Po utracie trzeciego życia pojawia się czarny ekran KONIEC GRY, po czym gra się zamyka.
Teraz dostrój grę pod siebie. Zmieniaj po jednej liczbie i graj — sam wyczujesz, kiedy zrobi się za łatwo, a kiedy za trudno. Odpowiedzi nie zdradzamy; rozgrywka pokaże je najlepiej.
🧪 Eksperyment — wyreguluj poziom trudności
1. Daj sobie więcej szans: zycia = 5.
2. Skróć ochronę po trafieniu: zmień 600 na 200 — jak teraz?
3. Wydłuż ją do 1500 — czujesz różnicę?
4. Zmień kolor błysku tła z (80, 0, 0) na własny.
🧩 Częsty błąd — trzy życia znikają w mgnieniu oka
Ktoś pomija flagę i odejmuje życie przy każdej kolizji. Zgadnij, co stanie się przy jednym dotknięciu:
if statek_rect.colliderect(asteroida_rect):   # BRAK: and not trafiony
    zycia -= 1
Pokaż odpowiedź
Wszystkie życia znikną natychmiast po jednym dotknięciu. Zderzenie trwa wiele klatek z rzędu, a pętla chodzi 60 razy na sekundę — więc zycia -= 1 wykona się kilkadziesiąt razy, zanim zdążysz odlecieć. KONIEC GRY pojawi się od razu. Dlatego potrzebna jest flaga: liczy trafienie tylko raz, a kolejne klatki tego samego zderzenia ignoruje.
❌ ZAPAMIĘTAJ — zdarzenie, które trwa wiele klatek (jak kolizja), licz tylko raz: użyj flagi True/False, inaczej naliczy się dziesiątki razy na sekundę.
🎯 Zadanie dla Ciebie — domknij swoją grę
Masz już grywalny szkielet — nadaj mu charakter:
1. dodaj licznik przetrwanych sekund w HUD, korzystając z pygame.time.get_ticks() // 1000
2. pokaż ten czas także na ekranie KONIEC GRY (drugi render + blit pod napisem)
3. zmień start na zycia = 1 i sprawdź, jak ostra robi się gra
Sprawdź się
Czas to get_ticks() // 1000 — milisekundy podzielone całkowicie przez 1000 dają pełne sekundy (przyda się // z lekcji 6). Wstaw go do napisu przez .format, a na ekranie końca dorysuj drugim blit wynik typu „Przetrwałeś 27 s".


To Twoja pierwsza skończona gra 🏆 — statek, uniki, życia i ekran końca. Pobaw się nią, pokaż znajomym, pobij własny rekord czasu. A potem... to dopiero rozgrzewka.
🏆 Ukończyłeś całą darmową część — masz własną, grywalną grę! Zanim ruszysz dalej, jest jeszcze jedno: egzamin na pierwszą rangę. Zalicz go, a oficjalnie zostaniesz Pilotem. 🚀
🎖️ Egzamin

Egzamin na Pilota

Przeleciałeś 11 lekcji i pierwszy samodzielny projekt — czas na egzamin na pierwszą prawdziwą rangę. Sześć pytań z tego, co już umiesz: okno i pętla, kolory RGB, mapa ekranu, ruch statku i kolizje. Zalicz (minimum 70%), a awansujesz na Pilota 🚀. Zła odpowiedź to nie koniec świata — możesz powtarzać egzamin do skutku.
🏁 Koniec darmówki

Co dalej? Najlepsze przed Tobą

🚀 Twój statek umie uciekać — ale tylko uciekać. A gdyby tak zaczął strzelać? W płatnej części kursu dokładamy laser wystrzeliwany spacją, niszczenie trafionych asteroid, prawdziwy wynik i poziomy trudności — z gry w uniki robi się pełnoprawna strzelanka kosmiczna. Najlepsze dopiero przed Tobą.
Kadr z finalnej gry Asteroidy: statek strzelający laserem, asteroidy, wrogowie i HUD z wynikiem
Prawdziwy kadr z finalnej gry, którą zbudujesz w pełnym kursie 🚀
W pełnym kursie, krok po kroku, zbudujesz:
laser — strzał spacją, pocisk mknący w górę
niszczenie asteroid — trafiona znika, a Ty zdobywasz punkty
wynik i rekord — punkty za każdą zestrzeloną asteroidę
poziomy trudności — im dalej, tym szybciej i gęściej
menu startowe i wielki finał — aż do kompletnej gry, którą zagrasz i pokażesz światu
🔓 Odblokuj pełny kurs
Przeszedłeś 11 lekcji i pierwszy projekt — masz już własną grę. Czas zrobić z niej coś, w co naprawdę chce się grać.