Program Arcade Games
With Python And Pygame

Chapter 8: Inleiding tot animatie

8.1 De botsende rechthoek

Video: Botsende rechthoek

Om te beginnen met de eerste animatie vertrekken we van het basis pygame programme van hoofdstuk 5 dat een blanko scherm toont. De code van pygame_base_template.py kan je hier vinden :
ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

We gaan een programma maken dat een witte rechthoek laat botsen op het scherm met een zwarte achtergrond. Je mag zelf je kleuren kiezen, zorg er alleen voor dat de achtergrond en de rechthoek een verschillende kleur hebben!

fig.bouncing_rectangle

Eerst start je met de basis template en wijzig je de achtergrond van wit naar zwart. Dit moet je doen in lijn 46.

screen.fill(black)

Daarna teken je een rechthoek die we willen laten bewegen. Een simpele rechthoek is genoeg. Deze code hoort thuis nadat het scherm wordt gewist en voor het nieuwe scherm wordt getoont (geflipt).

pygame.draw.rect(screen, white, [50, 50, 50, 50])

Elke keer dat we door de lus gaan tekenen we de rechthoek met coordinaten (x,y) of dezelfde locatie (50,50). Het zijn de eerste twee 50'en in de list die de positie bepalen. Zolang deze twee getallen niet wijzigen zal de rechthoek niet bewegen.

De rechthoek is 50 pixels breed en 50 pixels hoog. De grootte van de rechthoek wordt bepaalt door de laatste twee getallen van de list. We kunnen deze rechthoek eigenlijk een vierkant noemen aangezien de breedte en de hoogte gelijk zijn. Toch blijf ik het een rechthoek noemen aangezien elk vierkant een rechthoek is en afhankelijk van de resolutie van het scherm niet alle pixels vierkantig zijn. Indien je meer hierover wil weten zoek je maar wat meer info op over Pixel Aspect Ratio.

Hoe kunnen we de positie nu wijzigen in plaats van de rechthoek altijd te tekenen op (50, 50)? Door een variabele te gebruiken natuurlijk! De code hieronder zet alvast de eerste stap :

rect_x = 50
pygame.draw.rect(screen, white, [rect_x, 50, 50, 50])

Om de rechthoek naar rechts te bewegen moet je x verhogen met 1 voor elk frame. De code is bijna juist, maar we zijn er nog niet helemaal :

rect_x = 50
pygame.draw.rect(screen, white, [rect_x, 50, 50, 50])
rect_x += 1

Het probleem is de bovenste code is dat rect_x elke keer opnieuw op 50 gezet wordt in de lus? Om dit op te lossen moeten we de initialisatie buiten de lus houden. De volgende code zal de rechthoek mooi laten schuiven naar rechts.

# Startpositie van x voor de rechthoek
# Dit moet buiten de lus.
rect_x = 50

# -------- Main Program Loop -----------
while not done:
	for event in pygame.event.get(): # De user heeft iets gedaan
		if event.type == pygame.QUIT: # De user wil stoppen
			done = True # Flag dat we willen stoppen

	# Zet de achtergrond
	screen.fill(black)

	pygame.draw.rect(screen, white, [rect_x, 50, 50, 50])
	rect_x += 1

Om de rechthoek sneller te verschuiven kunnen we rect_x verhogen met 5 in plaats van met 1 :

rect_x += 5

We kunnen de code nu uitbreiden voor zowel x als y, zodanig dat de rechthoek naar rechtsonder gaat :

# Startpositie van de rechthoek
rect_x = 50
rect_y = 50

# -------- Main Program Loop -----------
while not done:
	for event in pygame.event.get():
		if event.type == pygame.QUIT: 
			done = True

	# Zet de achtergrond
	screen.fill(black)
	
	# Teken de rechthoek
	pygame.draw.rect(screen, white, [rect_x, rect_y, 50, 50])

	# Verplaats de rechthoek
	rect_x += 5
	rect_y += 5

