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