Tutoriály Pygame
Opička řádku po řádce

autor: Pete Shinners pete@shinners.org
překlad (plus drobné úpravy): Pavel Kosina pkosina@seznam.cz

Revize 2.1, 9. leden 2001



Úvod

V příkladech pygame je jeden příklad z názvem "chimp" (opička). Tento příklad znázorňuje opičku, která se může hladit a která se pohybuje tam a zpátky v malém okně a slibuje velkou výhru. Samotný příklad je velmi jednoduchý, ale bez ošetření možných chyb. Tento program ukazuje mnoho možností a schopností pygame, jako třeba vytváření grafického okna, nahrávání obrázků, vyobrazování TTF písem a základní obsluhu událostí a myši.

Tento tutoriál projde celým kódem blok po bloku. Vysvětluje, jak kód funguje. Bude také zmínka o možných vylepšeních a jak vyřešit případné chyby.

Je to excelentní tutoriál pro lidi, kteří jsou u pygame poprvé. Pokud máte pygame dobře nainstalovánu, toto demo můžete nalézt v adresáři s příklady nebo na konci tohoto tutoriálu..
Chimp Screenshot

Importujeme moduly

Tento kousek kódu importuje všechny potřebné moduly do vašeho programu. Také zjišťuje dostupnost některých volitelných modulů.
# -*- coding: cp1250 -*-

import os, sys
import pygame
from pygame.locals import *

if not pygame.font:
print u'Upozornění: písmo nebude dostupné.'
if not pygame.mixer:
print u'Upozornění: zvuky nebudou dostupné.'

Nejprve importujeme standardní moduly os a sys. Tyto nám umožní vytvářet věci jako cesty k souborům nezávislé na platformě.

Na další řádce importujeme balíček pygame. Když se importuje balíček pygame, zároveň se importují všechny moduly, které do něj patří. Některé moduly jsou však volitelné, a pokud se nenajdou, jejich hodnota se nastaví na None.

Existuje speciální modul jménem locals. Tento modul obsahuje část z pygame. Členy tohoto modulu jsou nejčastěji používané konstanty a funkce, které je užitečné umístnit přímo do vašeho programového prostoru jmen. Tento modul obsahuje funkce jako Rect na vytváření obdélníků, a mnoho konstant, jako"QUIT, HWSURFACE", které se používají pro interakci se zbytkem pygame. Import modulu locals do vašeho globálního prostoru jmen je zcela dobrovolné. Pokud se rozhodnete ho neimportovat, všichni členi z locals budou vždy dostupní přes modul pygame.

Nakonec jsme se rozhodli tisknout jemné upozornění o tom, že moduly font a mixer nejsou řádně nainstalované.

Nahrajeme zdroje

Máme tady dvě funkce na nahrátí obrázků a zvuků. Podíváme se na každou zvlášť.

def nahrajObrazek(jmeno, klicovaBarva=None):
plneJmeno = os.path.join('data', jmeno) # obrázky leží v podadresáři 'data'
try:
obrazek = pygame.image.load(plneJmeno)
except:
raise 'Nemohu nahrat obrazek:', plneJmeno
obrazek = obrazek.convert()
if not klicovaBarva:
klicovaBarva = obrazek.get_at((0,0))
obrazek.set_colorkey(klicovaBarva, RLEACCEL)
return obrazek, obrazek.get_rect()

Tato funkce přijímá jako argument jméno obrázku, který se má nahrát. Dobrovolně také přijímá argument, který bude použit na nastavení klíčové barvy u obrázku. Klíčová barva představuje u grafiky barvu, která bude průhledná.

První věc, kterou funkce dělá je vytvoření plné cesty k souboru. V tomto případě jsou všechny zdroje v podadresáři "data". Použitím funkce os.path.join() se vytvoří cesta, která bude fungovat na všech platformách.

Dále nahrajeme obrázek pomocí funkce pygame.image.load(). Jen ji obalíme do bloku try/except, aby jsme mohli při problémech elegantně odejít. To vytvoří novou kopii Plochy a konvertuje její formát barev a hloubky tak, aby odpovídal displeji. To pak způsobí vykreslování obrázku nejvyšší možnou rychlostí.