De richting en de snelheid van de rechthoek kunnen we bewaren in een vector. Dit maakt met makkelijk om de richting en de snelheid van een verplaatsend object aan te passen. Het volgende stukje coding toont het gebruik van variabelen voor de snelheid en bewaart de gegevens in x en y in plaats van (5,5).

# Startpositie van de rechthoek
rect_x = 50
rect_y = 50

# snelheid en richting van de rechthoek
rect_change_x = 5
rect_change_y = 5

# -------- Main Program Loop -----------
while done == False:
	for event in pygame.event.get(): # user geeft iets in
		if event.type == pygame.QUIT: # user wil stoppen
			done = True # Flag dat we uit de lus willen

	# zet de achtergrond
	screen.fill(black)

	# tekent de rechthoek
	pygame.draw.rect(screen, white, [rect_x, rect_y, 50, 50])

	# verplaatst het startpunt van de rechthoek        
	rect_x += rect_change_x
	rect_y += rect_change_y

Eenmaal dat de rechthoek tegen de rand van het scherm, blijft hij verder gaan en verdwijnt hij van het scherm. Niets dat ervoor zorgt dat de rechthoek terug wordt gebotst. Om er voor te zorgen de rechthoek verder naar rechts gaat eenmaal het tegen de bodem komt, moeten we rect_change_y wijzigen van 5 naar -5 van zodra de rechthoek de bodem raakt. De rechthoek is op de bodem van zodra rect_ygroter is dan de hoogte van het scherm. De code hieronder controleert daarop en verandert dan de richting :

# Botsen van de rechthoek indien nodig.
if rect_y > 450:
	rect_change_y = rect_change_y * -1
fig.450
Figure 8.1: De positie van de rechthoek tov de y coordinaat

Waarom moeten we rect_y controleren op 450 als het scherm 500 pixels hoog is? Omdat de startpunt linksboven is en de hoogte van de rechthoek ook dient meegerekent te worden natuurlijk. Wannner anders de rechthoek op 500 komt wordt deze getekent van 500 tot 550 en is die volledig buiten het scherm. Zie figuur 8.1.

Rekening houdend dat de rechthoek 50 pixels hoog is, is de correcte botspositie :
$500-50=450$.

De code hieronder zorgt ervoor dat de rechthoek kan botsen tegen alle kanten voor een window van 700x400.

# Bots de rechthoek indien nodig
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

Geintresseerd om moeilijkere vormen te tekenen dan een rechthoek? Verschillende teken commando's kunnen gebruikt worden op basis van rect_x en rect_y. De code hieronder tekent een rode rechthoek binnen een witte rechthoek. De rode rechthoek heeft een verschuiving van 10 pixels in de x,y richting van de linkerbovenhoek van de witte rechthoek. Het is eveneens 20 pixels kleiner dan de omtrek van de witte rechthoek. Zie figuur 8.2.

fig.two_part_rectangle
Figure 8.2: Witte rechthoek met een rood vierkant in het midden
# Teken een rode rechthoek met een witte rechthoek rond
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 Sneeuw animatie

Is animatie van 1 item niet genoeg? Wat met honderden objecten in 1 keer? Hier is een voorbeeld gebruikmakend van de techniek van hoofdstuk 8.1 om sneeuwvlokjes te laten vallen.

8.2.1 Uitleg van de code

Video: Sneeuw animatie

Om te beginnen met dit programma, starten we met onze basis pygame template dat een blanko scherm toont. Opnieuw, de source pygame_base_template.py kan je hier vinden :
ProgramArcadeGames.com/python_examples/show_file.php?file=pygame_base_template.py

Het is mogelijk om de x en y positie van dingen zoals sterre, sneeuw, regen te creeren door willekeurige getallen. De simpelste manier om deze te genereren is door gebruik te maken van een for lus en cirkels te tekenen met een wilekeurige x en y positie. Probeer deze code op de juiste plaats te zetten binnen de while lus.

for i in range(50):
    x = random.randrange(0, 400)
    y = random.randrange(0, 400)
	pygame.draw.circle(screen, white, [x, y], 2)

