11. Car Pathfinding & Spawning
Implementeer car pathfinding met NavigationAgent3D en auto-spawning systeem
Overzicht
In dit hoofdstuk leer je:
Car.cs (CharacterBody3D) met NavigationAgent3D
Pathfinding met GetNextPathPosition()
Smooth rotation met Basis.Slerp()
Path length calculation via PathChanged signal
Money rewards bij bestemming
CarSpawner.cs timer-based spawning
Random building selection voor routes
Spawn position calculation (adjacent roads)
Wat is een NavigationAgent?
Een NavigationAgent3D is een node die pathfinding automatiseert. Je geeft een target positie en de agent:
Berekent het kortste pad via de NavigationMesh
Geeft waypoints terug langs het pad
Detecteert wanneer de destination is bereikt
In dit hoofdstuk:
Maak een Car scene met NavigationAgent3D
Implementeer movement door waypoints te volgen
Roteer smooth naar de bewegingsrichting
Bereken path length voor revenue
Maak een CarSpawner die automatisch cars spawnt tussen random buildings
Verdien geld wanneer cars hun bestemming bereiken
Complete gameplay loop: Player places buildings/roads → CarSpawner spawns cars → Cars navigate and earn money → Player builds more infrastructure
Car script aanmaken
Maak een nieuwe script voor het voertuig.
Nieuwe Godot-specifieke elementen:
NavigationAgent3D- Agent voor 3D pathfindingCharacterBody3D- Physics body voor characters/vehiclesBasis- 3D rotation matrixSlerp()- Spherical linear interpolation voor smooth rotatie
Maak een nieuw script
scripts/Car.csMaak de class aan die inherit van CharacterBody3D:
Voeg exports toe voor configuratie:
Exports uitgelegd:
Speed- Hoe snel de car beweegt (units per seconde)RotationSpeed- Hoe snel de car draait (multiplier voor slerp)MoneyPerUnit- Hoeveel geld per tile traveled (economy)_navigationAgent- Reference naar de NavigationAgent3D child node
Voeg een field toe voor path length tracking:
Sla het script op
SetTargetPosition method
Maak een method om de destination in te stellen.
Nieuwe Godot-specifieke elementen:
TargetPosition- De destination voor pathfinding
Voeg een lege
SetTargetPosition()method toe:
Zet de target positie. De NavigationAgent berekent automatisch het pad:
Wat gebeurt er intern?
NavigationAgent zoekt naar de dichtstbijzijnde NavigationMesh polygon
A* pathfinding algoritme berekent kortste route
PathChangedsignal wordt gefired wanneer het pad klaar is
Sla het script op
CalculatePathLength implementeren
Maak een method om de afgelegde afstand te berekenen (voor revenue).
Nieuwe Godot-specifieke elementen:
GetCurrentNavigationPath()- Array van waypoints langs het padPathChanged- Signal dat fired wanneer een nieuw pad is berekend
Voeg een lege
CalculatePathLength()method toe:
Haal het pad op:
Check of het pad geldig is. Een pad met minder dan 2 punten is onbruikbaar:
Waarom < 2?
0 waypoints = geen pad gevonden
1 waypoint = alleen start positie, geen movement
2+ waypoints = geldig pad (start → destination)
Bereken path length. Elk waypoint is ongeveer 1 grid tile:
Waarom -1?
path.Lengthtelt ook de start positieWe willen alleen traveled tiles tellen
Start → waypoint 1 → waypoint 2 = 2 tiles traveled, maar 3 waypoints
Debug print (tijdelijk, om te testen):
Disconnect de signal. We hoeven path length maar 1x te berekenen:
Waarom disconnecten?
PathChanged kan meerdere keren firen (rerouting)
We willen alleen het originele pad tellen
Voorkomt memory leaks (event handlers blijven anders actief)
Sla het script op
_Ready implementeren
Koppel de PathChanged signal aan CalculatePathLength.
Voeg een lege
_Ready()method toe:
Connect de PathChanged signal:
Signal flow:
SetTargetPosition()wordt aangeroepenNavigationAgent berekent pad (asynchroon)
PathChangedsignal firedCalculatePathLength()wordt aangeroepen_pathLengthwordt ingesteldSla het script op
_PhysicsProcess implementeren - Destination check
Implementeer de movement loop. Start met destination checking.
Voeg een lege
_PhysicsProcess()method toe:
Check of we de destination hebben bereikt:
IsNavigationFinished:
truewanneer de car binnen de target radius isDefault radius = 0.5 units
Voorkomt "orbiting" rondom het target
Bereken money earned:
Voeg het geld toe aan de MoneyManager:
Singleton access:
/root/MoneyManager= AutoLoad node (hoofdstuk 9)GetNode<MoneyManager>()= type-safe node lookupAddMoney()= method uit hoofdstuk 9
Despawn de car:
QueueFree:
Verwijdert de node aan het einde van het frame
Veiliger dan
Free()(voorkomt crashes tijdens callbacks)
Sla het script op
_PhysicsProcess implementeren - Movement
Voeg movement toe door waypoints te volgen.
Nieuwe Godot-specifieke elementen:
GetNextPathPosition()- Volgende waypoint in het padMoveAndSlide()- Beweeg met collision detection
Haal de volgende waypoint op:
Bereken de richting naar de waypoint:
Direction berekening:
nextPosition - GlobalPosition= offset vector.Normalized()= lengte 1 behouden, alleen richtingBijvoorbeeld: (3, 0, 4) → (0.6, 0, 0.8)
Zet de velocity:
Velocity:
CharacterBody3D.Velocitybepaalt bewegingsrichtingMoveAndSlide()gebruikt deze propertyDirection * Speed = snelheid in die richting
Roep MoveAndSlide aan:
Sla het script op
_PhysicsProcess implementeren - Rotation
Voeg smooth rotatie toe zodat de car naar de bewegingsrichting kijkt.
Nieuwe C#-specifieke elementen:
Basis.LookingAt()- Creëert rotatie matrix die naar een richting kijktBasis.Slerp()- Smooth interpolatie tussen twee rotaties
Maak een look direction. Negeer de Y component om alleen horizontale rotatie te hebben:
Waarom Y = 0?
Cars moeten horizontaal draaien, niet kantelen
directionkan een Y component hebben (heuvel op/af)lookDirectionheeft alleen X en Z voor yaw rotatie
Bereken de target rotation:
Basis.LookingAt:
Eerste parameter = forward direction (-Z richting in Godot)
Tweede parameter = up vector (meestal Vector3.Up)
Return value = rotation matrix
Interpoleer smooth naar de target rotation:
Slerp parameters:
Basis= huidige rotatietargetBasis= gewenste rotatieRotationSpeed * delta= interpolation factor (0-1)Hogere RotationSpeed = snellere draai
Waarom Slerp?
Lineaire interpolatie (Lerp) werkt niet goed voor rotaties
Slerp behoudt "spherical" motion (constante hoeksnelheid)
Voorkomt gimbal lock
Sla het script op
Car scene opzetten

