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

Chapter 10: Kontrollerek és grafika

Hogyan adjunk meg olyan objektumokat, amiknek a feladata, hogy használják a billentyűzetet, egeret, vagy játékkontrollert?

10.1 Bevezetés

fig.controller

Eddig azt mutattuk meg, hogyan animáljunk dolgokat a képernyőn, de azt nem, hogy hogyan legyenek interaktívak. Hogyan kell használni az egeret, billentyűzetet, vagy játék kontrollert ahhoz, hogy irányítsuk az akciót a képernyőn? Szerencsére, ez elég könnyű.

Kezdésnek, szükséges, hogy legyen egy objektumunk(tárgyunk), amit mozgathatunk a képernyőn. A legjobb módja ennek az, hogy azt a függvényt használjuk, ami majd nyomon követi az x és y koordinátákat, és azután kirajzolja oda az tárgyunkat. Szóval, ugorjunk vissza a 9-s fejezethez, és nézzük át, hogyan írjunk meg egy függvényt, ami kirajzol egy objektumot.

Videó: Függvénnyel való rajzolás

Minden Pygame rajzolós függvénynek szüksége van egy képernyő(screen) paraméterre, hogy a Pygame tudja azt, hogy melyik ablakra rajzoljon. Átpasszoljuk ezt a feladatot a függvénynek, majd ő kirajzolja az objektumot a képernyőre.

A függvénynek tudnia kell továbbá, hogy hová rajzolja ki az objektumot. A függvénynek szüksége van az x és y koordinátára. A helyzetet is a függvény kezeli, majd egy paraméterrel. Itt van egy példa arra kódra, ami definiálja a függvényt, ami egy hóembert rajzol majd ki.

def draw_snowman(screen, x, y):
    # Rajzolunk egy kört a fejnek
    pygame.draw.ellipse(screen, WHITE, [35+x, 0+y, 25, 25])
    # Rajzolunk egy kört a hóember hasának
    pygame.draw.ellipse(screen, WHITE, [23+x, 20+y, 50, 50])
    # Rajzolunk egy kört a hóember aljának
    pygame.draw.ellipse(screen, WHITE, [0+x, 65+y, 100, 100])

Azután a főprogram ciklusa több hóembert kezd el kirajzolni, ahogyan az az ábrán látszódik: 10.1.

fig.snowmen
Figure 10.1: Hóember, amit egy függvény rajzol ki.
# Hóember bal fent
draw_snowman(screen, 10, 10)

# Hóember jobbra fent
draw_snowman(screen, 300, 10)

# Hóember balra lent
draw_snowman(screen, 10, 300)
Videó: A létező kódot függvénnyé alakítjuk

A teljes munka példa elérhető online, a következő helyről:
ProgramArcadeGames.com/python_examples/f.php?file=functions_and_graphics.py

Esélyes, hogy a korábbi laborokból már van egy kódod, ami kirajzol valami vagány dolgot. De hogyan tudnád ezt egy függvénybe rakni? Vegyünk egy példát, egy olyan kódra, ami kirajzol egy pálcika figurát:

# Fej
pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)

# Lábak
pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)
pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)

# Test
pygame.draw.line(screen, RED, [100,100], [100,90], 2)

# Karok
pygame.draw.line(screen, RED, [100,90], [104,100], 2)
pygame.draw.line(screen, RED, [100,90], [96,100], 2)
fig.stick_figure
Figure 10.2: Pálcika emberke

Ezt a kódot könnyen betehetjük egy függvénybe, mindössze úgy, hogy a def parancsot használjuk, majd következik alá a behúzással megírt kód. Alá kell írnunk az összes kódot a függvénynek, ami a pálcika emberkét kirajzolja. Szükségünk van egy képernyő(screen) változóra, hogy tudjuk melyik ablakba rajzoljunk, és egy x és y koordinátával tartjuk nyilván, hogy hová rajzoljuk a pálcikaemberkét.

