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í.

5. diel - Pygame - Pong - Logika stavov hry a dokončenie

V minulej lekcii, Pygame - Pong - Prostredie a herné objekty , sme si pripravili prostredie a objekty pre hru Pong v pygame. Dnes pridáme logiku jednotlivých herných stavov a celú hru sprevádzkujeme.

Stavy hry

Teraz nastáva tá najťažšia časť - musíme popremýšľať do budúcnosti a rozhodnúť sa, aké stavy naša hra bude mať. Môžeme dôjsť k nasledujúcim trom stavom:

  • výber, či má byť druhý hráč ovládaný hráčom alebo počítačom
  • začiatok kola, kedy je hra pozastavená, aby mali hráči možnosť sa nachystať
  • samotné koleso, v ktorom sa obaja hráči pohybujú a lieta medzi nimi loptu

Ako tieto stavy reprezentovať? Najjednoduchšia asi bude každý stav napísať ako metódu, pričom budeme mať premennú, v ktorej bude uložený odkaz na aktuálny stav, ktorý sa bude volať. Nazvime si teda stavy napríklad nasledovne:

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

Ďalej si vytvoríme nový atribút logic (ako logika aktuálneho stavu), ktorý potom na začiatku funkcie main() nastavíme na prvý 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

Keď sa ešte zamyslíme, môžeme dospieť k záveru, že niektoré objekty (konkrétne oboch hráčov, loptu a skóre) budeme chcieť mať vykreslené úplne zakaždým, takže je nemusíme členiť do jednotlivých logik a môžeme ich pridať priamo do cyklu v main(). Všetky naše objekty budú biele a skóre bude hore uprostred:

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 by sme dokončili náš hlavný herný cyklus, takže nám ostáva už len vytvoriť jednotlivé stavy a máme hotovo!

Začiatok hry

Začnime s prvým stavom, teda so začiatkom hry. V tomto stave po užívateľovi chceme, aby sa rozhodol, či chce hrať proti druhému človeku, alebo počítaču. Dajme tomu, že ho vyzveme, nech stlačí H (human), ak bude chcieť hrať proti človeku, alebo C (computer), ak bude chcieť hrať proti počítaču. Našu otázku môžeme umiestniť doprostred obrazovky a podľa odpovede užívateľa nastavíme premennú 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

Celkom priamočiare, že?

Začiatok kola

Pred začiatkom každého kola je dobrý nápad umiestniť hráča a loptu do pôvodnej pozície a vyresetovať lopte smer a rýchlosť. Potom nám už stačí iba hráča vyzvať, aby stlačil Medzerník, čím signalizuje, že sú všetci pripravení a hra môže začať:

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))

Samotnej koleso

Ako ste správne vytušili, toto je najťažšia časť celého programu. Práve v nej sa totiž odohráva drvivá väčšina všetkých aktivít:

def logic_game_body(self):

Pohyby hráčov

Začneme tým, že sa vysporiadame s pohyby hráčov. Povedzme, že hráč číslo 1 bude pre pohyb hore používať W a pre pohyb nadol S. Pohybovať sa bude samozrejme môcť iba v prípade, že nenaráža do vrchnej alebo spodnej časti 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, pokiaľ nie je ovládaný počítačom, bude používať pre pohyb hore a dole šípky:

# ...
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ča

Pre prípad, že je druhý hráč ovládaný počítačom, vytvoríme jednoduchú dvojriadkový AI. Tá sa bude pokúšať byť na rovnakej výškovej hladine, ako je lopta. Pohyb vypočítame jednoducho:

Ak je lopta nižšie než hráč, musí ísť hráč nižšie (tj. Posun hráčov sa musí pohybovať v kladných číslach). Pokiaľ je ale lopta vyššie, musí sa hráč posunúť o záporné číslo. Hráč sa v jednom kroku herného cyklu nikdy nemôže posunúť ďalej, než je jeho rýchlosť. Zároveň, keďže nechce lopta preletieť, posunie sa maximálne toľko, aby sa presne zarovnala s loptou. Vzdialenosť pohybu tak vypočítame pomocou min(player_speed, abs(player2.centery - ball.centery)).

