Rozdział 21 · Projekt: kosmiczny strzelec

Jak znaleźć i naprawić błędy w strzelance

Kompendium według objawu — plus szczegółowa analiza trzech najmniej zauważalnych błędów projektu.

W trakcie tego rozdziału natknęliśmy się już na ponad dwadzieścia sposobów przypadkowego zepsucia strzelanki – od starych, technik opartych na klatkach, po subtelne błędy z podwójnym punktowaniem. Tutaj są zebrane w Jedna książka referencyjna.

ObjawPrzyczynaGdzie dowiedzieć się więcej
Prędkość statku zależy od FPSRuch jest ustawiony na piksele na klatkę, a nie px/s przez dt Funkcje21.11
Zwolnione tempo w ogóle się nie poruszaPozycja jest przechowywana tylko w Rect (liczbach całkowitych) – część ułamkowa jest tracona21.11
Statek diagonalny jest szybszy niż linia prostaWektor kierunku nie jest znormalizowany21.11
Statek wylatuje z pola gryZapomniano clamp po X i/lub Y w Player.move()21.11
Statek przepływa pod HUDGranice ruchu to całe okno, a nie self.playfield21.12
Spacja nagrywa każdą renderowaną klatkęBrak sprawdzenia fire_cooldown przed strzałem21.13
Szybkostrzelność zależy od FPSInterwał między ujęciami mierzony jest w klatkach (-= 1), a nie sekundach (-= dt)21.13
Po długiej pauzie debuggera — salwa z dziesiątek kulInterwał Shot jest wprowadzany po while zamiast if21.13
Pociski lecą wiecznie, gra zwalnia coraz bardziej z każdą minutąBullet nie wycofuje się z pola gry21.4 (usuwanie pocisków zza ekranu)
Pojawienie się wrogów zależy od FPSTimer pojawiania się wrogów liczy klatki, a nie sekundy21.14
Po osłabieniu FPS planowani przeciwnicy „znikają”Timer pojawiania się wrogów używa if zamiast while21.14
Wróg pojawia się w połowie krawędzi ekranux_poyavleniya_vraga nie uwzględnia szerokości przeciwnika21.14
Jeden wróg jest dwukrotnie oznaczany jako zniszczonyLista zmienia się podczas brutalnej siły (jak w sekcji 21.6)21.15
Jednemu wrogowi nalicza się punkty dwukrotnieWszystkie pary pocisków-przeciwników są liczone, a nie wiele unikalnych przeciwników21.15
Trzech jednoczesnych wrogów odbiera trzy życiaObrażenia są przypisywane na każdą kolizję zamiast raz na klatkę21.16
Nietykalność tyka po zatrzymaniuAktualizacja timera nieśmiertelności poza update_playing() bez sprawdzania state21.16, 21.18
Na ekranie Game Over statek wciąż się poruszahandle_input() nie sprawdza self.state przed obsługą kluczy21.18
Po restarcie pozostają stare pociski lub przeciwnicystart_new_game() nie czyści wszystkich grup sprite’ów21.19
Po restarcie wróg pojawia się natychmiastspawn_timer nie zostaje zresetowany do początkowego interwału21.19
Animacja eksplozji „połyka”Explosion.update() używa if zamiast while21.20
Gra zwalnia przed każdym strzałem lub eksplozjąObraz lub dźwięk jest ładowany wewnątrz pętli gry, a nie w AssetStore21.10, 21.21
Gra zawiesza się bez karty dźwiękowejSound.play() jest wywoływany bez sprawdzania None21.21
HUD zablokowane przez wroga lub eksplozjęKolejność renderowania nie obejmuje HUD i nakłada się na ostatnie warstwy21.22
rect i position z czasem „rozchodzą się”Rect zaktualizowany bezpośrednio, a nie odbudowany od position21.11
Test kolizji przechodzi i zawiesza się bez zmian koduRandom jest używany bezpośrednio, bez przekazywania seed21.24

Wbudowany nakładka debugowania

Naciśnij F3włączyć lub wyłączyć nakładkę debugowania w finalnej grze. On wyznacza czerwone granice Rect statek, wrogowie i kule, i niżej pokazuje rzeczywistą FPS. W ten sposób można zobaczyć prawdziwe hitboxy podczas parsowania kolizji i Rozróżnić spadki liczby klatek spowodowane błędem w obliczaniu ruchu przez dt. Nakładka zmienia tylko renderowanie: stan świata gry, Timery i kolizje pozostają takie same.

Przyjrzyjmy się bliżej trzem najbardziej niepozornym błędom

Ruch subpikselowy jest zanikany na wolnych obiektach
Objaw: obiekt o odległości mniejszej niż jeden piksel na klatkę w ogóle się nie przesuwa. Wersja zepsuta: self.rect.x += self.speed * dt – Rect przechowuje tylko liczby całkowite, a ułamkowy przyrost jest zaokrąglany do zera na każdej klatce osobno. Dlaczego: zaokrąglenie odbywa się za każdym razem, a nie raz na końcu. Poprawka: kumulować ruch w self.position (Vector2, Rozdział 21.11), oraz rect.center z niego złożyć ponownie round() każda klatka. Zasada: pozycja zawsze jest float, Rect jest jej pochodną, nigdy odwrotnie.
Jeden wróg liczył dwa razy
Objaw: wynik czasem skacze o 200 zamiast 100 dla jednego harcerza. Wersja zepsuta: for pulya, vragi in collisions.items(): schet += sum(v.points for v in vragi) to suma wszystkich par pocisków-przeciwników. Dlaczego: jeśli dwie kule trafią tego samego wroga w jednej aktualizacji, zostanie ona uwzględniona w obu listach punktów. Poprawka: zbierać wszystkich trafionych wrogów set przed punktacją (Rozdział 21.15). Zasada: liczyć punkty za unikalnie zniszczone obiekty, a nie za liczbę kolizji.
Trzech wrogów zabiera jednocześnie trzy życia
Objaw: bezpośrednie zderzenie z grupą wrogów natychmiast przenosi grę na Game Over, choć były trzy życia. Wersja zepsuta: for vrag in hit: self.lives -= 1 - Obrażenia w cyklu od zderzających się wrogów. Dlaczego: cykl odpisuje życie każdego wroga, a nie samego samego zderzenia. Poprawka: sprawdzić „czy była przynajmniej jedna kolizja” Zasada: obrażenia mierzone są przez zdarzenie „dotknięte”
Praktyka: Znajdź i napraw błąd
Pygame otwiera natywne okno Python – uruchomione lokalnie w VS Code, PyCharm lub Jupyter
Praktyka kursuje lokalnie
Otwórz praktykę →