Zum Inhalt springen
DasPeTe Tutorials

Inventarsystem · Teil 1 von 2 der Serie

Einfaches Inventarsystem in Unity

So erstellst du ein einfaches Inventarsystem in Unity

Fortgeschritten45 Min.

Heute lernen wir, wie man ein einfaches Inventarsystem in Unity erstellt. Ein Inventarsystem ist für viele Spiele unverzichtbar, denn damit können Spielende Gegenstände einsammeln, aufbewahren und verwalten, die sie auf ihrem Abenteuer finden. Wir legen mehrere Arten von Inventargegenständen an, zum Beispiel Buildables und Collectibles. Buildables sind Gegenstände, mit denen Spielende Bauwerke oder Objekte in der Spielwelt errichten können. Collectibles sind Gegenstände, die für verschiedene Zwecke gesammelt werden, etwa um Quests abzuschließen, Erfolge freizuschalten oder andere Gegenstände herzustellen.

Das Interface für Inventargegenstände

Zuerst erstellen wir ein Interface, das die Eigenschaften und Methoden unserer Inventargegenstände festlegt. So stellt das Interface sicher, dass alle Inventargegenstände einheitlich aufgebaut sind.

Erstelle ein neues C#-Skript namens IInventoryItem.cs und füge den folgenden Code ein:

csharp
public interface IInventoryItem<TItem, TItemType> {
    // The item itself, which can be of any type (like a Collectible or Buildable)
    TItem Item { get; set; }

    // The Type of the item
    TItemType Type { get; }

    // The amount of the item
    int Amount { get; set; }
}

Mit diesem generischen Interface können wir verschiedene Arten von Inventargegenständen umsetzen und sicherstellen, dass alle die Eigenschaften Item, Type und Amount haben.

Der Gegenstandstyp „Collectible“

Wir möchten verschiedene Arten von Inventargegenständen haben. Fangen wir mit dem Gegenstandstyp Collectible an. Beispiele für Collectibles sind Ressourcen wie Holz, Stein oder Gold. Wir brauchen also ein Objekt, in dem wir diese Collectibles definieren können. In Unity kannst du dafür ein ScriptableObject erstellen.

Außerdem brauchen wir ein Enum, das die verschiedenen Arten von Collectibles festlegt.

Erstelle ein neues C#-Skript namens CollectibleItemType.cs und füge den folgenden Code ein:

csharp
public enum CollectibleItemType {
    None = 0,
    Wood = 1000,
    Stone = 2000,
    Iron = 3000,
    Silver = 4000,
    Gold = 5000,
}

Erstelle jetzt ein neues C#-Skript namens CollectibleItem.cs und füge den folgenden Code ein:

csharp
[CreateAssetMenu(fileName = "New Collectible Item", menuName = "Items/Collectible Item")]
public class CollectibleItem : ScriptableObject {
    // The type of the collectible item
    public CollectibleItemType type;

    // The icon for the collectible item
    public Sprite icon;
}

Nun können wir in Unity verschiedene Collectibles anlegen, indem wir neue Instanzen des ScriptableObjects CollectibleItem erstellen. Klicke dazu mit der rechten Maustaste in das Project-Fenster, wähle Create > Items > Collectible Item und fülle die Details für jeden Collectible-Typ aus. Am Ende solltest du fünf Collectibles namens „Wood“, „Stone“, „Iron“, „Silver“ und „Gold“ haben.

Um unsere CollectibleItems im Inventar zu speichern, brauchen wir eine Klasse, die das Interface IInventoryItem implementiert.

Erstelle also ein neues C#-Skript namens CollectibleInventoryItem.cs und füge den folgenden Code ein:

csharp
using System;
using UnityEngine;

[Serializable]
public class CollectibleInventoryItem: IInventoryItem<CollectibleItem, CollectibleItemType> {
    public CollectibleItem Item { get => item; set => item = value; }
    [SerializeField] CollectibleItem item;

    public int Amount { get => amount; set => amount = value; }
    [SerializeField] int amount;

    public CollectibleItemType Type {
        get { return item.type; }
    }

    public Sprite Icon {
        get { return item.icon; }
    }
}

Jetzt haben wir einen Inventargegenstand für unsere Collectibles.

Der Gegenstandstyp „Buildable“

Als Nächstes erstellen wir den Gegenstandstyp Buildable. Beispiele für Buildables sind Bauwerke wie Häuser, Mauern oder Türme. Wie bei den Collectibles erstellen wir ein ScriptableObject für Buildables und ein Enum, das die verschiedenen Arten von Buildables festlegt.

Erstelle ein neues C#-Skript namens BuildableItemType.cs und füge den folgenden Code ein:

csharp
public enum BuildableItemType {
    None = 0,
    Storage = 1000,
}

