← 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.
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 et Inventory at 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.
Koden i dette trin bruger nogle ting, du kender fra tidligere:
[Export] foran en property gør den redigerbar i Inspector.{ get; set; }, som andre klasser kan læse og skrive.partial skal 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.
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:
// 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:
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.null betyder „ingenting“. Er HeldItem lig med null, er slotten tom.IsEmpty() og Clear() gør koden andre steder lettere at læse: slot.IsEmpty() er tydeligere end slot.HeldItem == null.
Opret Inventory.cs i Scripts/Inventory med dette indhold:
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 : Node gø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.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.for gentager kode et bestemt antal gange. foreach gentager kode for hvert element i en samling.public og private bestemmer, hvem der må bruge et medlem. Arrayet _slots er private, så andre klasser skal gå gennem metoderne GetItem, AddItem og RemoveItem. Så ligger reglerne ét sted. Når stacking senere skal tilføjes, ændrer du kun AddItem, og alt andet virker uændret.bool er en værdi, der er enten true eller false. AddItem returnerer true, hvis der var plads. Den, der kalder metoden, kan dermed reagere, hvis inventoryet er fuldt.StartItems er en liste af items, som du udfylder i Inspector. Vi bruger den til test og til at give inventoryet noget indhold fra start.IsValidIndex beskytter mod fejl: Hvis nogen beder om slot 50 i et inventory med 9 slots, får vi null i stedet for at spillet går ned.Tryk på Build, og kontrollér, at der ikke er fejl.
Nu bruger du komponenten. Du laver en Player-scene, der har et Inventory som barn.
Player ved at dobbeltklikke på navnet.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.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.
.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.
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:
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 navnet Inventory og giver det tilbage som typen Inventory. 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æse Inventory, men kun Player selv må ændre det.Inventory kørt sin _Ready() og oprettet slotsene, når Player._Ready() kører. Rækkefølgen er vigtig, og vi bruger den igen i trin 5.
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:
AddItem returnerer false, og itemet bliver ikke lagt i inventoryet.)Player.tscn findes og indeholder et Inventory.