De nem tudjuk definiálni a függvényt a program-ciklusunk közepén! A kódot el kéne távolítani a program főrészétől. A függvény deklarációja a program elejére kéne hogy essen. Ezt a kódot tehát a legelejére kell tennünk. Lásd a következő ábrát: 10.3

def draw_stick_figure(screen,x,y):
    # Fej
    pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)

    # Lábak
    pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)
    pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)

    # Test
    pygame.draw.line(screen, RED, [100,100], [100,90], 2)

    # Kezek
    pygame.draw.line(screen, RED, [100,90], [104,100], 2)
    pygame.draw.line(screen, RED, [100,90], [96,100], 2)
fig.making_a_function
Figure 10.3: Függvény létrehozása és jó helyre pakolása

Mostmár ez a kód kezeli az x és y koordinátákat. Sajnos, nem csinál velük semmit. Bármilyen koordinátát meghatározhatsz, amilyet csak szeretnél, a pálcikaemberke mindig pontosan ugyanott lesz. Nem túl hasznos. A következő példánk szó szerint hozzáadja az x és y koordinátákat a már létező programunkhoz.

def draw_stick_figure(screen, x, y):
    # Fej
    pygame.draw.ellipse(screen, BLACK,[96+x,83+y,10,10],0)

    # Karok
    pygame.draw.line(screen, BLACK, [100+x,100+y], [105+x,110+y], 2)
    pygame.draw.line(screen, BLACK, [100+x,100+y], [95+x,110+y], 2)

    # Test
    pygame.draw.line(screen, RED, [100+x,100+y], [100+x,90+y], 2)

    # Karok
    pygame.draw.line(screen, RED, [100+x,90+y], [104+x,100+y], 2)
    pygame.draw.line(screen, RED, [100+x,90+y], [96+x,100+y], 2)

De a probléma az, hogy a figurát már kirajzoltuk egyazon távolságra az origótól. Feltételezzük, hogy az origó az (0,0), és a pálcikaemberkét lentre 100 pixelnyire rajzolja ki. Lásd az ábrát 10.4 és azt, ahogyan a pálcikaemberke nem lett kirajzolva a (0,0) koordináta szerint.

fig.stick_figure_2
Figure 10.4: Pálcikaemberke

Az x és y hozzáadásával, elcsúsztatjuk az origóját a pálcikaemberünknek, ennyivel. Például, ha ezt írjuk:

draw_stick_figure(screen, 50, 50)

A kód nem teszi ki a figurát az (50,50)-re. Elcsúsztatja az origóját le és 50 pixellel. Mivel az emberkénket már kirajzoltuk a (100,100)-ra, az eltolással, most (150,150)-re helyeztük az origót. Hogyan hozzuk rendbe ezt az egészet, hogy kívánt helyre rajzoljon a függvényünk?

fig.code_example
Figure 10.5: Megkeressük a legkisebb x és y értéket

A legkisebb x érték megtalálása és a legkisebb y értéke látható az ábrán: 10.5. Azután kivonjuk az értéket mind az x és y értékéből. Ne keverd ezt most össze a magasság és szélesség értékekkel. Itt van egy példa arra, ahogy a legkisebb x és y értéket kivontuk:

def draw_stick_figure(screen, x, y):
    # Fej
    pygame.draw.ellipse(screen, BLACK,[96-95+x,83-83+y,10,10],0)

    # Lábak
    pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [105-95+x,110-83+y], 2)
    pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [95-95+x,110-83+y], 2)

    # Test
    pygame.draw.line(screen, RED, [100-95+x,100-83+y], [100-95+x,90-83+y], 2)

    # Kezek
    pygame.draw.line(screen, RED, [100-95+x,90-83+y], [104-95+x,100-83+y], 2)
    pygame.draw.line(screen, RED, [100-95+x,90-83+y], [96-95+x,100-83+y], 2)

Vagy, tegyük még egyszerűbbé a programot, vonja ki saját maga:

