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ść.
self.spawn_timer -= dt
while self.spawn_timer <= 0.0:
self._spawn_enemy()
self.spawn_timer += interval_poyavleniya_vraga(self.score)
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.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”
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
| Scout (Zwiadowca) | Fighter (wojownik) | |
|---|---|---|
| Speed | Powyżej | Poniżej |
| Rozmiar | mniej | Więcej |
| Punkty do zniszczenia | 100 | 200 |
| Prawdopodobieństwo pojawienia się | Wyżej na początku gry | Rośnie wraz z liczeniem (Rozdział 21.17) |
@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"),
}
@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.