Nakonec, nastavíme klíčovou barvu obrázku. Pokud uživatel žádnou klíčovou barvu nedodal, použije se barva levého horního pixelu z obrázku. Pokud dodal barvu, obvykle bílou (255,255,255), použije se ta. Funkce vrací samotný obrázek a jeho obdélník (rozměry).

def nahrajZvuk(jmeno):
class BezZvuku:
def play(self): pass
if not pygame.mixer:
return BezZvuku()
plneJmeno = os.path.join('data', jmeno)
try:
zvuk = pygame.mixer.Sound(plneJmeno)
except:
raise 'Nemohu nahrat zvuk:', plneJmeno
return zvuk


Další funkce nahrává zvuk. Nejprve zkontroluje, jestli byl  správně importovaný modul pygame.mixer. Pokud ne, vrací funkce nastrčenou instanci třídy BezZvuku, která má prázdnou metodu play(). Takto to bude fungovat jako obyčejný objekt Zvuk (Sound) bez jakéhokoliv dalšího ošetřování chyb.

Tato funkce je dosti podobná funkci pro nahrátí obrázků, ale potýká se s trochu jinými problémy. Nejprve vytvoříme plnou cestu ke zvuku, a pak zvuk nahráváme opěr uvnitř bloku try/except. Pak jednoduše vracíme nahraný objekt Zvuk.

Třídy objektů hry

Zde vytvoříme dvě třídy, které budou představovat objekty v naší hře. Téměř veškerá herní logika leží v těchto třídách. Probereme si každou zvlášť.

class Dlan(pygame.sprite.Sprite):

def __init__(self, image):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = nahrajObrazek(image)
self.hlazeni = 0

def update(self):
"presouva dlan na pozici mysi"
pos = pygame.mouse.get_pos()
self.rect.midtop = pos
if self.hlazeni:
self.rect.move_ip(5, 10)

def trefa(self, cil):
"vraci pravdu, pokud dlan zasahla cil"
if not self.hlazeni:
self.hlazeni = 1
zasahObd = self.rect.inflate(-5, -5)
return zasahObd.colliderect(cil.rect)

def trefaNavrat(self):
"vratit ruku do vychozi pozice"
self.hlazeni = 0

Zde vytváříme třídu, která bude představovat hráčovu dlaň. Je odvozená ze třídy Sprite, obsažené v modulu pygame.sprite. Funkce __init__() se volá pokaždé, když se vytváří nová instance. První věc, kterou zde musíme udělat, je zavolat funkci __init__() naší základní třídy Sprite. Tato funkce připraví naši instanci na použití ve hře jako obyčejný přízrak (sprite), se všemi jejími metodami. Tato hra využívá jednoho z příznaků tříd Group. Tyto třídy umí kreslit přízraky, které mají atributy image a rect. Jednoduchou změnou těchto dvou atributů, vykreslovací funkce nakreslí současný obrázek na současnou pozici.

Všechny přízraky mají metodu update(). Tato funkce se volá jednou za snímek. Mělo by to být tam, kde se posouvají a aktualizují  proměnné přízraků. Metoda Dlane update() posunuje dlan na pozici myši. Také nastavuje jemný posun dlaně, pokud se opička otáčí.

Následující dvě metody trefa() a trefaNavrat() mění stav hlazení. Metoda trefa() vrací také pravdu, pokud dlan se dotkla zadaného cílového přízraku.

class Opicka(pygame.sprite.Sprite):
"""posunuje opicku po obrazovce. Pokud je pohlazena, zatoci se."""
def __init__(self, image):
pygame.sprite.Sprite.__init__(self) #call Sprite intializer
self.image, self.rect = nahrajObrazek(image)
obrazovka = pygame.display.get_surface()
self.oblast = obrazovka.get_rect()
self.rect.topleft = 10, 10
self.posun = 9
self.vrteni = 0

def update(self):
"jdi nebo se toc podle stavu opicky"
if self.vrteni:
self._otocka()
else:
self._jdi()

def _jdi(self):
"pohybuj s opickou sem a tam"
novaPozice = self.rect.move((self.posun, 0))
if novaPozice.left < self.oblast.left or novaPozice.right > self.oblast.right:
self.posun = -self.posun
novaPozice = self.rect.move((self.posun, 0))
self.image = pygame.transform.flip(self.image, 1, 0)
self.rect = novaPozice

