Monstren kommer nu från säkra punkter nära banans kanter. Power-ups fylls också på, men båda systemen har tydliga maxgränser.
Vad vi bygger
De enskilda monster- och power-up-instanserna tas bort ur Main.tscn och ersätts av två fristående spawners:
| Spawner | Start | Intervall | Max samtidigt |
|---|---|---|---|
| Monster | 2 | 6 sekunder | 4 levande |
| Hälsopower-up | 2 | 10 sekunder | 3 tillgängliga |
Monsterspawnern kräver dessutom minst 3.5 meters avstånd mellan spelarens aktuella position och den valda spawnpunkten.
Varför använda fasta spawnpunkter?
Slumpmässiga koordinater kan hamna i ett hinder, utanför golvet eller precis bakom spelaren. I den här första versionen använder vi därför Marker3D-noder på kontrollerade platser. Spawnern väljer slumpmässigt bland de punkter som är giltiga just nu.
Det är enklare än navigation och ger ändå variation. Senare kan samma system byta markörer när modulära rum införs.
Skapa monsterspawnern
Skapa Blueprints/monster_spawner.tscn:
MonsterSpawner (Node3D)
├── SpawnPoints (Node3D)
│ ├── NorthWest
│ ├── NorthEast
│ ├── SouthWest
│ ├── SouthEast
│ └── South
└── SpawnTimer
Placera markörerna nära golvets kanter:
NorthWest: (-4, 0.05, -4)
NorthEast: ( 4, 0.05, -4)
SouthWest: (-4, 0.05, 4)
SouthEast: ( 4, 0.05, 4)
South: ( 0, 0.05, 4.3)
Lägg spawnerns rotnod i gruppen monster_spawners och fäst monster_spawner.gd.
Filtrera bort farliga punkter
Innan ett monster skapas hämtar spawnern spelarens fysikkropp från gruppen player_body. Varje markör kontrolleras på markplanet:
if _flat_distance(marker.global_position, player.global_position) < minimum_player_distance:
continue
if _is_point_occupied(marker.global_position):
continue
valid_points.append(marker)
_flat_distance() använder X och Z men ignorerar höjden. Det är den horisontella spelradien som spelar roll. Standardvärdet 3.5 meter gör att inget startmonster dyker upp direkt intill spelaren.
Punkter närmare än en meter från ett redan levande monster väljs också bort. Om ingen punkt är giltig hoppar spawnern över försöket i stället för att skapa ett orättvist monster.
Begränsa antalet monster
Spawnern håller referenser till skapade monster och tar bort ogiltiga referenser innan varje försök:
func spawn_monster() -> SimpleMonster:
_remove_invalid_monsters()
if _spawned_monsters.size() >= max_alive_monsters:
return null
När ett monster har förstörts kan dess gamla referens fortfarande ligga kvar i listan. Rensa den bakifrån, så att borttagna poster inte ändrar index för de poster som återstår:
func _remove_invalid_monsters() -> void:
for index in range(_spawned_monsters.size() - 1, -1, -1):
var monster := _spawned_monsters[index]
if not is_instance_valid(monster) or monster.is_queued_for_deletion():
_spawned_monsters.remove_at(index)
Använd inte Array.filter() med en typad lambda för just den här listan: Godot kan försöka omvandla en redan frigjord nod till SimpleMonster innan lambdan får kontrollera den. Det gav felet Cannot convert argument 1 from Object to Object när timern skulle skapa nästa monster.
Två monster skapas med ett uppskjutet anrop efter scenstarten. Det ger main.gd tid att skapa rätt spelarkropp innan säkerhetsavståndet räknas ut. Därefter försöker timern skapa ett monster var sjätte sekund tills fyra lever samtidigt.
Koppla nya monster till räknaren
Varje lyckad spawn skickar signalen monster_spawned. main.gd lyssnar på alla noder i gruppen monster_spawners och registrerar varje nytt monster i GameState:
func _on_monster_spawned(monster: SimpleMonster) -> void:
game_state.track_monster(monster)
Den befintliga texten MONSTERS KILLED räknar därför även dynamiskt skapade monster.
Skapa power-up-spawnern
HealthPowerUpSpawner använder samma grundidé men behöver inget säkerhetsavstånd till spelaren. Den har fem egna markörer spridda över banan:
FrontLeft: (-2, 0.55, -2.5)
FrontRight: ( 2, 0.55, -2.5)
BackLeft: (-3.5, 0.55, 2)
BackRight: ( 3.5, 0.55, 2)
BackCenter: ( 0, 0.55, 4)
Två power-ups skapas vid start. Var tionde sekund görs ett nytt försök tills tre finns tillgängliga. En redan upptagen markör filtreras bort, så kristallerna hamnar inte ovanpå varandra.
Power-up-spawnern behöver samma säkra rensning efter att spelaren plockat upp en kristall:
func _remove_invalid_power_ups() -> void:
for index in range(_spawned_power_ups.size() - 1, -1, -1):
var power_up := _spawned_power_ups[index]
if not is_instance_valid(power_up) or power_up.is_queued_for_deletion():
_spawned_power_ups.remove_at(index)
Uppdatera huvudscenen
Ta bort de gamla enskilda noderna SimpleMonster och HealthPowerUp ur Main.tscn. Instansiera i stället:
MonsterSpawner
HealthPowerUpSpawner
Monster- och power-up-scenerna förblir självständiga. Spawnern ansvarar endast för när och var de skapas.
Testa i desktopläget
- Tryck på Godots vanliga Play-knapp.
- Kontrollera att inget monster skapas inom 3,5 meter från startpositionen.
- Kontrollera att två monster närmar sig från banans kanter.
- Vänta och kontrollera att högst fyra monster lever samtidigt.
- Kontrollera att två power-ups finns vid start.
- Använd en power-up och kontrollera att systemet senare fyller på, men aldrig över tre.
- Besegra flera monster och vänta på nästa spawn. Kontrollera att nya monster dyker upp utan fel i Godots debugger och att HUD-räknaren fortsätter öka.
Testa i Quest
- Starta med Android/Quest-knappen och stå still de första sekunderna.
- Bekräfta att inga monster dyker upp precis bredvid eller bakom kroppen.
- Kontrollera att riktningen till de första monstren känns läsbar och rättvis.
- Spela tills fyra monster finns och bedöm om tempot är bekvämt.
- Kontrollera att flera gröna kristaller går att hitta utan att de överlappar.
Kontrollera spawnersystemen
Nästa steg
Nu finns en enkel, pågående stridsslinga. I kapitel 17 lägger vi till ett tydligt slut när hälsan når noll och ett sätt att starta en ny omgång.