A Python programozási nyelv

Pygame

A Pygame játékok fejlesztését segítő python modulok gyűjteménye. A platformfüggetlen SDL könyvtárra épül. Ez az oka annak, hogy a pygame programok szinte minden platformon és operációs rendszeren futtathatók.
A Pygame egy ingyenes bővítése a nyelvnek. Részleteket ld. az LGPL licensz leírásában.
A következőkben megpróbálom egyszerű példákkal bemutatni a pygame használatát, működését. Ezt a tutoriált Pete Shinners: Line By Line Chimp című munkája alapján készítettem. Többnyire annak fordítása, ill. saját tapasztalatokkal való kiegészítése.

Szükséges modulok

A következő kódrészlet tartalmazza a szükséges modulokat. Egyben példát láthatunk arra, hogyan ellenőrizhetjük egyes, opcionális modulok jelenlétét a futtató környezetben.
import os, sys import pygame from pygame.locals import * if not pygame.font: print 'Warning, fonts disabled' if not pygame.mixer: print 'Warning, sound disabled'
Először importáljuk az "os" és "sys" python modulokat. Ezek sok mindenre használhatók, például lehetővé teszik platform független fájl-útvonalak megadását. A következő sorban importáljuk a pygame csomagot. Ezzel az összes pygame modult importáltuk. Van néhány opcionális modul is, melyek None értéket vesznek fel, ha nem sikerült őket beimportálni. Ezt ellenőrizzük az utolsó két sorban.
A "locals" egy speciális pygame csomag. Ez olyan gyakran használható konstansokat és függvényeket tartalmaz, melyeket célszerű a program globális névterébe elhelyezni. Ez a modul tartalmazza többek között a Rect függvényt, mellyel téglalap objektumokat hozhatunk létre, vagy olyan konstansokat, mint a 'QUIT, HWSURFACE', melyeket a pygame más részeivel való interakciókhoz használjuk. Természetesen nem kötelező ezt importálni. Ekkor a pygame modulon keresztül is elérhetjük.
Végül példát láthatunk arra, hogyan írassunk ki hibajelzéseket, ha egy adott modul pl. a font vagy a sound nem elérhető.

Erőforrások betöltése

Két függvényt adunk képek és hangok betöltésére.

Kép betöltése

def load_image(name, colorkey=None): fullname = os.path.join('data', name) try: image = pygame.image.load(fullname) except pygame.error, message: print 'Cannot load image:', name raise SystemExit, message image = image.convert() if colorkey is not None: if colorkey is -1: colorkey = image.get_at((0,0)) image.set_colorkey(colorkey, RLEACCEL) return image, image.get_rect()
Az első paramétere a betöltendő kép nevét tartalmazza. A függvény rendelkezik egy második, opcionális paraméterrel is. Ezt a kép "színkulcsának" beállításához használhatjuk. A "színkulcsot" az átlátszó színek reprezentálására használjuk.
A függvény első sorában elkészítjük a fájlhoz tartozó teljes útvonalat. Ebben a példában az összes erőforrás a "data" alkönyvtárban van. Az os.path.join használatával olyan fájl-útvonalakat gyárthatunk, melyek bármilyen platformon használhatók.
A következő lépésben a pygame.image.load függvénnyel betöltjük a képet. Ezt a függvényhívást egy kivételkezelő részbe ágyaztuk, így a betöltés során fellépő hibákat könnyen lekezelhetjük. A betöltés után fontos a convert() függvény meghívása. Ez egy új másolatot készít az objektumról és a képernyőhöz igazítja a kép színformátumát és színmélységét. Ez azért kell, hogy a képernyőre való kirajzolás a lehető leggyorsabban menjen.
Végül beállítjuk a "színkulcsot" a képhez. Ha a felhasználó megadta a colorkey paramétert, akkor ez lesz a képhez rendelve. Általában ez egy szín RGB értéke, pl. a (255,255,255) tuple a fehér színt jelenti. Ezenkívül megadhatunk -1-et is színkulcsként. Ebben az esetben a kép bal-felső pixelének színe lesz a színkulcs.

Hang betöltése

def load_sound(name): class NoneSound: def play(self): pass if not pygame.mixer: return NoneSound() fullname = os.path.join('data', name) try: sound = pygame.mixer.Sound(fullname) except pygame.error, message: print 'Cannot load sound:', wav raise SystemExit, message return sound
Ezzel a függvénnyel egy hangfájlt tudunk betölteni. Az első dolog annak ellenőrzése, hogy sikerült-e rendesen importálnunk a pygame.mixer modult. Sikertelen esetben egy speciális osztálypéldányt adunk vissza, mely rendelkezik egy állejátszó metódussal. Ez az objektum ugyanúgy viselkedik és használható, mint egy normális Sound objektum azzal a kivétellel, hogy nem ad hangot. Ezáltal nincs is szükségünk további hiba ellenőrzésre.
A fenti két függvény nagyon hasonlít egymásra. Itt is először definiálunk egy teljes útvonalat a hangfájlhoz, majd a try-except részben betöltjük. Végül egy Sound objektumot adunk vissza.

