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
- Sørg for å ha Pygame Zero installert (se uke 8)
- Last ned alle bildene fra denne mappen og legg i en mappe du kaller
images
som skal ligge inne i mappen du tenker å skrive dette programmet.
Hva skal vi lage?
- Et spill der monstre kommer nedover skjermen og et romskip på bunnen skal skyte monstrene. Man taper hvis et monster kommer helt ned
- Vi må kunne representere romskipet, et ukjent antall monstre og kuler som romskipet skyter mot monstrene
- Vi må kunne representere et spillbrett som holder på all denne informasjonen, og vi må blant annet kunne sjekke om kuler treffer monstre.
- Det er mange kule utvidelser man kan gjøre (men vi starter ganske enkelt)
- Man kan ha oppgraderinger til romskipet, slik at det kan bli bedre og få bedre våpen som skader monstrene mer
- Ulike monstre kan tåle ulik mengde skudd
- Man kan implementere ulike levler der ulike typer monstre kommer
- osv osv ...
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:
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:
_posisjon_venstre
: Et sted ca midt på skjermen, f. eks 300_posisjon_topp
: Vi vil ha romskipet nesten på bunnen av skjermen, og skjermen vår er 700 px høy, så 620 bør være en passe verdi_level
: 1 (denne vil vi øke senere)_bilde
: "romskip1" (planen er å endre bildet når romskipet går opp i level)
Romskip trenger også noen metoder:
beveg_hoyre
: Flytter romskipet f. eks 5 px mot høyrebeveg_vensre
: Flytter tilsvarende mot venstre- I tillegg bør det være metoder for å hente ut de to posisjonene
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
.
- Konstruktøren skal ta bilde, antall_liv, posisjon_venstre og posisjon_topp som parametere
- Antall liv er et tall og sier hvor mange liv monsteret har (noen kan overleve flere skudd)
- Monster må ha en beveg-metode (denne blir kalt mange ganger i sekundet). Velg selv hvordan du ønsker at monsteret beveger seg. En mulighet er:
- Beveg mot høyre helt til monsteret har kommet til enden av skjermen, gå deretter et hakk ned og beveg mot venstre tilbake igjen. Gå et hakk ned og gjenta
- En tegn-metode som tar en "skjerm" som parameter. Skjermen er et objekt i Pygame Zero som vi tegner monsteret på.
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:
- En kule beveger seg alltid rett oppover. I beveg-metoden holder det derfor å bare endre posisjonen fra toppen.
Spill bør ha en liste over kuler på samme måte som monstre. Skyt-metoden til spill kan implementeres slik:
- Lag et nytt Kule-objekt som starter med posisjonen til romskipet (gjerne midt på toppen av romskipet).
- Legg kulen til i listen som holder på alle kuler.
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
- La romskipet få 1 i score hver gang den enten treffer eller dreper et monster. La romskipet gå opp i level og få bedre kuler som tar mer skade etter hvert som det får høyere score. Du kan bruke de ulike romskip-bildene og kule-bildene til dette.
- Implementer at man dør og at spillet er over hvis et monster kommer helt ned til bunnen av skjermen.
- Implementer ulike levler der ulike typer monstre kommer i de ulike levlene.
- Implementer lyd (f. eks når man skyter eller treffer monstre). Sjekk gjerne dokumentasjonen til Pygame Zero her.