80 % bodů zdarma na online výuku díky naší Letní akci!
Pouze tento týden sleva až 80 % na e-learning týkající se PHP

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

V minulé lekci, Pygame - Pong - Prostředí a herní objekty, jsme si připravili prostředí a objekty pro hru Pong v pygame. Dnes přidáme logiku jednotlivých herních stavů a celou hru zprovozníme.

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

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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

Teď vám již 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.

Příště, v lekci , se podíváme již na něco obtížnějšího.


 

Stáhnout

Staženo 32x (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
Článek pro vás napsal Adam Hlaváček
Avatar
Jak se ti líbí článek?
1 hlasů
vývoji užitečných aplikací zjednodušujících každodenní život
Aktivity (4)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!