Játék osztályok

A következőkben egy játék két lehetséges objektumait mutatjuk be. A játék "logikáját" ez a két osztály tartalmazza.
class Fist(pygame.sprite.Sprite): """moves a clenched fist on the screen, following the mouse""" def __init__(self): pygame.sprite.Sprite.__init__(self) #call Sprite initializer self.image, self.rect = load_image('fist.bmp', -1) self.punching = 0 def update(self): "move the fist based on the mouse position" pos = pygame.mouse.get_pos() self.rect.midtop = pos if self.punching: self.rect.move_ip(5, 10) def punch(self, target): "returns true if the fist collides with the target" if not self.punching: self.punching = 1 hitbox = self.rect.inflate(-5, -5) return hitbox.colliderect(target.rect) def unpunch(self): "called to pull the fist back" self.punching = 0
A fenti osztály reprezentálja a játékos öklét. Ezt a pygame.sprite modul Sprite osztályából származtattuk. Az __init__ függvény új példány létrehozásakor hívódik meg. Ebben a függvényben először a bázisosztály __init__ függvényét hívjuk. A játékunkban a sprite-ok kirajzolásához Group osztályokat használunk. Ezen osztályok olyan sprite-ok megjelenítésére használhatók, amelyek rendelkeznek image és rect attribútumokkal. Ezen attribútumok módosításával érhetjük el, hogy a megjelenítő egy adott képet egy adott helyre rajzoljon ki.
Minden sprite rendelkezik egy update() metódussal. Ezt a függvény általában képkockánként egyszer hívjuk. Ebbe helyezhetünk olyan kódokat, melyekkel mozgathatjuk és frissíthetjük a sprite változóit. Jelen esetben az update() metódus az öklöt az egér kurzor helyére mozgatja. Eltolást is alkalmaz, ha az objektum "punching" állapotban van.
A punch() és az unpunch() metódusokkal a "punching" állapot kapcsolható be és ki. A punch() függvény igaz értéket ad vissza, ha az ököl eltalálta a célt, azaz a két sprite ütközött.
class Chimp(pygame.sprite.Sprite): """moves a monkey critter across the screen. it can spin the monkey when it is punched.""" def __init__(self): pygame.sprite.Sprite.__init__(self) #call Sprite intializer self.image, self.rect = load_image('chimp.bmp', -1) screen = pygame.display.get_surface() self.area = screen.get_rect() self.rect.topleft = 10, 10 self.move = 9 self.dizzy = 0 def update(self): "walk or spin, depending on the monkeys state" if self.dizzy: self._spin() else: self._walk() def _walk(self): "move the monkey across the screen, and turn at the ends" newpos = self.rect.move((self.move, 0)) if not self.area.contains(newpos): if self.rect.left < self.area.left or \ self.rect.right > self.area.right: self.move = -self.move newpos = self.rect.move((self.move, 0)) self.image = pygame.transform.flip(self.image, 1, 0) self.rect = newpos def _spin(self): "spin the monkey image" center = self.rect.center self.dizzy += 12 if self.dizzy >= 360: self.dizzy = 0 self.image = self.original else: rotate = pygame.transform.rotate self.image = rotate(self.original, self.dizzy) self.rect = self.image.get_rect(center=center) def punched(self): "this will cause the monkey to start spinning" if not self.dizzy: self.dizzy = 1 self.original = self.image
A chimp osztálynak egy kicsit több feladata van, mint a Fist-nek, de semmivel sem bonyolultabb annál. Az osztály feladata a csimpánz mozgatása a képernyőn, és ha eltaláljuk a majmot, akkor elkezd forogni. Ezt az osztályt is a Sprite osztályból származtatjuk, és a Fisthez hasonlóan inicializáljuk. Az inicializálás során az area attribútumot a képernyő méretéhez igazítjuk.
Az update függvény tevékenysége a csimpánz "dizzy" állapotától függ. Ez akkor igaz, ha eltalálták a majmot és éppen forog. Ennek függvényében vagy a _spin vagy a _walk metódusokat hívja. Az aláhuzás prefix a python függvények nevében azt jelenti, hogy ezt csak a tartalmazó osztály használhatja. Ez csupán egy konvenció, azaz nem nyelvi szintű deklarátum.
A walk metódus egy adott értékkel eltolja a csimpánz képét. Ha az új pozíció érinti a kép szélét, akkor az eltolás irányát megfordítjuk. Ezen kívül a csimpánz képét is tükrözzük a pygame.transform.flip függvénnyel. Ezzel elértük, hogy a csimpánz mindig testtel előrefelé mozogjon.
A spin metódust, akkor használjuk, amikor eltalálták a makit, és épp szédül, azaz a "dizzy" változó nem nulla (logikai értéke igaz). Ebben az attribútumban tároljuk a aktuális forgatás mértékét (0-360 fokig). Miután teljesen körbeforgattuk, ismét az eredeti, forgatás nélküli képet rajzoljuk a csimpi helyére. A pygame.transform.rotate hívása helyett, először egy referenciát állítunk erre a függvényre. Ez a rotate változó. Később - a rövidebb forma miatt - ezen keresztül végezzük a forgatást. Ezt nem kötelező így csinálnunk, de adunk erre is példát. Mindig az eredeti képet fogjuk forgatni. Erre azért van szűkség, mert a forgatás során romlik a kép minősége. Így egy forgatás sorozat jelentősen rontana a képminőségen. Ezen kívül a forgatással a kép mérete is változhat. A rotate függvény első paramétere a forgatandó kép, a második pedig a forgatás mértéke fokokban. A rotáció után a forgatás középpontja, a keletkezett kép középpontja kell, hogy legyen. Ezzel elérhetjük, hogy ne lépjen fel elmozdulás.
Végül nézzük meg a punched() metódust. Ennek meghívásával lép a sprite a forgó, szédült állapotba. Az aktuális képről készít egy másolatot az original attribútumba. Ez a kép lesz a forgatás alapja.

