4. diel - Pygame - Pong - Prostredie a herné objekty
Naposledy, v lekcii Pygame - Pong - Príprava , sme si zistili, ktoré znalosti potrebujeme k úspešnému vytvoreniu našej prvej hry Pong v pygame. Teraz nám teda už nič nebráni sa do nej rovno pustiť! Hra bude vyzerať takto:
Definícia prostredie
Podobne ako v našich predchádzajúcich kódoch, aj teraz musíme vytvoriť pevný základ našej hry.
Príprava pygame
Povedzme, že hru budeme pripravovať na rozlíšenie 1920
x
1080
, ale bude spustiteľná s rozlíšením ľubovoľným. Preto
si vytvoríme oddelené premenné display
a screen
.
Zároveň by sa nám asi páčilo, aby okno malo nejaký titulok, ktorý by
reprezentoval našu hru. K tomu slúži pre nás nová metóda
pygame.display.set_caption(name)
, ktorá nastaví titulok okna na
hodnotu name
.
Posledná vec, ktorá by sa nám mohla hodiť, je premenná
round_tick
. Tú si vytvoríme a budeme ju inkrementovať každým
krokom počas kola, bude nám fungovať ako časovač. Toho využijeme
napríklad, keď budeme chcieť zrýchliť loptu každých pár sekúnd.
Trieda Game
Naše hry by mali mať objektovú architektúru a ideálne by všetky objekty v hre mali byť inštancie samostatných tried. Pygame nám k tomu aj ponúka niekoľko tried, z ktorých môžeme svoje objekty oddědit.
Keďže je to naša prvá pygame hra, štruktúru súboru si trochu
zjednodušíme, avšak sa bude stále jednať o objekt. Celú hru implementujeme
ako triedu Game
, v ktorej budeme nastavovať všetky jej atribúty.
To hlavne aby sme sa vyhli zbytočnému prenosu premenných funkciám hry cez
parametre alebo dokonca cez nebezpečné globálne premenné. Takto budú zo
všetkých metód triedy dostupné. Trieda Game
bude teraz vyzerať
takto:
import pygame class Game: def __init__(self): pygame.init() self.running = True self.display: pygame.Surface = pygame.display.set_mode((1366, 768)) pygame.display.set_caption("PyONG") self.screen = pygame.Surface((1920, 1080)) self.round_tick = 0 self.clock = pygame.time.Clock() self.FPS = 30 # maximální počet FPS
Príprava písiem
Vždy je dobrý nápad nemiešať mnoho druhov písiem v jednej aplikácii. Preto si vyberieme jedno písmo, ktoré budeme používať. Keďže budeme kresliť skóre, u ktorého je žiaduce, aby pozície jednotlivých znakov zostala vždy rovnaká, mohli by sme dôjsť k tom, že sa nám najviac hodí písmo s pevnou šírkou znakov (monospace).
Ak by sme ale boli nerozhodní, ktoré z tých všetkých nainštalovaných
písiem si máme vybrať, mohli by sme za nás nechať vybrať také písmo
náš program podľa toho, či názov písma obsahuje text
'mono'
:
from random import choice # ... def __init__(self): # ... font_name = choice([x for x in pygame.font.get_fonts() if 'mono' in x.lower()])
Ak poznáme priamo meno písma, ktoré chceme použiť, upravíme tento kód podobne, ako sme si ukázali v minulej lekcii.
Potom nám už zostáva iba vytvoriť jednotlivé písma. Budeme určite potrebovať jedno menšie, na stále zobrazovanie skóre a druhé väčšie, na zverejnenie výziev a informácií:
# ... self.font_score = pygame.font.SysFont(font_name, 30) self.font_info = pygame.font.SysFont(font_name, 50)
Príprava herných objektov
Základné pygame prostredie máme nachystané, prejdime k herným objektom.
Hráči
V našej hre budú dvaja hráči. Prvý hráč bude vľavo a druhý vpravo. Obaja sa budú môcť pohybovať nejakú rýchlosťou. Zároveň, keďže sú obaja hráči obdĺžniky, bola by škoda ich rovno ako pygame obdĺžniky nevytvoriť, keď k tomu máme pripravenú triedu. Každý hráč tiež bude mať skóre au hráčov číslo 2 budeme chcieť vedieť, či ak namiesto neho hrá počítač:
# ... player_size = (50, 200) self.player_speed = 10 # umisti hrace 1 ze zacatku vlevo doprostred self.player1_orig = pygame.Rect((0, self.screen.get_height() // 2), player_size) # a vytvor jeho kopii, abychom pri kazdem kole se mohli jednoduse vratit k puvodni pozici self.player1 = self.player1_orig.copy() self.player1_score = 0 # hrace cislo 2 umisti vpravo doprostred self.player2_orig = pygame.Rect((self.screen.get_width() - player_size[0], self.screen.get_height() // 2), player_size) self.player2 = self.player2_orig.copy() self.player2_score = 0 self.player2_ai = False # True pokud misto hrace c. 2 hraje pocitac
Lopta
Podobne ako hráča si vytvoríme tiež loptu. Ten sa ale, na rozdiel od hráčov, kde smer pohybu určuje vstup od užívateľa, môže pohybovať ľubovoľným smerom. Budeme si teda musieť uložiť jeho súčasný smer. Lopta sa tiež postupom času môže zrýchľovať až do určitej maximálnej rýchlosti:
ball_size = (20, 20) # umísti mic doprostred herni plochy self.ball_orig = pygame.Rect((self.screen.get_width() // 2, self.screen.get_height() // 2), ball_size) self.ball = self.ball_orig.copy() self.ball_direction = 0 # nastavi se na zacatku kazdeho kola, hodnoty <0; 360) self.ball_speed_orig = 15 self.ball_speed_max = self.ball_speed_orig * 4 self.ball_speed = self.ball_speed_orig
Aby sme si uľahčili výpočty a zaručili, že lopta nebude mať nikdy hodnotu smere mimo rozsahu, napíšeme si rovno metódu, pomocou ktorej budeme jeho smer nastavovať:
def set_ball_direction(self, val: int): if abs(val) >= 360: # zmensi mozny rozsah hodnot na (-360; 360) pomoci zjisteni zbytku po deleni val %= 360 if val < 0: # prevede zapornou velikost uhlu na jeji kladnou reprezentaci v kruznici val = 360 + val self.ball_direction = val
Hranice obrazovky
Posledný objekty, ktoré budeme vytvárať, síce nebudú viditeľné, ale napriek tomu sú vysoko dôležité. Jedná sa o hranice obrazovky, ktoré si vytvoríme ako pygame obdĺžniky. Vďaka tomu potom nebudeme musieť porovnávať súradnice každého ďalšieho objektu, či náhodou neopustil obrazovku, ale namiesto toho nám bude stačiť porovnať kolízii dvoch obdĺžnikov. Tieto obdĺžniky budú vždy pokrývať celú jednu hranu hernej plochy, to nás asi neprekvapí. Čo by nás ale mohlo prekvapiť, je ich hrúbka. Tá musí byť minimálne tak veľká ako maximálnu rýchlosť lopty, inak by sa totiž mohlo stať, že by lopta pri vyšších rýchlostiach naše okraje jednoducho preskočil. Tento ich presah premietneme za hranice viditeľnej časti hernej plochy:
def __init__(self): # ... self.top = pygame.Rect((0, -self.ball_speed_max), (self.screen.get_width(), self.ball_speed_max)) self.bottom = pygame.Rect((0, self.screen.get_height()), (self.screen.get_width(), self.ball_speed_max)) self.left = pygame.Rect((-self.ball_speed_max, 0), (self.ball_speed_max, self.screen.get_height())) self.right = pygame.Rect((self.screen.get_width(), 0), (self.ball_speed_max, self.screen.get_height()))
A máme hotovo! Teda aspoň máme hotové naše objekty.
Herné cyklus
Pokračovať budeme tým, že si vytvoríme klasický herný cyklus, ktorý sme v predchádzajúcich lekciách robili už niekoľkokrát:
# ... def main(self): while self.running: # UDALOSTI for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_q): self.running = False # KRESLENI pygame.transform.scale(self.screen, self.display.get_size(), self.display) pygame.display.flip() self.clock.tick(FPS) self.screen.fill((0, 0, 0)) if __name__ == '__main__': Game().main()
Tento náš základný herný cyklus beží rýchlosťou maximálne
FPS
snímok za sekundu. V každej svojej iterácii skontroluje, či
ak niekto nepoužil kláves Q, aby sa hra vypla, popr. jej vypnutie
nebolo vyžiadané inak. Ak nie, tak vykreslí plochu screen
na
obrazovku.
V budúcej lekcii, Pygame - Pong - Logika stavov hry a dokončenie , sa budeme venovať stavom hry.