Probeer het en je zal zien dat deze code een vreemd probleem heeft! 20 keer per seconde. Elke keer dat we door de lus gaan, wordt de sneeuw getekent op een nieuwe willekeurige positie. Probeer het aantal sneeuwvlokjes aan te passen en kijk hoe het beeld wijzigt.

Het is duidelijk dat we de sneeuwvlokjes op dezelfde plaats willen houden en ze niet 20 keer per seconde op een nieuwe plaats willen tonen. We moeten dus een lijst bijhouden van waar de sneeuwvlokjes zich bevinden. Het programma kan hier gebruik maken van een python list. Dit moet wel gebeuren voor de hoofdlus (the main loop), anders gaat het programma 50 nieuwe sneeuwvlokjes aanmaken elke 1/20ste van een seconde.

for i in range(50):
    x = random.randrange(0, 400)
    y = random.randrange(0, 400)
    snow_list.append([x, y])

Eenmaal de locatie is toegevoegd van de sneeuwvlokjes, kunnen deze benaderd worden zoals een gewone list. The volgende code zal zowel de x als de y coordinaat printen van de eerste positie, bewaard in positie nul :

print(snow_list[0])

Wat als we de x en de y coordinaten willen bewaren? We hebben een lists binnenin een list. De buitenste lijst (main list) bevat de coordinaten. Binnenin die list, is er telkens een andere list met de x (positie 0), en de y coordinaat (position 1). Bijvoorbeeld, we hebben drie coordinaten :

[[34, 10],
 [10, 50],
 [20, 18]]

Om de y coordinaat op positie 0 te printen selecteren we eerst coordinaat 0, en dan de y waarde die staat op positie 1. De code ziet er als volgt uit :

print(snow_list[0][1])

Om de x waarde van de 21ste coordinaat (positie 20)te printen, selecteer je eerst coordinaat 20 en dan de waarde op positie 0 :

print(snow_list[20][0])

Binnenin de (main) while lus, wordt er een for lus gebruikt om telkens de items te tekenen van de sneeuw list. Denk eraan, len(snow_list) geeft je terug hoeveel elementen er in de sneeuwvlokkenlijst zitten.

# Behandel elke sneeuwvlok
for i in range(len(snow_list)):
	# Tekenen van de sneeuwvlok
	pygame.draw.circle(screen, white, snow_list[i], 2)

Denk eraan, er zijn twee types van for lussen. Het andere type lus kan gebruikt worden en dan ziet die er als volgt uit :

# Neem een COPY van elke positie van een sneeuwvlok uit de lijst
for xy_coord in snow_list:
	# Teken de sneeuwvlok
	pygame.draw.circle(screen, white, xy_coord, 2)

Omdat we echter het plan hebben om de locatie van de sneeuwvlokken te wijzigen, kunnen we dit type van for lus niet gebruiken.

Het doel is nu om al die objecten (sneeuwvlokjes) naar omlaag te doen vallen en daarom moeten we de for lus uitbreiden en de y coordinaat telkens verhogen :

# Behandel elke sneeuwvlok in de list
for i in range(len(snow_list)):

	# Teken de sneeuwvlok
	pygame.draw.circle(screen, white, snow_list[i], 2)
	
	# Verplaats de sneeuwvlok met 1 pixel naar beneden
	snow_list[i][1] += 1

Dit zorgt ervoor dat de sneeuw naar beneden valt, maar wanneer het sneeuwvlokje van het scherm valt komt er geen nieuw te voorschijn. Door de onderstaande code komt er telkens een nieuw sneeuwvlokje bij op een willekeurige positie, wanneer er eentje onderaan verdwijnt :

	# Wanneer het sneeuwvlokje verdwenen is van het scherm.
	if snow_list[i][1] > 400:
		# breng het sneeuwvlokje opnieuw naar boven.
		y = random.randrange(-50, -10)
		snow_list[i][1] = y
		# Geef een nieuwe x positie
		x = random.randrange(0, 400)
		snow_list[i][0] = x

8.2.2 De volledige 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()

