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