7. Building Selection UI
Introduceer abstract base classes en implementeer road placement zonder auto-tiling
Overzicht
In dit hoofdstuk leer je:
Abstract base classes maken (BasePlacer)
BuildingPlacer refactoren naar inheritance pattern
RoadPlacer implementeren
Polymorphisme toepassen in BuildingSelector
Switch tussen building en road placement via UI
Waarom Abstract Base Classes?
BuildingPlacer bestaat al, RoadPlacer komt nu. Ze delen veel code:
Beide gebruiken GridMapRaycaster
Beide gebruiken GridMap
Beide hebben CanBuild(), Build(), Remove() methods
Een abstract base class (BasePlacer) biedt:
Gedeelde code op één plek
Consistente interface voor alle placers
Makkelijk nieuwe placer types toevoegen
Polymorphisme - BuildingSelector kan beide types aansturen met één referentie!
Dit is een professioneel design pattern dat je veel zal tegenkomen!
BasePlacer implementeren
Maak de abstract base class voor alle placer types.
Nieuwe C#-specifieke elementen:
abstract- Keyword voor classes die niet geΓ―nstantieerd kunnen worden, alleen geΓ«rfdprotected- Access modifier voor members die toegankelijk zijn in child classes
Maak een nieuw script:
Klik rechtermuisknop op de scripts/ folder
Selecteer Create New β Script...
Configureer: Language: C#, Inherits: Node, Class Name: BasePlacer
Path:
scripts/BasePlacer.csKlik Create
Voeg de
abstractkeyword toe. Dit voorkomt dat je BasePlacer direct kan instantiΓ«ren.
Voeg shared export properties toe. Deze properties zijn beschikbaar voor alle child classes.
Waarom protected?
Child classes (BuildingPlacer, RoadPlacer) kunnen deze properties gebruiken
Externe code heeft geen directe toegang nodig
Elk placer type krijgt zijn eigen Raycaster/GridMap referenties in de Inspector
Voeg abstract methods toe. Child classes MOETEN deze implementeren.
Abstract methods:
Geen body
{}- alleen de signatureCompiler geeft een error als child classes deze niet implementeren
Elke placer kan eigen logica hebben (BuildingPlacer: check grass + adjacent road, RoadPlacer: check grass only)
Sla het script op!
Abstract vs Interface
Je vraagt je misschien af: waarom geen interface?
Abstract class voordelen:
Kan shared fields/properties hebben (Raycaster, GridMap)
Kan concrete methods hebben (shared helpers)
Kan constructors hebben
Interface:
Alleen method signatures, geen implementation
Geen fields, alleen properties
Een abstract class is beter voor shared state (Raycaster/GridMap)!
BuildingPlacer refactoren
Pas BuildingPlacer aan om van BasePlacer te erven.
Nieuwe C#-specifieke elementen:
override- Keyword om een virtual of abstract method te implementeren
Wijzig de class declaratie. Vervang
Node3DdoorBasePlacer.
Van Node3D naar BasePlacer:
BuildingPlacer erft nu van BasePlacer (die erft van Node)
We verliezen Node3D functionaliteit, maar die gebruikten we toch niet
We krijgen Raycaster en GridMap properties van BasePlacer
Verwijder duplicate properties. Deze komen nu van BasePlacer.
VERWIJDER DEZE REGELS:
BEHOUD DEZE:
Voeg
overridekeywords toe aan de methods:
LET OP: De method bodies blijven EXACT hetzelfde - verander niets aan de logica!
Voeg een lege
Remove()method toe. BasePlacer vereist deze, maar buildings hebben nog geen demolition code.
Sla het script op!
Scene configuratie controleren:
Open scenes/main.tscn
Selecteer de BuildingPlacer node
In de Inspector zie je nu Base Type:
BasePlacer(wasNode3D)Controleer dat Raycaster en GridMap exports nog steeds ingesteld zijn
Als ze leeg zijn, koppel ze opnieuw
Sla de scene op!
RoadPlacer implementeren
Maak de RoadPlacer met simpele placement en removal logica.
Voeg een Node child toe aan de Main root node in scenes/main.tscn
Hernoem deze naar RoadPlacer
De scene structuur:
Sla de scene op!
Maak een nieuw script:
Klik rechtermuisknop op de scripts/ folder
Selecteer Create New β Script...
Configureer: Language: C#, Inherits: BasePlacer, Class Name: RoadPlacer
Path:
scripts/RoadPlacer.csKlik Create
LET OP: Bij "Inherits" typ je BasePlacer - dit is je custom base class!
Definieer export properties. Roads kunnen alleen op grass geplaatst worden.
Implementeer
CanBuild(). Roads hebben alleen een grass check nodig (geen adjacency check zoals buildings).
Implementeer
Build(). Plaats het straight road tile (auto-tiling volgt in hoofdstuk 8).
Implementeer
Remove(). Vervang roads door grass.
Sla het script op!
RoadPlacer script koppelen:
Ga terug naar scenes/main.tscn
Selecteer de RoadPlacer node
Sleep
scripts/RoadPlacer.csnaar het Script veldConfigureer de exports in de Inspector:
Raycaster: Sleep de GridMapRaycaster node naar dit veld
GridMap: Sleep de GridMap node naar dit veld
Grass Tile Id: 7 (default is correct)
Road Tile Id: 14 (default is correct)
Sla de scene op!
BuildingSelector uitbreiden
Voeg polymorphisme toe aan BuildingSelector om beide placer types aan te sturen.
Voeg een RoadPlacer export toe:
Voeg een private field toe voor de actieve placer:
Polymorphisme:
_selectedPlaceris van typeBasePlacerKan verwijzen naar BuildingPlacer OF RoadPlacer
Je hoeft niet te weten welke type het is - roep gewoon Build() aan!
Sla het script op!
Pas
ExtractBuildingTextures()aan. Voeg de road preview toe als 6e texture.
wordt:
Sla het script op!
Voeg een lege
UpdatePlacer()method toe:
Check of de road geselecteerd is. Index 5 is de road, 0-4 zijn buildings.
Stel de
_selectedPlacerin op basis van de selectie:
Polymorphisme in actie:
_selectedPlacer = RoadPlacer- BasePlacer referentie wijst naar RoadPlacer_selectedPlacer = BuildingPlacer- BasePlacer referentie wijst naar BuildingPlacerBeide zijn geldig omdat ze beide erven van BasePlacer!
Sla het script op!
Pas
ChangeBuilding()aan. RoepUpdatePlacer()aan om de actieve placer te wijzigen.
Vervang:
Door:
Pas
_UnhandledInput()aan. Gebruik de polymorphe_selectedPlacerreferentie.
Vervang:
Door:
Polymorphisme voordeel:
Je weet niet of
_selectedPlacereen BuildingPlacer of RoadPlacer isDat hoeft ook niet! Roep gewoon CanBuild() en Build() aan
De juiste implementation wordt automatisch uitgevoerd
Voeg demolish input toe:
Sla het script op!
Demolish Input Action
Dit systeem gebruikt een nieuwe input action: demolish. Voeg deze toe aan de Input Map:
Ga naar Project β Project Settings β Input Map
Type demolish in het veld
Klik Add
Klik op het + icoontje bij demolish
Selecteer Mouse Button
Selecteer Right Button
Klik OK
Rechtermuisklik tijdens het spelen om roads te verwijderen!
UI exports configureren
Koppel de RoadPlacer referentie in de BuildingSelector.
Open scenes/main.tscn
Selecteer de UI node
In de Inspector zie je nu een nieuw export veld: Road Placer
Sleep de RoadPlacer node naar dit veld
Sla de scene op!
Testen
Test de polymorphe placer switching en beide placer types.
Druk F5 om het spel te runnen.
Test building placement:
Selecteer building 0 met left/right arrows
Klik linkermuisknop op grass naast road β building verschijnt
Selecteer andere buildings β plaatsen werkt
Test road placement:
Klik right arrow tot je bij de road preview bent (6e tile)
Klik linkermuisknop op grass β road verschijnt
Klik rechtermuisknop op road β road verdwijnt
Test switching:
Selecteer road, plaats een paar roads
Selecteer een building, plaats een building
Selecteer road weer, plaats nog een road
Alles werkt seamless!
Wat moet werken:
5 buildings + 1 road in UI selector
Buildings plaatsen (alleen naast roads)
Roads plaatsen op grass
Roads verwijderen met rechtermuisknop
Seamless switching tussen building en road mode
Werkt alles? Sluit het spel (F8)
Veelgemaakte fouten
Abstract class instantiation error: Je kan geen
new BasePlacer()maken - alleen child classes instantiΓ«ren!Override keyword vergeten: Als je
overridevergeet, krijg je compiler warnings. De method signature moet EXACT overeenkomen.BuildingPlacer exports verdwenen: Na refactoring naar BasePlacer inheritance moet je de Raycaster/GridMap exports opnieuw koppelen in de Inspector.
NullReferenceException in _UnhandledInput:
_selectedPlaceris null omdatUpdatePlacer()niet is aangeroepen in_Ready(). RoepChangeBuilding(0)aan om te initialiseren.Demolish werkt niet: Controleer of je de
demolishinput action hebt toegevoegd in Project Settings!
Volledige scripts
Laatst bijgewerkt