Arcade-pelien ohjelmointi
Pythonilla ja Pygamella

Chapter 13: Perehdytään Spriteihin

Video: Introduction to Sprites

Tarvitsemme peleihimme objektien törmaysten käsittelyn. PAllo kimpoaa mailasta, lasersäde osuu avaruusolioon tai pelihahmo kerää kolikoita. Kaikkien näiden aikaan saamiseksi tarvitaan törmäysten havaitsemista.

Pygamen kirjastossa on tuki spriteille. Sprite on kaksiulotteiden (2D) kuva, joka on osa laajempaa graafista näkymää. Tyypillisesti sprite voisi olla eräänlainen objekti pelinäkymässä, joka vuorovaikuttaa jonkin reaalimaailman esineen tavoin; esim. auton, sammakon tai putkimiehen.

fig.sprite

Alunperin videopeleissä on ollut sisäänrakennettu tuki spriteille. Tällainen ominaisuus on sinänsä tarpeeton, mutta edelleen termi “sprite” on käytössä.

13.1 Perussritet ja törmäykset

Käydään läpi tarkoin esimerkkiohjlema, jossa on spritejä. Tässä esimerkissä lisätään näytölle mustia laatikoita ja niitä kerätään punaisella laatikolla. Punainen laatikko seuraa hiiren osoitinta. Katso kuva 13.1. Ohjelma tallentaa muuttujaan kerättyjen laatikoiden lukumäärän. Ohjelman koodi löytyy täältä:
ProgramArcadeGames.com/python_examples/f.php?file=sprite_collect_blocks.py

fig.example_sprite_game1
Figure 13.1: Example Sprite Game

Ohjelman ensimmäiset rivit ovat jo ennestään tuttuja, näinhän peliohjelma aloitetaan:

import pygame
import random

# Määritetään muutamia värejä
BLACK = (  0,   0,   0)
WHITE = (255, 255, 255)
RED   = (255,   0,   0)

Rivillä 1 'importataan' pygame -kirjasto, jotta saadaan tuki spriteille. Random kirjaston, rivillä 2, avulla saadaan mustat palikat sijoitettua satunnaisesti pelialueelle. Värin määritys riveillä 5-7 on varsin tuttua entuudestaan.

class Block(pygame.sprite.Sprite):
    """
    Tämä luokka esittää laatikon. 
    Se johdetaan "Sprite" luokasta Pygamessa.
    """

Rivillä 9 alkaa luokan Block määritys. Huomaa, että rivillä 9 tämä luokka on luokan Sprite aliluokka. pygame.sprite. osoittaa kirjaston ja paketin. Näistä puhutaan tarkemmin kappaleessa 14. Kaikki Sprite luokan toiminnallisuus periytyy nyt myös luokan Block ominaisuuksiksi.

    def __init__(self, color, width, height):
        """ Konstruktori. Välitetään parametrit color ja 
        paikkatieto x ja y. """

        # Kutsutaan yliluokan (Sprite) konstruktoria.
        super().__init__()

Block luokan konstruktori rivillä 15 saa parametrikseen self aivan kuin muissakin konstruktoreissa. Se saa parametreina myös objektin värin, korkeuden ja leveyden (color, height, width).

Yliluokan konstruktorin kutsu, Sprite, tekee mahdolliseksi spritejen alustuksen. Tämä on rivillä 20.

        # Luodaan palikasta kuva ja täytetään se annetulla värillä.
        # Tässä voisi vaikka ladata kuvatiedoston levyltä.
        self.image = pygame.Surface([width, height])
        self.image.fill(color)

Riveillä 24 ja 25 luodaan kuva joka lopulta näytölle piirretään. Rivillä 24 luodaan tyhjä kuva. Rivillä 25 se täytetään mustalla värillä. Jos ohjelmassa tarvitaan jotain muuta kuin mustia neliöitä, niin tässä kohtaa koodirivejä pitäisi modifioida.