Erstelle jetzt ein neues C#-Skript namens BuildableItem.cs und füge den folgenden Code ein:

csharp
using UnityEngine;

[CreateAssetMenu(fileName = "New Buildable Item", menuName = "Items/Buildable Item")]
public class BuildableItem : ScriptableObject {
    public BuildableItemType type;
    public Sprite icon;
    public Buildable prefab;
}

Wie du siehst, haben wir jetzt eine Prefab-Eigenschaft, die das eigentliche Buildable-GameObject enthält. Dafür brauchen wir eine einfache Buildable-Klasse.

Erstelle ein neues C#-Skript namens Buildable.cs und füge den folgenden Code ein:

csharp
using UnityEngine;

public class Buildable : MonoBehaviour {

}

Nun können wir in Unity verschiedene Buildables anlegen, indem wir neue Instanzen des ScriptableObjects BuildableItem erstellen. Klicke dazu mit der rechten Maustaste in das Project-Fenster, wähle Create > Items > Buildable Item und fülle die Details für jeden Buildable-Typ aus. Am Ende solltest du ein Buildable namens „Storage“ haben.

Die Klasse Inventory

Da unsere Gegenstandstypen jetzt definiert sind, brauchen wir die Klasse Inventory, die diese Gegenstände aufnimmt und verwaltet.

Wir brauchen eine generische Inventory-Klasse, die mit verschiedenen Arten von Inventargegenständen umgehen kann.

Erstelle ein neues C#-Skript namens Inventory.cs und füge den folgenden Code ein:

csharp
using System.Collections.Generic;
using System;
using Unity.VisualScripting;

[Serializable]
public class Inventory<
    InventoryItem,
    Item,
    ItemType
> where InventoryItem : IInventoryItem<
    Item,
    ItemType
>, new() {
    public int capacity;

    public List<InventoryItem> items = new List<InventoryItem>();

    public Action<InventoryItem> OnItemDeposited;
    public Action<InventoryItem> OnItemWithdrawn;

    public int Total {
        get {
            int total = 0;

            foreach(InventoryItem item in items) {
                total += item.Amount;
            }

            return total;
        }
    }

    public bool IsFull {
        get {
            return Total >= capacity;
        }
    }

    public bool IsEmpty {
        get {
            return Total == 0;
        }
    }

    public int Deposit(List<InventoryItem> itemsToDeposit) {
        if(IsFull) {
            return 0;
        }

        int totalDeposited = 0;

        foreach(InventoryItem item in itemsToDeposit) {
            totalDeposited += Deposit(item);
        }

        return totalDeposited;
    }

    public int Deposit(InventoryItem itemToDeposit) {
        if(IsFull) {
            return 0;
        }

        int existingItemIndex = GetInventoryItemIndexByType(itemToDeposit.Type);

        if(Total + itemToDeposit.Amount > capacity) {
            int restCapacity = capacity - Total;
            itemToDeposit.Amount = restCapacity;
        }

        if(existingItemIndex == -1) {
            items.Add(itemToDeposit);
        } else {
            InventoryItem inventoryItem = items[existingItemIndex];
            inventoryItem.Amount += itemToDeposit.Amount;
            items[existingItemIndex] = inventoryItem;
        }

        OnItemDeposited?.Invoke(itemToDeposit);

        return itemToDeposit.Amount;
    }

    public List<InventoryItem> Withdraw(ItemType type, int amount = 1) {
        List<InventoryItem> withdrawnItems = new List<InventoryItem>();

        if(IsEmpty) {
            return withdrawnItems;
        }

        int existingItemIndex = GetInventoryItemIndexByType(type);
        if(existingItemIndex == -1) {
            return withdrawnItems;
        }

        InventoryItem inventoryItem = items[existingItemIndex];

        InventoryItem withdrawnItem = new InventoryItem();
        withdrawnItem.Item = inventoryItem.Item;
        withdrawnItem.Amount = 0;

        if(inventoryItem.Amount <= amount) {
            withdrawnItem.Amount = inventoryItem.Amount;
            items.RemoveAt(existingItemIndex);
        } else {
            withdrawnItem.Amount = amount;
            inventoryItem.Amount -= amount;
            items[existingItemIndex] = inventoryItem;
        }

        withdrawnItems.Add(withdrawnItem);

        OnItemWithdrawn?.Invoke(withdrawnItem);

        return withdrawnItems;
    }

    public List<InventoryItem> Withdraw(List<InventoryItem> itemsToWithdraw) {
        List<InventoryItem> withdrawnItems = new List<InventoryItem>();

        foreach(InventoryItem item in itemsToWithdraw) {
            List<InventoryItem> withdrawn = Withdraw(item.Type, item.Amount);
            withdrawnItems.AddRange(withdrawn);
        }

        return withdrawnItems;
    }

    public List<InventoryItem> Withdraw(int amount = 1) {
        List<InventoryItem> withdrawnItems = new List<InventoryItem>();

        if(IsEmpty) {
            return withdrawnItems;
        }

        do {
            InventoryItem inventoryItem = items[0];
            InventoryItem withdrawnItem = new InventoryItem();
            withdrawnItem.Item = inventoryItem.Item;
            withdrawnItem.Amount = 0;

            if(inventoryItem.Amount <= amount) {
                withdrawnItem.Amount = inventoryItem.Amount;
                amount -= inventoryItem.Amount;
                items.RemoveAt(0);
            } else {
                withdrawnItem.Amount = amount;
                inventoryItem.Amount -= amount;
                amount = 0;
                items[0] = inventoryItem;
            }

            withdrawnItems.Add(withdrawnItem);

            OnItemWithdrawn?.Invoke(withdrawnItem);
        } while(amount > 0 && !IsEmpty);

        return withdrawnItems;
    }

    public bool ContainsRequiredItems(List<InventoryItem> requiredItems) {
        foreach(InventoryItem requiredItem in requiredItems) {
            if(!HasEnoughOfItemType(requiredItem.Type, requiredItem.Amount)) {
                return false;
            }
        }

        return true;
    }

    public int GetInventoryItemIndexByType(ItemType type) {
        return items.FindIndex(item => item.Type.Equals(type));
    }

    public int GetAmountOfItemType(ItemType type) {
        int total = 0;

        foreach(InventoryItem item in items) {
            if(item.Type.Equals(type)) {
                total += item.Amount;
            }
        }

        return total;
    }

    public bool HasEnoughOfItemType(ItemType itemType, int requiredAmount) {
        int availableAmount = GetAmountOfItemType(itemType);
        return availableAmount >= requiredAmount;
    }
}

