Ekstraoppgave med Pygame-Zero

Dette er en oppgave der man får lekt seg litt mer med Pygame Zero ved å lage et enkelt spill. Gjør gjerne denne oppgaven hvis du synes obligen går greit.

I denne oppgaven skal vi lage et enkelt Space Invaders-inspirert spill. Dette dokumentet inneholder noen instrukser og steg man kan følge, men vær gjerne kreativ og implementer dine egne ideer eller gjør ting på din egen måte.

Før man starter

Hva skal vi lage?

Instruksene er med vilje litt vage og åpne slik at man kan ta egne valg underveis.

Eksempel på hvordan et spill kan se ut til slutt:

bilde

Steg 1: Lage et romskip

Lag en klasse Romskip i en fil romskip.py.

Konstruktøren til Romskip trenger ikke å ta noen parametere. Et romskip starter alltid på samme posisjon, så konstruktøren kan initialisere disse instansvariablene:

Romskip trenger også noen metoder:

Sørg for at romskipet ikke får lov til å havne utenfor skjermen når det beveger seg.

Steg 2: Få romskipet opp på skjermen og implementer at man kan bevege romskipet med piltastene

Lag en klasse Spill i en fil spill.py, hvor du kan ta utgangspunkt i prekoden under.

I konstruktøren blir det opprettet et romskip, og vi lager en liste som skal holde på monstre (disse skal vi lage senere).

class Spill:
    def __init__(self):
        self._monstre = []
        self._oppdatering = 0
        self._romskip = Romskip()

    def oppdater(self, keyboard):
        # Sjekk for trykk på keyboard
        if keyboard.left:
            self._romskip.beveg_venstre()
        elif keyboard.right:
            self._romskip.beveg_hoyre()

        if keyboard.space and self._oppdatering - self._forrige_skudd > 5:
            self.skyt()
            self._forrige_skudd = self._oppdatering

        self._oppdatering += 1

    def skyt(self):
        # denne metoden gjør ingenting enda
        return


    def tegn(self, skjerm):
        self._romskip.tegn(skjerm)

Oppdater-metoden vår vil bli kalt mange ganger i sekundet. Her sjekker vi om noen av piltastene blir trukket inn. Hvis space-tasten er inne, skal vi skyte og kaller en skyt-metode som vi ikke trenger å implemente helt enda. For å unngå at man får skutt hver eneste gang oppdater kalles, lar vi det alltid være minst 5 oppdateringer mellom hver gang det er mulig å skyte.

Lag også følgende fil hovedprogram.py med prekode for å initialisere Pygame Zero og lage et Spill:

from monster import Monster
import pgzrun
from spill import Spill

# Dette er prekode som gjoer at pygame-zero fungerer. Ikke endre dette:
WIDTH = 900
HEIGHT = 700

spill = Spill()

# draw() er en metode Pygame Zero kaller hver gang den skal tegne noe på skjermen (som den gjør mange ganger i sekundet)
# Her sier vi at hver gang Pygame Zero skal tegne noe, så vil vi at den skal kalle tegn-metoden til sauen vår
def draw():
    # Tegn først et rektangel (bakgrunnen vår)
    screen.fill((0, 0, 0))
    # Tegn deretter sauen
    spill.tegn(screen)

# update() kalles også mange ganger i sekundet. Her vil vi bevege sauen vår
def update():
    spill.oppdater(keyboard)


pgzrun.go()

Fordi vi importerer pgzrun og kaller pgzrun.go nederst kan dette programmet kjøres som et vanlig python-program i terminalen (i obligen har vi brukt pgzrun-kommandoen, men det tilsvarer å kalle pgzrun.go).

Kjør programmet og sjekk at du ser et romskip som du kan styre med piltastene. Sjekk at romskipet ikke går utenfor skjermen.

Steg 3: Monstre

Lag en klasse Monster i en fil monster.py.

Et mulig skjelett til klassen Monster er:


class Monster:
    def __init__(self, bilde, posisjon_venstre, posisjon_topp, antall_liv):
        self._bilde = bilde
        self._posisjon_venstre = posisjon_venstre
        self._posisjon_topp = posisjon_topp
        self._antall_liv = antall_liv
        self._retning = 1
        self._lever = True

    def lever(self):
        return self._lever

    def beveg(self):
        if self._retning == 1:
            self._posisjon_venstre += 4
            if self._posisjon_venstre >= 900 - 64:
                self._posisjon_topp += 64
                self._retning = -1
        else:
            self._posisjon_venstre -= 4
            if self._posisjon_venstre <= 0:
                self._retning = 1
                self._posisjon_topp += 64

    def tegn(self, skjerm):
        skjerm.blit(self._bilde, (self._posisjon_venstre, self._posisjon_topp))

    def hent_posisjon_venstre(self):
        # ..

    def hent_posisjon_topp(self):
       # ..

Utvid oppdater-metoden i Spill-klassen slik at den kaller beveg-metoden til alle monstre i listen self._monstre (men kun hvis monsteret lever). Utvid også tegn-metoden i Spill-klassen slik at den tegner alle levende monstre.

Utvid også oppdater-metoden slik at det av og til lages nye monstre. Husk at oppdater kalles mange ganger i sekundet, så du kan f. eks la det være 5% sannsynlighet for at et monster opprettes hver gang denne metoden kalles.

Steg 5: Skyte monstre

Vi trenger en klasse som representer hver kule. Lag en slik klasse Kule. Kuler må kunne bevege seg og tegnes på skjermen:

Spill bør ha en liste over kuler på samme måte som monstre. Skyt-metoden til spill kan implementeres slik:

Utvid oppdater-metoden og tegn-metoden i Spill-klassen slik at kuler beveger seg og blir tegnet.

Sjekk at det blir skutt synlige kuler som beveger seg oppover når du spiller spillet ditt nå og trykker på space.

Steg 6: Sjekk om kulene treffer monstrene

Lag en metode sjekk_kollisjoner som går gjennom alle levende monstre og sjekker om noen av de har blitt truffet av en kule. Hvis det har skjedd, sett monsteret til død. Det kan også være lurt å implementere en måte slik at kuler som har truffet monstre også settes til å være "døde" slik at de ikke fortsetter å bevege seg og slik at de ikke kan treffe flere monstre.

En mulig implementasjon man kan ta utgangspunkt i er (her er for enkelhets skyld kuler enten levende eller døde):

    def sjekk_kollisjoner(self):
        for monster in self._monstre:
            if not monster.lever():
                continue

            for kule in self._kuler:
                if not kule.lever():
                    continue
                if kule.hent_posisjon_venstre() >= monster.hent_posisjon_venstre() and kule.hent_posisjon_venstre() < monster.hent_posisjon_venstre() + 64 - 24:
                    if kule.hent_posisjon_topp() > monster.hent_posisjon_topp() and kule.hent_posisjon_topp() < monster.hent_posisjon_topp() + 64:
                        print("Monster blir truffet!")
                        monster.blir_truffet_av_kule(kule)
                        self._score += 1

Merk at monstre som blir truffet f. eks kan miste liv eller bare dø direkte. Hvis de mister liv, kan dette implementeres i en metode blir_truffet_av_kule i Monster-klassen. Denne metoden kan ta trekke et liv fra monsteret og sette monsteret til å være død hvis det har 0 liv igjen.

Kall metoden sjekk_kollisjoner fra oppdater-metoden.

Mulige utvidelser