====== Trin 4: Inventory-UI med 9-patch baggrund og grid ====== [[trin3_slot_ui|← Forrige: Grafik til én slot]] | [[start|Forside]] | [[trin5_spiller|Næste: Vis spillerens inventory →]] I dette trin bygger du selve inventory-vinduet: en baggrund, der skalerer pænt, med et grid af slots i. Du skriver også koden, der opretter ét slot-UI for hvert slot i et ''Inventory''. ===== Nye begreber ===== * ''NinePatchRect'' er en node til baggrunde. Den deler et billede op i ni felter og skalerer dem forskelligt, så hjørner og kanter ikke bliver forvrænget (forklares nedenfor). * ''GridContainer'' er en container, der placerer sine børn i et gitter. Du fortæller den, hvor mange **kolonner** den skal have, og den lægger børnene ud række for række. Med 3 kolonner og 9 børn får du 3 rækker. * ''MarginContainer'' er en container, der giver luft (en margin) rundt om sine børn. * ''PackedScene'' er en scene, der er pakket som data, så koden kan oprette (//instantiere//) kopier af den. Vi bruger den til at oprette slots. ===== Scenens opbygning ===== Opret en ny scene med ''MarginContainer'' som rod, og lav disse noder: InventoryUI (MarginContainer) ├── Background (NinePatchRect) └── Content (MarginContainer) └── Grid (GridContainer) Gem den som ''Scenes/InventoryUI.tscn''. Rækkefølgen er vigtig: ''Background'' skal stå **før** ''Content'', fordi noder, der står først, tegnes først (bagest). **Hvorfor er scenen bygget sådan?** En container giver alle sine børn samme plads. Roden ''InventoryUI'' har ingen margin og strækker derfor både ''Background'' og ''Content'' ud, så de fylder hele roden. Roden bliver samtidig mindst så stor som sit største barn. Da ''Content'' (med grid og margin) er størst, får baggrunden automatisk den rette størrelse. Hvis du senere ændrer antal slots, vokser både grid og baggrund med, uden at du skal regne på størrelser. ===== Indstillinger i Inspector ===== ^ Node ^ Indstilling ^ Værdi ^ | ''Content'' | Theme Overrides → Constants → Margin Left, Top, Right, Bottom | 16 (samme i alle fire) | | ''Grid'' | Columns | 3 | | ''Grid'' | Theme Overrides → Constants → H Separation og V Separation | 4 | ''Columns = 3'' er det, der gør inventoryet til et grid med 3 kolonner. Med 9 slots giver det 3x3. Antallet af slots styres i ''Inventory'' (''Slot Count''), og antallet af kolonner styres her i UI'et. ===== NinePatchRect: en baggrund der skalerer pænt ===== Hvis du strækker et almindeligt billede af en ramme, bliver rammens kanter tykke og hjørnerne forvrængede. En **9-patch** løser det ved at dele billedet i ni felter: +---+-----------+---+ | 1 | 2 | 3 | 1, 3, 7, 9: hjørnerne. Strækkes aldrig. +---+-----------+---+ 2, 8: top og bund. Strækkes kun vandret. | 4 | 5 | 6 | 4, 6: siderne. Strækkes kun lodret. +---+-----------+---+ 5: midten. Strækkes både vandret og lodret. | 7 | 8 | 9 | +---+-----------+---+ Hjørnerne forbliver derfor skarpe, uanset hvor stor baggrunden bliver. Det er præcis det, vi har brug for, fordi inventoryet får forskellig størrelse alt efter antal slots. Markér ''Background'', og indstil: ^ Indstilling i Inspector ^ Værdi ^ | Texture | Træk dit panel-billede fra ''Assets'' hertil | | Patch Margin → Left, Top, Right, Bottom | Tykkelsen på billedets kant i pixels, fx 12 | | Axis Stretch → Horizontal og Vertical | Stretch (eller Tile, hvis kanten har et mønster) | | Draw Center | Til (slå fra, hvis kun kanten skal vises) | **Sådan vælger du patch margins:** Patch margins er afstandene fra billedets kanter ind til de linjer, der deler billedet i ni felter. Sæt dem til tykkelsen af den dekorative ramme, altså hjørnerne. Er rammen i dit billede 12 pixels tyk, bruger du 12. Er værdien for lille, bliver hjørnerne strakt og uskarpe. Er den for stor, bliver midten for lille. Prøv dig frem, indtil hjørnerne ser ens ud, uanset størrelse. **Axis Stretch** bestemmer, hvordan top, bund og sider fyldes ud. //Stretch// strækker billedet, //Tile// gentager det som fliser, og //Tile Fit// gentager det, så det går op i størrelsen. Til de fleste paneler er //Stretch// fint. ===== Script til UI'et ===== Højreklik på rod-noden ''InventoryUI'', vælg **Attach Script**, vælg **C#**, og sæt stien til ''Scripts/Inventory/InventoryUI.cs''. Erstat indholdet med: using Godot; using System.Collections.Generic; // InventoryUI viser indholdet af ét Inventory i et grid. // Den kan vise ALLE Inventory-komponenter: den får bare at vide, hvilken den skal vise. public partial class InventoryUI : MarginContainer { // Slot-scenen, som skal oprettes én gang pr. slot. Sættes i Inspector. [Export] public PackedScene SlotScene { get; set; } private GridContainer _grid; private Inventory _inventory; // En liste over de slot-grafikker, vi har oprettet. private readonly List _slotUIs = new List(); public override void _Ready() { _grid = GetNode("Content/Grid"); } // Fortæller UI'et, hvilket inventory det skal vise, og bygger slots. public void SetInventory(Inventory inventory) { if (SlotScene == null) { GD.PushError("InventoryUI: SlotScene er ikke sat i Inspector."); return; } _inventory = inventory; // Fjern eventuelle gamle slots, hvis SetInventory kaldes igen. foreach (SlotUI oldSlot in _slotUIs) { oldSlot.QueueFree(); } _slotUIs.Clear(); // Opret én SlotUI pr. slot i inventoryet. for (int i = 0; i < _inventory.GetSize(); i++) { SlotUI slotUi = SlotScene.Instantiate(); _grid.AddChild(slotUi); // Først nu kører slottens _Ready _slotUIs.Add(slotUi); } Refresh(); } // Opdaterer alle slots, så de matcher inventoryets indhold. // Skal kaldes, hver gang inventoryets indhold er ændret. public void Refresh() { for (int i = 0; i < _slotUIs.Count; i++) { _slotUIs[i].SetItem(_inventory.GetItem(i)); } } } **Forklaring:** * ''[Export] public PackedScene SlotScene'' er et felt i Inspector, hvor du trækker ''SlotUI.tscn'' ind. Vi gør det på denne måde frem for at hardkode stien i koden. Det er nemt at skifte slot-scene senere, og der er ingen filstier, du kan stave forkert. * ''SetInventory(Inventory inventory)'' er den vigtigste metode. UI'et ved ikke på forhånd, hvilket inventory det skal vise. Det får det at vide udefra. Det er netop det, der gør UI'et genbrugeligt: samme UI-scene kan vise spillerens inventory ét sted og kistens et andet. * ''%%SlotScene.Instantiate()%%'' laver en ny kopi af slot-scenen. ''AddChild'' sætter den ind i gridet. Først når den er tilføjet, kører slottens ''_Ready()''. Derfor kalder vi ''SetItem'' bagefter i ''Refresh()'' og ikke før. * Løkken kører ''GetSize()'' gange og opretter én slot pr. slot i inventoryet. Derfor ændrer antallet af slots i UI'et sig automatisk, hvis du ændrer ''Slot Count'' i inventoryet. * En **liste** (''%%List%%'') er en samling, der kan vokse. Vi gemmer de oprettede slot-UI'er i den, så ''Refresh()'' kan opdatere dem én for én og ''SetInventory'' kan slette dem igen. * ''QueueFree()'' sletter en node sikkert, når Godot er færdig med den aktuelle opgave. * ''Refresh()'' henter hvert item fra inventoryet og beder den tilsvarende slot vise det. Det er en simpel og tydelig løsning: Når noget er ændret, kalder vi ''Refresh()''. Vi bruger ikke avancerede mekanismer til at holde UI'et opdateret, og det er nemt at gennemskue. * ''GD.PushError(...)'' skriver en rød fejlbesked i Output. Det er hjælp til dig selv, hvis du glemmer at sætte ''Slot Scene''. Tryk på **Build**. Markér derefter ''InventoryUI'' i scenen, og træk ''Scenes/SlotUI.tscn'' fra FileSystem ind i feltet **Slot Scene** i Inspector. ===== Test af 9-patch baggrunden ===== Slots oprettes først af koden i trin 5. For at se baggrunden nu, kan du midlertidigt lave dem i hånden: - Træk ''SlotUI.tscn'' ind på ''Grid'' ni gange. (Eller træk den ind én gang, og tryk ''Ctrl+D'' otte gange.) - Kør scenen med ''F6''. Du skal se et 3x3 grid med en baggrund bagved. - Tilføj en tiende slot med ''Ctrl+D''. Gridet får en fjerde række, og baggrunden vokser med, uden at hjørnerne bliver forvrænget. Sæt til sammenligning Patch Margin til 0 på ''Background'', og se, hvordan hjørnerne bliver strakt. Sæt værdien tilbage bagefter. - **Slet de ni slots igen**, så ''Grid'' er tom. Ellers får du ekstra tomme slots ved siden af dem, koden opretter. - Gem scenen. ===== Tjekliste ===== * ''InventoryUI.tscn'' har ''Background'', ''Content'' og ''Grid'' i den rigtige rækkefølge. * Grid har Columns = 3, og baggrunden skalerer pænt. * ''Slot Scene'' er sat til ''SlotUI.tscn''. * ''Grid'' er tom i editoren, og Build giver ingen fejl. [[trin5_spiller|Næste: Vis spillerens inventory →]]