IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 5 - Pygame - Pong - Logika stavů hry a dokončení

V minulé lekci, Pygame - Pong - Prostředí a herní objekty, jsme si do hry Pong přidali hráče, míč a nastavili herní cyklus.

V dnešní lekci kurzu Tvorba her v Pythonu, přidáme do naší hry Pong logiku jednotlivých herních stavů. Celou hru bychom tak měli úspěšně dokončit 🙂

Stavy hry

Nyní nastává ta nejtěžší část, musíme zapřemýšlet do budoucnosti a rozhodnout se, jaké stavy naše hra bude mít. Můžeme dojít k následujícím třem stavům:

  • výběr, jestli má být druhý hráč ovládán hráčem nebo počítačem
  • začátek kola, kdy je hra pozastavena, aby měli hráči možnost se nachystat
  • samotné kolo, ve kterém se oba hráči pohybují a létá mezi nimi míč

Jak tyto stavy reprezentovat? Nejsnazší asi bude každý stav napsat jako metodu, přičemž budeme mít proměnnou, ve které bude uložen odkaz na aktuální stav, který se bude volat. Nazvěme si tedy stavy například následovně:

  • logic_game_start()
  • logic_round_start()
  • logic_game_body()

Dále si vytvoříme nový atribut logic (jako logika aktuálního stavu), který pak na začátku funkce main() nastavíme na první stav:

from typing import Optional, Callable
    # ...
    def __init__(self):
        logic: Optional[Callable] = None

    # ...
    def main(self):
        self.logic = self.logic_game_start
        while self.running:
            # UDALOSTI
            # ...
            self.logic()

            # KRESLENI
            # ...
        # ...
    def logic_game_start(self):
        pass

    def logic_round_start(self):
        pass

    def logic_game_body(self):
        pass

Když se ještě zamyslíme, můžeme dojít k závěru, že některé objekty (konkretně oba hráče, míč a skóre) budeme chtít mít vykreslené úplně pokaždé, takže je nemusíme členit do jednotlivých logik a můžeme je přidat přímo do cyklu v main(). Všechny naše objekty budou bílé a skóre bude nahoře uprostřed:

def main(self):
    # ...

    while self.running:
        # ...
        # KRESLENI
        pygame.draw.rect(screen, (255, 255, 255), self.player1)
        pygame.draw.rect(screen, (255, 255, 255), self.player2)
        pygame.draw.rect(screen, (255, 255, 255), self.ball)

        text = font_score.render(f"{self.player1_score} : {self.player2_score}", True, (255, 255, 255))
        self.screen.blit(text, ((self.screen.get_width() - self.text.get_width()) / 2, 0))

Tím bychom dokončili náš hlavní herní cyklus, takže nám zbývá už pouze vytvořit jednotlivé stavy a máme hotovo!

Začátek hry

Začněme s prvním stavem, tedy se začátkem hry. V tomto stavu po uživateli chceme, aby se rozhodl, jestli chce hrát proti druhému člověku, nebo počítači. Dejme tomu, že ho vyzveme, ať stiskne H (human), pokud bude chtít hrát proti člověku, nebo C (computer), pokud bude chtít hrát proti počítači. Naši otázku můžeme umístit doprostřed obrazovky a podle odpovědi uživatele nastavíme proměnnou player2_ai:

def logic_game_start(self):
    # vykresleni otazky na obrazovku
    text = self.font_info.render("Press C to play with computer or H to play against human", True, (255, 255, 255))
    self.screen.blit(text, ((self.screen.get_width() - text.get_width()) / 2,
                            (self.screen.get_height() * 0.75 - text.get_height()) / 2))

    # kontrola odpovedi
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_h] or pressed[pygame.K_c]:
        self.player2_ai = bool(pressed[pygame.K_c])
        # uzivatel si vybral, presun do dalsiho stavu
        self.logic = self.logic_round_start

Celkem přímočaré, že?

Začátek kola