def draw_stick_figure(screen, x, y):
    # Fej
    pygame.draw.ellipse(screen, BLACK, [1+x,y,10,10], 0)

    # Lábak
    pygame.draw.line(screen, BLACK ,[5+x,17+y], [10+x,27+y], 2)
    pygame.draw.line(screen, BLACK, [5+x,17+y], [x,27+y], 2)

    # Test
    pygame.draw.line(screen, RED, [5+x,17+y], [5+x,7+y], 2)

    # Kezek
    pygame.draw.line(screen, RED, [5+x,7+y], [9+x,17+y], 2)
    pygame.draw.line(screen, RED, [5+x,7+y], [1+x,17+y], 2)

10.2 Egér

Videó: Mozgatás az egérrel

Remek, most már tudjuk, hogyan írjunk olyan függvényt, ami kirajzol egy objektumot egy specifikus koordinátára. Hogyan tudjuk megkapni ezeket a koordinátákat? A legkönnyebb, ha az egérrel dolgozunk. Ez csak egy kódsor:

pos = pygame.mouse.get_pos()

A trükk az, hogy a koordináta, amit kapunk, az egy lista, vagy specifikusabb nem-módosítható verem (tuple). Mind az x és y értékek ugyanabban a változóban lesznek tárolva. Szóval, ha van egy print(pos) parancsunk, akkor azt kapjuk, amit az alábbi ábra mutat 10.6.

fig.coordinates
Figure 10.6: koordináták

A pos változó egy verem két számmal. Az x koordináta a 0, az y koordináta az 1-s pozícióban vannak. Ezeket könnyű kinyerni és a függvénybe behejettesíteni, hogy rajzolhasson:

# Játék logikája
pos = pygame.mouse.get_pos()
x = pos[0]
y = pos[1]

# Rajzolós rész
draw_stick_figure(screen, x, y)

Az egérre vonatkozó sor, a játék logikája lesz részben a fő program-ciklus. A függvény hívása a rajzolós részben szintén a főprogram ciklusának része lesz.

Az egyetlen probléma ezzel az az, hogy az egér mutató jobbra fent fog megjelenni a pálcikaemberhez képest. Lásd az ábrát: 10.7.

fig.mouse_and_figure
Figure 10.7: Pálcikaemberke egérkurzorral a tetején

Az egeret el tudjuk rejteni a következő kóddal, még mielőtt a főprogram elindulna:

# Az egér kurzor elrejtése
pygame.mouse.set_visible(False)

A teljes, működő példa itt található:
ProgramArcadeGames.com/python_examples/f.php?file=move_mouse.py

10.3 Billentyűzet

Videó: Billentyűzettel való mozgatás

A billentyűzet irányítása kicsit nehezebb lesz. Nem csak annyi, hogy megkapjuk az x és y-t az egértől. A billentyűzet nem ad nekünk x és y értéket. Ezt kell tennünk:

Nehéznek tűnik, de ez csak annyira, mint a pattogó háromszög kirajzolása volt korábban, annyi kitétellel, hogy a sebességet most a billentyűzettel adjuk meg.

Kezdésként, határozzuk meg a helyzetet és sebességet mielőtt a főciklus elindul:

# Sebesség pixel per frame-ben
x_speed = 0
y_speed = 0

# Jelenlegi helyzet
x_coord = 10
y_coord = 10

A fő while cikluson belül, kell, hogy adjunk meg még néhány dolgot, hogy az eseményeket követhessük. Továbbá, meg kell adnunk egy pygame.QUIT eseményt is, mivel a programnak kezelnie kell a billentyűzeten bevitt változásokat is. Minden alkalommal generálunk egy eseményt, amikor lenyomunk egy billentyűt.