Katso esimerkkikoodia alla:

    def __init__(self, color, width, height):
        """
        Ellipsi konstruktori. Välittää ellipsin värin ja koon parametrit.
        """
        # Kutsutaan yliluokan (Sprite) konstruktoria.
        super().__init__()

        # Asetetaan taustaväri ja sen läpinäkyvyys
        self.image = pygame.Surface([width, height])
        self.image.fill(WHITE)
        self.image.set_colorkey(WHITE)

        # piirretään ellipsi
        pygame.draw.ellipse(self.image, color, [0, 0, width, height])

Jos yllä oleva koodi sijoitetaan alkuperäiseen koodiin, tulostuu ellipsejä. Rivillä 29 piirrwetään ellipsi ja rivillä 26 siihen tehdään läpinäkyyvyysasetus (transparent). Tämä tehtiin aiemmin kappaleessa 11, jossa valkoinen tausta poistettiin asettamalla se läpinäkyväksi.

    def __init__(self):
        """ Graphic Sprite Constructor. """

        # Kutsutaan yliluokan (Sprite) konstruktoria
        super().__init__()

        # Ladataan kuva 
        self.image = pygame.image.load("player.png").convert()

        # Asetetaan läpinäkyvä väri
        self.image.set_colorkey(WHITE)

Jos haluammekin käyttää bittikarttakuvaa, korvataan rivit yllä olevilla. Koodissa rivillä 22 ladataan kuva ja rivillä 25 asetetaan sen taustaväri läpinäkyväksi. Tässä tapauksessa spriten mittoja ei tarvita (leveys, korkeus), koska mitata asetetaan automaattisesti ladattavan kuvan mittojen mukaisiksi. Huomaa siis, että rivin 15 parametrit puuttuvat.

Konstruktorissa on vielä eräs tärkeä koodirivi, joka ei huomioi millaisen spriten luomme:

        # Tuotetaan suorakulmainen objekti, jossa on samat mitat kuin
        # kuvalla.
        # Päivitetään kuvan paikka asettamalla muuttujat 
        # rect.x ja rect.y
        self.rect = self.image.get_rect()

Attribuutti rect on muuttuja, joka on Pygamen Rect luokasta peritty. Suorakulmio saa mitoikseen spriten mitat. Tällä suorakulmiolla on attribuutteina x ja y ja niille voidaan asettaa arvot. Pygame piirtää spriten attribuuttien x ja y paikkaan. Eli, jos spriten paikkaa pitää muuttaa, niin muutetaan mySpriteRef.rect.x ja mySpriteRef.rect.y arvoja. Tässä mySpriteRef on muuttuja, joka viittaa spriteen.

Nyt luokka Block on valmis. Siirrytäänpä alustuskoodin pariin.

# Alustetaan Pygame
pygame.init()

# Asetetaan näytön leveys ja korkeus
screen_width = 700
screen_height = 400
screen = pygame.display.set_mode([screen_width, screen_height])

Yllä oleva koodi alustaa Pygamen ja luo peli-ikkunan. Koodissa ei ole mitään uutta Pygamen osalta.

# Tämä on lista 'spritet.' Jokainen neliö lisätään listaan.
# Listaa käsitellään kutsumalla luokkaa 'Group.'
block_list = pygame.sprite.Group()

# Tässä listassa ovat kaikki spritet.
# Samoin kaikki neliöt ja pelaaja.
all_sprites_list = pygame.sprite.Group()

Suurin hyöty spritejen käyttämisestä on, että niitä voidaan käsitellä ryhmänä. Voimme piirtää tai siirtää kaikki spritet yhdellä komentorivillä, jos ne ovat ryhmässä. Myös spritejen törmäystapahtumat on helppo tarkistaa.

Yllä olevassa koodissa luodaan kaksi listaa. Muuttuja all_sprites_list tulee sisältämään kaikki pelin spritet. Tätä listaa käytetään spritejen piirtämiseen. Muuttujassa block_list on tallennettu kaikki objektit (palikat), joilla on törmäystapahtuma pelaajan kanssa. Tässä esimerkissä se sisältää kaikki muut objektit paitsi pelaajan. Pelaajaa ei voi lisätä tähän listaan, koska tarkoitus on tarkistaa pelaajan ja listassa block_list olevien objektien törmäykset. Pygame palauttaisi kaiken aikaa törmäystapahtuman, jos pelaaja olisi tässä listassa.

