Sprint 7 ยท Methodes & self  ยท  US3: Methodes die het spel besturen

Als gamedeveloper, wil ik dat mijn speler, kogels en vijanden zichzelf besturen via methodes met parameters en returnwaarden, zodat de engine alleen nog de methodes hoeft aan te roepen.

Taak 3: De engine roept jouw methodes aan

Een spelengine schrijf je niet zelf. Hij roept methodes aan die jij levert. In deze taak leer je dat contract kennen en schrijf je de methodes voor je vijand.

Een stuk code dat jouw methodes aanroept

In een programma zit vaak een deel dat je niet zelf schrijft: een engine, een bibliotheek, een raamwerk. Dat deel kent jouw class niet. Het kent alleen de namen van de methodes die het wil aanroepen. Die afspraak heet een contract.

def run(things, ticks):                  # dit stuk schrijft iemand anders
    for tick in range(ticks):
        for thing in things:
            thing.update()               # het contract: elk ding heeft update()


class Plant:
    def __init__(self, height):
        self.height = height

    def update(self):                    # jouw deel van het contract
        self.height += 1


class Candle:
    def __init__(self, length):
        self.length = length

    def update(self):
        self.length -= 1


run([Plant(10), Candle(8)], 3)

run weet niet of een ding een plant of een kaars is. Het roept alleen update() aan. Elke class beslist zelf wat update() doet.

Eรฉn tikfout en het contract breekt. Heet je methode updat() of Update()? Dan krijg je een AttributeError. De foutmelding noemt precies de naam die ontbreekt.

Zie het contract werken

>>> Oefenomgeving: Python REPL Simulator

Laat een functie run jouw objecten aansturen, en kijk wat er misgaat zonder update().

  1. plant = Plant(10)
  2. candle = Candle(8)
  3. things = [plant, candle] โ€” een lijst met beide objecten.
  4. run(things, 3) โ€” de engine draait 3 rondes.
  5. plant.height โ€” wat is er gebeurd?
  6. candle.length โ€” en hier?
  7. rock = Rock() โ€” een ding zonder update().
  8. run([rock], 1) โ€” lees de foutmelding goed.
Python 3.11 REPL Simulator ยท typ een commando en druk op Enter. # run(things, ticks) roept op elk ding update() aan # Plant.update(): height + 1 # Candle.update(): length - 1 # Rock heeft geen update()
>>> 
๐Ÿงฉ

Jouw opdracht: Enemy.update() en Enemy.respawn(width)

Je maakt een vijand die de minimotor hieronder kan aansturen. De motor krijg je kant-en-klaar; die pas je niet aan.

  1. Maak een class Enemy met __init__(self, x, y, speed) en de attributen x, y, speed, width = 30, height = 30 en color = (220, 60, 60).
  2. Schrijf update(self). De vijand valt een stap: self.y wordt groter met self.speed.
  3. Schrijf respawn(self, width). De vijand gaat terug naar boven: self.y = -40, en self.x wordt een willekeurig getal tussen 0 en width - self.width (gebruik random.randint).
  4. Maak een lijst met drie vijanden op y = 600 met snelheden 3, 4 en 5. Start met run_ticks(enemies, 12, 480) en kijk naar de uitvoer.
  5. Schrijf in commentaar op: welke vijand respawnde als eerste en in welke tick? Wat deed de motor, en wat deed jouw class?

Kopieer de motor naar je script, boven je eigen code:

import random

def run_ticks(enemies, ticks, width):
    for tick in range(1, ticks + 1):
        for enemy in enemies:
            enemy.update()
            if enemy.y > 640:            # onder het scherm
                enemy.respawn(width)
        print("tick", tick, [(e.x, e.y) for e in enemies])

Acceptatiecriteria

  • Enemy(100, 0, 3).update() zet y op 3.
  • Na respawn(480) is y < 0 en ligt x tussen 0 en 450.
  • run_ticks draait ongewijzigd met jouw vijanden en print per tick de posities.
  • In de uitvoer zie je dat een vijand die onder 640 komt, weer bovenaan begint.
  • Je commentaar benoemt wie wat doet: de motor roept aan, de class beslist.