Een beat-em-up game maken met Godot - bewegende vijand
Tags: beginners, godot, gamedev, tutorial
Published: 2023-05-05 17:43:41



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

Wat hebben we nu al gemaakt? Een wereld met een speler die kan rondlopen, en een vijand, die gewoon wat staat te staan... nog niet bepaald een leuke game... 

Wat willen we: vijanden die net buiten beeld vertrekken en de speler komen aanvallen. We gaan ons werk in 3 delen splitsen:
- vijanden moeten naar de speler toe lopen
- vijanden en de speler moeten kunnen vechten en doodgaan.
- net buiten beeld moeten telkens nieuwe vijanden 'spawnen'

We starten met het lopen van de vijand. Plaats eerst de speler en de vijand op een goede startpositie in de wereld. De speler start links-midden, en de vijand uiterst rechts net buiten beeld:

![Starting positions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubke4h7jppqiyurseeck.png)

Nu gaan we code schrijven om onze vijand te laten bewegen. Open de minotaurus `scene` en het script voor de vijand.

![Open minotaur script](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l59pekokeds6r97d697j.png)

Net als bij de speler gaan we een 'constante' maken voor de snelheid waarmee dit personage beweegt. We zetten hem op 250, langzamer dan de speler, zodat de speler nog kan weglopen. Deze regel komt in het bovenste deel van het script:

```gdscript
const SPEED = 250.0
```

De beweging van onze vijand hangt niet af van het toetsenbord, zoals bij onze speler, maar van de positie van die speler tegenover onze vijand. 

De code om te bewegen komt bij in onze bestaande `_physics_process` functie die we al hadden.

Eerste stap is het opzoeken van de node van onze player. Dit steken we in een nieuwe variabele 'target', het doelwit van onze minotaurus.

```gdscript
func _physics_process(_delta):
	var target = self.get_parent().get_node("Player")

	sprite.play("idle")
```

Hoe werkt dit? We hebben in onze 'game' scene ervoor gezorgd dat zowel de 'Player' als de 'Minotaur' kinderen zijn van de 'Arena' node. 

![Arena parent node](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5pbh3cxqy8hyf8146pe2.png)

In bovenstaande code starten we met `self`, de Minotaurus node (waar het script aanhangt). `get_parent` geeft dan de 'Arena' node erboven. Doen we dan `get_node("Player")` om tussen de child nodes van 'Arena' te zoeken naar een node met de naam 'Player'. Dat is degene die we willen hebben, en die slaan we op in onze nieuwe variabele `var target`. 

Volgende stap is controleren of we wel een tegenstander hebben gevonden. 

```gdscript
func _physics_process(_delta):
	var target = self.get_parent().get_node("Player")
	if target != null:
		# movement code 

	sprite.play("idle")
```

Enkel als de node 'Player' gevonden is (misschien is de speler al dood?) dan gaan we onze minotaurus laten bewegen. `null` is een speciale waarde die 'niets' betekend. Enkel als onze target NIET null is, gaan we verder.


Nu volgt een stukje wiskunde, om de beweging van onze minotaurus te berekenen. 

Denk je ooit tijdens de les wiskunde: waarom moet ik dit allemaal kennen? Wel, computergames gebruiken een hoop wiskunde! En schrijf je uiteindelijk een succesvolle game? Dan heb je ook wiskunde nodig om je hopen geld te kunnen tellen.

De code is:

```gdscript
func _physics_process(_delta):
	var target = self.get_parent().get_node("Player")
	if target != null:
		var delta = (target.position - self.position)
		var direction = delta.normalized()
		velocity = direction * SPEED

	sprite.play("idle")
```

Als je nog nooit van vectoren of coördinaten hebt gehoord, geen probleem, hier in het kort wat er gebeurt:

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

De posities van de 2 personages kan je in getallen uitdrukken.
- De horizontale as (links-rechts) noemen we `x`. De speler staat op `x`=4000. 
- De verticale as (boven-onder) noemen we `y`. De speler staat op `y`=500.
- Samen noemen we dat de coördinaten van de speler (x,y). De speler staat op (4000, 500), de minotaurus staat op (5000, 1000).

We kunnen rekenen met coördinaten, zoals we met eenvoudige getallen doen. 
- Het verschil tussen de positie van de speler (`target`) en de minotaurus (`self`) geeft ons de blauwe pijl op de tekening. 
- In code uitgedrukt: `target.position - self.position`.
- In ons voorbeeld is de uitkomst (4000,500) - (5000, 1000) = (-1000, -500).

We slaan dit op in variabele `delta`. 
`Delta` geeft ons de richting en afstand naar de target. 

We willen enkel de richting gebruiken, en dan met een vaste snelheid een stukje bewegen in die richting. Zonder te veel details te geven, maar `normalized` doet net dat, en geeft als uitkomst de groene pijl. 