def _otocka(self):
"otacej s obrazkem opicky"
center = self.rect.center
self.vrteni += 12
if self.vrteni >= 360:
self.vrteni = 0
self.image = self.original
else:
self.image = pygame.transform.rotate(self.original, self.vrteni)
self.rect = self.image.get_rect()
self.rect.center = center

def zasah(self):
"zde se zacina opicka otacet"
if not self.vrteni:
self.vrteni = 1
self.original = self.image


Třída Opicka dělá trochu více práce než Dlan, ale nic víc složitějšího. Tato třída posunuje opičku tam a zpět po obrazovce. Když je opička pohlazená, otočí se radostí kolem dokola. Tato třída je také potomkem třídy Sprite a je inicializována stejným způsobem, jako Dlan. Při inicializaci se také nastavuje atribut oblast, ve kterém se uchovává velikost displeje.

Funkce update() se jednoduše podívá na současný stav "vrtění", který je pravda, když se opička právě samou radostí otáčí po pohlazení. Volá buď metodu _otocka() nebo _jdi(). Tyto funkce mají na začátku podtržítko. To je takový standardní pythonýrský zvyk, který napovídá, že by se měly tyto metody používat jen v Opičce. Mohli bychom jít ještě dále a dát tam podtržítka dvě, čímž by se Pythonu skutečně řeklo, aby s nich udělal soukromé metody, ale takovou ochranu nepotřebujeme. :-)

Metoda _jdi() vytváří nové umístnění opičky pomocí posunu aktuálního obdélníku o zadaný posun. Pokud je nová pozice mimo oblast displeje, posun se otáčí. A také se překlápí obraz pomocí funkce pygame.transform.flip(). Venkovským efektem tohoto je, že opička vypadá, jako by se vždy otáčela tam, kam jde.

Metoda _otocka() se volá vždy, když se opička právě "vrtí". Atribut vrteni se používá pro uložení aktuálního stupně otočení se. Když se opička otočila kolem dokola (360 stupňů), vrací se obraz opičky zpět na originální verzi. Všimněte si, že vždy když voláme funkci rotate(), používáme originální obrázek opičky. Při rotaci je vždy mírná ztráta kvality obrázku. Opakované rotování stejného obrázku se zhoršuje kvalita násobně. A také, při rotaci obrázku se vlastně mění velikost obrázku. To proto, že se přetáčí rohy obrázku, a tak se obdélník vlastně zvětšuje. Ujistíme se také, že střed nového obrázku bude na místě středu starého obrázku, abychom rotovali bez pohybu.

Poslední metoda zasah() řekne přízraku, aby vstoupil do stavu vrtění se. To způsobí začátek otáčení se obrázku. Také se zde vytváří kopie aktuálního obrázku pod jménem original.

Vše inicializujeme

Před pořádnou prací s pygame musíme vše řádně inicializovat. Současně hned otevřeme jednoduché grafické okno. Nyní se tedy nalézáme ve funkci  main(), kde se vše spouští.

    pygame.init()
obrazovka = pygame.display.set_mode((468, 60))
pygame.display.set_caption('Pohlaď opičku')
pygame.mouse.set_visible(0)
pygame.display.set_icon(nahrajObrazek('dlan.bmp')[0])

První řádek dělá trochu práce za nás. Při importování všech modulů pygame je zároveň inicializuje. Je možné provést zpětnou kontrolu správné inicializace všech modulů, ale tím se teď nebudeme zabývat. Je také možné převzít plnou kontrolu nad inicializací každého modulu. Obvykle to není nutné, ale možné to je.

Dále nastavujeme mód grafického displeje. Všimněte si, že modul pygame.display se používá na ovládání veškerých nastavení displeje. V našem případě žádáme o jednoduché úzké okno. Existuje celý samostatný tutoriál na nastavování displeje, nicméně když nám na tom moc nezáleží, pygame je schopna udělat spousty práce za nás. Pygame zvolí nejvhodnější barevnou hloubku,  pokud žádnou nežádáme.

Dále nastavíme titulek a systémovou ikonu našeho okna. A vypneme kurzor myši. To jsou tedy úplné základy. Nyní máme malé černé okno připravené na naše rozkazy. Předvoleně je kurzor viditelný, takže pokud chceme, aby byl vidět, nemusíme dělat nic.

Vytváříme pozadí