Aby sme určili, či sa má hráč pohybovať v kladných, alebo záporných číslach, potrebujeme funkciu, ktorá nám zistí znamienko rozdielov súradníc lopty a hráča. Na tento účel si vytvoríme funkciu 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

Teraz vieme, o koľko sa má hráč ovládaný pomocou AI pohnúť. Nesmieme ale zabudnúť, že ani on nemôže ísť za hranice obrazovky. Vo výsledku kód našej AI môže teda vyzerať nejako 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 lopty

Pohyby hráčov už máme teda vyriešené. Teraz si ešte vyriešime pohyb lopty, ktorý priamo vyplýva z jednotkovej kružnice. Použijeme funkcie sin() a cos() pre získanie novej pozície lopty podľa smeru, ktorým práve letí a jeho rýchlosti:

# ...
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ážanie

Keď si teraz spustíme našu hru, tak sa nám nebude príliš dariť, pretože, aj napriek všetkej našej snahe, lopta vždy opustí hraciu plochu. Preto musíme ešte definovať, čo má loptu robiť pri náraze do hráčov alebo niektorého okraje.

Odrazenie od hornej a dolnej hrany

Pre vrchnú alebo spodnú hranu je riešenie veľmi jednoduché - stačí nám obrátiť smer lopty:

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

Pokiaľ ale lopta narazí z ľavej alebo pravej strany do hráča, musíme už jeho uhol odrazu vypočítať manuálne:

# ...
elif self.ball.collidelist([self.player1, self.player2]) > -1:
    self.set_ball_direction(180 - self.ball_direction)
Odrazenie od ľavej a pravej hrany

Posledný situácia, ktorú nám ešte zostáva vyriešiť, je, keď lopta narazí do pravej alebo ľavej strany. V takom prípade to znamená, že jeden z hráčov nebol schopný loptu dostatočne rýchlo odraziť. Druhému hráči teda pripočítame bod a vrátime sa na stav začiatku 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
Zrýchľovanie

A posledným vylepšením, ktoré sme si sľúbili, ale ešte sme ho nezakomponovali, je samozrychlující sa lopta. Povedzme, že každé 2 sekundy kola sa lopta sám zrýchli o 10% až do svojej maximálnej rýchlosti (ľudia majú svoje limity, kedy lopta ešte dokážu sledovať :) )

Na zistenie toho, či ak uplynuli práve 2 sekundy, nám poslúži náš časovač round_tick, ktorého hodnota sa každú iteráciu v kole zvýši o 1. A každé 2 sekundy to sú práve, keď round_tick je násobkom 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ša prvá hra je na svete! Zvládli ste to!

Pyong – Hra Pong v pygame a Pythone - Pygame

Teraz vám už nič nebráni si ju rozšíriť, ako len uznáte za vhodné. Alebo sa pustiť do niečoho vlastného, predstavivosti sa medze nekladú.

Nabudúce, v lekcii Skákačka v Pygame - Plánujeme hru , sa pozrieme už na niečo ťažšieho.

V ďalšej lekcii Skákačka v Pygame - Plánujeme hru, sa pozrieme na nový veľký projekt - Skákačku v Pygame. Vytvoríme zložkovú štruktúru a hernú slučku.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 244x (8.03 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python

 

Predchádzajúci článok
Pygame - Pong - Prostredie a herné objekty
Všetky články v sekcii
Pygame
Preskočiť článok
(neodporúčame)
Skákačka v Pygame - Plánujeme hru
Článok pre vás napísal Adam Hlaváček
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
vývoji užitečných aplikací zjednodušujících každodenní život
Aktivity