Ügyességi játékok programozása
Pythonnal és Pygame-melChapter 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
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.
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.
# 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)
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)
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)
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.
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?
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
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.
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.
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
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:
- Készítsünk egy kezdő x és y kiinduló pozíciót.
- Állítsunk be egy "sebesség" változót pixel per frame mértékegységgel, amikor egy iránybillentyűt lenyomunk. (keydown)
- Nullázzuk ki a sebességet nullára, amikor egy iránygombot lenyomunk. (keyup)
- Igazítsuk az x és y-t minden frame-n a sebesség szerint.
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ód | ASCII | Hétköznapi elnevezés |
---|---|---|
K_BACKSPACE | \b | backspace |
K_RETURN | \r | return |
K_TAB | \t | tabulátor |
K_ESCAPE | ^[ | escape |
K_SPACE | space | |
K_COMMA | , | vessző |
K_MINUS | - | mínusz |
K_PERIOD | . | pont |
K_SLASH | / | perjel |
K_0 | 0 | 0 |
K_1 | 1 | 1 |
K_2 | 2 | 2 |
K_3 | 3 | 3 |
K_4 | 4 | 4 |
K_5 | 5 | 5 |
K_6 | 6 | 6 |
K_7 | 7 | 7 |
K_8 | 8 | 8 |
K_9 | 9 | 9 |
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_a | a | a |
K_b | b | b |
K_c | c | c |
K_d | d | d |
K_e | e | e |
K_f | f | f |
K_g | g | g |
K_h | h | h |
K_i | i | i |
K_j | j | j |
K_k | k | k |
K_l | l | l |
K_m | m | m |
K_n | n | n |
K_o | o | o |
K_p | p | p |
K_q | q | q |
K_r | r | r |
K_s | s | s |
K_t | t | t |
K_u | u | u |
K_v | v | v |
K_w | w | w |
K_x | x | x |
K_y | y | y |
K_z | z | z |
K_DELETE | delete, törlés | |
K_KP0 | keypad | 0 |
K_KP1 | keypad | 1 |
K_KP2 | keypad | 2 |
K_KP3 | keypad | 3 |
K_KP4 | keypad | 4 |
K_KP5 | keypad | 5 |
K_KP6 | keypad | 6 |
K_KP7 | keypad | 7 |
K_KP8 | keypad | 8 |
K_KP9 | keypad | 9 |
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_UP | fel | nyíl |
K_DOWN | le | nyíl |
K_RIGHT | jobb | nyíl |
K_LEFT | bal | nyíl |
K_INSERT | beilleszt | |
K_HOME | home | |
K_END | end | |
K_PAGEUP | oldallapozás | fel |
K_PAGEDOWN | oldallapozás | le |
K_F1 | F1 | |
K_F2 | F2 | |
K_F3 | F3 | |
K_F4 | F4 | |
K_F5 | F5 | |
K_F6 | F6 | |
K_F7 | F7 | |
K_F8 | F8 | |
K_F9 | F9 | |
K_F10 | F10 | |
K_F11 | F11 | |
K_F12 | F12 | |
K_NUMLOCK | numlock | |
K_CAPSLOCK | capslock | |
K_RSHIFT | jobb | shift |
K_LSHIFT | bal | shift |
K_RCTRL | jobb | ctrl |
K_LCTRL | bal | ctrl |
K_RALT | jobb | alt |
K_LALT | bal | alt |
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.
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.
""" 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.
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