Arcade-pelien ohjelmointi
Pythonilla ja PygamellaChapter 8: Perehdytään animaatioon
8.1 Kimpoava suorakulmio
Animaation opiskelun aluksi palautamme mieleen kappaleessa 5 käsitellyn tyhjän ikkunan avaamisen
ja siihen piirtämisen. Tämän koodin pygame_base_template.py löydät tästä:
ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py
Teemme seuraavaksi ohjelman, jossa valkoinen suorakulmio liikkuu ikkunassa mustalla taustalla. Voit tietysti käyttää haluamiasi taustavärejä ja objektin värejä.
Ensimmäiseksi aloita mallikoodilla ja vaihda taustan väri valkoisesta mustaksi. Tämä koodataan suurin piirtein rivillä 46.
screen.fill(BLACK)
Seuraavaksi teemme animoitavan suorakulmion. Yksinkertainen suorakulmio tai neliö kelpaa tässä oikein hyvin. Tämä koodi pitää sijoittaa koodiin näytön tyhjennyksen jälkeen kuitenkin ennen 'flippaamista'.
pygame.draw.rect(screen, WHITE, [50, 50, 50, 50])
Kullakin silmukkakierroksella piirretty suorakulmio on koordinaatistossa pisteessä (x,y), eli (50,50). Ja, jos koordinaattipisteitä ei muuteta, se ei myöskään liiku minnekään.
Tässä piirretään siis neliö, joka on 50 pikseliä leveä ja 50 pikseliä korkea. Nämä mitat on asetettu kahdella viimeisellä parametrilla. Tässä siis suorakulmio on näillä mitoilla neliö, mutta huomaa, että aina näytölle ei muodostu samoilla leveys ja korkeusarvoilla neliötä, koska monitorin resoluutio vaikuttaa pikseleiden kokoon. Katso ('googleta') tietoa aiheesta Näytön kuvasuhde, Pixel Aspect Ratio, jos haluat tietää tästä enemmän.
Miten saisimme neliön liikkee tuosta paikasta (50, 50)? Käytetään tietenkin muuttujia! Alla oleva koodi on ensimmäinen kehitysversio neliön liikuttamiseksi:
rect_x = 50 pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50])
Neliön liikuttamiseksi oikealle, pitää koordinaatti (muuttujaa) x kasvattaa jokaisella silmukkakierroksella. Tämä koodi on lähes oikein, mutta vaatii hieman 'säätöä':
rect_x = 50 pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50]) rect_x += 1
Ongelmakohta tässä algoritmissa on, että rect_x muuttujan arvo asetetaan arvoon 50 uudelleen ja uudelleen kullakin silmukkakierroksella. Tämä on helppo korjata siirtämällä muuttujan rect_x alkuasetus silmukan ulkopuolelle. Seuraavalla koodilla saadaankin neliö liikkumaan oikealle.
# Aloituskoordinaattipiste x # Huomaa alkuarvon asetus silmukan ulkopuolella. rect_x = 50 # -------- ohjelman pääsilmukka ----------- while not done: for event in pygame.event.get(): # käyttäjä teki jotain if event.type == pygame.QUIT: # jos käyttäjä klikkasi sulje done = True # 'booleanlippu', joka lopettaa silmukan # Asetetaan näytön/ikkunan tausta screen.fill(BLACK) pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50]) rect_x += 1
Neliön liikkeen nopeutta voidaan säätää kasvattamalla rect_x muuttujan kasvatus arvoon 5:
rect_x += 5
Lisätään koodiin sekä x ja y- koordinaattien liike, jolloin neliö liikkuu oikealle alas:
# Aloituspiste rect_x = 50 rect_y = 50 # -------- ohjelman pääsilmukka ----- while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True # Taustavärin asetus screen.fill(BLACK) # piirretään neliö pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50]) # siirretään neliötä lisäämällä arvoihin luku 5 rect_x += 5 rect_y += 5
'Boksin' (neliön) nopeus ja suunta voidaan muuttaa koordinaattipisteiden muutoksella. Seuraavassa esimerkissä kordinaattipistettä (x,y) muutetaan jokaisella rect_change_x ja rect_change_y muuttujien arvojen mukaisesti. Muutos pisteessä on siis (5, 5).
# Aloituspiste rect_x = 50 rect_y = 50 # Neliön suunta ja nopeus rect_change_x = 5 rect_change_y = 5 # -------- ohjelman pääsilmukka --- while done == False: for event in pygame.event.get(): # Käyttäjä teki jotain if event.type == pygame.QUIT: # Jos klikattiin sulje done = True # 'booleanlippu', jolla poistutaan silmukasta # Asetetaan tausta mustaksi screen.fill(BLACK) # Piirretään neliö pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50]) # Liikutetaan neliötä aloituspisteestä rect_x += rect_change_x rect_y += rect_change_y
Kun neliö osuu ikkunan reunaan, se vain jatkaa matkaa. Mikään toiminto ei saa aikaiseksi neliön kimpoamista ikkunan reunasta. Neliön liikkeen suunta voidaan kääntää päinvastaiseksi siten, että muutetaan rect_change_y muuttujan arvo 5:stä -5:een kun neliö osuu ikkunan alareunaan. Neliö on alareunassa, kun sen rect_y muuttujan arvo on suurempi kuin ikkunan korkeus (height). Alla oleva koodi tekee tarkistuksen ja kääntää suunnan päinvastaiseksi:
# Bounce the rectangle if needed if rect_y > 450: rect_change_y = rect_change_y * -1
Miksiköhän rect_ymuuttujaa verrataan arvoon 450? Jos ikkuna on 500 pikseliä korkea, silloin vertaaminen 500:aan olisi loogisempaa. Muistat kuitenkin, että neliö piirretään alkaen vasemmasta yläkulmasta. Joten piirtämällä neliön y-pisteeseen 500, sen alareuna piirretään y-pisteseen 550, eli kokonaan ikkunan ulkopuolelle. Katso kuva 8.1.
Yhteenvetona 50 pikseliä korkea neliö tulee piirtää pisteeseen:
$500-50=450$.
Alla olevassa koodissa neliö piirretään niin, että se mahtuu 700x400kokoiseen ikkunaan:
# Objekti kimpoaa tarvittaessa if rect_y > 450 or rect_y < 0: rect_change_y = rect_change_y * -1 if rect_x > 650 or rect_x < 0: rect_change_x = rect_change_x * -1
Kiinnostaisiko koodata monimutkaisempia kuvioita? Useimmat piirtokomennot ovat periaatteessa kuin rect_x ja rect_y. Alla oleva koodi piirtää punaisen neliön valkoisen sisään. Punaisen neliön piirrossa on 10 pikselin 'sisennys' (offset) sekä x- että y-suunnassa vasemmassa yläreunassa. Se on siis 20 pikseliä pienempi sekä leveys- että korkeussuunnassa. Tuloksena saadaan valkoreunainen neliö, jossa on punainen sisus. Katso kuva 8.2.
# Piirretään punainen neliö valkoisen sisään pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50]) pygame.draw.rect(screen, RED, [rect_x + 10, rect_y + 10 ,30, 30])
8.2 Animaatio Lumisade
Yhden objektin animoiminen tuskin riittää, vaan tarvitsemme enemmän. Miten animoidaan satoja objekteja samalla kertaa? Tässä kappaleessa esitetään tekniikka, joka perustuu kappaleessa 8.1 esitettyyn animaatioon ja saadaan tällä keinoin animoitua lumisade.
8.2.1 Koodin selitys
Heti aluksi tarvitsemme tässä ohjelmassa koodipohjan (templaten), jossa luodaan tyhjä ikkuna.
Tämän koodipohjan voit kopioida tästä pygame_base_template.py:
ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py
Erilaisten tähtien, lumisateen, vesisateen animointi eri x, y koordinaatteihin saadaan luotua käyttämällä satunnaislukugeneraattoria (random numbers). Helpoiten tämä ohjelma saadaan for silmukalla, jossa luodaan ympyröitä satunnaisiin x, y pisteisiin. Kokeile seuraavaa koodia ja sijoita se pää while silmukan sisään.
for i in range(50): x = random.randrange(0, 400) y = random.randrange(0, 400) pygame.draw.circle(screen, WHITE, [x, y], 2)
Kokeile, ja huomaat varmaan jonkin ongelman! 20 kertaa sekunnissa jokaisella silmukkakierroksella, ohjelma piirtää lumihiutaleita satunnaisiin pisteisiin. Yritä säätää lumihiutaleiden määrää ja huomaa miten se vaikuttaa kuvan piirtoon.
Ilmeisesti tässä pitää siis luoda lumihoitale satunnaiseen paikkaan ja pitää se samassa paikassa. Ei tarvitse generoida uuttaa pistettä 20 kertaa sekunnissa. Tarvitsemmekin siis listan hiutaleiden sijainnista. Ohjelmassa voimme käyttää tuttua Pythonin listaa. Tämä lista pitää tehdä ennen ohjelman pääsilmukkaa. Ohjelma lisää 50 uutta lumihiutaletta listaan joka 1/20 sekuntti.
for i in range(50): x = random.randrange(0, 400) y = random.randrange(0, 400) snow_list.append([x, y])
Kun lumihiutaleiden sijainnit on lisätty listaan, päästään alkioihin 'käsiksi' aivan tutulla tavalla. Seuraavassa koodissa printataan listan x- että y-koordinaatti, jotka ovat tallennettu listan ensimmäiseen alkioon, paikkaan nolla:
print(snow_list[0])
Miten saisimme listasta haettua vain joko x-koordinaatin tai y-koordinaatin? Tässä on lista listan sisällä. 'Pää'lista sisältää kaikki koordinaatit. Tämän listan sisässä on listana x- ja y-koordinaattipisteet (paikoissa 0 ja 1). Tässä on esimerkiksi kolme koordinaattia listattuna listaan:
[[34, 10], [10, 50], [20, 18]]
Paikassa 0 olevan y-koordinaatin tulostuksessa valitaan ensin koordinaatti paikassa 0, ja sitten alilistan paikka 1. Koodi näyttää tältä:
print(snow_list[0][1])
Vastaavasti 21. koordinaattipisteen (paikka 20) arvo saadaan siten, että ensin valitaan paikka 20 ja sitten x piste paikassa 0:
print(snow_list[20][0])
Pääohjelman while silmukan sisällä voidaan käyttää for silmukkaa lumihiutalelistan alkioiden piirtämiseen. Muistathan, että len(snow_list) palauttaa lumihiutalelistan pituuden, eli viimeisen alkion indeksin.
# Prosessoidaan jokainen lumihiutale listassa for i in range(len(snow_list)): # Piirretään lumihiutale pygame.draw.circle(screen, WHITE, snow_list[i], 2)
Muistathan, että for silmukoita on kahta eri tyyppiä. Lumihiutaleiden piirtämiseen voitaisiin käyttää alla esitettyä silmukkaa:
# Prosessoidaan lumihiutalelistan alkioiden KOPIOITA listassa for xy_coord in snow_list: # Piirretään lumihiutale pygame.draw.circle(screen, WHITE, xy_coord, 2)
Kuitenkin, kun haluamme käsitellä lumihiutaleiden paikkaa, emme voi käyttää tämän tyyppistä for silmukkaa. Tässähän muokataan lumihiutalelistan alkioiden kopioita alkuperäisen lista-alkion sijasta.
Jos ohjelmassa halutaan siirtää objektia alas päin, kuten lumisateessa tapahtuu, pitää for silmukassa y-koordinaattia kasvattaa jokaisella toistokierroksella:
# Prosessoidaan jokainen lumihiutale listassa for i in range(len(snow_list)): # Piirretään lumihiutale pygame.draw.circle(screen, WHITE, snow_list[i], 2) # Siirretään lumihiutale 1 pikseli alas päin snow_list[i][1] += 1
Tämä siirtää lumihiutaleet alas päin, mutta liikuttuaan näytön ulkopuolelle uusia ei enää ilmesty. Alla lisätään koodiin algoritmi, jolla palautetaan lumihiutale takaisin näytön yläosaan satunnaisees paikkaan:
# Jos hiutale on liikkunut näytön ulkopuolelle alhaalta if snow_list[i][1] > 400: # Palaute se takaisin näytön yläosaan y = random.randrange(-50, -10) snow_list[i][1] = y # annetaan uusi paikka x = random.randrange(0, 400) snow_list[i][0] = x
Koodissa listaan on mahdollista lisätä muitakin eri kokoisia objekteja, muotoja, värejä, nopeuksia ja suuntia. Tämä tekee koodista monimutkaisempaa, koska tällöin tarvitaan moninaisia datatyyppejä listattavaksi. Pidetään asia vielä yksinkertaisena, mutta kappaleessa 12 käsittelemme "luokkia", joilla on helpompaa hallita erilaisia objekteja ja attribuutteja.
8.2.2 Full Program Listing
""" Animating multiple objects using a list. Sample Python/Pygame Programs Simpson College Computer Science http://programarcadegames.com/ http://simpson.edu/computer-science/ Explanation video: http://youtu.be/Gkhz3FuhGoI """ # Import a library of functions called 'pygame' import pygame import random # Initialize the game engine pygame.init() BLACK = [0, 0, 0] WHITE = [255, 255, 255] # Set the height and width of the screen SIZE = [400, 400] screen = pygame.display.set_mode(SIZE) pygame.display.set_caption("Snow Animation") # Create an empty array snow_list = [] # Loop 50 times and add a snow flake in a random x,y position for i in range(50): x = random.randrange(0, 400) y = random.randrange(0, 400) snow_list.append([x, y]) clock = pygame.time.Clock() # Loop until the user clicks the close button. done = False while not done: for event in pygame.event.get(): # User did something if event.type == pygame.QUIT: # If user clicked close done = True # Flag that we are done so we exit this loop # Set the screen background screen.fill(BLACK) # Process each snow flake in the list for i in range(len(snow_list)): # Draw the snow flake pygame.draw.circle(screen, WHITE, snow_list[i], 2) # Move the snow flake down one pixel snow_list[i][1] += 1 # If the snow flake has moved off the bottom of the screen if snow_list[i][1] > 400: # Reset it just above the top y = random.randrange(-50, -10) snow_list[i][1] = y # Give it a new x position x = random.randrange(0, 400) snow_list[i][0] = x # Go ahead and update the screen with what we've drawn. pygame.display.flip() clock.tick(20) # Be IDLE friendly. If you forget this line, the program will 'hang' # on exit. pygame.quit()
Tämä esimerkkikoodi liikuttaa kutakin lumihiutaletta samaan suuntaan. Jospa haluammekin animoida hiutaleet erikseen, jokainen omaan suuntaansa? Jos tarvitset tällaisen omaan peliisi, katso kappale 12 luokkien käyttäminen. Tehtävä 8 näyttää askel askeleelta, miten saat animoitua satoja eri objekteja kunkin omaan suuntaansa.
8.3 3D Animaatio
Laajennus 2D ympäristöstä 3D ympäristöön ei ole pelin rakenteen kannalta niin hankalaa kuin voisi kuvitella. Vaikkakin asia menee tämän tunnin aiheen ulkopuolelle, niin on hyödyllistä katsoa, mitä tuohon liittyy.
Vapaasti saatavilla oleva 3D ohjelma Blender, jossa on sopiva “pelimoottori” 3D pelien ohjelmointiin, toimii myös Python koodiin 'importattuna'. 3D objekteja voidaan kontrolloida Python koodilla.
Katso kuva 8.3. Siinä vihreällä tarjottimella on useita erilaisia objekteja. Sinistä objektia voidaan kontrolloida Python skriptillä, joka siirtää sinistä objektiä ympäriinsä ja törmäilee muihin. Alla olevassa koodiesimerkissä on paljon samoja ominaisuuksia kuin 2D ohjelmissakin on. Siinä on pääsilmukka, siinä on myös listat x,y sijainneille ja lisäksi vielä muuttujat vektoreiden kontrollointiin.
Pääohjelmasilmukkaa ohjataan Blenderistä. Python koodia kutsutaan Blenderistä jokaisella pelin renderöinti “framessa”. Tästä syystä Python koodi ei ole pääohjelmasilmukassa. Se on kuitenkin mukana ohjelmassa.
Sinisellä objektilla on koordinaatteina x,y,z piste. Objektin sijaintiparametrien arvoja voidaan muuttaa blue_object.position muuttujan avulla. Taulukon paikassa 0 on x, paikassa 1 on y ja paikassa 2 on z koordinaatin arvo.
Sen sijaan, että käytetään change_x ja change_y muuttujia,
(kuten 2D esimerkissä) käytössä tässä Blender esimerkissä on assosiatiiviset taulukko sijainnit:
blue_object["x_change"]
blue_object["y_change"]
if lauseessa tehdään tarkistus, onko sininen objekti saavuttanut näytön reunan ja jos on, niin muutetaan suunta. Poiketen 2D pelin pikseleihin perustuvaan sijaintitietoon, tässä voidaan käyttää ns. liukulukuja (desimaalilukuja, floating point). Sijaintiarvo objektille voisi olla esimerkiksi 5.5. (Kääntäjän huomio: seuraavaa esimerkkikoodia ei ole käännetty terminologian oppimisen kannalta. Käy se huolella läpi)
# Import Blender Game Engine import bge # Get a reference to the blue object cont = bge.logic.getCurrentController() blue_object = cont.owner # Print the x,y coordinates where the blue object is print(blue_object.position[0], blue_object.position[1] ) # Change x,y coordinates according to x_change and # y_change. x_change and y_change are game properties # associated with the blue object. blue_object.position[0] += blue_object["x_change"] blue_object.position[1] += blue_object["y_change"] # Check to see of the object has gone to the edge. # If so reverse direction. Do so with all 4 edges. if blue_object.position[0] > 6 and blue_object["x_change"] > 0: blue_object["x_change"] *= -1 if blue_object.position[0] < -6 and blue_object["x_change"] < 0: blue_object["x_change"] *= -1 if blue_object.position[1] > 6 and blue_object["y_change"] > 0: blue_object["y_change"] *= -1 if blue_object.position[1] < -6 and blue_object["y_change"] < 0: blue_object["y_change"] *= -1
Blenderin voit ladata täältä:
http://www.blender.org/
Täydellinen blender ohjelmatiedosto on saatavilla täältä:
ProgramArcadeGames.com/chapters/08_intro_to_animation/simple_block_move.blend.
8.4 Review
8.4.1 Multiple Choice Quiz
Klikkaa tästä monivalintatehtävään.
8.4.2 Short Answer Worksheet
Klikkaa tästä kappaleen kertaustehtäviin.
8.4.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