Egy pygame.KEYDOWN esemény lesz generálva akkor, amikor lenyomjuk a gombot. Egy pygame.KEYUP esemény lesz generálva, amikor a felhasználó felenged egy gombot. Amikor a felhasználó megnyom egy gombot, a sebesség vektor 3 vagy -3 pixel per frame beállításon lesz. Amikor a elhasználó felenged egy gombot, akkor sebesség vektor visszaáll nullára. Végül, az objektum koordinátái állítják be a vektort, és azután az objektum kirajzolódik. Figyeld meg a lenti kódot:

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        done = True

    # A felhasználó lenyom egy gombot
    if event.type == pygame.KEYDOWN:
        # Megvizsgáljuk, hogy iránygomb volt e. Ha igen,
        # akkor a sebességen változtat.
        if event.key == pygame.K_LEFT:
            x_speed = -3
        if event.key == pygame.K_RIGHT:
            x_speed = 3
        if event.key == pygame.K_UP:
            y_speed = -3
        if event.key == pygame.K_DOWN:
            y_speed = 3

    # A felhasználó felengedi a billentyűt
    if event.type == pygame.KEYUP:
        # Ha ez egy iránygomb, akkor visszaáll nullára a sebesség
        if event.key == pygame.K_LEFT:
            x_speed = 0
        if event.key == pygame.K_RIGHT:
            x_speed = 0
        if event.key == pygame.K_UP:
            y_speed = 0
        if event.key == pygame.K_DOWN:
            y_speed = 0

# Mozgassuk a vektort a meghatározott sebesség alapján.
x_coord += x_speed
y_coord += y_speed

# Pálcikaemberke kirajzolása
draw_stick_figure(screen, x_coord, y_coord)

A teljes példát itt tekintheted meg:
ProgramArcadeGames.com/python_examples/f.php?file=move_keyboard.py

Itt jegyzem meg, hogy ez a példa nem akadályozza meg, hogy a karakter a képernyő szélén túl ne tudjon menni. Ha ezt szeretnénk, akkor a játék logikai részében, egy if elágazás szükséges, amivel le kell ellenőrízni az x_coord és az y_coord értékeket. Ha azok kívül esnek a határain a képernyőnek, akkor vissza kell állítani a koordinátákat a szélekhez. A teljes kód ehhez az olvasóra marad, gyakorlásképpen.

Az alábbi táblázat a kulcskódokat mutatja meg, amiket a Pygame használ:

Pygame kódASCIIHétköznapi elnevezés
K_BACKSPACE\bbackspace
K_RETURN\rreturn
K_TAB\ttabulátor
K_ESCAPE^[escape
K_SPACE space
K_COMMA,vessző
K_MINUS-mínusz
K_PERIOD.pont
K_SLASH/perjel
K_000
K_111
K_222
K_333
K_444
K_555
K_666
K_777
K_888
K_999
K_SEMICOLON;spontosvessző
K_EQUALS=egyenlőségjel
K_LEFTBRACKET[bal szögletes zárójel
K_RIGHTBRACKET]jobb szögletes zárójel
K_BACKSLASH\fordított perjel
K_BACKQUOTE`aposztróf
K_aaa
K_bbb
K_ccc
K_ddd
K_eee
K_fff
K_ggg
K_hhh
K_iii
K_jjj
K_kkk
K_lll
K_mmm
K_nnn
K_ooo
K_ppp
K_qqq
K_rrr
K_sss
K_ttt
K_uuu
K_vvv
K_www
K_xxx
K_yyy
K_zzz
K_DELETEdelete, törlés
K_KP0keypad0
K_KP1keypad1
K_KP2keypad2
K_KP3keypad3
K_KP4keypad4
K_KP5keypad5
K_KP6keypad6
K_KP7keypad7
K_KP8keypad8
K_KP9keypad9
K_KP_PERIOD.periódus
K_KP_DIVIDE/osztás
K_KP_MULTIPLY*szorzás
K_KP_MINUS-mínusz
K_KP_PLUS+plussz
K_KP_ENTER\r enter
K_KP_EQUALS=egyenlő
K_UPfelnyíl
K_DOWNlenyíl
K_RIGHTjobbnyíl
K_LEFTbalnyíl
K_INSERTbeilleszt
K_HOMEhome
K_ENDend
K_PAGEUPoldallapozásfel
K_PAGEDOWNoldallapozásle
K_F1F1
K_F2F2
K_F3F3
K_F4F4
K_F5F5
K_F6F6
K_F7F7
K_F8F8
K_F9F9
K_F10F10
K_F11F11
K_F12F12
K_NUMLOCKnumlock
K_CAPSLOCKcapslock
K_RSHIFTjobbshift
K_LSHIFTbalshift
K_RCTRLjobbctrl
K_LCTRLbalctrl
K_RALTjobbalt
K_LALTbalalt

10.4 Játék-kontroller

A játék kontrollerek másmilyen típusú kódot igényelnek, de az ötlet egyszerű.

Kezdésként, ellenőrízd, hogy a számítógépnek van-e joystickja, és inicializáld azt még használat előtt. Ezt általában egyszer kell megtenni. Azután gyerünk a főprogram ciklusához:

# Jelenlegi pozíció
x_coord = 10
y_coord = 10

# Vedd számba, hogy mennyi joystick van a számítógépen
joystick_count = pygame.joystick.get_count()
if joystick_count == 0:
    # No joysticks!
    print ("Error, I didn't find any joysticks.")
else:
    # a #0-s joystick inícializálása és használatba vétele
    my_joystick = pygame.joystick.Joystick(0)
    my_joystick.init()

Egy joystick két lebegő pontos értéket fog visszaadni. Ha a joystick teljesen középen van, akkor az (0,0)-t ad vissza. Ha teljesen fent van, bal sarokban, akkor (-1,-1) értéket ad vissza. Ha a jobb alsó sarokban van, akkor az (1,1) értéket adja vissza. Ha ezek közt van valahol, akkor értelemszerűen egy köztes számot ad vissza. Figyeld meg, a kontrollert a képeken, hogyan működik 10.8.

fig.c
Figure 10.8: Középen (0,0)
fig.ul
Figure 10.9: Bal fent (-1,-1)
fig.u
Figure 10.10: Fent (0,-1)
fig.ur
Figure 10.11: Jobb fent (1,-1)
fig.r
Figure 10.12: Jobbra (1,0)
fig.dr
Figure 10.13: Jobbra lent (1,1)
fig.d
Figure 10.14: Lent (0,1)
fig.dl
Figure 10.15: Bal lent (-1,1)
fig.controller_left
Figure 10.16: Balra (-1,0)

A főcikluson belül, a joystick értékei majd többszörözve érkeznek be, attól függően, hogy az objektum mennyit mozgott. A lenti kód alapján, a joystick teljes mozgatása során 10 pixel per frame sebességgel számolhatunk, mivel a joystick értéke 10-zel van megszorozva.

# ez megy a főprogramba

# addig, amíg van joystick-unk
if joystick_count != 0:

    # Ez adja meg a játk kontrollernek az axis pozícióját
    # Visszaad egy számot -1.0 és +1.0 közt.
    horiz_axis_pos = my_joystick.get_axis(0)
    vert_axis_pos = my_joystick.get_axis(1)

    # A megadott axisig mozgatja az x-t. Ezt megszoroztuk 10zel a sebesség miatt.
    # Alakítsuk át egész számmá, mivel nem tudunk 3.5 pixelt kirajzolni csak 3-t vagy 4-t.
    x_coord = x_coord + int(horiz_axis_pos * 10)
    y_coord = y_coord + int(vert_axis_pos * 10)

# képernyő törlése
screen.fill(WHITE)

# kirajzolja az objektumot a megfelelő koordinátára
draw_stick_figure(screen, x_coord, y_coord)

A teljes példát itt tudod megtekinteni
ProgramArcadeGames.com/python_examples/f.php?file=move_game_controller.py.

A kontrollereken van egy rakat joystick, gomb, és még funkció kapcsolók is. Alább van egy példa program arra, hogyan nyomtass ki mindent a képernyőre, amit a kontroller tud. Ellenőrízd, hogy a kontroller be van dugva, mielőtt ezt a programot elindítod, különben a program nem tudja érzékelni.

fig.joystick_calls
Figure 10.17: Joystick ellenőrző program
"""
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/

Show everything we can pull off the joystick
"""
import pygame

# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)


class TextPrint(object):
    """
    This is a simple class that will help us print to the screen
    It has nothing to do with the joysticks, just outputting the
    information.
    """
    def __init__(self):
        """ Constructor """
        self.reset()
        self.x_pos = 10
        self.y_pos = 10
        self.font = pygame.font.Font(None, 20)

    def print(self, my_screen, text_string):
        """ Draw text onto the screen. """
        text_bitmap = self.font.render(text_string, True, BLACK)
        my_screen.blit(text_bitmap, [self.x_pos, self.y_pos])
        self.y_pos += self.line_height

    def reset(self):
        """ Reset text to the top of the screen. """
        self.x_pos = 10
        self.y_pos = 10
        self.line_height = 15

    def indent(self):
        """ Indent the next line of text """
        self.x_pos += 10

    def unindent(self):
        """ Unindent the next line of text """
        self.x_pos -= 10


pygame.init()

# Set the width and height of the screen [width,height]
size = [500, 700]
screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.
done = False

# Used to manage how fast the screen updates
clock = pygame.time.Clock()

# Initialize the joysticks
pygame.joystick.init()

# Get ready to print
textPrint = TextPrint()

# -------- Main Program Loop -----------
while not done:
    # EVENT PROCESSING STEP
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

        # Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN
        # JOYBUTTONUP JOYHATMOTION
        if event.type == pygame.JOYBUTTONDOWN:
            print("Joystick button pressed.")
        if event.type == pygame.JOYBUTTONUP:
            print("Joystick button released.")

    # DRAWING STEP
    # First, clear the screen to white. Don't put other drawing commands
    # above this, or they will be erased with this command.
    screen.fill(WHITE)
    textPrint.reset()

    # Get count of joysticks
    joystick_count = pygame.joystick.get_count()

    textPrint.print(screen, "Number of joysticks: {}".format(joystick_count))
    textPrint.indent()

    # For each joystick:
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()

        textPrint.print(screen, "Joystick {}".format(i))
        textPrint.indent()

        # Get the name from the OS for the controller/joystick
        name = joystick.get_name()
        textPrint.print(screen, "Joystick name: {}".format(name))

        # Usually axis run in pairs, up/down for one, and left/right for
        # the other.
        axes = joystick.get_numaxes()
        textPrint.print(screen, "Number of axes: {}".format(axes))
        textPrint.indent()

        for i in range(axes):
            axis = joystick.get_axis(i)
            textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis))
        textPrint.unindent()

        buttons = joystick.get_numbuttons()
        textPrint.print(screen, "Number of buttons: {}".format(buttons))
        textPrint.indent()

        for i in range(buttons):
            button = joystick.get_button(i)
            textPrint.print(screen, "Button {:>2} value: {}".format(i, button))
        textPrint.unindent()

        # Hat switch. All or nothing for direction, not like joysticks.
        # Value comes back in an array.
        hats = joystick.get_numhats()
        textPrint.print(screen, "Number of hats: {}".format(hats))
        textPrint.indent()

        for i in range(hats):
            hat = joystick.get_hat(i)
            textPrint.print(screen, "Hat {} value: {}".format(i, str(hat)))
        textPrint.unindent()

        textPrint.unindent()

    # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

    # Go ahead and update the screen with what we've drawn.
    pygame.display.flip()

    # Limit to 60 frames per second
    clock.tick(60)

# Close the window and quit.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit()

10.5 Áttekintés

10.5.1 Kvízkérdések

Click here for a multiple-choice quiz.

10.5.2 Feladatlap

Click here for the chapter worksheet.

10.5.3 Labor feladat

Click here for the chapter lab.