Arcade-pelien ohjelmointi
Pythonilla ja PygamellaChapter 10: Kontrollia ja grafiikkaa
Miten saamme objektit liikkumaan näppäimistön, hiiren tai peliohjaimen avulla?
10.1 Johdantoa
Tähän mennessä olemme saaneet objektit animoitua näyttöruudulla, mutta nyt pitäisi saada vuorovaikutettua objekteihin. Tähän on käytössä siis hiiri, näppäimistö ja/tai peliohjain. Onneksi tämä ei ole niin vaikeaa kuin aluksi voisi luulla.
Heti alussa tarvitsemme siis objektin, jota voidaan liikuttaa näytön jokaisen suuntaan. Tämä kannattaa toteuttaa käyttämällä funktiota, joka piirtää ojektin annettuun x-y-koordinaatiston pisteeseen. Eli takaisin kappaleen 9 asiaan, funktiot! Kerrataan, miten tehdään funktio, joka piirtää objektin.
Kaikki Pygamen piirtofunktiot vaativat screen parameterin, jolla asetetaan Pygamelle piirron kohdeikkuna. Tämä parametri pitää välittää jokaiselle funktiolle, jolla luodaan piirtoa näytölle.
Funtiolle pitää myös kertoa, mihin kohtaan näytöllä objekti piirretään. Tarvitaan siis x ja y parametrit. Seuraavassa on esimerkkikoodi funktiosta, joka piirtää lumiukon aina kun funktiota kutsutaan:
def draw_snowman(screen, x, y): # Piirretään pää ympyrällä pygame.draw.ellipse(screen, WHITE, [35+x, 0+y, 25, 25]) # Piirretään keskivartalo ympyränä pygame.draw.ellipse(screen, WHITE, [23+x, 20+y, 50, 50]) # Piirretään alakroppa ympyrällä pygame.draw.ellipse(screen, WHITE, [0+x, 65+y, 100, 100])
Sitten pääohjelmasilmukassa voidaan piirtää useita lumiukkoja, kuten nähdään kuvassa 10.1.
# Lumiukko ylös vasemmalle draw_snowman(screen, 10, 10) # Lumiukko ylös oikealle draw_snowman(screen, 300, 10) # Lumiukko alas vasemmalle draw_snowman(screen, 10, 300)
Toimiva ohjelmaesimerkki on saatavilla 'on-line' tästä:
ProgramArcadeGames.com/python_examples/f.php?file=functions_and_graphics.py
Tämä koodi on erilainen kuin vastaava koodi, jossa piirrettiin vastaavasti. Miten tuo piirtäminen saadaan toteutettua funktiolla. Katsotaan seuraavaksi koodi, joka piirtää tikku-ukkoja:
# Pää pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0) # Jalat pygame.draw.line(screen, BLACK, [100,100], [105,110], 2) pygame.draw.line(screen, BLACK, [100,100], [95,110], 2) # Kroppa pygame.draw.line(screen, RED, [100,100], [100,90], 2) # Kädet pygame.draw.line(screen, RED, [100,90], [104,100], 2) pygame.draw.line(screen, RED, [100,90], [96,100], 2)
Tästä koodista on helppo määrittää funktio lisäämällä varattu sana def ja sisentämällä koodi sen alle. Funktiolle tarvitsee tuoda kaikki tikku-ukon piirtämiseen tarvittavat tiedot. screen muuttujalla määrätään, mihin ikkunaan piirretään, x ja y-koordinaateilla kerrotaan paikkatieto, johon tikku-ukko tulee piirtää.
Funktiota ei saa sijoittaa keskelle ohjelman pääsilmukkaa! Funktion koodi pitää siirtää pääohjelman koodista erilleen. Funktion määrittelyt tulee sijoittaa ohjelmakoodin alkuun. Siirretään funktio siis koodin alkuun. Katso kuva 10.3 , jossa asia on visualisoituna .
def draw_stick_figure(screen,x,y): # Pää pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0) # Jalat pygame.draw.line(screen, BLACK, [100,100], [105,110], 2) pygame.draw.line(screen, BLACK, [100,100], [95,110], 2) # Kroppa pygame.draw.line(screen, RED, [100,100], [100,90], 2) # Kädet pygame.draw.line(screen, RED, [100,90], [104,100], 2) pygame.draw.line(screen, RED, [100,90], [96,100], 2)
Funktiossa on määriteltynä koordinaatit x ja y parametreina, mutta ne eivät vaikuta tässä mitään. Voit asettaa mitä tahansa x ja y-arvoja ja kuitenkin tikku-ukko piirretään aina samaan paikkaan. Ei siis kovin yleiskäyttöinen funktio. Seuraavassa koodissa lisäämme funktion koodiin x ja y parametrien käytön.
def draw_stick_figure(screen, x, y): # Pää pygame.draw.ellipse(screen, BLACK,[96+x,83+y,10,10],0) # Jalat 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) # Kroppa pygame.draw.line(screen, RED, [100+x,100+y], [100+x,90+y], 2) # Kädet 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)
Ongelmaksi muodostuu nyt se, että ukko piirretään aina tietylle etäisyydelle origosta. Oletuksena on, että origo on (0, 0):ssa ja piirto tehdään aina vähintään 100 pikselin etäisyydelle origosta. Katso kuvasta 10.4 , miten kuva piirretään origosta (0, 0) katsottuna vähintään tietylle etäisyydelle.
Lisäämällä x:n ja y:n arvot funktioon, saadaan tikku-ukon paikkaa siirrettyä näiden koordinaattiarvojen verran asetetusta minimikohdasta alkaen, jos siis kutsutaan:
draw_stick_figure(screen, 50, 50)
Tikku-ukkoa ei siis piirretä paikkaan (50, 50), vaan nämä parametriarvot lisätään funktiossa oleviin 'minimi'arvoihin. Ukko siis piirretään paikkaan (150, 150). Miten saisimme funktion piirtämään juuri siihen paikkaan kuin kutsussa paikaksi annetaan.
Etsitään pienin x arvo ja pienin y arvo. Katso kuvaa 10.5. Sitten vähennämme nämä arvot jokaisesta x ja y arvosta funktiossa. älä sekoita korkeus (height) ja leveys (width) arvoja. Tässä on esimerkki, kun pienin x ja pienin y arvot vähennetään:
def draw_stick_figure(screen, x, y): # Pää pygame.draw.ellipse(screen, BLACK,[96-95+x,83-83+y,10,10],0) # Jalat 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) # Kroppa pygame.draw.line(screen, RED, [100-95+x,100-83+y], [100-95+x,90-83+y], 2) # Kädet 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)
Tai, jos tehtäisiin tämä vieläkin yksinkertaisemmin ja tehdään tuo vähennys itse funktion parametrissa:
def draw_stick_figure(screen, x, y): # Head pygame.draw.ellipse(screen, BLACK, [1+x,y,10,10], 0) # Legs 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) # Body pygame.draw.line(screen, RED, [5+x,17+y], [5+x,7+y], 2) # Arms 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 Hiiri
Hienoa, nyt osaamme kirjoittaa funktion, joka piirtää objektin määrättyyn kohtaan näytölle. Miten saamme nuo koordinaattipisteet? Helpoiten tämä selviää hiirellä. Tarvitaan ainoastaan yksi rivi koodia, jolla selviää koordinaattipiste:
pos = pygame.mouse.get_pos()
Jujuna tässä on se, että koordinaattipisteiden arvot palautetaan listaan, tarkemmin sanottuna ei-mutatoituvaan monikkoon. Molemmat, sekä x että y-koordinaattiarvo, palautetaan yhtenä lukuna. Jos siis kirjoitetaan print(pos) saamme aikaiseksi tulostuksen kuvassa 10.6.
Muuttuja pos on monikko, jossa on kaksi lukua. x koordinaatti on paikassa 0 ja y koordinaatti paikassa 1. Nämä arvot on helposti saatavissa ja välitettävissä funktiolle kuvan piirtämistä varten:
# Peli logiikka pos = pygame.mouse.get_pos() x = pos[0] y = pos[1] # Piirtämisen komennot draw_stick_figure(screen, x, y)
Hiiren paikan 'metsästys, pitäisi sijoittaa “pelin logiikka” osaan ohjelman pääsilmukkaan. Funktion kutsu pitää sijoittaa “piirto” osaan pääohjelmasilmukkaan.
Ainoa ongelma tässä tulee, kun hiiri piirtää ukon yläosaan, niin osoitin jää piirrettävän objektin päälle. Tämä vaikeuttaa piirroksen havaitsemista, kuten näkyy kuvassa 10.7.
Hiiren osoitin saadaan piilotettua juuri ennen piirtämistä seuraavalla koodilla:
# Piilota hiiren osoitin pygame.mouse.set_visible(False)
Täydellinen toimiva ohjelmakoodi löytyy tästä:
ProgramArcadeGames.com/python_examples/f.php?file=move_mouse.py
10.3 Näppäimistö
Näppäimistöllä kontrolloiminen on hieman monimutkaisempaa. Näppäimistöllä ei voida 'napata' x- ja y-koordinaatteja kuten hiirellä. Niinpä meidän onkin tehtävä seuraavaa:
- Luotava alkuarvot x ja y koordinaateille aloituspisteessä.
- Asetettava “nopeus” pikseleinä per freimi, kun nuolinäppäimiä painetaan. (keydown)
- Palautetaan nopeus nollaksi kun näppäin vapautetaan. (keyup)
- Asetetaan x ja y jokaisessa freimissä nopeuden mukaan.
Tämä näyttää monimutkaiselta, mutta kyseessä on samantapainen toimintamalli kuin teimme liikkuvalla suorakulmiolla aiemmin sillä poikkeuksella, että nopeutta kontrolloidaan näppäimistöllä.
Heti aluksi asetamme sijainnin ja nopeuden ennen pääohjelmasilmukan aloitusta:
# Nopeus pikseleinä per freimi x_speed = 0 y_speed = 0 # Nykyinen sijainti x_coord = 10 y_coord = 10
Pääohjelmasilmukan while loopissa meidän pitää lisätä muutamia kohtia tapahtumasilmukkaan. Sen lisäksi, että ohjelma tutkii lopetustapahtumaa pygame.QUIT, pitää ohjelman tunnistaa näppäintapahtumat. Tapahtuma generoidaan aina, kun käyttäjä painaa näppäintä.
pygame.KEYDOWN tapahtuma generoidaan aina kun näppäin painetaan alas. pygame.KEYUP tapahtuma taasen aina kun näppäin palautetaan ylös. Kun käyttäjä painaa näppäintä, nopeusvektori asetetaan arvoon 3 tai -3 per freimi. JA kun käyttäjä päästää näppäimen ylös palautetaan nopeusvektorin arvo nollaksi. Lopulta objektin koordinaattipisteen arvoja säädetään tämän vektorin avulla ja objekti piirrtetään uudelleen uuteen paikkaan. Katso alla olevaa koodia:
for event in pygame.event.get(): if event.type == pygame.QUIT: done = True # Käyttäjä painoi näppäintä if event.type == pygame.KEYDOWN: # selvitetään oliko kyseessä nuolinäppäin # säädetään nopeus 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 # käyttäjä vapautti näppäimen if event.type == pygame.KEYUP: # Jos näppäin oli nuolinäppäin, aseta nopeusvektori nollaan 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 # Siirrä objektia nopeusvektorin mukaan. x_coord += x_speed y_coord += y_speed # Piirretään tikku-ukko draw_stick_figure(screen, x_coord, y_coord)
Tästä löytyy koko esimerkkikoodi:
ProgramArcadeGames.com/python_examples/f.php?file=move_keyboard.py
Huomaa, että tässä esimerkissä ei ole estetty pelihahmon liikkumista näytön ulkopuolelle. Tämä tehdään lisäämällä pelin logiikkaosaan joukko if lauseita, joilla tarkistetaan objektin x_coord ja y_coord arvot, eli paikkakoordinaatit, ovatko ne ulkopuolella ja jos ovat niin palautetaan objekti näytön reunaan. Tähän tarvittava koodi jääköön harjoitukseksi.
Alla on taulukoituna kaikki Pygamessa käytettäävät näppäinkoodit:
Pygame koodi | ASCII | yleinen nimitys |
---|---|---|
K_BACKSPACE | \b | backspace |
K_RETURN | \r | return |
K_TAB | \t | tab |
K_ESCAPE | ^[ | escape |
K_SPACE | välilyönti | |
K_COMMA | , | pilkku |
K_MINUS | - | miinus |
K_PERIOD | . | piste |
K_SLASH | / | kauttaviiva |
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 | ; | puolipiste |
K_EQUALS | = | yhtäsuuri |
K_LEFTBRACKET | [ | hakasulku auki |
K_RIGHTBRACKET | ] | hakasulku kiinni |
K_BACKSLASH | \ | takakenoviiva |
K_BACKQUOTE | ` | painotusmerkki |
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 | |
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 period |
K_KP_PERIOD | . | keypad divide |
K_KP_DIVIDE | / | keypad multiply |
K_KP_MULTIPLY | * | keypad minus |
K_KP_MINUS | - | keypad plus |
K_KP_PLUS | + | keypad enter |
K_KP_ENTER | \r | keypad equals |
K_KP_EQUALS | = | keypad |
K_UP | up | nuoli ylös |
K_DOWN | down | nuoli alas |
K_RIGHT | right | nuoli oikealle |
K_LEFT | left | nuoli vasemmalle |
K_INSERT | insert | |
K_HOME | home | |
K_END | end | |
K_PAGEUP | page | up |
K_PAGEDOWN | page | down |
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 | right | shift |
K_LSHIFT | left | shift |
K_RCTRL | right | ctrl |
K_LCTRL | left | ctrl |
K_RALT | right | alt |
K_LALT | left | alt |
10.4 Peliohjain
Peliohjain vaatii erilaisia ohjauskoodeja, mutta perusajatus on samanlainen.
Heti aluksi tarkistetaan, onko peliohjain kytkettynä ja jos on niin asennetaan se käyttöön. Tämä toimenpide tehdään vain kerran ennen ohjelman pääsilmukkaa:
# Current position x_coord = 10 y_coord = 10 # Tutkitaan tietokoneeseen liitetyt joystickit joystick_count = pygame.joystick.get_count() if joystick_count == 0: # Ei joystickia! print("Error, I didn't find any joysticks.") else: # Käytä joystick #0 ja asenna se my_joystick = pygame.joystick.Joystick(0) my_joystick.init()
Joystick palauttaa kaksi desimaalilukuarvoa. Jos tikku on keskiasennossa, palauttaa se arvon (0, 0). Jos tikku on ylös etuvasempaan palauttaa se arvon (-1, -1). Jos tikku on alaoikealla palauttaa se arvon (1, 1). Jos tikku on joidenkin suuntien välissä, skaalataan arvoa näiden suuntien välissä jatkuvasti. Katso peliohjaimen kuvia aloittaen kuvasta 10.8. Näistä saat hyvän kuvan tikun toiminnasta.
Ohjelman pääsilmukan sisällä peliohjaimen palauttamia arvoja kerrotaan toistuvasti, jotta saadaan laskettua objektia siirrettävä matka. Alla olevassa koodissa peliohjaimen kääntäminen täysillä johonkin suuntaan liikuttaa objektia 10 pikseliä jokaisessa freimissä. Ohjaimen arvot siis kerrotaan luvulla 10.
# Tämä sijoitetaan ohjelman pääsilmukkaan # Onko peliohjain kytketty if joystick_count != 0: # Tässä tutkitaan peliohjaimen asento ja sijoitetaan arvo muuttujaan # Palautetaan arvo väliltä -1.0 ja +1.0 horiz_axis_pos = my_joystick.get_axis(0) vert_axis_pos = my_joystick.get_axis(1) # Siirrä x arvoa jatkuvasti suuntaansa. Kerrotaan luvulla 10 jotta nopeutta tulee lisää # Muutetaan luku kokonaisluvuksi (integer), koska emme voi piirtää pisteeseen 3.5, vain 3 tai 4. x_coord = x_coord + int(horiz_axis_pos * 10) y_coord = y_coord + int(vert_axis_pos * 10) # Tyhjennä näyttö screen.fill(WHITE) # Piittä otus tiettyyn paikkaan draw_stick_figure(screen, x_coord, y_coord)
Katso tästä täysin toimiva koodi:
ProgramArcadeGames.com/python_examples/f.php?file=move_game_controller.py.
Peliohjaimissa on paljon erilaisia kontrolleja; tikku, painikkeet ja tikussa "hattupainike". Alla on esimerkkiohjelma, joka tulostaa kaikki ohjaimen tapahtumat ikkunaan. Huomaathan, että peliohjain pitää olla kytkettynä koneeseen ennen kuin käynnistät ohjelman. Muuten ohjelma ei löydä ohjainta.
""" 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 Review
10.5.1 Multiple Choice Quiz
Klikkaa tästä monivalintatehtävään.
10.5.2 Short Answer Worksheet
Klikkaa tästä kappaleen kertaustehtäviin.
10.5.3 Lab
Klikkaa tästä ohjelmointitehtäviin.
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