In dit voorbeeld bewegen de sneeuwvlokjes zich allemaal in dezelfde richting. Wat als elk element zich moet bewegen op een individuele manier, en in zijn eigen richting? Als je dat nodig heby moet je wachten tot hoofdstuk 13, over hoe je te werk gaat met classen. Oefening 8 toont je hoe je te werk moet gaan met honderden verschillende elementen die elk hun eigen richting uitgaan.

8.3 3D Animatie

Video: Blender Game Engine Demo

Een uitbreiding van een 2D omgeving naar een 3D omgeving, compleet met spelbewegingen is niet zo moeilijk als het er uit ziet. Alhoewel het buiten dit boek gaat, is het toch de moeite om het even aan te raken.

Er is een gratis beschikbaar 3D programma Blender welke beschikt over een “game engine” dat de programmeurs toelaat om 3D games te maken. De 3D objecten in het spel kunnen via Pyton code bestuurt worden om de nodige acties te doen in het spel.

fig.blender01
Figure 8.3: Voorbeeld Blender bestand

Kijk naar figuur 8.3. Dit toont een groen dienblad met verschillende objecten op. Het blauwe object wordt gecontrolleerd door een Python script. Het object kan je besturen en tegen de andere items laten botsen. Het script , dat je hieronder kan vinden heeft veel gelijkenissen met een 2D programma. Er is een hoofdlus, er zijn x en y coordinaten en de variabelen controleren de vectors.

De hoofdlus is onder controle van Blender. De python code die getoont wordt in de listing wordt elke keer opgeroepen door Blender bij het renderen van een frame. Dit is de reden waarom de Python code geen hoofdlus bevat. Ze bestaat reeds ergens anders.

Het blauwe object heeft de positie bewaard in x,y,z formaat en kan benaderd worden via wijzigingen van de blueobject.position variabele. De array positie 0 bewaart x, positie 1 bewaart y, en positie 2 bewaart de z locatie.

Naast de change_x en change_y variabelen die gebruikt worden in het 2D voorbeeld, gebruikt dit Blender voorbeeld ook assosiatieve array posities :
blueObject["x_change"]
blueObject["y_change"]

De if statements checken wanneer de randen worden geraakt of wanneer de richting van het object dient verandert te worden. Hoewel we in een 2D spel gebruik maken van pixels, kunnen de posities van objecten ook bepaald worden door reele getallen. Omd e positie van een object te tonen tussen 5 en 6 is het toegestaan om deze op 5,5 te zetten.

# Importeer de Blender Game Engine
import bge

# Vraag de referentie van het blauwe object
cont = bge.logic.getCurrentController()
blueObject = cont.owner

# Print de x,y coordinaten van het blauwe object
print(blueObject.position[0], blueObject.position[1] )

# Wijzig de x,y coordinates volgens de x_change en de
# y_change. x_change en y_change zijn spel eigenschappen
# die geassocieert zijn met het blauwe object.
blueObject.position[0] += blueObject["x_change"]
blueObject.position[1] += blueObject["y_change"]

# Check of het object aan de rand komt.
# Indien dit zo is keer je de richting om en dit voor alle zijden.
if blueObject.position[0] > 6 and blueObject["x_change"] > 0:
    blueObject["x_change"] *= -1

if blueObject.position[0] < -6 and blueObject["x_change"] < 0:
    blueObject["x_change"] *= -1

if blueObject.position[1] > 6 and blueObject["y_change"] > 0:
    blueObject["y_change"] *= -1

if blueObject.position[1] < -6 and blueObject["y_change"] < 0:
    blueObject["y_change"] *= -1

Blender kan je downloaden van :
http://www.blender.org/

Hier kan je een volledig Blender voorbeeld vinden :
ProgramArcadeGames.com/chapters/08_intro_to_animation/simple_block_move.blend.

8.4 Herhaling

8.4.1 Multiple Choice kwis

Click here for a multiple-choice quiz.

8.4.2 Korte inhoud werkblad

Click here for the chapter worksheet.

8.4.3 Oefening

Click here for the chapter lab.


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