for i in range(50):
    # Tässä tehdään palikka (block)
    block = Block(BLACK, 20, 15)

    # Asetetaan random-sijainti palikalle
    block.rect.x = random.randrange(screen_width)
    block.rect.y = random.randrange(screen_height)

    # Lisätään palikka objektilistaan
    block_list.add(block)
    all_sprites_list.add(block)

Riviltä 49 alkava silmukka lisää rivillä 50 mustia lockkeja näytölle. Rivillä 51 luodaan uusi blocki, asetetaan väri, leveys ja korkeus. Rivillä 54 ja 55 asetetaan tämän objektin paikkakoordinaatit. Rivillä 58 block lisätään listaan, jotka voivat törmätä pelaajan kanssa. Rivillä 59 block lisätään vielä kaikkien spritejen listaan. Tämä on samanlainen koodi kuin aiemmin oli tehtävässä (Lab 13).

# Luodaan RED punainen pelaajaobjekti
player = Block(RED, 20, 15)
all_sprites_list.add(player)

Riveillä 62-63 asetetaan pelaaja peliin. Rivillä 62 luodaan punainen palikka, joka on pelaajapalikka. Pelaajapalikka lisätään all_sprites_list listaan rivillä 63 ja siten voidaan piirtää. Sitä ei kuitenkaan lisätä block_list listaan.

# Pelisilmukka suoritetaan kunnes käyttäjä klikkaa sulje
done = False

# Kuinka nopeasti näyttöä päivitetään
clock = pygame.time.Clock()

score = 0

# -------- Pääohjelmasilmukka-----------
while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

    # Tyhjennä näyttö
    screen.fill(WHITE)

Yllä oleva koodi on tavallinen ohjelmaluuppi, joka esitettiin jo kappaleessa 5. Rivi 71 asettaa pelin score muuttujan arvoksi 0.

    # 'Nappaa' (getteri) hiiren sijainti. Funktio palauttaa hiiren koordinaatit
    # kahden luvun listana.
    pos = pygame.mouse.get_pos()

    # Otetaan x ja y arvot ulos listasta
    # samoin kuin otettiin merkkejä merkkijonosta.
    # Asetetaan pelaajaobjekti hiiren kohtaan.
    player.rect.x = pos[0]
    player.rect.y = pos[1]

Rivi 84 tallentaa hiiren paikkakoordinaatit samalla tavalla kuin aiemmin oli esillä. Tärkeä uusi osa tässä on, kun riveillä 89-90 spriten sisältävä suorakulmio siirretään uuteen paikkaan. Muista, että tämä suorakulmio luotiin rivillä 31, eikä koodi toimisi ilman tuota riviä.

    # Tarkistetaan  pelaajaobjektin mahdolliset törmaykset johonkin toiseen objektiin.
    blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)

Tällä rivillä viitataan pelaajan spriteen ja tarkistetaan mahdolliset törmäykset block_list olevien spritejen kanssa. Koodi palauttaa listan törmänneistä (päällekkäin olleista) spriteista. Jos törmäyksiä ei ollut, palautetaan tyhjä lista. Totuusarvo True poistaa törmänneet spritet listasta. Jos arvo asetettaisiin False, niin spritejä ei poistettaisi.

    # Tarkista listasta törmäykset.
    for block in blocks_hit_list:
        score +=1
        print(score)

Tässä silmukassa 'loopataan' kaikki törmäyslistan spritet, jotka olivat siis rivin 93 listassa ja lisätään pistetitä jokaisesta törmäyksestä. Pisteet (score) tulostetaan näytölle. Huomaa, että print-komento rivillä 98 ei tulosta pistetitä peli-ikkunaan vaan konsoli-ikkunaan. Kuinka pisteet saadaan tulostumaan konsoli-ikkunan sijasta peli-ikkunaan selvitetään harjoituksessa 14 (Lab 14).

    # Piirretään kaikki spritet
    all_sprites_list.draw(screen)

