Zbuduj Grę z Asteroidami
Kurs Pygame dla Pasjonatów Gier
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ć.python --version
python -m pip install pygame
'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.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.python --version pokazuje 3.12.xpython -m pip install pygame kończy się „Successfully installed pygame-2.6.1"gra.py w folderze projektuimport pygame
pygame.init()
SZEROKOSC = 800
WYSOKOSC = 600
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!")
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()
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()
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ę.set_caption.(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:(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)(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ę.(0, 0, 30) na (30, 0, 0) — na jaki kolor zmieni się kosmos?(0, 100, 0) — a teraz?SZEROKOSC = 400 i WYSOKOSC = 400 — jak zmieni się okno?1200 na 900 — co się stanie?while True: i zostawiasz tylko ekran.fill oraz pygame.display.flip(). Najpierw zgadnij: okno zostanie otwarte, zniknie, a może pojawi się błąd?
while True jest tym, co utrzymuje okno przy życiu; to nie ozdoba, lecz serce każdej gry.set_caption na własną nazwę gry.pygame.event.get()
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
for wyjmuje z niej element po elemencie.
for przechodzi po liście element po elemencie. Ten kod:
ekwipunek = ["miecz", "tarcza"]
for przedmiot in ekwipunek:
print(przedmiot)
wypisze w konsoli kolejno:
pygame.event.get().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()
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()
ekran.fill i pygame.display.flip(), znasz już z lekcji 1 — robią dokładnie to samo, co wcześniej.for, która czyta zdarzenia.pygame.QUIT to sygnał wysyłany po kliknięciu krzyżyka.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ę.print("klik") na print(zdarzenie) — co teraz wypisuje konsola przy ruchu myszą i klawiszach?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?
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.print("Zamykam gre!") tuż przed pygame.quit() i sprawdź w konsoli, że napis pojawia się dokładnie w chwili kliknięcia krzyżyka.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ę".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
tick na końcu pętli, bo bez niego marnuje moc komputera bez żadnego zysku.zegar = pygame.time.Clock() PRZED pętlą.zegar.tick(60) na samym końcu pętli, po pygame.display.flip().tick to limit klatek na sekundę — i najlepiej poczujesz, co robi, gdy nią pokręcisz. Najpierw zgadnij efekt, potem uruchom.zegar.tick(60) na zegar.tick(5), uruchom i poruszaj myszą nad oknem oraz spróbuj zamknąć krzyżyk — jak „ciężko" reaguje okno?zegar.tick(120) — czujesz różnicę względem 60?zegar.tick(60) i tak zostaw — to wartość, na której zbudujemy resztę gry.zegar.tick(60) i uruchom grę. Zgadnij: zobaczysz różnicę na ekranie czy nie?
tick to hamulec, który temu zapobiega — dlatego zostaje na końcu pętli na stałe.zegar.tick(30) i zegar.tick(60); klikając krzyżyk, oceń, przy którym oknie zamyka się odrobinę szybciej.zegar.tick(1) — ile razy na sekundę odświeża się wtedy gra?x i pionowej y. Ten system adresów nazywamy układem współrzędnych.(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) — 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
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.(0, 0) jest w lewym górnym rogu okna, nie na środku.x (w prawo) i y (w dół).(400, 300).(x, y) wiesz, że pierwsza liczba to odległość w prawo, a druga w dół.(400, 300) — w której części okna?(0, 599) — przy której krawędzi?(800, 0) — w którym rogu?(790, 590) — blisko którego rogu?(100, 50) od (50, 100):
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).(x, y). Okno ma 800×600. Wskaż punkt, który leży:(0, 0) | 2. (800, 0) | 3. (0, 600) | 4. (800, 600) | 5. (400, 300) — połowa z 800 i połowa z 600.(x, y) podasz wtedy poleceniu draw.rect, którym go narysujesz.(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ę!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
ekran, czyli powierzchnia okna stworzona w lekcji 1; to na niej maluje się wszystko, co widać.(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ół.
(R, G, B), każda od 0 do 255 (poznałeś to w lekcji 1):(255, 0, 0) czerwień · (0, 255, 0) zielony · (0, 0, 255) niebieski(0, 0, 0) czarny · (255, 255, 255) biały(0, 200, 255) to dużo błękitu z domieszką zieleni — jasny niebieski.(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:(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.statek_x = 350 # pozioma pozycja (od lewej)
statek_y = 500 # pionowa pozycja (od gory)
statek_szerokosc = 40 # szerokosc prostokata
statek_wysokosc = 50 # wysokosc prostokata
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).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)
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.statek_x, a która statek_y.statek_y, statek przesuwa się w dół (nie w górę) — i wiesz, dlaczego.(0, 200, 255) na (255, 0, 0) — jakiego koloru będzie statek?statek_y z 500 na 550 — w którą stronę przesunie się statek?statek_szerokosc = 120 — co stanie się z kształtem statku?statek_x = 0 i statek_y = 0 — w którym rogu wyląduje?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ź.
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.draw.rect(ekran, (0, 200, 255), (statek_x, statek_y, 40, 50))
ekran.fill((0, 0, 30)) # uwaga na kolejnosc!
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.ekran.fill, POTEM rysowanie. Nigdy odwrotnie!(statek_x, statek_y) to jego lewy górny róg. Dobierz statek_x i statek_y tak, żeby CAŁY statek dotykał:statek_x i statek_y zadziała nawet, gdyby okno albo statek miały inny rozmiar?
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.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.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.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.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.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)
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.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)
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.
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.SysFont, render i blit.font jest tworzona przed pętlą, a render i blit — w środku pętli."Mój statek bojowy".(255, 255, 255) na (255, 255, 0) — jaki kolor liter teraz?(10, 10) wpisz (10, 560).SysFont daj 48 zamiast 24.render wprost zmienną liczbową. Zgadnij, czy to zadziała:
napis = font.render(statek_x, True, (255, 255, 255)) # statek_x to LICZBA
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 {}.font.render wyświetla wyłącznie TEKST. Liczbę najpierw zamień w tekst (przez .format albo str()), inaczej dostaniesz błąd.render + blit:"Ekran: 800 x 600"(x, y) wybierzesz, żeby nie nachodził na napis z pozycją?(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.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()
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
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: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)
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.statek_y += predkosc przesuwa w dół, bo y rośnie w dół.predkosc = 12 i polataj — jak zmienia się sterowanie?predkosc = 1 — jak teraz lata statek?#) cztery if z granicami i dolecień do krawędzi — co się stanie ze statkiem?statek_x -= predkosc na += przy strzałce w lewo — w którą stronę poleci teraz po wciśnięciu lewej?+= 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 =
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.+= 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ócejif klawisze[pygame.K_a]: statek_x -= predkosc (A w lewo)K_d w prawo, K_w w górę, K_s w dół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ł.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.asteroida_x, asteroida_y, asteroida_rozmiar (np. 40)
(160, 160, 160)
asteroida_y, np. 50) i gdzieś w poziomie (np. asteroida_x = 200)
pygame.draw.rectif). 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ąć.pygame.draw.rect(ekran, kolor, (x, y, szerokosc, wysokosc))
(160, 160, 160)
zmienna += ile, np. asteroida_y += 3
ekran.fill (inaczej tło zamaluje obiekt — pamiętasz zagadkę z lekcji 5?)
pygame.draw.rect(ekran, (160, 160, 160),
(asteroida_x, asteroida_y, asteroida_rozmiar, asteroida_rozmiar))
Wstaw ją w pętli, tuż po narysowaniu statku.+= z lekcji 7? Spróbuj z nim pokombinować. A jeśli nie wyjdzie — spokojnie, ruchu asteroidy nauczymy się razem w lekcji 9. 🚀# 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.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
SZEROKOSC - 50, a nie samo SZEROKOSC?asteroida_x aż do SZEROKOSC (800), co poszłoby nie tak? Zgadnij, zanim rozwiniesz.
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.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)
X, Y i rozmiar asteroidy.import random, a losowanie stoi przed pętlą.randint. Odpowiedzi nie podajemy; zobaczysz je na ekranie.asteroida_x = random.randint(350, 450) i uruchom kilka razy.random.randint(60, 90).(160, 160, 160) na własny.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.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
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.random.randint), dopisz na górze pliku import random. Bez tego dostaniesz NameError.asteroida2_x, asteroida2_y, asteroida2_rozmiar, każdą wylosowaną przez random.randintpygame.draw.rect w innym kolorzepygame.draw.rect z tymi nowymi nazwami. Skoro każda ma swoje random.randint, ich pozycje są od siebie niezależne.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ół.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)
if jest tak ważny: zamienia jednorazowy spadek w nieskończony strumień nadlatujących przeszkód.asteroida_y += predkosc powtarzane co klatkę.asteroida_predkosc = 3 na 9 — jak teraz spada?asteroida_predkosc = 1 — jak teraz?asteroida_y = -50 na asteroida_y = 300 — skąd zacznie pierwszy lot?random.randint(60, 100).asteroida_y += asteroida_predkosc
# ...i to wszystko - BRAK: if asteroida_y > WYSOKOSC: ...
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.asteroida_predkosc += 1pygame.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.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".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)
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.
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.Trafienia o jeden.statek_rect i asteroida_rect powstają wewnątrz pętli.asteroida_predkosc do 9 — łatwiej czy trudniej trafić?statek_szerokosc = 90) — jak to zmienia liczbę trafień?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
Rect musi powstawać w pętli, co klatkę, z aktualnych współrzędnych.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.punkty = 100 na górze plikutrafienia += 1) dopisz punkty -= 10punkty w HUD drugim napisem (pamiętasz render i blit z lekcji 6?)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).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.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.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ąć.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)
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.zycia = 5.600 na 200 — jak teraz?1500 — czujesz różnicę?(80, 0, 0) na własny.if statek_rect.colliderect(asteroida_rect): # BRAK: and not trafiony
zycia -= 1
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.True/False, inaczej naliczy się dziesiątki razy na sekundę.pygame.time.get_ticks() // 1000render + blit pod napisem)zycia = 1 i sprawdź, jak ostra robi się graget_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".