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!
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é 263x (8.03 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python