Een beat-em-up game maken met Godot - health
Tags: beginners, tutorial, gamedev, godot
Published: 2023-06-16 19:03:46

Terug naar [deel 7](https://dev.to/bgie/een-beat-em-up-game-maken-met-godot-4-deel-7-1h1l)

Onze speler is nog onsterfelijk, maar dat willen we niet. We gaan hem een beperkt aantal levens geven. 

In het player.gd script voegen we helemaal vanboven (na de `extends CharacterBody2D`) toe:
```gdscript
var health_points: int = 3
```

Wat wil dit zeggen? 
- `var` hebben we al gebruikt en betekend: maak een variabele.
- `health_points` is de naam de we hebben gekozen voor de variabele.
- `: int` zegt dat onze variabele altijd een integer is (geheel getal zonder komma). Als we ons vergissen in de code, gaat Godot ons waarschuwen. Dit is een hulpmiddel.
- `= 3` is de start waarde, onze speler begint met 3 levens.

We gaan deze variabele nu gebruiken in onze `take_damage()` functie. Die zou er nog zo uit moeten zien:
```gdscript
func take_damage():
	if state == State.READY:
		state = State.HURT
		sprite.play("hurt")
```

Die code zorgt enkel voor de juiste animatie als de speler geraakt wordt. We willen nu dat een aanval een leven kost.

```gdscript
func take_damage():
	health_points = health_points - 1
	if state == State.READY:
		state = State.HURT
		sprite.play("hurt")

```

De nieuwe code doet een eenvoudige berekening. De nieuwe `health_points` (links) wordt gelijk aan de oude `health_points` min 1. 
Dus iedere keer als we in `take_damage()` komen gaat de speler 1 leven minder krijgen.

We voegen nog een stukje voorlopige code toe, om nu snel te kunnen testen:
```gdscript
func take_damage():
	health_points = health_points - 1
	if health_points == 0:
		self.queue_free()
	if state == State.READY:
		state = State.HURT
		sprite.play("hurt")
```

Als de `health_points` zijn verminderd tot nul, laten we de speler verdwijnen met `self.queue_free()`, zoals bij de vijand. We gaan dadelijk een beter einde maken, maar deze code kunnen we nu al testen.

Het is vaak een goed idee je code in kleine stapjes aan te passen, en telkens te testen of het werkt. Schrijf je honderden regels ineens, zonder testen, dan kom je voor verrassingen te staan wanneer je het uiteindelijk wil testen. Dan is het moeilijker om de fout te vinden, tussen die honderden regels.

Dit is ons resultaat:

![Player death gone animation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9qzv6oyeay4pq0s12g93.gif)

Wat we eigenlijk willen, is een death animation zoals bij de vijand. Daarvoor voegen we nog een state toe: `DYING`.

```gdscript
enum State {READY, ATTACK, HURT, DYING}
```

Onze `take_damage()` wordt ook uitgebreid:

```gdscript
	if state == State.DYING:
		return
	health_points = health_points - 1
	if health_points == 0:
		state = State.DYING
		sprite.play("die")
	elif state == State.READY:
		state = State.HURT
		sprite.play("hurt")
```

Wat veranderd er?
- we starten met een `if` en een `return`, want als de speler al dood gaat, is een nieuwe `take_damage` niet meer nodig. `return` zorgt dat we onmiddellijk uit de functie gaan, de rest van de code wordt overgeslagen.
- de tijdelijke `self.queue_free()` vervangen we. We zetten onze state op `DYING` en starten de juiste animatie `"die"`
- de laatste `if` wordt een `elif`. Enkel als de eerste `if` niet waar is, gaan we deze tweede `elif` doen. `DYING` heeft voorrang op `HURT` (staat eerst), en als de speler dood gaat doen we nooit een `HURT`.

Er komt nog 1 regel bij in `_on_animated_sprite_2d_animation_finished`. 
De huidige code dient om na een `ATTACK` of een `HURT` terug naar de gewone `READY` te gaan, zodat we niet eeuwig blijven aanvallen of hurten.

Maar dood gaan is wel redelijk definitief... De nieuwe code:

```gdscript
func _on_animated_sprite_2d_animation_finished():
	if state != State.DYING:
		state = State.READY
```
Enkel voor states verschillend van `DYING` zetten we de state terug op `READY`.

Het resultaat:

![Player death animated](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j00wtotf6zvza0fyn75p.gif)

... en onze zeer enthousiaste vijand blijft lekker doormeppen!

Wat er nu nog ontbreekt aan ons player health systeem, is een manier om te tonen hoeveel levens de speler nog heeft.

Daarvoor maken we een HUD (heads-up-display). Dit is een vaste laag bovenop alle graphics van de game wereld, die nooit met de camera mee beweegt. We willen linksboven in het scherm met een reeks hartjes tonen hoeveel levens de speler nog heeft.

We maken een nieuwe scene voor onze HUD. Klik met de rechtermuisknop op `res://` in het `FileSystem` paneel en kies `New` en `Scene`.

![Add new scene](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bh8d3oij9o0s2pgmz1rg.png)

Vervolgens kiezen we als node type `CanvasLayer` en we geven ook de naam 'HUD' aan onze scene.

![Create new CanvasLayer scene](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ndf35uiqjcnvqydr5fln.png)

We krijgen nu een nieuwe scene, gelijkaardig aan onze andere scenes. Het verschil tussen een `CanvasLayer` en onze andere scenes, is dat de `CanvasLayer` NIET beïnvloed wordt door de camera. Dit is nu van geen belang, omdat we de camera (nog) niet verschuiven in onze game. Maar doen we dit later wel, dan willen we dat onze HUD op een vaste plaats in het scherm blijft staan.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/84jojb0865ro5pg4ovt5.png)