Před začátkem každého kola je dobrý nápad umístit hráče a míč do původní pozice a vyresetovat míči směr a rychlost. Pak nám již stačí pouze hráče vyzvat, aby stiskl Mezerník, čímž signalizuje, že jsou všichni připraveni a hra může začít:

def logic_round_start(self):
    # vyresetuje vse na hodnoty a pozice pred zacatkem kola
    self.player1 = self.player1_orig.copy()
    self.player2 = self.player2_orig.copy()
    self.ball = self.ball_orig.copy()
    self.ball_speed = self.ball_speed_orig
    self.round_tick = 0

    # vybere novy prvotni smer mice
    # (smer musi byt dostatecne ostry, jinak se mic odrazi prilis dlouho od zdi)
    self.set_ball_direction(choice(
        (randrange(-75, -15), randrange(15, 75), randrange(195, 255))
    ))

    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_SPACE]:
        # vsichni pripraveni, muzeme spustit kolo
        self.logic = self.logic_game_body

    # vykresli hlasku pro uzivatele
    text = self.font_info.render("Press SPACE to start", True, (255, 255, 255))
    self.screen.blit(text, ((self.screen.get_width() - text.get_width()) / 2,
                            (self.screen.get_height() * 0.75 - text.get_height()) / 2))

Samotné kolo

Jak jste správně vytušili, toto je nejtěžší část celého programu. Právě v ní se totiž odehrává drtivá většina všech aktivit:

def logic_game_body(self):

Pohyby hráčů

Začneme tím, že se vypořádáme s pohyby hráčů. Řekněme, že hráč číslo 1 bude pro pohyb nahoru používat W a pro pohyb dolů S. Pohybovat se bude samozřejmě moci pouze v případě, že nenaráží do vrchní nebo spodní části obrazovky:

pressed = pygame.key.get_pressed()
if pressed[pygame.K_s] and not self.player1.colliderect(self.bottom):
    self.player1.move_ip(0, self.player_speed)
elif pressed[pygame.K_w] and not self.player1.colliderect(self.top):
    self.player1.move_ip(0, -self.player_speed)

Hráč 2, pokud není ovládán počítačem, bude používat pro pohyb nahoru a dolů šipky:

if not self.player2_ai:
    if pressed[pygame.K_DOWN] and not self.player2.colliderect(self.bottom):
        self.player2.move_ip(0, self.player_speed)
    elif pressed[pygame.K_UP] and not self.player2.colliderect(self.top):
        self.player2.move_ip(0, -self.player_speed)

AI počítače

Pro případ, že je druhý hráč ovládán počítačem, vytvoříme jednoduchou dvouřádkovou AI. Ta se bude pokoušet být na stejné výškové hladině, jako je míč. Pohyb vypočítáme jednoduše:

Pokud je míč níže než hráč, musí jít hráč níže (tj. posun hráče se musí pohybovat v kladných číslech). Pokud je ale míč výše, musí se hráč posunout o záporné číslo. Hráč se v jednom kroku herního cyklu nikdy nemůže posunout dále, než je jeho rychlost. Zároveň, jelikož nechce míč přeletět, posune se maximálně tolik, aby se přesně zarovnal s míčem. Vzdálenost pohybu tak vypočítáme pomocí min(player_speed, abs(player2.centery - ball.centery)).

Abychom určili, jestli se má hráč pohybovat v kladných, nebo záporných číslech, potřebujeme funkci, která nám zjistí znaménko rozdílů souřadnic míče a hráče. Za tímto účelem si vytvoříme funkci sign:

# jelikož sign není omezena na použití s naší hrou, vytvoříme ji raději jako funkci, ne metodu třídy
def sign(num: int) -> int:
    return 1 if num > 0 else -1 if num < 0 else 0

Nyní víme, o kolik se má hráč ovládaný pomocí AI pohnout. Nesmíme ale zapomenout, že ani on nesmí jít za hranice obrazovky. Ve výsledku kód naší AI může tedy vypadat nějak takto:

else:
    move_y = sign(self.ball.centery - self.player2.centery) * min(self.player_speed, abs(self.player2.centery - self.ball.centery))
    if (move_y > 0 and not self.player2.colliderect(self.bottom)) or (move_y < 0 and not self.player2.colliderect(self.top)):
        self.player2.move_ip(0, move_y)

Pohyb míče

Pohyby hráčů již máme tedy vyřešené. Teď si ještě vyřešíme pohyb míče, který přímo vyplývá z jednotkové kružnice. Použijeme funkce sin() a cos() pro získání nové pozice míče podle směru, kterým zrovna letí a jeho rychlosti:

ball_direction_radians = radians(self.ball_direction)
self.ball.move_ip(self.ball_speed * cos(ball_direction_radians), ball_speed * sin(ball_direction_radians))

Odrážení

Když si nyní spustíme naši hru, tak se nám nebude příliš dařit, protože, i přes všechnu naši snahu, míč vždy opustí hrací plochu. Proto musíme ještě definovat, co má míč dělat při nárazu do hráčů nebo některého okraje.

Odražení od horní a dolní hrany

Pro vrchní nebo spodní hranu je řešení velice snadné - stačí nám obrátit směr míče:

if self.ball.collidelist([self.top, self.bottom]) > -1:
    self.set_ball_direction(-self.ball_direction)

Pokud ale míč narazí z levé nebo pravé strany do hráče, musíme již jeho úhel odrazu vypočítat manuálně:

elif self.ball.collidelist([self.player1, self.player2]) > -1:
    self.set_ball_direction(180 - self.ball_direction)
Odražení od levé a pravé hrany

Poslední situace, kterou nám ještě zbývá vyřešit, je, když míč narazí do pravé nebo levé strany. V takovém případě to znamená, že jeden z hráčů nebyl schopný míč dostatečně rychle odrazit. Druhému hráči tedy přičteme bod a vrátíme se na stav začátku kola:

if self.ball.colliderect(self.left):
    self.player2_score += 1
    self.logic = self.logic_round_start
elif self.ball.colliderect(self.right):
    self.player1_score += 1
    self.logic = self.logic_round_start
Zrychlování

A posledním vylepšením, které jsme si slíbili, ale ještě jsme jej nezakomponovali, je samozrychlující se míč. Řekněme, že každé 2 sekundy kola se míč sám zrychlí o 10% až do své maximální rychlosti (lidé mají své limity, kdy míč ještě dokáží sledovat :) )

Ke zjištění toho, zda-li uplynuly právě 2 sekundy, nám poslouží náš časovač round_tick, jehož hodnota se každou iteraci v kole zvýší o 1. A každé 2 sekundy to jsou právě, když round_tick je násobkem dvojnásobku FPS:

if self.round_tick > 0 and self.round_tick % (2 * self.FPS) == 0:
    self.ball_speed = min(1.1 * self.ball_speed, self.ball_speed_max)

self.round_tick += 1

Máme hotovo!

A je to! Naše první hra je na světě! Zvládli jste to!

PyONG – Hra Pong v pygame a Pythonu - Pygame - Tvorba her v Pythonu

Teď vám už nic nebrání si ji rozšířit, jak jen uznáte za vhodné. Nebo se pustit do něčeho vlastního, představivosti se meze nekladou 🙂

V další lekci { NEXT}, se podíváme na nový velký projekt - Skákačku v Pygame. Vytvoříme složkovou strukturu a herní smyčku.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 244x (8.03 kB)
Aplikace je včetně zdrojových kódů v jazyce Python

 

Předchozí článek
Pygame - Pong - Prostředí a herní objekty
Všechny články v sekci
Pygame - Tvorba her v Pythonu
Přeskočit článek
(nedoporučujeme)
Skákačka v Pygame - Plánujeme hru
Článek pro vás napsal Adam Hlaváček
Avatar
Uživatelské hodnocení:
12 hlasů
vývoji užitečných aplikací zjednodušujících každodenní život
Aktivity