Ügyességi játékok programozása
Pythonnal és Pygame-melChapter 13: Ismerkedés a grafikus hátteret kezelő elemekkel(sprite-okkal)
A játékunknak szüksége van arra, hogy kezelni tudjuk az ütköző tárgyakat. Labdák pattognak, lézerágyúk találnak el idegeneket, vagy a kedvenc karakterünk gyűjt be érmét. Minden ilyen példa, egy-egy ütközés érzékelést igényelt.
A Pygame könyvtár támogatja a sprite-okat. A sprite egy kétdimenziós kép, ami egy nagyobb grafikus jelenet része. Tipikusan, a sprite egyfajta objektum a képernyőn, ami kapcsolatba lép egy autóval, békával vagy egy kis vízvezetékszerelővel.
Eredetileg a videójáték konzoloknak beépített támogatásuk volt a hardverben a sprite-okhoz. Most már ez a különleges hardvertámogatás nem szükséges, mégis használjuk a sprite terminust.
13.1 Alapvető sprite-ok és ütközések
Nézzük meg a példa programunkat, ami sprite-okat használ.
A példán azt láthatjuk, hogyan készítsünk egy fekete téglákból
álló képernyőt, és hogyan gyűjtsük össze őket egy vörös téglával,
amit az egérrel irányítunk. 13.1.
A program számolja a pontokat, aszerint, mennyi téglát gyűjtöttünk be.
A kódja ennek a példa programnak megtalálható itt:
ProgramArcadeGames.com/python_examples/f.php?file=sprite_collect_blocks.py
Az első néhány programsorunk ugyanúgy kezdődik, mint más játékaink eddig:
import pygame import random # Definiáljuk a színeket BLACK = ( 0, 0, 0) WHITE = (255, 255, 255) RED = (255, 0, 0)
A Pygame könyvtár fontos lesz a sprite kezelés szempontjából már az első sorban. A véleten (random) könyvtár felelős a téglák véletlenszerű elhelyezéséért, a második sortól hívtuk meg. A színek megadás az 5-7 sorokban történik, semmi új nincs ebben a példában.
class Block(pygame.sprite.Sprite): """ Ez az osztály jeleníti meg a labdát. Ez a sprite osztályból lesz leegyszerűsítve. """
A kilencedik sor azzal kezdi, hogy definiálja a Block(tégla) osztályt. Figyeld meg, hogy a 9-s sorban, ez az osztály a gyermeke a kbd>Sprite osztálynak. A pygame.sprite. specifikálja a könyvtárat és a csomagot, de ezekről majd a 15-dik fejezetben ejtünk szót. Az összes alapértelmezett beállítása a Sprite osztálynak most már a Block osztály része is.
def __init__(self, color, width, height): """ Konstruktor. Megadja a tégla színét, és x,y pozícióját . """ # Meghívjuk a szülő osztályt(Sprite) a konstruktorhoz super().__init__()
A konstruktor a Block osztályhoz, a 15-s sorban a self paraméterrel rendelkezik, mint bármely más konstruktor. Vannak még paraméterei, amik megadják a színt, magasságot és szélességet.
Fontos, hogy meghívjuk a szülő osztályát a konstruktornak a Sprite-ban, mivel így tudjuk a sprite-okat kezdő értékekkel ellátni. Ezt a 20-dik sorban tesszük meg.
# Létrehozunk egy tégla képet, és kitöltjük színnel. # Ez lehet egy kép, amit lemezről töltünk be. self.image = pygame.Surface([width, height]) self.image.fill(color)
A 24 és 25-s sorban olyan képet alkotunk, ami mindig megjelenik majd a képernyőn. A 24-s sor egy üres képet alkot. A 25-s tölti ki feketével. Ha a program számára fontos, hogy valami más legyen ott, ne csak egy fekete négyzet, akkor ez a pár sor kód kell neked:
Íme a példánk kódja:
def __init__(self, color, width, height): """ Ellipszis konstruktor. Megadja az ellipszis színét és méretét. """ # Meghívjuk a szülő osztályt (Sprite) konstruktornak super().__init__() #Megadjuk a háttér színét és áttetszővé állítjuk self.image = pygame.Surface([width, height]) self.image.fill(white) self.image.set_colorkey(white) # Kirajzoljuk az ellipszist pygame.draw.ellipse(self.image, color, [0, 0, width, height])
Ha a fenti kódot behelyettesítjük, akkor minden ellipszis alakot ölt majd. A 29-s sor kirajzolja az ellipszist, és a 26-s sor fehérré és áttetszővé teszi. Ugyanaz az elképzelés, mint a Tizenkettedik Fejezetben volt. Fehér hátteret készítünk, és a képünk fehér részét áttetszővé tesszük.
def __init__(self): """ Grafikus sprite konstruktor """ # Meghívjuk a szülő osztályt (Sprite) a konstruktornak super().__init__() # betöltjük a képet self.image = pygame.image.load("player.png").convert() # áttetszővé tesszük a színt self.image.set_colorkey(white)
Ha ehelyett egy bit-térképes grafikát szeretnénk, akkor másoljuk át a fenti kódot, ami a grafikát tölti be (22-s sor) és állítsuk be fehérnek az áttetsző színt (25-s sor). Ezesetben, a sprite-unk dimenziói automatikusan a grafikai dimenziói lesznek, és így nem kell tovább foglalkoznunk vele. Nézd meg a 15-s sorban, ahogy már nincsenek meg ezek a paraméterek.
Van még egy fontos sor, ami szükséges a konstruktorunknal, nem számít, milyen sprite-t szeretnénk:
# adjuk meg a téglalapot, aminek a dimenziója a kép # Frissítsük ennek a tárgynak a pozícióját, a megadott értékekkel. # az értékeke a rect.x és rect.y self.rect = self.image.get_rect()
A rect változó egy attribútum, ami egy példánya a Rect osztálynak, amit a Pygame biztosít számunkra. A téglalap ábrázolja a sprite dimenzióit. Ez a téglalap osztály rendelkezik az x és y attribútumokkal. A Pygame oda fogja rajzolni a téglalapot, ahol az x és y vannak. Szóval, ahhoz, hogy mozgatni tudjuk ezt a sprite-ot, a programozónak szüksége van a mySpriteRef.rect.x és a mySpriteRef.rect.y értékekre, ahol a mySpriteRef az a változó, ami a sprite-ra mutat.
Készen vagyunk a Tégla osztállyal. Ideje tovább menni az alap beállítások felé.
# Pygame alap beállítása pygame.init() # A képernyő szélessége és magassága screen_width = 700 screen_height = 400 screen = pygame.display.set_mode([screen_width, screen_height])
A fenti kód alapértékekkel látja el a Pygame-t és egy, a játék számára készült ablakot hoz létre. Semmi új nincs még itt, más programjainkhoz képest.
#Sprite-ok listája. Minden tégla a programban a #listához fog adódni # A listát egy "Group/csoport" nevű osztály kezeli block_list = pygame.sprite.Group() # Ez itt az összes sprite listája. # Minden tégla és a játékos téglája is itt van. all_sprites_list = pygame.sprite.Group()
A legnagyobb előny a sprite-okkal való munkának az, hogy csoportban lehet velük dolgozni. Rajzolhatunk és mozgathatunk minden sprite-ot egyetlen parancs kiadásával, ha csoportban vannak. Sőt, ellenőrízhetjük csoportosan a pattogásukat is.
A fenti kód két listát hozott létre. Az all_sprites_list változó minden sprite-t magába foglal a játékban. Ez a lista lesz használva arra, hogy kirajzoljuk az összes sprite-t. A block_list változó tartalmazza az összes objektumot, amit a játékos pattogtathat. Ebben a példában ez fogja magába foglalni az összes objektumot, még a játékost is. De, nekünk nincs szükségünk a játékosra ebben a listában, mivel ha ellenőrízzük, hogy a játékos ütközött e valakivel a block_list-ban, akkor azt találjuk majd, hogy igen, mivel ő maga is a lista része.
for i in range(50): # Ez egy téglát jelent block = Block(BLACK, 20, 15) # Tegyük egy véletlen helyre a téglát block.rect.x = random.randrange(screen_width) block.rect.y = random.randrange(screen_height) # Adjuk a téglát egy lista objektumhoz block_list.add(block) all_sprites_list.add(block)
A ciklus elkezdődik a 49-s sorban és 50 fekete sprite téglát ad majd a képernyőhöz. Az 51-s sor egy új téglát fog létrehozni, beállítja a színt, szélességet, magasságot. Az 54-55-s sor megadja a koordinátákat, ahol majd az objektum megjelenik. Az 58-s sor pedig a téglát a listához adja, amiben a többi tégla is van, és amikkel a játékos összeütközhet. Az 59-s sor hozzáadja az összes téglát tartalmazó listához. Ez egy elég egyszerű kód, amit már megírtál a 13-s laborban.
# létrehozzuk a Vörös színű játékos tégláját player = Block(RED, 20, 15) all_sprites_list.add(player)
a 61-63 sorok a játékosnak adnak értékeket a játék elején. A 62-s sor egy vörös téglát hoz létre, ami végig a játékban a játékost fogja jelenteni. Ez a tégla hozzá lesz adva az all_sprites_list a 63-s s sorban, így kirajzolható lesz, de nem lesz hozzáadva a block_list-hoz.
# Ismételd amíg a játékos rá nem kattint a bezár gombra. done = False # Kezelnünk kell azt is, hogy a képernyő milyen gyakran frissüljön. clock = pygame.time.Clock() score = 0 # -------- Főprogram ciklusa ----------- while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True # letöröljük a képernyőt screen.fill(WHITE)
A fenti kód egy standard program ciklus, amivel először az ötödik fejezet- ben találkoztunk. A 71-s sor fogja a score változót 0-ra állítani kezdetben.
#Adjuk meg a jelenlegi egér pozíciót. ez vissza fog adni egy pozíciót, # ami két szám lesz egy listában. pos = pygame.mouse.get_pos() # Nyerjük ki az x és y értékét a listából, # hasonlóan ahhoz, ahogyan egy szövegből kinyertük a betűket. # A játékos objektumát jelenítsük meg az egér helyén. player.rect.x = pos[0] player.rect.y = pos[1]
A 84-s sor megadja az egér pozícióját, hasonlóan más Pygame programokhoz, amikről már beszéltünk. A fontos dolog ebben a 89-90-s sor, ahol a téglatest magába foglalja a sprite-t, ami majd egy új helyre mozgatja azt. Emlékezz rá, hogy a "rect"-t a 31-s sorban hoztuk létre, és ez a kód nem működik nélküle.
# Figyeld meg, ha a játékos téglája ütközik e valamivel. blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)
Ez a kódsor veszi a player sprite referenciáját és összehasonlítja minden sprite-tal a block_list-ban. A kód ezután visszaadja a sprite-ok listáját, amik fedik egymást. Ha nincsenek egymást fedő sprite-ok, akkor egy üres lista fog visszajönni. A True (igaz) boolean változó letörli az ütköző sprite-okat a listáról. Ha ezt False (hamis)-ra állítjuk, akkor a sprite-ok nem fognak törlődni.
# Ellenőrízd a pattogás listáját: for block in blocks_hit_list: score +=1 print(score)
Ez a ciklus minden sprite-ot a pattogós listában visszaad a 93-s sorban. Ha ezek a sprite-ok a listában vannak, akkor meg kell növelni a pontot minden pattogás esetén. Azután a pontszámot kiírjuk a a képernyőre. Jegyezd meg, hogy a print a 98-s sorban nem fogja kiírni a pontszámot a fő ablakba, hanem csak csak a konzol ablakba. A 14-s labor mutatja meg, hogyan kell megjeleníteni a pontszámot a fő ablakban.
# rajzold ki az összes sprite-ot all_sprites_list.draw(screen)
A Group (csoport) osztálynak az all_sprites_list tagja, és így van egy eljárása, amit draw(rajz)-nak hívnak. Ez az eljárás ciklus ismételni fog minden sprite-ot a listában és meghívja ahhoz a draw eljárást. Ez azt jelenti, hogy csak egy sornyi kód kell, hogy a program all_sprites_list-ből az összes sprite-t megjelenítsük.
# Csak 60 frame per másodperc clock.tick(60) # Gyerünk tovább, és frissítsük a képernyőt amivel rajzoltuk. pygame.display.flip() pygame.quit()
A 103-109 sorok felugranak a képernyőre és meghívják a quit eljárást, amikor a főciklusnak vége.
13.2 Sprite-ok mozgatása
Az eddigi példákban csak a játékos sprite-ja mozgott. Hogyan képes egy program az összes sprite-ot mozgásra bírni? Ez is könnyű, mindössze két lépést igényel.
Az első lépésben egy új eljárást fogunk létrehozni a Block osztályban. Ezt az új eljárást nevezzük update (frissítés)-nek. A frissítés függvény automatikusan meg lesz hívva, amikor az update-t futtatjuk/meghívjuk a program során.
Tegyük ezt a sprite-ba:
def update(self): """ Minden frame-t meghívunk. """ # Egy pixellel lejjeb mozog a tégla self.rect.y += 1
Tegyük ezt a főprogram ciklusba:
#Meghívjuk az update() eljárást minden téglára, a block_list-ban block_list.update()
A kód nem tökéletes, mivel a tégla ki fog esni a képernyőről és nem fog többet megjelenni. Ezt a kódot még ki kell egészíteni az update függvénnyel, így a tégla megjelenik majd a tetején.
def update(self): # mozgassuk lejjebb egy pixellel a téglát self.rect.y += 1 if self.rect.y > screen_height: self.rect.y = random.randrange(-100, -10) self.rect.x = random.randrange(0, screen_width)
Ha a program reseteli a téglát, amit a képernyő tetejére gyűjtöttünk, akkor a sprite-ot meg tudjuk változtatni az alábbi kóddal:
def reset_pos(self): """ Kinullázzuk a pozíciót a képernyő tetejére, véletlen x helyre Meghívjuk az update() vagy a főciklust, ha ütközés vam . """ self.rect.y = random.randrange(-300, -20) self.rect.x = random.randrange(0, screen_width) def update(self): """ minden frame-t meghívunk """ # mozgassunk egy pixellel lejjeb egy téglát self.rect.y += 1 # Ha egy tégla túl lent van, akkor reseteljük ki a képernyő tetejére if self.rect.y > 410: self.reset_pos()
Ahelyett, hogy elpusztítanánk a téglát, amikor egy ütközés történik, a program meghívhat egy függvényt, amit reset_pos -nak nevezünk el, és a tégla a képernyő tetejére mozog, begyűjtésre várván.
# Lássuk, hogy ajátékos téglája ütközik e. blocks_hit_list = pygame.sprite.spritecollide(player, block_list, False) # Ellenőrízzük az ütközések listáját. for block in blocks_hit_list: score += 1 print(score) # reseteljük a téglát a képernyő tetejére, hogy újra leessen. block.reset_pos()
A teljes példakódunk itt lesz:
ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites.py
Ha inkább ugráló kódokat látnál, akkor ezt nézd meg:
ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites_bounce.py
Ha köröket szeretnél mozgatni:
ProgramArcadeGames.com/python_examples/f.php?file=sprite_circle_movement.py
13.3 A játék osztály
Lapozzunk vissza a Kilencedik Fejezethez, ahol megismerkedtünk a függvénnyel. A fejezet végén beszéltünk egy lehetőségről, a main függvényről. Ahogy a programunk egyre nagyobb lesz, ez a technika fog minket kihúzni a bajból, és így nem kell annyi sor kódot írnunk. A programunk nem túl nagy még. Habár, tudom, hogy némely emberek szeretnek aprólékosan tervezni már a kezdettől.
Nos, azoknak az embereknek, abban a táborban, itt van még egy technika,
amivel lehetőség nyílik a kód szervezettebbé tételére.
(Ha nem tartozol ahhoz a táborhoz, akkor át is ugorhatod ezt a részt, és
majd visszakanyarodsz, amikor a programot nagyobbá kezd válni. )
Nézd meg a videót, hogy képet kapj arról, ahogyan a program működik.
ProgramArcadeGames.com/python_examples/f.php?file=game_class_example.py
13.4 További példák
Van még másféle példa is arra, ahogyan tudjuk kezelni a sprite-okat. Egypárról van itt videó, amiből kiderül, hogy működik a kód.
13.4.1 Dolgok lelövése
Érdekelnek a lövöldözős játékok? Valami olyasmi, mint a klasszikussá vált
Space Invaders? Ez a példa be fogja mutatni, hogyan készíts sprite-okat, amik
lövedékeket jelenítenek meg:
ProgramArcadeGames.com/python_examples/f.php?file=bullets.py
13.4.2 Falak
Jobban kedveled a kaland játékokat? És persze nem szeretnéd, hogy a játékos
csak úgy bárhova elkószálhasson. Ez itt megmutatja, hogyan adhatsz falakat,
amik gátolják a játékos mozgását:
ProgramArcadeGames.com/python_examples/f.php?file=move_with_walls_example.py
Egy pillanat! Egy szoba nem elég egy kalandhoz? Szeretnéd, ha a játékos
mozoghatna képernyőről, képernyőre? Megtehetjük. Nézd csak meg ezt a példát,
ahol a játékos egy több-szobás labirintusban mászkálhat:
ProgramArcadeGames.com/python_examples/f.php?file=maze_runner.py
13.4.3 Platformok
Szeretnél platform játékot, mint amilyen a Donkey Kong? Ehhez szükség
lesz egy ahhoz hasonló dologra, mint a falak, csak gravitációt kell
hozzá adnunk. :
ProgramArcadeGames.com/python_examples/f.php?file=platform_jumper.py
A jó platformjáték oldaltól oldalig mozog. Ez egy oldalra scrollozó
platformjáték: :
ProgramArcadeGames.com/python_examples/f.php?file=platform_scroller.py
Még a jobb platformjátékokban is vannak platformok, amik mozognak.
Nézzük meg egy példán át ezeket:
ProgramArcadeGames.com/python_examples/f.php?file=platform_moving.py
13.4.4 Kígyó/százlábú
Meg szoktam kérni a diákokat, hogy készítsenek kígyó, vagy százlábú játékot. Van egy több részből álló kígyód, amit irányítsz. Ehhez minden szegmenst egy listában kell tárolni. Ehhez két új parancsot kell megtanulni, de maga a koncepció, ahogy az egész működik, nem nehéz.
Irányítsd a kígyódat, vagy százlábúdat a képernyőn körbe:
ProgramArcadeGames.com/python_examples/f.php?file=snake.py
13.4.5 sprite-lapok használata
Ez egy példa arra, ahogyan sprite-lapokat használhatsz, hogy megtámogasd a platform játékot ezzel. Ez támogatja a több szintet és a mozgó platformokat is. A játék több fájlra tördelhető. ProgramArcadeGames.com/python_examples/en/sprite_sheets
13.4.6 Kvízkérdések
Click here for a multiple-choice quiz.
13.4.7 Feladatlap
There is no worksheet for this chapter.
13.4.8 Labor feladat
Click here for the chapter lab.
You are not logged in. Log in here and track your progress.
English version by Paul Vincent Craven
Spanish version by Antonio Rodríguez Verdugo
Russian version by Vladimir Slav
Turkish version by Güray Yildirim
Portuguese version by Armando Marques Sobrinho and Tati Carvalho
Dutch version by Frank Waegeman
Hungarian version by Nagy Attila
Finnish version by Jouko Järvenpää
French version by Franco Rossi
Korean version by Kim Zeung-Il
Chinese version by Kai Lin