Bij onze graphics zitten nog geen hartjes. Maar je kan gemakkelijk `png` afbeeldingen van het web gebruiken in je game. 

Maak eerst een aparte map bij je assets. Je kan met rechtermuisknop klikken op de 'assets' map en via `New` en `Folder` een nieuwe map maken die we 'HUD' gaan noemen.

![Add new folder to assets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aeff6ma171jlu2ssie5n.png)

De nieuwe map komt onder je assets te staan:

![new hud folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3b7ueodtc8xo72m0rzdy.png)

Dit is het hartje dat we gaan gebruiken:

![Heart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbqq67adzwivl897t6h3.png)

Afhankelijk van je web browser is dit iets verschillend, maar meestal kan je rechts klikken en is er een `save image as` optie. Verander de naam naar iets duidelijker zoals 'heart.png'. Je kan de naam ook veranderen vanuit Godot.

![Heart.png asset](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gg9afx69xkoweisn6c68.png)

Nu gaan we de hartjes toevoegen aan onze scene.
Zorg dat de 'HUD' `node` is geselecteerd in je scene en klik de grote plus `+` om een node toe te voegen. We kiezen voor een `TextureRect`.

![Add texture rect node](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxslrqq6wbcfa0pwlt72.png)

We veranderen de naam van onze `TextureRect` naar 'Hearts', en voegen een `Texture` toe. We kiezen `load` en zoeken onze 'hearts.png' in de 'assets/HUD' map.

![Rename texture rect etc](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0uq6yzo0yylg236d0v3.png)

Dit levert ons 1 hartje op:

![Single heart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xbqlem8zwzlms7ad09bc.png)

Er zijn meerdere manieren om nu meerdere hartjes te krijgen. Het ligt voor de hand om simpelweg meerdere `TextureRect` nodes te maken. Maar we gaan een truc gebruiken, waarbij we de grafische kaart al het werk laten doen. De grafische kaart kan zonder veel moeite een texture herhalen, dat is iets wat bijvoorbeeld in 3D games voortdurend gebruikt wordt.

![Tile heart texture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uml319sjfroh90ji1qon.png)

We veranderen:
- `Expand Mode` naar `Ignore Size`. De grafische kaart negeert de vaste afmetingen van het hartje .
- `Stretch Mode` zetten we op `Tile`. De grafische kaart gaat de afbeelding 'tegelen' (=herhalen).
- `Custom Minimum Size` wordt `x=360` en `y=90`.