Wie du siehst, gibt es mehrere Varianten der Methoden Deposit und Withdraw für unterschiedliche Anwendungsfälle.

Außerdem gibt es öffentliche Methoden, die prüfen, ob das Inventar die benötigten Gegenstände enthält, die Menge eines bestimmten Gegenstandstyps liefern und prüfen, ob genug von einem bestimmten Gegenstandstyp vorhanden ist.

Wir haben Events, die ausgelöst werden, wenn ein Gegenstand ins Inventar gelegt oder daraus entnommen wird.

Und schließlich gibt es Eigenschaften, die prüfen, ob das Inventar voll oder leer ist, sowie eine Eigenschaft für die Gesamtzahl der Gegenstände im Inventar.

Anwendungsbeispiel

Jetzt, wo wir unsere Inventory-Klasse haben, können wir eine Instanz davon erstellen und damit unsere Inventargegenstände verwalten.

Erstelle ein neues C#-Skript namens PlayerController.cs und füge den folgenden Code ein:

csharp
using UnityEngine;

public class PlayerController : MonoBehaviour {
    public static PlayerController Instance;

    public Inventory<
        CollectibleInventoryItem,
        CollectibleItem,
        CollectibleItemType
    > collectibleItemsInventory;

    public Inventory<
        BuildableInventoryItem,
        BuildableItem,
        BuildableItemType
    > buildableItemsInventory;

    private void Awake() {
        Instance = this;
    }

}

Jetzt kannst du von überall in deinem Code über PlayerController.Instance.collectibleItemsInventory und PlayerController.Instance.buildableItemsInventory auf die Inventare des Spielers zugreifen.

Am Ende solltest du diese Struktur in deinem Unity-Projekt haben:

text
Assets/
├── Objects/
│   ├── Collectibles/
│   │   ├── Wood.asset
│   │   ├── Stone.asset
│   │   ├── Iron.asset
│   │   ├── Silver.asset
│   │   └── Gold.asset
│   └── Buildables/
│       └── Storage.asset
└── Scripts/
    ├── Inventory/
    │   ├── Inventory.cs
    │   └── IInventoryItem.cs
    ├── Items/
    │   ├── Collectibles/
    │   │   ├── CollectibleItem.cs
    │   │   ├── CollectibleItemType.cs
    │   │   └── CollectibleInventoryItem.cs
    │   └── Buildables/
    │       ├── BuildableItem.cs
    │       ├── BuildableItemType.cs
    │       ├── BuildableInventoryItem.cs
    │       └── Buildable.cs
    └── PlayerController.cs

Kommentare

Die Kommentare sind GitHub Discussions: Zum Schreiben mit GitHub anmelden.

Die Kommentare kommen über giscus von GitHub. Beim Laden gehen deine IP-Adresse an giscus.app und GitHub.