Ügyességi játékok programozása
Pythonnal és Pygame-mel

Chapter 13: Ismerkedés a grafikus hátteret kezelő elemekkel(sprite-okkal)

Videó: Ismerkedés a 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.

fig.sprite

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

fig.example_sprite_game1
Figure 13.1: Példa sprite-os játék

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

Videó: 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

fig.bullets
Figure 13.2: 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

fig.walls
Figure 13.3: Falak, amikre felmászhatunk

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

fig.maze_runner
Figure 13.4: Többszobás labirintus

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

fig.platform_jumper
Figure 13.5: Platformokon ugrálás

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

fig.platform_scroller
Figure 13.6: Oldalra scrollozó platform játék

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

fig.platform_moving
Figure 13.7: Mozgó platformok

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

fig.snake
Figure 13.8: Kígyü

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

fig.sprite_sheet
Figure 13.9: Sprite lap platform játék

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.