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:

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:

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:

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:

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.

  1. Vælg Scene → New Scene. Under Other Node vælger du Node (den enkleste node) som rod.
  2. Omdøb roden til Player ved at dobbeltklikke på navnet.
  3. 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.
  4. 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.

  1. Klik på Start Items, og sæt Size til 2.
  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:

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:

Tjekliste

Næste: Grafik til én slot →