Inicializáljunk mindent

Mielőtt bármit is tennénk a pygame-mel, meg kell bizonyosodnunk, hogy minden szükséges modul inicializálva lett. Ha ez teljesült, akkor létrehozhatunk egy egyszerű grafikus ablakot. A következőket a main() függvénybe fogjuk elhelyezni.
pygame.init() screen = pygame.display.set_mode((468, 60)) pygame.display.set_caption('Monkey Fever') pygame.mouse.set_visible(0)
Az első sorral inicializáljuk a pygame-et. Ellenőrzi az importált pygame modulokat, és inicializálja azokat. Lehetőségünk van saját kezűleg is inicializálni és ellenőrizni a modulokat, de erre most nem térünk ki.
A következő lépésben a képernyőt állítjuk be. Jegyezzük meg, hogy a képernyő beállításokért a pygame.display modul felelős. Ezen keresztül tudjuk azokat módosítani, stb. Jelen esetben egy üres, 468x60-as felbontású ablakot készítünk. A képernyő módok beállításának témája meghaladná ennek a tutoriálnak a kereteit. A fenti esetben ezt a feladatot a pygame elvégzi helyettünk. Végül beállítjuk az ablak címét, és eltüntetjük az egérkurzort az ablak felett. Ezzel a módszerrel egy fekete ablakhoz jutottunk.

Csináljunk hátteret

A programunk hátterében különböző szöveges üzeneteket szeretnénk megjeleníteni. Jó lenne egy egyszerű felületet készíteni, ami a programunk hátterét reprezentálná, és a továbbiakban ezt használni. Készítsük el ezt a felületet!
background = pygame.Surface(screen.get_size()) background = background.convert() background.fill((250, 250, 250))
Ezzel egy új felületet hoztunk létre, melynek mérete egyezik a képernyőével. Itt is szűkség van a convert() hívásra. Az argumentum nélküli convert függvénnyel a képernyő formátumával megegyező lesz a háttér. Ezzel a programunk hatékonyságát növeljük.
Végül fehérre színezzük a hátteret. A fill művelet egy RGB hármast, egy tuple-t vár paraméternek.

Szöveg elhelyezése a háttéren

Tehát rendelkezünk egy felülettel, és ezen szeretnénk szöveget megjeleníteni. Ezt csak akkor tehetjük meg, ha a pygame.font modult megfelelően importáltuk. Ha nem, akkor átugorjuk ezt a részt.
if pygame.font: font = pygame.font.Font(None, 36) text = font.render("Pummel The Chimp, And Win $$$", 1, (10, 10, 10)) textpos = text.get_rect(centerx=background.get_width()/2) background.blit(text, textpos)
Látható, hogy egy egyszerű szöveg megjelenítéséhez is több lépésre van szükség. Először létrehozunk a font objektumot és megjelenítjük azt egy új felületen. Eztán megkeressük ennek a felületnek a középpontját, majd beillesztjük a háttérbe.
Font-ot a font modul Font() konstruktorával hozhatunk létre. Paramétereiben megadhatod egy truetype fontfájl nevét. None-nal jelezhetjük az alapértelmezett betűtípus használatát. A Font konstruktorának még meg kell adni a használni kívánt font méretét.
A render függvény egy új felületet hoz létre, mely megfelelő méretű a megjelenítendő szövegnek. A paraméterekben megadjuk, hogy antialiased, sötét szürke színű szöveget akarunk kirakni.
Eztán megkeressük a szöveg középpontját. Létrehozunk egy - a szöveg dimeziónak megfelelő - Rect objektumot, ami már egyszerűen felrakható a képernyő közepére.
Végül a blit művelettel feltesszük a háttérre.