Group luokan ilmentymä all_sprites_list sisältää metodin (aliohjelmans) draw. Tämä metodi luuppaa läpi kaikki listassa olevat spritet ja kutsuu spriten draw metodia. Tässä saadaan ainoastaan yhdellä rivillä aikaiseksi kaikkien spritejen piirtäminen all_sprites_list listasta.

    # Asetetaan rajaksi 60 'freimiä' sekunnissa
    clock.tick(60)

    # Jatketaan ja päivitetään piirrokset näytölle.
    pygame.display.flip()

pygame.quit()

Rivit 103-109 päivittävät näytön ja kutsuu quit metodia, kun pääohjelmaluuppi lopetetaan.

13.2 Spritet liikkeelle

Video: Moving Sprites

Tähän mennessä pelissä liikkuu ainoastaan pelaajan sprite. Miten saisimme kaikki spritet liikkumaan? Oikeastaan aika helposti. Tarvitaan ainoastaan kaksi vaihetta lisää.

Ensimmäiseksi pitää lisätä uusi metodi Block luokkaan. Tämä uusi metodi on nimeltään update. update funktiota kutsutaan automaattisesti, kun update metodia kutsutaan listasta.

Kirjoita tämä spriteen:

    def update(self):
        """ kutsutaan jokaissella freimillä"""

        # Siirtää palikkaa yhden pikselin alas päin
        self.rect.y += 1

Lisää tämä pääohjelmasilmukkaan:

    # Kutsutaan update() metodia jokaiselle 'palikalle' block_list listassa 
    block_list.update()

Koodi ei toimi ihan oikein, koska palikat liikkuvat alas näytön ulkopuolelle eivät ilmesty takaisin. Tämä koodi parantaa update funktion toimintaa siten, että palikat saadaan ilmestymään uudelleen ylös.

    def update(self):
        # Siirrä palikkaa pikselin verran alas
        self.rect.y += 1
        if self.rect.y > screen_height:
			self.rect.y = random.randrange(-100, -10)
			self.rect.x = random.randrange(0, screen_width)

Jos ohjelman pitäisi palauttaa alkuarvoihinsa palikat, jotka on kerätty näytön yläosaan, voidaan spritet vaihtaa seuraavalla koodilla:

    def reset_pos(self):
        """ Palautetaan paikka näytön yläosaan, satunnaiseen x koordinaattiin.
        Kutsutaan update() avulla tai pääohjelmasilmukkaa, jos törmäyksiä.
        """
        self.rect.y = random.randrange(-300, -20)
        self.rect.x = random.randrange(0, screen_width)

    def update(self):
        """ Kutsutaan jokaisella freimillä. """

        # Siirrä palikkaa pikselin verran alas
        self.rect.y += 1

        # Jos palikka on alhaalla ikkunan ulkopuolella, palauta takaisin ylös.
        if self.rect.y > 410:
            self.reset_pos()

Palikoiden tuhoamisen sijasta, törmäyksen jälkeen palikka palautetaan reset_pos funktiolla takaisn näytön yläosaan.

    # Tutkitaan, onko palikka törmännyt johonkin.
    blocks_hit_list = pygame.sprite.spritecollide(player, block_list, False)

    # Tarkistetaan törmäysten lista
    for block in blocks_hit_list:
        score += 1
        print(score)

        # Palauta palikka takaisin näytön yläosaan. 
        block.reset_pos()

Koko esimerkin koodi löytyy tästä:
ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites.py

Jos haluat mieluummin nähdä koodin kimpoavilla palikoilla, niin katso tätä:
ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites_bounce.py

Jos haluaisit niiden kulkevän ympyrässä:
ProgramArcadeGames.com/python_examples/f.php?file=sprite_circle_movement.py

13.3 Game luokka

Kappaleessa 9 tutustuimme funktioihin. Kappaleen loppuosassa puhuimme mahdollisuudesta käyttää main funktioita. Ohjelmien laajentuiessa suuremmiksi, tällä tekniikalla voimme välttää pitkien koodien hallitsemista. Tähän mennessä tekemämme ohjelmat eivät ole kuitenkaan vielä laajoja. Siitä huolimatta tämän tekniikan opettelusta on hyötyä myöhemmin.

