Indholdsfortegnelse
Trin 2: Inventory som genbrugelig data-komponent
← Forrige: Opsætning | Forside | Næste: Grafik til én slot →
I dette trin bygger du selve inventoryet: en komponent, der ved, hvilke items der ligger i hvilke slots. Der er endnu ingen grafik. Du tester med tekst i Output-panelet.
Hvorfor adskille data og UI?
Det er fristende at lade den samme klasse både gemme items og tegne dem. Det bliver dog svært at genbruge. Hvis inventoryet selv er en grafisk boks, kan en kiste, der ikke altid er synlig, ikke have ét uden at få en boks med.
Vi deler derfor opgaven:
Inventory(dette trin) kender kun data: slots og items. Den har ingen grafik.InventoryUI(trin 4) kender kun visning: den får etInventoryat vide og tegner det.
Fordelene er, at du kan ændre udseendet uden at røre logikken, du kan teste logikken uden grafik, og samme Inventory kan bruges overalt: hos spilleren, i en kiste, hos en fjende eller i en butik.
Kort genopfriskning
Koden i dette trin bruger nogle ting, du kender fra tidligere:
[Export]foran en property gør den redigerbar i Inspector.- En property er en variabel med
{ get; set; }, som andre klasser kan læse og skrive. partialskal stå på klasser, der arver fra Godots typer.[GlobalClass]gør, at klassen kan vælges i editoren, fx under Add Child Node. Husk at trykke på Build først.
Først: InventorySlot
I stedet for en liste af items bruger vi en liste af slots. En slot er et lille objekt, der holder et item og et antal. Det virker som overkill, når hver slot højst rummer ét item, men det er netop det, der forbereder stacking: Den dag en slot skal rumme tre drikke, findes feltet Amount allerede.
Opret InventorySlot.cs i Scripts/Inventory:
- InventorySlot.cs
// En slot er ren data: den ved, hvilket item den holder, og hvor mange. // Klassen arver ikke fra Godot-typer, så den er en helt almindelig C#-klasse. public class InventorySlot { // Itemet i slotten. null betyder, at slotten er tom. public Item HeldItem { get; set; } // Antal items i slotten. Er altid 1 indtil videre (non-stackable), // men feltet er klar til, at stacking kan tilføjes senere. public int Amount { get; set; } public bool IsEmpty() { return HeldItem == null; } public void Clear() { HeldItem = null; Amount = 0; } }
Forklaring:
- Klassen har ikke
using Godot;og arver ikke fra noget. Den er en helt almindelig C#-klasse og ikke en node eller ressource. Den behøver ikke Godot, fordi den kun gemmer data. nullbetyder „ingenting“. ErHeldItemlig mednull, er slotten tom.IsEmpty()ogClear()gør koden andre steder lettere at læse:slot.IsEmpty()er tydeligere endslot.HeldItem == null.
Så: Inventory
Opret Inventory.cs i Scripts/Inventory med dette indhold:
- Inventory.cs
using Godot; // Inventory er en genbrugelig komponent, der KUN håndterer data: // hvilke items ligger i hvilke slots. Den ved intet om grafik. // Den kan sættes som barn under en spiller, en kiste, en fjende osv. [GlobalClass] public partial class Inventory : Node { // Antal slots. Ændres i Inspector. Range begrænser tallet til 1-64. [Export(PropertyHint.Range, "1,64,1")] public int SlotCount { get; set; } = 9; // Items, inventoryet starter med. Praktisk til test. [Export] public Godot.Collections.Array<Item> StartItems { get; set; } = new Godot.Collections.Array<Item>(); // Selve slotsene. "private" betyder, at kun denne klasse kan røre dem direkte. // Alle andre skal bruge metoderne herunder, så reglerne samles ét sted. private InventorySlot[] _slots; public override void _Ready() { // Opret en tom slot for hvert felt i inventoryet. _slots = new InventorySlot[SlotCount]; for (int i = 0; i < _slots.Length; i++) { _slots[i] = new InventorySlot(); } // Læg start-items i inventoryet. foreach (Item item in StartItems) { AddItem(item); } } // Hvor mange slots har inventoryet? public int GetSize() { return _slots.Length; } // Giver itemet i en slot, eller null hvis slotten er tom eller ikke findes. public Item GetItem(int index) { if (!IsValidIndex(index)) { return null; } return _slots[index].HeldItem; } // Lægger et item i den første tomme slot. // Returnerer true, hvis det lykkedes, og false, hvis inventoryet er fuldt. public bool AddItem(Item item) { if (item == null) { return false; } // Her kan stacking senere tilføjes: led først efter en slot med samme item, // som ikke er fyldt op, og læg kun i en tom slot, hvis der ikke er nogen. for (int i = 0; i < _slots.Length; i++) { if (_slots[i].IsEmpty()) { _slots[i].HeldItem = item; _slots[i].Amount = 1; return true; } } return false; // Ingen tomme slots } // Fjerner itemet i en slot og giver det tilbage. // Returnerer null, hvis slotten var tom eller ikke findes. public Item RemoveItem(int index) { if (!IsValidIndex(index) || _slots[index].IsEmpty()) { return null; } Item removed = _slots[index].HeldItem; _slots[index].Clear(); return removed; } // Skriver indholdet i Output-panelet. Bruges til at teste uden UI. public void PrintContents() { GD.Print($"--- Inventory i '{GetParent().Name}' ({_slots.Length} slots) ---"); for (int i = 0; i < _slots.Length; i++) { string text = _slots[i].IsEmpty() ? "(tom)" : _slots[i].HeldItem.ItemName; GD.Print($"Slot {i}: {text}"); } } // Tjekker, at et slot-nummer er inden for grænserne. private bool IsValidIndex(int index) { return index >= 0 && index < _slots.Length; } }
Forklaring af de nye begreber:
Inventory : Nodegør komponenten til en node. Så kan den sættes som barn under en spiller eller en kiste i scenetræet, og Godot kalder automatisk dens_Ready().[Export(PropertyHint.Range, „1,64,1“)]giver et talfelt i Inspector, der kun accepterer tal fra 1 til 64. Det er stedet, hvor du senere ændrer antallet af slots. Kisten kan have 9, og en rygsæk kan have 20, uden at koden ændres._Ready()kaldes af Godot én gang, når noden og alle dens børn er klar. Her opretter vi slotsene. Du kalder den aldrig selv.- Et array (
InventorySlot[]) er en række af værdier af samme type med fast længde._slots[0]er den første slot,_slots[1]den anden. Tælling starter altid ved 0. forgentager kode et bestemt antal gange.foreachgentager kode for hvert element i en samling.publicogprivatebestemmer, hvem der må bruge et medlem. Arrayet_slotserprivate, så andre klasser skal gå gennem metoderneGetItem,AddItemogRemoveItem. Så ligger reglerne ét sted. Når stacking senere skal tilføjes, ændrer du kunAddItem, og alt andet virker uændret.booler en værdi, der er ententrueellerfalse.AddItemreturnerertrue, hvis der var plads. Den, der kalder metoden, kan dermed reagere, hvis inventoryet er fuldt.StartItemser en liste af items, som du udfylder i Inspector. Vi bruger den til test og til at give inventoryet noget indhold fra start.IsValidIndexbeskytter mod fejl: Hvis nogen beder om slot 50 i et inventory med 9 slots, får vinulli stedet for at spillet går ned.
Tryk på Build, og kontrollér, at der ikke er fejl.
Sæt inventoryet på en spiller
Nu bruger du komponenten. Du laver en Player-scene, der har et Inventory som barn.
- Vælg Scene → New Scene. Under Other Node vælger du Node (den enkleste node) som rod.
- Omdøb roden til
Playerved at dobbeltklikke på navnet. - Højreklik på
Player, og vælg Add Child Node. Søg efter Inventory, og vælg den. Den står nu i listen, fordi klassen har[GlobalClass]og er bygget. - Gem scenen som
Scenes/Player.tscn(Ctrl+S).
Træet skal se sådan ud:
Player (Node) └── Inventory (Inventory)
Vælg Inventory-noden. Inspector viser nu Slot Count (9) og Start Items.
- Klik på Start Items, og sæt Size til 2.
- Træk to af dine item-filer (
.tres) ind i element 0 og 1, fx et sværd og en drik.
Hvis du vil have mere end ét item i listen, så øg Size, og træk flere ind. Tomme felter ignoreres af AddItem.
Midlertidigt script til test
Spilleren skal have et script, der giver adgang til inventoryet. Højreklik på Player-noden, vælg Attach Script, vælg C#, og sæt stien til Scripts/Game/Player.cs. Erstat derefter indholdet med denne midlertidige test-version. Linjen med PrintContents fjerner du igen i trin 5:
- Player.cs
using Godot; // Spilleren er en "ejer" af et inventory. // Inventory-komponenten ligger som barn i Player-scenen. public partial class Player : Node { public Inventory Inventory { get; private set; } public override void _Ready() { // Find barnet med navnet "Inventory" i scenen. Inventory = GetNode<Inventory>("Inventory"); // MIDLERTIDIGT: skriv indholdet i Output-panelet, så vi kan se, at det virker. Inventory.PrintContents(); } }
Forklaring:
GetNode<Inventory>("Inventory")finder barnet med navnetInventoryog giver det tilbage som typenInventory. Teksten i parentesen er stien til noden, og den skal passe præcist til navnet i Scene-panelet.{ get; private set; }betyder, at alle må læseInventory, men kunPlayerselv må ændre det.- Børn bliver klar før deres forældre. Derfor har
Inventorykørt sin_Ready()og oprettet slotsene, nårPlayer._Ready()kører. Rækkefølgen er vigtig, og vi bruger den igen i trin 5.
Test
Tryk på Build, og kør derefter scenen med Run Current Scene (F6). Der kommer et tomt vindue. Kig i Output-panelet nederst. Du skal se noget i retning af dette (dine item-navne kan være anderledes):
--- Inventory i 'Player' (9 slots) --- Slot 0: Sværd Slot 1: Helbredelsesdrik Slot 2: (tom) Slot 3: (tom) Slot 4: (tom) Slot 5: (tom) Slot 6: (tom) Slot 7: (tom) Slot 8: (tom)
Lav gerne små eksperimenter:
- Sæt Slot Count til 3 og tilføj tre start-items. Hvad sker der, hvis du tilføjer et fjerde? (
AddItemreturnererfalse, og itemet bliver ikke lagt i inventoryet.) - Sæt Slot Count tilbage til 9.
Tjekliste
Player.tscnfindes og indeholder etInventory.- Output viser de rigtige items i de første slots.
- Der er ingen røde fejl i Output eller Build.