Het hartje zelf is 120x90 pixels groot. Omdat we tegelen  opzetten en vragen om een gebied van `360x90` op te vullen, gaat de grafische kaart het hartje meermaals tekenen om het gevraagde gebied op te vullen. 360 is 3 x 120, we krijgen 3 hartjes.

Wat we nu nog gaan doen, is de hartjes in de linkerbovenhoek plaatsen, met een kleine marge zodat ze niet helemaal tegen de rand van het scherm plakken.

Verander de `Anchor Preset` naar `Custom`. Dan krijg je extra parameters, onder `Anchor Points`, `Anchor Offsets` en `Grow Direction`.

![Anchor topleft with margins](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cgy7zmgdpmfurci6d29d.png)

Het systeem van anchors (anker-punten) leggen we later beter uit. Zorg nu gewoon dat de `Anchor Offsets` voor `Left` en `Top` op `25 px` staan. Daarmee zorgen we voor een afstand van 25 pixels tussen de hartjes en de linker- en bovenrand van het scherm. Kijk even na of de andere waarden (aangeduid met groen) hetzelfde zijn.

De hartjes gaan lichtjes verspringen wanneer je de offsets intypt, maar verder zien we niet veel. We moeten onze HUD nog toevoegen aan de game scene. 

Ga naar de 'game' scene en sleep 'HUD.tscn' beneden naar de root node 'Game'. 

![drag hud onto game scene root node](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owrmoy560zmgkl4ltj54.png)

Onze hartjes verschijnen, maar staan niet linksboven! 

![Hearts on scene](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jks2qd7xi63ka8zlizas.png)

Dit is verwarrend, maar toch komen de hartjes juist als we het spel starten.
Het beeld in de editor toont niet wat de camera ziet, maar wat er op de scene staat. Starten we de game, dan kijken we _wel_ door de camera.

![Run game with HUD](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35brjgj4wf9rb5i6antm.png)

Nu rest er enkel nog wat code, om ook werkelijk het aantal levens van de speler te tonen in de HUD.

In de 'HUD' scene selecteren we de 'HUD' root node en voegen een nieuw script toe. Dit krijgt automatisch de naam 'HUD.gd', wat prima is.

![Create hud script](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atcve3lo6tu1mozl0w4f.png)

Het script ziet er zo uit:
```gdscript
extends CanvasLayer

@onready var hearts: TextureRect = $Hearts

func set_health(health: int):
	hearts.custom_minimum_size.x = health * 120
```

Wat doen we hier? De variabele 'hearts' verwijst naar de TextureRect die onze hartjes toont. De functie `set_health` krijgt in de parameter 'health' mee hoeveel hartjes er getoond moeten worden. We weten dat een hartje 120 pixels breed is, en als we 120 maal 'health' doen, hebben we de breedte voor onze `TextureRect` om het juiste aantal hartjes te tonen.

Dit moeten we nog verbinden met de player. 
Omdat onze player en onze HUD twee aparte scenes zijn, die niets van mekaar afweten, gaan we de verbinding leggen in onze game scene.

We voegen ook een script toe aan de root node 'Game' van onze game scene.

![Add script to game scene root node](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gufry59wwwj8hvz7qss.png)

De eenvoudigste code is als volgt:
```gdscript
extends Node2D

@onready var player: CharacterBody2D = $Arena/Player
@onready var hud: CanvasLayer = $HUD

func _process(_delta):
	hud.set_health(player.health_points)
```

We maken de variabelen 'player' en 'hud' die de juiste node opzoeken en opslaan (via het $ symbool). De functie `_process(_delta)` wordt bij elke frame opgeroepen. Hier vragen we `player.health_points` op en geven dit door aan onze `set_health` functie van de HUD.

Dit kan efficiënter als we player een signaal laten uitsturen enkel wanneer health_points wijzigt, maar voorlopig is dit goed genoeg.

![Hud hearts animation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xfflryvps0h1j8r6602m.gif)

Met onze HUD kunnen we nog meer doen. In het volgende deel maken we een "Game Over" scherm en de mogelijkheid om een nieuw spel te starten.