Lekce 4 - Pygame - Pong - Prostředí a herní objekty
V minulé lekci, Pygame - Pong - Příprava, jsme začali vytvářet naši první hru Pong. Začali jsme prací s textem a pronikli do tajů kolize.
V dnešní lekci kurzu Tvorba her v Pythonu pokročíme s naším Pygame projektem dále. Už máme znalosti potřebné k úspěšnému vytvoření naší hry Pong a nic nám již nebrání pustit se do práce. Dnes si přidáme hráče a míč. Hra bude vypadat takto:

Definice prostředí
Podobně jako v našich předchozích kódech, i nyní musíme vytvořit pevný základ naší hry.
Příprava pygame
Řekněme, že hru budeme připravovat na rozlišení
1920
x1080
, ale bude spustitelná s rozlišením
libovolným. Proto si vytvoříme oddělené proměnné display
a
screen
. Zároveň by se nám asi líbilo, aby okno mělo nějaký
titulek, který by reprezentoval naši hru. K tomu slouží pro nás nová
metoda pygame.display.set_caption(name)
, která nastaví titulek
okna na hodnotu name
.
Poslední věc, která by se nám mohla hodit, je proměnná
round_tick
. Tu si vytvoříme a budeme ji inkrementovat každým
krokem během kola, bude nám fungovat jako časovač. Toho využijeme
například, když budeme chtít zrychlit míč každých pár sekund.
Třída Game
Naše hry by měly mít objektovou architekturu a ideálně by všechny objekty ve hře měly být instance samostatných tříd. Pygame nám k tomu i nabízí několik tříd, ze kterých můžeme své objekty oddědit.
Jelikož je to naše první pygame hra, strukturu souboru si trochu
zjednodušíme, nicméně se bude stále jednat o objekt. Celou hru
implementujeme jako třídu Game
, ve které budeme nastavovat
všechny její atributy. To hlavně abychom se vyhnuli zbytečnému
předávání proměnných funkcím hry přes parametry nebo dokonce přes
nebezpečné globální proměnné. Takto budou ze všech metod třídy
dostupné. Třída Game
bude nyní vypadat 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
Příprava písem
Vždy je dobrý nápad nemíchat mnoho druhů písem v jedné aplikaci. Proto si vybereme jedno písmo, které budeme používat. Jelikož budeme kreslit skóre, u kterého je žádoucí, aby pozice jednotlivých znaků zůstala vždy stejná, mohli bychom dojít k tom, že se nám nejvíce hodí písmo s pevnou šířkou znaků (monospace).
Pokud bychom ale byli nerozhodní, které z těch všech nainstalovaných
písem si máme vybrat, mohli bychom za nás nechat vybrat takové písmo náš
program podle toho, zda název 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()])
Známe-li přímo jméno písma, které chceme použít, upravíme tento kód podobně, jako jsme si ukázali v minulé lekci.
Pak nám již zbývá pouze vytvořit jednotlivá písma. Budeme určitě potřebovat jedno menší, na stálé zobrazování skóre a druhé větší, na vypisování výzev a informací:
self.font_score = pygame.font.SysFont(font_name, 30) self.font_info = pygame.font.SysFont(font_name, 50)
Příprava herních objektů
Základní pygame
prostředí máme nachystané, přejděme k
herním objektům.
Hráči
V naší hře budou dva hráči. První hráč bude vlevo a druhý vpravo. Oba se budou moci pohybovat nějakou rychlostí. Zároveň, jelikož jsou oba hráči obdélníky, byla by škoda je rovnou jako pygame obdélníky nevytvořit, když k tomu máme připravenou třídu. Každý hráč také bude mít skóre a u hráče číslo 2 budeme chtít vědět, zda-li místo něj hraje 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
Míč
Podobně jako hráče si vytvoříme také míč. Ten se ale, na rozdíl od hráčů, kde směr pohybu určuje vstup od uživatele, může pohybovat libovolným směrem. Budeme si tedy muset uložit jeho současný směr. Míč se také postupem času může zrychlovat až do určité maximální rychlosti:
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
Abychom si usnadnili výpočty a zaručili, že míč nebude mít nikdy hodnotu směru mimo rozsah, napíšeme si rovnou metodu, pomocí které budeme jeho směr nastavovat:
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, které budeme vytvářet, sice nebudou viditelné, ale přesto jsou vysoce důležité. Jedná se o hranice obrazovky, které si vytvoříme jako pygame obdélníky. Díky tomu pak nebudeme muset porovnávat souřadnice každého dalšího objektu, jestli náhodou neopustil obrazovku, ale místo toho nám bude stačit porovnat kolizi dvou obdélníků. Tyto obdélníky budou vždy pokrývat celou jednu hranu herní plochy, to nás asi nepřekvapí. Co by nás ale mohlo překvapit, je jejich tloušťka. Ta musí být minimálně tak velká jako maximální rychlost míče, jinak by se totiž mohlo stát, že by míč při vyšších rychlostech naše okraje prostě přeskočil. Tento jejich přesah promítneme za hranice viditelné části herní 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! Tedy alespoň máme hotovy naše objekty.
Herní cyklus
Pokračovat budeme tím, že si vytvoříme klasický herní cyklus, který jsme v předchozích lekcích dělali již několikrá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 běží rychlostí maximálně
FPS
snímků za sekundu. V každé své iteraci zkontroluje, jestli
někdo nepoužil klávesu Q, aby se hra vypnula, popř. její
vypnutí nebylo vyžádáno jinak. Pokud ne, tak vykreslí plochu
screen
na obrazovku.
A to je pro dnešek vše 🙂
V příští lekci, Pygame - Pong - Logika stavů hry a dokončení, si naprogramujeme logiku jednotlivých stavů hry a hru Pong tím dokončíme.