6. Score en game over#

We hebben weliswaar obstakels aan het spel toegevoegd, maar die doen nog niets; onze dinosaurus kan er dwars doorheen lopen. In dit deel gaan we ervoor zorgen dat een score wordt bijgehouden en dat het spel afgelopen is zodra de dino tegen een cactus aanloopt.

Score bijhouden#

Om de score bij te houden, moeten we een aantal dingen doen:

  1. Een globale variabele aanmaken die de score bijhoudt.

  2. De score verhogen wanneer de dino een cactus voorbijloopt.

  3. De score weergeven in het venster.

Maak de score variabele net onder het blokje constanten in je code:

endlessrunner.py#
 9# Constanten
10HORIZON = 400
11BASELINE = HORIZON + 45
12GRAVITY = 1
13SPEED = 8
14
15# Variabelen
16score = 0

Nogmaals: constanten zijn variabelen waarvan de waarde niet verandert tijdens de uitvoering van het programma. De waarde van de score variabele zal (hopelijk) wél veranderen tijdens het spel, dus dat is geen constante. We schrijven de naam dan ook met kleine letters.

In het vorige deel hebben we om Memory leaks te voorkomen in de update() functie obstakels uit de lijst van obstakels verwijderd zodra ze links uit het scherm verdwenen. Dat is een goed moment om meteen de score te verhogen:

endlessrunner.py#
69   for obstacle in obstacles.copy():
70      if obstacle.right < 0:
71            obstacles.remove(obstacle)
72            score += 1

Let op: score is een globale variabele. Om de waarde ervan in de update() functie te kunnen veranderen, moeten we dat weer expliciet aangeven met het global keyword:

endlessrunner.py#
52# Functie update()
53def update():
54   global obstacle_timeout, score

Voor het weergeven van de score gebruiken we de functie screen.draw.text(). Aan deze functie kun je een hele reeks argumenten meegeven, waaronder natuurlijk de tekst die je wilt tonen, maar ook de positie, kleur, lettertype, enzovoort. Voor meer informatie en voorbeelden kun je kijken op de website van Pygame Zero. Voeg de volgende regel toe aan de draw() functie:

endlessrunner.py#
45# Functie draw()
46def draw():
47   draw_background()
48   player.draw()
49   for obstacle in obstacles:
50      obstacle.draw()
51   screen.draw.text(f'Score: {score}', (15, 10), color = 'darkorchid4', fontsize = 48)

Run de code en zie hoe de score wordt verhoogd, telkens wanneer links een cactus uit het venster verdwijnt.

../_images/score.png

Game over#

Zodra de dinosaurus een cactus raakt, moet het spel afgelopen zijn. Je weet inmiddels dat we het raken van twee sprites een collision noemen. Omdat we hier met meerdere cactussen in een lijst werken, gaan we de functie collidelist() gebruiken. Deze functie kijkt of de player sprite in aanraking komt met een van de cactussen in de lijst obstacles. Zoals je op de website van pygame kunt lezen, is collidelist() een functie van de Rect class. Alle Actors in Pygame Zero kunnen deze gebruiken. In de beschrijving van collidelist() staat het volgende:

collidelist()

test if one rectangle in a list intersects
collidelist(list) -> index

Test whether the rectangle collides with any in a sequence of rectangles. The index of the first collision found is returned. If no collisions are found an index of -1 is returned.

De functie verwacht dus een lijst van rectangles (in ons geval de cactussen) en geeft de index van de eerste collision terug. Als er geen collision is, geeft de functie -1 terug. Dit gaan we gebruiken om te kijken of de dinosaurus een cactus raakt. Voeg het volgende if statement toe aan de update() functie:

endlessrunner.py#
80   if player.collidelist(obstacles) != -1:
81      print(f'Collision!')

Run het programma en houd de console in de gaten. Zodra de dinosaurus een cactus raakt, verschijnt er Collision in de console.

../_images/collisions.png

Je ziet dat bij het raken van de eerste cactus meteen een aantal keren het woord Collision verschijnt. Dit komt doordat de collidelist() functie 60 keer per seconde wordt uitgevoerd; hij bevindt zich immers in de update() functie van ons spel. Zolang de dinosaurus door de cactus heen loopt, blijft in de console Collision verschijnen.

Laten we een voorlopige versie maken, waarin het spel direct afgelopen is zodra de dinosaurus een cactus raakt. Daarvoor hebben we een globale game_over variabele nodig:

endlessrunner.py#
15# Variabelen
16score = 0
17game_over = False

De waarde van deze variabele is in eerste instantie False. Zodra de dinosaurus een cactus raakt, zetten we de waarde op True:

endlessrunner.py#
81   if player.collidelist(obstacles) != -1:
82      game_over = True

Ook van deze variabele moeten we in de update() functie expliciet aangeven dat hij globaal is:

endlessrunner.py#
54# Functie update()
55def update():
56   global obstacle_timeout, score, game_over

Om aan de speler duidelijk te maken dat het spel is afgelopen, passen we de draw() functie aan. We geven de tekst Game Over weer in het midden van het venster en daaronder de score. Deze tekst moet alleen worden getoond als game_over de waarde True heeft. We gebruiken hiervoor een if statement.

endlessrunner.py#
46# Functie draw()
47def draw():
48   draw_background()
49   if game_over:
50      screen.draw.text('Game Over', midbottom = (WIDTH / 2, HEIGHT / 2 - 10), color = 'white', fontsize = 60)
51      screen.draw.text(f'Score: {score}', midtop = (WIDTH / 2, HEIGHT / 2 + 10), color = 'white', fontsize = 60)
52   else:
53      player.draw()
54      for obstacle in obstacles:
55            obstacle.draw()
56      screen.draw.text(f'Score: {score}', (15, 10), color = 'darkorchid4', fontsize = 48)

Run het programma en kijk of de tekst Game Over verschijnt als de dinosaurus een cactus raakt. Als het goed is, werkt dat nu, maar met de score gebeurt iets vreemds.

../_images/game_over2.png

Wanneer het Game Over is, loopt de scoretelling nog gewoon door!

Vraag

Hoe kan het dat de score nog steeds oploopt, terwijl het spel is afgelopen?

Antwoord

In de draw() functie hebben we er met een if statement voor gezorgd dat de dinosaurus en de cacti niet meer worden getekend als game_over waar is. De update() functie wordt echter nog steeds uitgevoerd. Er bewegen dus nog steeds obstakels van rechts naar links door het venster, we zien ze alleen niet!

Dit probleem is heel eenvoudig op te lossen. Voeg bovenaan de update() functie het volgende if statement toe:

endlessrunner.py#
58# Functie update()
59def update():
60   global obstacle_timeout, score, game_over
61
62   if game_over:
63      return

Het keyword return zorgt ervoor dat de functie wordt verlaten. De rest van de code in de update() functie wordt dan niet meer uitgevoerd. Run het programma opnieuw en kijk of de score nu niet meer oploopt als het spel is afgelopen.

En nu hebben we een speelbaar spel! Hoewel… speelbaar. Je zult merken dat het met een zwaartekracht van 1 en een sprongetje van -15 een hele uitdaging is om de dino überhaupt over de eerste cactus te laten springen. Experimenteer met andere waarden om het spel wat makkelijker te maken. Probeer bijvoorbeeld player.vy = -20 in de on_key_down() event handler.