Bouw de Car scene met mesh, collision, en navigation.
Maak een nieuwe scene: Scene → New Scene
Gebruik een CharacterBody3D als root node
Hernoem naar
CarSelecteer Car in de Scene Tree
Klik en sleep één van de auto's vanuit het File venster naar je scene
Verklein de auto tot een schaal van 0.2
Voeg een CollisionShape3D toe als child van Car
Selecteer CollisionShape3D
In de Inspector → Shape: kies New BoxShape3D
Klik op de BoxShape3D resource
Pas de size aan zodat die ongeveer overeenkomt met de auto
Plaats nu je auto wat meer naar rechts! Zo zal de auto aan de rechterkant van de baan rijden. Vergeet niet om je Collision ook te verplaatsen.
Voeg een NavigationAgent3D toe als child van Car
Hernoem naar
NavigationAgentSelecteer NavigationAgent
In de Inspector → NavigationAgent3D:
Target Desired Distance: 0.5 (stop 0.5 units voor target)
Path Desired Distance: 0.1 (waypoint bereikt bij 0.1 units)
Selecteer Car (root node)
In de Inspector → Car script sectie:
Sleep NavigationAgent naar de Navigation Agent export
Sla de scene op als
scenes/Car.tscn(Ctrl+S)
CarSpawner script aanmaken
Maak een script die automatisch cars spawnt.
Nieuwe Godot-specifieke elementen:
PackedScene- Scene resource die geïnstantieerd kan wordenInstantiate<T>()- Creëert een instance van de sceneTimer- Node voor interval-based events
Maak een nieuw script
scripts/CarSpawner.csMaak de class aan die inherit van Node:
Voeg exports toe:
Exports:
CarScene- Reference naar Car.tscn (drag & drop in Inspector)GridMap- Reference om buildings/roads te vindenSpawnInterval- Seconden tussen spawns (5 = elke 5 seconden)
Voeg een field toe voor de timer:
Sla het script op
_Ready implementeren (CarSpawner)
Maak een timer die periodiek cars spawnt.
Voeg een lege
_Ready()method toe:
Creëer een Timer instance:
Configureer de timer:
Connect de Timeout signal:
Voeg de timer toe als child (moet in scene tree zitten om te werken):
Start de timer:
Timer lifecycle:
new Timer()- creëer timer objectWaitTime- stel interval inTimeout += callback- subscribe naar timeout eventAddChild()- voeg toe aan scene (MOET, anders werkt timer niet!)Start()- begin countdownSla het script op
OnSpawnTimerTimeout implementeren
Maak een callback die fired elke keer dat de timer timeout.
Nieuwe C#-specifieke elementen:
RandomNumberGenerator- Godot's random number generatorRandfRange()- Random float tussen min en max
Voeg een lege
OnSpawnTimerTimeout()method toe:
Creëer een random generator:
Randomize het volgende spawn interval. Dit voorkomt voorspelbare spawns:
Randomization:
Base interval = 5 seconden
Range = 3-7 seconden (5 ± 2)
Geeft natuurlijker spawn pattern
Probeer een car te spawnen:
Sla het script op
TrySpawnCar implementeren
Maak een method die een car spawnt tussen twee random buildings.
Voeg een lege
TrySpawnCar()method toe:
Haal alle building positions op:
Check of er minstens 2 buildings zijn (anders geen route mogelijk):
Kies een random start building:
Vind een road naast de start building:
Kies een random target building (moet verschillend zijn van start):
GetRandomIndex met exclude:
Eerste parameter = aantal buildings
Tweede parameter = index om te skippen (start building)
Garandeert dat start ≠ target
Vind een road naast de target building:
Spawn de car:
Sla het script op
GetBuildingPositions implementeren
Maak een method die alle building cells vindt.
Voeg een lege
GetBuildingPositions()method toe:
Maak een lege lijst:
Haal alle used cells op:
Loop door alle cells en filter buildings:
Return de lijst:
Sla het script op
FindRoadNearBuilding implementeren
Maak een method die een adjacent road vindt naast een building.
Voeg een lege
FindRoadNearBuilding()method toe:
Definieer de vier richtingen:
Loop door alle richtingen:
Check de adjacent cell:
Return de positie als het een road is:
MapToLocal:
Converteer GridMap cell → wereld positie
Car spawnt op deze positie
Return Vector3.Zero als geen road gevonden (fallback):
Sla het script op
SpawnCar implementeren
Maak een method die de car instantiates en configureert.
Voeg een lege
SpawnCar()method toe:
Instantiate de car scene:
Instantiate():
Template syntax voor type-safe instantiation
Return type = Car (niet gewoon Node)
Toegang tot Car-specific methods (SetTargetPosition)
Voeg de car toe aan de scene tree:
Waarom GetParent()?
CarSpawner is child van Main
We willen car als child van Main (niet van CarSpawner)
GetParent()= Main node
Zet de spawn positie:
Zet de target:
Sla het script op
Helper methods toevoegen
Voeg utility methods toe voor random selection en tile checking.
Voeg de
GetRandomIndex()method toe. Deze heeft twee overloads - met en zonder exclude:
Exclude logic:
Als
excludedIndex= -1 → gewoon random tussen 0 en count-1Anders: random tussen 0 en count-2, dan shift naar rechts als >= excluded
Bijvoorbeeld: 5 buildings, exclude index 2 → kies uit [0,1,3,4]
Voeg de tile type checkers toe onderaan:
Tile ID ranges:
0-4 = buildings (5 types)
10-14 = roads (straight, corner, T, intersection, etc.)
7 = grass (niet checked hier)
Sla het script op
CarSpawner node toevoegen
Voeg de CarSpawner toe aan de Main scene.
Open
scenes/Main.tscn(als deze nog niet open is)Voeg een Node toe als child van Main
Hernoem naar
CarSpawnerSelecteer CarSpawner
In de Inspector → Script: attach
scripts/CarSpawner.csIn de Inspector → CarSpawner script sectie:
Car Scene: klik het folder icoontje → selecteer
scenes/Car.tscnGrid Map: sleep GridMap node naar deze property
Spawn Interval: 5.0 (default is OK)
Sla de scene op (Ctrl+S)
Testen
Test het complete systeem.
Start het spel (F5)
Check of de MoneyDisplay "1000$" toont (starting money)
Plaats minstens 2 buildings
Plaats roads tussen de buildings (adjacent aan buildings)
Wacht 5-7 seconden (spawn interval):
Een car moet spawnen op een road naast een building
Car rijdt naar destination
Bij aankomst: money increase
Console: "Earned X. Total: Y" (X = path length × 10)
Observeer meerdere spawns:
Elke 3-7 seconden spawnt een nieuwe car
Interval is gerandomized
Test edge cases:
Plaats alleen 1 building → geen cars spawnen (need 2+ buildings)
Verwijder roads tussen buildings → cars kunnen niet spawnen (geen adjacent roads)
Bouw een complex wegnetwerk → cars nemen verschillende routes
Check money system:
Langere routes geven meer geld
Console prints path length bij elke spawn
Werkt alles? Sluit het spel (F8)
Veelgemaakte fouten
Car beweegt niet: Controleer of NavigationAgent export is ingesteld. Check ook of er een geldig NavigationMesh is (hoofdstuk 10).
Car rijdt niet over roads: NavigationMesh is leeg of niet gekoppeld. Controleer RoadNavigation setup in hoofdstuk 10.
Car kantelt/valt om: Vergeet niet
lookDirection.Y = 0in te stellen. Rotatie mag alleen yaw hebben, geen pitch/roll.Cars spawnen niet: Check of CarScene export is ingesteld. Check console voor errors. Verify dat er 2+ buildings EN adjacent roads zijn.
Cars spawnen in de lucht: FindRoadNearBuilding returnt Vector3.Zero (geen road gevonden). Plaats roads direct naast buildings.
Timer werkt niet:
AddChild(_spawnTimer)vergeten. Timers moeten in de scene tree zitten om te werken!GetRandomIndex crash: Buildings.Count < 2 check faalt. Verify de early return in TrySpawnCar.
Path length is altijd 0:
CalculatePathLength()wordt niet aangeroepen. Check of PathChanged signal correct is verbonden in_Ready().Slerp rotatie is te snel/langzaam: Pas
RotationSpeedexport aan (10.0 is standaard, hogere waarden = snellere draai).
Volledige scripts
Laatst bijgewerkt