Rozdział 21 · Projekt: kosmiczny strzelec

Jak pojawiają się wrogowie

Timer pojawienia się w sekundach — z tym samym akumulatorem while-, co timer animacji w rozdziale 20.

W sekcji 21.3 mierzyliśmy już czas do następnego wroga w sekundach: licznik zmniejszał się o dt każdą klatkę, a po wyzwalaniu dodawałem interwał ponownie, i nie przyjął ją, więc reszta nie została utracona. Teraz przenieśmy tę samą zasadę do finalnej części architektura gry oraz uczynić timer odpowiednim dla interwału zmieniającego się wraz z złożoność.

fragment_spawn_timer.py
self.spawn_timer -= dt
while self.spawn_timer <= 0.0:
    self._spawn_enemy()
    self.spawn_timer += interval_poyavleniya_vraga(self.score)
Dwa niezależne mechanizmy, nie jeden
self.spawn_timer += interval_poyavleniya_vraga(self.score) — to, co potrzebne w każdym kadrze: pozostały czas ponad interwał nie jest odrzucany, lecz przenoszony na następnego przeciwnika, dokładnie tak samo jak w sekcji 21.3 i jak licznik animacji w sekcji 20.23. To zachowanie reszty działa również z zwykłym if — nie wymaga while. Sam while jest potrzebne do czegoś innego: jeśli jeden przetworzony krok trwałby dłużej niż kilka odstępów pojawiania się wrogów jednocześnie, szczerze mówiąc, stworzyłby kilku wrogów pod rząd podczas jednego wezwania, oraz if tylko przyspieszyłoby pojawienie się jednego wroga i po cichu traciło resztę zgromadzonego czasu.
W tej konkretnej grze drugi przypadek jeszcze nie ma miejsca
Ostateczna wersja gry ogranicza dt znaczenie MAX_DT = 0.05 sekund przed każdą aktualizacją (ochrona przed skakaniem po pauzie debugera lub zamrożeniu systemu), a dolna granica interwału pojawiania się wrogów to MIN_SPAWN_INTERVAL = 0.35 sekundy: nawet najdłuższa dopuszczalna klatka jest krótsza niż jakikolwiek możliwy odstęp między wrogami. Zatem ciało while w tej grze nie robi się już więcej niż raz na klatkę — i jest zapisane if kod zachowywałby się tutaj dokładnie tak samo. while pozostawiona jako bardziej ogólna i niezawodna technika: nie zepsuje się, jeśli później podniesiesz MAX_DT lub skracz interwał pojawiania się wrogów bardziej niż teraz, oraz if w tym przypadku musiałoby zostać zmienione.

Gdzie dokładnie pojawia się wróg?

Współrzędna X powinna zapewnić, że przeciwnik całkowicie mieści się na polu gry — bez częściowego „przycinania”

fragment_spawn_x.py
def x_poyavleniya_vraga(rng, shirina_vraga, pole):
    levaya, pravaya = pole.left, pole.right - shirina_vraga
    if pravaya <= levaya:
        return float(levaya)
    return rng.uniform(levaya, pravaya)

To odrębna, czysta funkcja — akceptuje rng wyraźnym argumentem (Rozdział 21.24 wyjaśnia dlaczego) zamiast zajmować się globalnym problemem random bezpośrednio, dzięki czemu można ją wygodnie testować bez uruchamiania całej gry.

Dwa typy wrogów

Real Window: Wielu przeciwników o różnych rozmiarach i kolorach zjeżdża na planszę jako grupa
Rzeczywiste okno — fala wrogów: małe, szybkie zwiadowce i większe myśliwce pojawiają się na przemian.
Scout (Zwiadowca)Fighter (wojownik)
SpeedPowyżejPoniżej
RozmiarmniejWięcej
Punkty do zniszczenia100200
Prawdopodobieństwo pojawienia sięWyżej na początku gryRośnie wraz z liczeniem (Rozdział 21.17)
fragment_enemy_spec.py
@dataclass(frozen=True)
class EnemySpec:
    name: str
    base_speed: float
    points: int
    image_key: str

ENEMY_SPECS = {
    "scout": EnemySpec("scout", base_speed=150.0, points=100, image_key="enemy_scout"),
    "fighter": EnemySpec("fighter", base_speed=85.0, points=200, image_key="enemy_fighter"),
}
EnemySpec opisuje typ, a nie pojedynczego wroga
@dataclass z rozdziału 14 automatycznie tworzy konstruktor dla czterech pól. Parametr frozen=True zabrania zmiany tych pól po utworzeniu obiektu: ogólna specyfikacja scout lub fighter pozostaje niezmieniona, a współrzędne i bieżąca prędkość są przechowywane w każdej instancji Enemy. Aby dodać typ wroga w ćwiczeniu 21.25, stwórz nowy EnemySpeczamiast modyfikować istniejący.
Praktyka: timer pojawiania się wrogów
Zeskanowanie bezpośrednio w przeglądarce – nie wymaga instalacji Pygame
Praktyka kursuje lokalnie
Otwórz praktykę →