De groene pijl is een pijl met lengte 1 met dezelfde richting als de blauwe pijl. Dit slaan we op in `var direction`.

De laatste regel `velocity = direction * SPEED` gebruikt de groene pijl (`direction`) en onze constante snelheid (`SPEED`) om de snelheid van bewegen voor onze minotaurus te bepalen. Dit steken we in `velocity`, een bestaande variabele die bij onze `CharacterBody2D` hoort.

Nu volgt enkel nog het bewegen zelf, met de juiste animatie.

Die code is gelijkaardig aan de code van de speler. Als onze minotaurus snelheid heeft (`velocity` is niet nul) dan spelen we de 'walk' animatie. Anders spelen we 'idle'. De `move_and_slide()` doet het feitelijke bewegen op basis van de `velocity`, rekening houdend met botsingen.

Alles te samen ziet onze code er nu zo uit:
```gdscript
extends CharacterBody2D

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D

const SPEED = 250.0

func _physics_process(_delta):
	var target = self.get_parent().get_node("Player")
	if target != null:
		var delta = (target.position - self.position)
		var direction = delta.normalized()
		velocity = direction * SPEED

	if velocity:
		sprite.play("walk")
	else:
		sprite.play("idle")

	move_and_slide()
```

Dit kunnen we testen met `Run Project` of `F5`. Daar komt ie!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dxf6u34jjsff28tj91xu.gif)

Merk op dat onze vijand nogal 'plakkerig' is... hij stopt nooit en loopt tot tegen de speler, en blijft maar lopen.

Hij loopt ook achteruit, maar dat kennen we al en kunnen we oplossen zoals bij de speler. Er is een klein verschil, we hebben de `scale` van onze minotaurus op 1.5 gezet omdat hij wat klein was.

Als we dezelfde code gebruiken als bij de speler:

```gdscript
	if velocity.x < 0:
		sprite.scale.x = -1
	elif velocity.x > 0:
		sprite.scale.x = 1
```

... dan gaat onze `scale` terug verspringen naar 1 of -1, en is onze minotaurus terug klein.

Met de wiskundige functie `abs` kunnen we dit oplossen. `abs` geeft de absolute waarde van een getal, wat wil zeggen dat een min-teken wordt weggelaten als dit er is, elk getal wordt positief gemaakt.

In de volgende code behouden we het getal dat in `sprite.scale.x` zit, en veranderen we enkel het teken van plus naar min of omgekeerd, zodat de scale van de minotaurus van 1.5 behouden blijft.

```gdscript
	if velocity.x < 0:
		sprite.scale.x = -abs(sprite.scale.x)
	elif velocity.x > 0:
		sprite.scale.x = abs(sprite.scale.x)
```

Het andere probleem, dat onze minotaurus eeuwig blijft lopen, kunnen we oplossen met een paar extra `if`s.

Als de minotaurus ver weg is, willen we dat hij naar de speler toeloopt. Is hij te dichtbij, dan moet hij naar achteren. Zit hij op de ideale afstand, dan mag hij blijven staan, dan gaat hij aanvallen. De afstanden steken we in een paar constanten:

```gdscript
const ATTACK_DISTANCE_MAX = 520.0
const ATTACK_DISTANCE_MIN = 480.0
```

De logica zelf komt bij in onze `_physics_process`. We berekenen de afstand tussen onze minotaurus en de speler uit de variabele delta die we al hadden. Met die afstand kunnen we `if`s schrijven.
```gdscript
		var distance = delta.length()
		if distance > ATTACK_DISTANCE_MAX:
			velocity = direction * SPEED
		elif distance < ATTACK_DISTANCE_MIN:
			velocity = direction * -SPEED
		else:
			velocity = Vector2()
```

Het uiteindelijke script:

```gdscript
extends CharacterBody2D

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D

const SPEED = 250.0
const ATTACK_DISTANCE_MAX = 520.0
const ATTACK_DISTANCE_MIN = 480.0

var target: Node2D

func _ready():
	target = self.get_parent().get_node("Player")

func _physics_process(_delta):
	if target != null:
		var delta: Vector2 = (target.position - self.position)
		var direction = delta.normalized()
		var distance = delta.length()
		if distance > ATTACK_DISTANCE_MAX:
			velocity = direction * SPEED
		elif distance < ATTACK_DISTANCE_MIN:
			velocity = direction * -SPEED
		else:
			velocity = Vector2()
		
		if delta.x < 0:
			sprite.scale.x = -abs(sprite.scale.x)
		elif delta.x > 0:
			sprite.scale.x = abs(sprite.scale.x)

	if velocity:
		sprite.play("walk")
	else:
		sprite.play("idle")

	move_and_slide()
```

In het [volgende deel](https://dev.to/bgie/een-beat-em-up-game-maken-met-godot-4-deel-6-19ci) starten we met het gevecht systeem.