Háttér megjelenítése

Mutassuk meg a hátteret, amíg a többi erőforrás betöltésére várunk.
screen.blit(background, (0, 0)) pygame.display.flip()
Ezzel a kódrészlettel a teljes hátteret megjelenítettük a képernyőn. A blit kiteszi a screen objektumra, míg a flip a screen objektum tartalmát jeleníti meg fizikailag az ablakban/képernyőn.
A pygame-ben nem közvetlenül a fizikai képernyőre rajzolunk, hanem egy screen objektumra. Ha mindent, amit meg akarunk jeleníteni feltettünk erre, akkor egy pillanat alatt ki tudjuk rajzolni a fizikai képernyőre.

Játékobjektumok elkészítése

Elkészítjük mindazokat az objektumokat, melyek a játék futtatásához szükségesek.
whiff_sound = load_sound('whiff.wav') punch_sound = load_sound('punch.wav') chimp = Chimp() fist = Fist() allsprites = pygame.sprite.RenderPlain((fist, chimp)) clock = pygame.time.Clock()
Először betöltünk két hang effektust a load_sound függvény segítségével. Eztán létrehozunk egy-egy példányt a sprite osztályainkból. Végül a spritejainkat elhelyezzük egy Group objektumba. Ez egy tároló, amivel egyszerűbbé válik a spriteok kezelése.
Egy speciális sprite Group-ot használunk, nevezetesen egy RenderPlain-t. Ez az összes benne elhelyezett spritot meg tudja jeleníteni. Vannak ennél sokkal összetettebb Render Groupok is. A mi kis játékunkban csak a megjelenítést kell elvégeznie. A Group objektumunkra hivatkozik az allsprites referencia.
A clock objektummal szabályozhatjuk a programunk framerátáját, azaz azt, hogy legfeljebb hány képkockát jelenítsen meg a program másodpercenként. Ezt majd a főciklusban fogjuk használni.

A fő ciklus

A főciklus egy végtelen ciklus lesz.
while 1: clock.tick(60)
Minden játék egy hasonló ciklusban fut. A clock.tick(60)-nal garantáljuk, hogy a programunk legfeljebb 60 képkocka per másodperces sebességgel fog futni/megjeleníteni.

Események kezelése

Az eseménysor nagyon egyszerű kezelésére mutatunk példát.
for event in pygame.event.get(): if event.type == QUIT: return elif event.type == KEYDOWN and event.key == K_ESCAPE: return elif event.type == MOUSEBUTTONDOWN: if fist.punch(chimp): punch_sound.play() #punch chimp.punched() else: whiff_sound.play() #miss elif event.type == MOUSEBUTTONUP: fist.unpunch()
Először elkérjük a pygame-től a rendelkezésre álló eseményeket. Ez szerepel a ciklus fejlécében. Az elágazás első két ágában két egyszerű eseményt kezelünk. Az első: a felhasználó kilépett a programból. A második: lenyomták az esc billentyűt. Ezekben az esetekben kilépünk a programból.
Eztán ellenőrizzük, hogy az egér gombja le lett-e nyomva, vagy fel lett-e engedve. Ha lenyomták a gombot, akkor megkérdezzük a fist objektumot, hogy ütközik-e a chimp objektummal. Eztán lejátsszuk a megfelelő hangeffektet, és ha eltalálta a majmot, akkor hívjuk a chimp punched metódusát. (Erre ő bepörög.)

Spriteok frissítése

allsprites.update()
A sprite csoportoknak van egy update metódusa, melynek hívására az összes tartalmazott sprite update metódusát meghívja. Ennek hatására minden frissül, ahogy azt a játék objektumoknál leírtuk.

Rajzoljunk ki mindent

A következő utasításokkal kirajzolhatjuk az objektumainkat.
screen.blit(background, (0, 0)) allsprites.draw(screen) pygame.display.flip()
Először kirakjuk a hátteret a screen objektumra. Ezt követően a allsprites csoportnak hívjuk a draw metódusát. Ezzel az összes benne tárolt objektumot kitesszük a screen objektumra. Végül a flip függvénnyel mindent megjelenítünk a fizikai képernyőn.

Vége!

Dőljünk hátra, és élvezzük a munkánk megérdemelt gyümölcsét. A pygame objektumok kitakarításával nem kell foglalkoznunk, azt elvégzi helyettünk a python keretrendszer és a garbage collector.

Példaprogram

Shepherd, a juhászkutya szimulátor