Náš program bude mít text na pozadí.Bylo by pohodlné, kdybychom vytvořili jednoduchou plochu (surface), která by představovala pozadí, a opakovaně ji mohli používat. První krok bude tedy vytvoření takové plochy.

    pozadi = pygame.Surface(obrazovka.get_size())
pozadi = pozadi.convert()
pozadi.fill((250, 250, 250))

Tímto vytváříme novou plochu v té samé velikosti, jako má displej. Všimněte si extra volání funkce convert() po vytvoření plochy. Convert bez argumentů zajistí převod našeho pozadí do stejného barevného formátu, jako má displej, což má za následek nejrychlejší možné výsledky.

Také vyplníme celé pozadí téměř bílou barvou. Funkce fill() má za argument barvu ve formátu trojice RGB.

Centrovaný text na pozadí

Teď když máme plochu pozadí, zobrazme (renderujme) na ní text. To můžeme udělat, jen když modul pygame.font se řádně naimportoval. Jestli ne, jednoduše sekci přeskočíme.

    if pygame.font:
pismo = pygame.font.Font(None, 36)
text = pismo.render(u"Pohlaď opičku a vyhraj hlavní výhru!", 1, (10, 10, 10))
textPozice = text.get_rect()
textPozice.centerx = pozadi.get_rect().centerx
pozadi.blit(text, textPozice)

Jak vidíte, je zde několik příkazů. Nejprve musíme vytvořit objekt písmo (font) a zobrazit ho na novou plochu. Pak najdeme střed té nové plochy a nakonec ho vykreslíme (blitneme) na pozadí.

Písmo se vytváří pomocí konstruktoru Font() z modulu pygame.font. Obvykle předáváte této funkci jméno truetypového písma, ale můžeme také předat None, což způsobí použití přednastaveného fontu. Konstruktor Font také potřebuje vědět, jakou velikost fontu chceme.

Pak zobrazíme ten font na novou plochu. Funkce render() vytváří novou plochu vhodné velikosti pro náš text. V našem případě také žádáme o vyhlazený (antialisovaný) text (pro hladký vzhled) a o tmavě šedou barvu.

Dále najdeme střed textu na našem displeji. Vytváříme objekt Obdélník (Rect) pomocí souřadnic textu, který nám umožní ho jednoduše přiřadit středu displeje.

Nakonec vykreslíme (blit) text na plochu pozadí.

Zobrazení displeje

Zatím máme stále černé okno na obrazovce. Zobrazme naše pozadí ještě před tím, než načteme další zdroje.

    obrazovka.blit(pozadi,(0,0))
pygame.display.flip()

Toto vykreslí celé pozadí na okno displeje. Vykreslení (blit) je zřejmé jak funguje, ale co je ta funkce flip()?

V pygame nejsou změny, učiněné na ploše, viditelné hned. Normálně musí být displej obnovován v oblastech, které se změnili. U displeji s dvojitou vyrovnávací pamětí musí být displej swapován (nebo flipován = zrcadlen), aby byly změny viditelné. V tomto případě funkce flip() pracuje pěkně, protože jednoduše obsluhuje celou oblast okna a umí obsloužit jak plochy s jednoduchou nebo dvojitou vyrovnávací pamětí.

Připravíme si objekty hry

Tady si připravíme všechny objekty, které budeme ve hře potřebovat.

    vedleZvuk = nahrajZvuk('vedle.wav')
zasahZvuk = nahrajZvuk('zasah.wav')
dlan = Dlan('dlan.bmp')
opicka = Opicka('opicka.bmp')
vsechnyPrizraky = pygame.sprite.RenderPlain((opicka, dlan))
casovac = pygame.time.Clock()

Nejprve nahrajeme oba zvukové efekty pomocí funkce nahrajZvuk(), kterou jsme si nadefinovali výše. Pak vytvoříme instanci každé z našich přízrakových tříd (z definic tříd class Opicka(pygame.sprite.Sprite) a class Dlan(pygame.sprite.Sprite)).Nakonec vytváříme Skupinu přízraků, která bude obsahovat všechny naše přízraky.

Zde používáme speciální přízrakovou skupinu jménem RenderPlain. Tato přízraková skupina umí kreslit všechny své přízraky na obrazovku. Nazývá se RenderPlain, protože se jedná o vylepšenou skupinu Render. Pro naši hru však potřebujeme jen jednoduché kreslení. Vytvoříme tedy skupinu jménem vsechnyPrizraky tak, že předáme n-tici všech přízraků, které do ní budou patřit. Později bychom mohli přidávat i odebírat přízraky z této skupiny, ale v naší hře toho nevyužijeme.