Jos haluat oppia organisoimaan koodiasi uudella tekniikalla, niin tässä on tilaisuus. Voit kuitenkin tässä vaiheessa ohittaa tämän kohda ja palata asiaan myöhemmin sitten, kun ohjelmasi kasvavat niin suuriksi, että niitä on vaikea hallita. Katso seuraava video ja nappaa kiinni ohjelman toiminnan ajatuksesta.
ProgramArcadeGames.com/python_examples/f.php?file=game_class_example.py

13.4 Toinen esimerkki

Tässä on muutamia esimerkkejä siitä, mitä spriteille voidaan tehdä. Joissakin on videolinkki, jossa koodin toiminta obn selvitetty.

13.4.1 Räiskintää

fig.bullets
Figure 13.2: Shooting things

Kiinnostaako räiskintäpeli? Esimerkiksi klassinen Space Invaders? Tässä esimerkissä luodaan ammus-spritejä:
ProgramArcadeGames.com/python_examples/f.php?file=bullets.py

13.4.2 Seiniä

Miten olisi seikkailutyyppinen peli? Haluatko rajoittaa pelaajan mahdollisuuksia liikkua? Tässä esimerkki seinien lisäämisestä peliin:
ProgramArcadeGames.com/python_examples/f.php?file=move_with_walls_example.py

fig.walls
Figure 13.3: Move with walls we can run into

Yksi kenttä ei riitä seikkailuun. Haluatko siis pelaajan liikkuvan kentästä toiseen? Sekin onnistuu! Katso tästä esimerkki, miten pelaaja saadaan liikkumaan useammassa kentässä:
ProgramArcadeGames.com/python_examples/f.php?file=maze_runner.py

fig.maze_runner
Figure 13.4: Multi-room maze

13.4.3 Tasohyppely, Platforms

Miten olisi tasohyppely, esimerkiksi Donkey Kong? Tässä pitää käyttää samaa periaatetta kuin edellä sinien lisäämisessä ja lisätään vielä painovoima, gravity:
ProgramArcadeGames.com/python_examples/f.php?file=platform_jumper.py

fig.platform_jumper
Figure 13.5: Jump around platforms

Hyvissä tasohyppelyissä voidaan liikkua puolelta toiselle. Tässä yksi esimerkki tasohyppelypelistä:
ProgramArcadeGames.com/python_examples/f.php?file=platform_scroller.py

fig.platform_scroller
Figure 13.6: Side scrolling platformer

Parempi versio tasohyppelystä olisi peli, jossa alustakin saadaan liikkumaan! Katso esimerkki, miten tämä saadaan aikaiseksi:
ProgramArcadeGames.com/python_examples/f.php?file=platform_moving.py

fig.platform_moving
Figure 13.7: Moving platforms

13.4.4 Matopeli

MAtopelin tyyppinen peli on aina kiinnostanut pelien koodaajia. Tässä moniosaista tuhatjalkaista voidaan kontrolloida. Jokainen osa/segmentti pitää tallentaa listaan. Tämän pelin periaatteen opit opettelemalla kaksi uutta komentoa.

Ohjaa matoa näytöllä:
ProgramArcadeGames.com/python_examples/f.php?file=snake.py

fig.snake
Figure 13.8: Snake

13.4.5 Using Sprite Sheets

Tässä esimerkissä laajennamme spritejen käyttöä eräänlaisena “sprite maskeina”. Tällä saadaan grafiikkaa pelaajahahmon taustalle. Tätä voidaan käyttää usealla tasolla ja liikkuvissa tasoissa myös. Peli on jaettu useaan tiedostoon. ProgramArcadeGames.com/python_examples/en/sprite_sheets

fig.sprite_sheet
Figure 13.9: Sprite sheet platformer

13.4.6 Multiple Choice Quiz

Klikkaa tästä monivalintatehtävään.

13.4.7 Short Answer Worksheet

Tässä kappaleessa ei ole työkirjaa.

13.4.8 Lab

Klikkaa tästä ohjelmointitehtäviin.


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