Ü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.


You are not logged in. Log in here and track your progress.