Objekt hodin vytvoříme, abychom získali lepší kontrolu nad rychlostí hry. Použijeme ho v hlavní smyčce pro zajištění, aby hra neběhala příliš rychle.

Hlavní smyčka

Moc toho zde není, jen nekonečná smyčka.

while 1:
casovac.tick(60)

Všechny hry běhají v nějakém druhu smyčky. Obvyklé pořadí  věcí je tento: kontrola vstupu od uživatele i počítače, pohyb a obnova všech objektů a nakonec jejich vykreslení na obrazovku. Uvidíte, že v tomto případě to nebude jiné.

Také zde voláme náš objekt hodin, který zajistí, aby hra neběhala rychleji než 60 snímků na sekundu.

Ošetření všech vstupních událostí

Toto je extrémně snadná ukázka fungování fronty událostí.

        for udalost in pygame.event.get():
if udalost.type == QUIT:
return
elif udalost.type == KEYDOWN and udalost.key == K_ESCAPE:
return
elif udalost.type == MOUSEBUTTONDOWN:
if dlan.trefa(opicka):
zasahZvuk.play()
opicka.zasah()
else:
vedleZvuk.play()
elif udalost.type == MOUSEBUTTONUP:
dlan.trefaNavrat()

Nejprve načteme všechny čekající události z pygame a každou zvlášť projdeme. První dva testy slouží ke kontrole, jestli uživatel chce odejít. V těchto případech jednoduše opustíme (return) funkce main() a program skončí.

V dalších testech kontrolujeme, jestli bylo tlačítko myši zmáčknuté nebo uvolněno. Pokud bylo zmáčknuté, zeptáme se dlaně, jestli se nesrazila s opičkou. V obou dvou případech zahrajeme příslušný zvuk a pokud jsme se trefili a opičku skutečně pohladili, řekneme jí, že jsme jí zasáhli a ona se začne otáčet.

Obnova všech přízraků

        vsechnyPrizraky.update()

Skupiny přízraků mají metodu update(), která jednoduše volá metodu update() pro všechny přízraky, které obsahuje. Každý z objektů se nějak pohne, v závislosti na stavu, v jakém se nachází. To je místo, kde opička popojde o jeden krok nebo se o kousek pootočí, pokud byla těsně předtím pohlazená.

Vykreslíme celou scénu

Nyní, když jsou všechny objekty na svých nových místech, je čas je vykreslit na obrazovku.

        obrazovka.blit(pozadi, (0, 0))
vsechnyPrizraky.draw(obrazovka)
pygame.display.flip()

První volání funkce blit() nakreslí pozadí na celou obrazovku. To vymaže vše, co tam zbylo z minulého snímku (trochu neefektivní, ale pro naši hru dostatečné). Dále voláme metodu skupiny přízraků draw(). Protože ve skutečnosti je skupina přízraků jen instance RenderPlain, umí tato kreslit naše přízraky. Nakonec ozrcadlíme (flip) obsah pygamové softwarové dvojité vyrovnávací paměti na obrazovku. To zviditelní vše najednou, co jsme doposud kreslili na obrazovku .

Hra skončila

Uživatel odešel, je čas na úklid.

Uklízení po hře je extrémně jednoduché. Ve skutečně jsou všechny proměnné zničeny automaticky a nemusíme dělat vůbec nic.

Poznámky překladatele

Zdroje: Celý program (komentáře odpovídají nadpisům), opička, ruka, zasahZvuk, vedleZvuk.

V originále nebylo hlazení opičky, ale bouchání, což jsem nedokázal přenést přes srdce, tak jsem si dovolil tuto malou změnu. Laskavý čtenář pochopí. Ukázky kódu v hnědých rámečcích jsem kopíroval přímo ze zdrojového programu, takže je pěkně vidět, hlavně ke konci, jak je kód odsazován. Nicméně si myslím, že tento tutoriál neodráží věrně stav vývoje programu, kdy nejdříve vznikne nějaká jednodušší forma a pak se program vyvíjí. Všechny části se předkládají vlastně hotové a popisuje se hotový stav. Přesto to může být velmi cenné. Ať slouží!