Arcade-pelien ohjelmointi
Pythonilla ja Pygamella

Chapter 8: Perehdytään animaatioon

8.1 Kimpoava suorakulmio

Video: Bouncing Rectangle

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

fig.bouncing_rectangle

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
fig.rect_loc
Figure 8.1: Rectangle location based on y coordinate

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.

fig.two_part_rectangle
Figure 8.2: White rectangle with a red square in the middle
# 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

Video: Animating Snow

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

Video: Blender Game Engine Demo

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.

fig.blender01
Figure 8.3: Blender-based Game

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.