Erstellen eines Inventar- und Gegenstandsherstellungssystems in Unity
In diesem Tutorial zeige ich, wie man in Unity ein Inventar- und Gegenstandsherstellungssystem im Minecraft-Stil erstellt.
Das Herstellen von Gegenständen in Videospielen ist ein Prozess, bei dem bestimmte (normalerweise einfachere) Gegenstände zu komplexeren Gegenständen mit neuen und verbesserten Eigenschaften kombiniert werden. Zum Beispiel die Kombination von Holz und Stein zu einer Spitzhacke oder die Kombination von Blech und Holz zu einem Schwert.
Das folgende Handwerkssystem ist mobilfreundlich und vollständig automatisiert, was bedeutet, dass es mit jedem UI-Layout funktioniert und die Möglichkeit bietet, benutzerdefinierte Handwerksrezepte zu erstellen.
Schritt 1: Einrichten der Crafting-Benutzeroberfläche
Wir beginnen mit der Einrichtung der Crafting-Benutzeroberfläche:
- Erstellen Sie eine neue Leinwand (Unity Obere Taskleiste: GameObject -> UI -> Canvas)
- Erstellen Sie ein neues Bild, indem Sie mit der rechten Maustaste auf Canvas-Objekt -> Benutzeroberfläche -> Bild klicken
- Benennen Sie das Bildobjekt in "CraftingPanel" um und ändern Sie sein Quellbild auf den Standardwert "UISprite"
- Ändern Sie "CraftingPanel" RectTransform-Werte in (Pos X: 0 Pos Y: 0 Breite: 410 Höhe: 365)
- Erstellen Sie zwei Objekte in "CraftingPanel" (Rechtsklick auf CraftingPanel -> Leere erstellen, 2 Mal)
- Benennen Sie das erste Objekt in "CraftingSlots" um und ändern Sie seine RectTransform-Werte in („Oben links ausrichten“ Pivot X: 0 Pivot Y: 1 Pos X: 50 Pos Y: -35 Breite: 140 Höhe: 140). Dieses Objekt enthält Handwerksplätze.
- Benennen Sie das zweite Objekt in "PlayerSlots" um und ändern Sie seine RectTransform-Werte in („Top Stretch Horizontally“ Pivot X: 0,5 Pivot Y: 1 Left: 0 Pos Y: -222 Right: 0 Height: 100). Dieses Objekt enthält Spielerplätze.
Abschnittsüberschrift:
- Erstellen Sie einen neuen Text, indem Sie mit der rechten Maustaste auf das Objekt "PlayerSlots" -> Benutzeroberfläche -> Text klicken und ihn umbenennen "SectionTitle"
- Ändern Sie "SectionTitle" RectTransform-Werte in („Oben links ausrichten“ Pivot X: 0 Pivot Y: 0 Pos X: 5 Pos Y: 0 Breite: 160 Höhe: 30)
- Ändern Sie den Text "SectionTitle" in "Inventory" und stellen Sie die Schriftgröße auf 18, die Ausrichtung auf „Mitte links“ und die Farbe auf (0,2, 0,2, 0,2, 1) ein.
- Duplizieren Sie das "SectionTitle"-Objekt, ändern Sie seinen Text in "Crafting", verschieben Sie es unter das "CraftingSlots"-Objekt und legen Sie dann die gleichen RectTransform-Werte wie beim vorherigen "SectionTitle" fest.
Handwerksplatz:
Der Handwerksplatz besteht aus einem Hintergrundbild, einem Gegenstandsbild und einem Zähltext:
- Erstellen Sie ein neues Bild, indem Sie mit der rechten Maustaste auf Canvas-Objekt -> Benutzeroberfläche -> Bild klicken
- Benennen Sie das neue Bild in "slot_template" um, setzen Sie seine RectTransform-Werte auf (Post
- Duplizieren Sie "slot_template" und benennen Sie es in "Item" um, verschieben Sie es in das "slot_template"-Objekt, ändern Sie seine RectTransform-Abmessungen in (Breite: 30, Höhe: 30) und seine Farbe in (1, 1, 1, 1).
- Erstellen Sie einen neuen Text, indem Sie mit der rechten Maustaste auf das Objekt "slot_template" -> Benutzeroberfläche -> Text klicken und ihn umbenennen "Count"
- Ändern Sie "Count" RectTransform-Werte in („Unten rechts ausrichten“ Pivot X: 1 Pivot Y: 0 Pos X: 0 Pos Y: 0 Breite: 30 Höhe: 30)
- Setzen Sie "Count" Text auf eine Zufallszahl (z. B. 12), Schriftart auf Fett, Schriftgröße auf 14, Ausrichtung auf rechts unten und Farbe auf (1, 1, 1, 1).
- Fügen Sie die Schattenkomponente zu "Count" Text hinzu und stellen Sie die Effektfarbe auf (0, 0, 0, 0,5) ein.
Das Endergebnis sollte so aussehen:
Ergebnis-Slot (wird für Herstellungsergebnisse verwendet):
- Duplizieren Sie das "slot_template"-Objekt und benennen Sie es in um "result_slot_template"
- Ändern Sie die Breite und Höhe von "result_slot_template" auf 50
Crafting-Button und zusätzliche Grafiken:
- Erstellen Sie eine neue Schaltfläche, indem Sie mit der rechten Maustaste auf das Objekt "CraftingSlots" -> Benutzeroberfläche -> Schaltfläche klicken und sie umbenennen "CraftButton"
- Setzen Sie "CraftButton" RectTransform-Werte auf („Middle Left align“ Pivot X: 1 Pivot Y: 0,5 Pos X: 0 Pos Y: 0 Breite: 40 Höhe: 40)
- Ändern Sie den Text von "CraftButton" in "Craft"
- Erstellen Sie ein neues Bild, indem Sie mit der rechten Maustaste auf "CraftingSlots" Objekt -> Benutzeroberfläche -> Bild klicken und es umbenennen "Arrow"
- Setzen Sie "Arrow" RectTransform-Werte auf („Middle Right align“ Pivot X: 0 Pivot Y: 0,5 Pos X: 10 Pos Y: 0 Breite: 30 Höhe: 30)
Als Quellbild können Sie das Bild unten verwenden (Rechtsklick -> Speichern unter..., um es herunterzuladen). Stellen Sie nach dem Import den Texturtyp auf "Sprite (2D and UI)" und den Filtermodus auf ein "Point (no filter)"
- Klicken Sie mit der rechten Maustaste auf "CraftingSlots" -> Leere erstellen und benennen Sie es in "ResultSlot" um. Dieses Objekt enthält den Ergebnisslot
- Setzen Sie "ResultSlot" RectTransform-Werte auf („Middle Right align“ Pivot X: 0 Pivot Y: 0,5 Pos X: 50 Pos Y: 0 Breite: 50 Höhe: 50)
Das UI-Setup ist fertig.
Schritt 2: Programmieren Sie das Crafting-System
Dieses Handwerkssystem besteht aus zwei Skripten, SC_ItemCrafting.cs und SC_SlotTemplate.cs
- Erstellen ein neues Skript, nennen Sie es "SC_ItemCrafting" und fügen Sie dann den folgenden Code ein:
SC_ItemCrafting.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SC_ItemCrafting : MonoBehaviour
{
public RectTransform playerSlotsContainer;
public RectTransform craftingSlotsContainer;
public RectTransform resultSlotContainer;
public Button craftButton;
public SC_SlotTemplate slotTemplate;
public SC_SlotTemplate resultSlotTemplate;
[System.Serializable]
public class SlotContainer
{
public Sprite itemSprite; //Sprite of the assigned item (Must be the same sprite as in items array), or leave null for no item
public int itemCount; //How many items in this slot, everything equal or under 1 will be interpreted as 1 item
[HideInInspector]
public int tableID;
[HideInInspector]
public SC_SlotTemplate slot;
}
[System.Serializable]
public class Item
{
public Sprite itemSprite;
public bool stackable = false; //Can this item be combined (stacked) together?
public string craftRecipe; //Item Keys that are required to craft this item, separated by comma (Tip: Use Craft Button in Play mode and see console for printed recipe)
}
public SlotContainer[] playerSlots;
SlotContainer[] craftSlots = new SlotContainer[9];
SlotContainer resultSlot = new SlotContainer();
//List of all available items
public Item[] items;
SlotContainer selectedItemSlot = null;
int craftTableID = -1; //ID of table where items will be placed one at a time (ex. Craft table)
int resultTableID = -1; //ID of table from where we can take items, but cannot place to
ColorBlock defaultButtonColors;
// Start is called before the first frame update
void Start()
{
//Setup slot element template
slotTemplate.container.rectTransform.pivot = new Vector2(0, 1);
slotTemplate.container.rectTransform.anchorMax = slotTemplate.container.rectTransform.anchorMin = new Vector2(0, 1);
slotTemplate.craftingController = this;
slotTemplate.gameObject.SetActive(false);
//Setup result slot element template
resultSlotTemplate.container.rectTransform.pivot = new Vector2(0, 1);
resultSlotTemplate.container.rectTransform.anchorMax = resultSlotTemplate.container.rectTransform.anchorMin = new Vector2(0, 1);
resultSlotTemplate.craftingController = this;
resultSlotTemplate.gameObject.SetActive(false);
//Attach click event to craft button
craftButton.onClick.AddListener(PerformCrafting);
//Save craft button default colors
defaultButtonColors = craftButton.colors;
//InitializeItem Crafting Slots
InitializeSlotTable(craftingSlotsContainer, slotTemplate, craftSlots, 5, 0);
UpdateItems(craftSlots);
craftTableID = 0;
//InitializeItem Player Slots
InitializeSlotTable(playerSlotsContainer, slotTemplate, playerSlots, 5, 1);
UpdateItems(playerSlots);
//InitializeItemResult Slot
InitializeSlotTable(resultSlotContainer, resultSlotTemplate, new SlotContainer[] { resultSlot }, 0, 2);
UpdateItems(new SlotContainer[] { resultSlot });
resultTableID = 2;
//Reset Slot element template (To be used later for hovering element)
slotTemplate.container.rectTransform.pivot = new Vector2(0.5f, 0.5f);
slotTemplate.container.raycastTarget = slotTemplate.item.raycastTarget = slotTemplate.count.raycastTarget = false;
}
void InitializeSlotTable(RectTransform container, SC_SlotTemplate slotTemplateTmp, SlotContainer[] slots, int margin, int tableIDTmp)
{
int resetIndex = 0;
int rowTmp = 0;
for (int i = 0; i < slots.Length; i++)
{
if (slots[i] == null)
{
slots[i] = new SlotContainer();
}
GameObject newSlot = Instantiate(slotTemplateTmp.gameObject, container.transform);
slots[i].slot = newSlot.GetComponent<SC_SlotTemplate>();
slots[i].slot.gameObject.SetActive(true);
slots[i].tableID = tableIDTmp;
float xTmp = (int)((margin + slots[i].slot.container.rectTransform.sizeDelta.x) * (i - resetIndex));
if (xTmp + slots[i].slot.container.rectTransform.sizeDelta.x + margin > container.rect.width)
{
resetIndex = i;
rowTmp++;
xTmp = 0;
}
slots[i].slot.container.rectTransform.anchoredPosition = new Vector2(margin + xTmp, -margin - ((margin + slots[i].slot.container.rectTransform.sizeDelta.y) * rowTmp));
}
}
//Update Table UI
void UpdateItems(SlotContainer[] slots)
{
for (int i = 0; i < slots.Length; i++)
{
Item slotItem = FindItem(slots[i].itemSprite);
if (slotItem != null)
{
if (!slotItem.stackable)
{
slots[i].itemCount = 1;
}
//Apply total item count
if (slots[i].itemCount > 1)
{
slots[i].slot.count.enabled = true;
slots[i].slot.count.text = slots[i].itemCount.ToString();
}
else
{
slots[i].slot.count.enabled = false;
}
//Apply item icon
slots[i].slot.item.enabled = true;
slots[i].slot.item.sprite = slotItem.itemSprite;
}
else
{
slots[i].slot.count.enabled = false;
slots[i].slot.item.enabled = false;
}
}
}
//Find Item from the items list using sprite as reference
Item FindItem(Sprite sprite)
{
if (!sprite)
return null;
for (int i = 0; i < items.Length; i++)
{
if (items[i].itemSprite == sprite)
{
return items[i];
}
}
return null;
}
//Find Item from the items list using recipe as reference
Item FindItem(string recipe)
{
if (recipe == "")
return null;
for (int i = 0; i < items.Length; i++)
{
if (items[i].craftRecipe == recipe)
{
return items[i];
}
}
return null;
}
//Called from SC_SlotTemplate.cs
public void ClickEventRecheck()
{
if (selectedItemSlot == null)
{
//Get clicked slot
selectedItemSlot = GetClickedSlot();
if (selectedItemSlot != null)
{
if (selectedItemSlot.itemSprite != null)
{
selectedItemSlot.slot.count.color = selectedItemSlot.slot.item.color = new Color(1, 1, 1, 0.5f);
}
else
{
selectedItemSlot = null;
}
}
}
else
{
SlotContainer newClickedSlot = GetClickedSlot();
if (newClickedSlot != null)
{
bool swapPositions = false;
bool releaseClick = true;
if (newClickedSlot != selectedItemSlot)
{
//We clicked on the same table but different slots
if (newClickedSlot.tableID == selectedItemSlot.tableID)
{
//Check if new clicked item is the same, then stack, if not, swap (Unless it's a crafting table, then do nothing)
if (newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
{
Item slotItem = FindItem(selectedItemSlot.itemSprite);
if (slotItem.stackable)
{
//Item is the same and is stackable, remove item from previous position and add its count to a new position
selectedItemSlot.itemSprite = null;
newClickedSlot.itemCount += selectedItemSlot.itemCount;
selectedItemSlot.itemCount = 0;
}
else
{
swapPositions = true;
}
}
else
{
swapPositions = true;
}
}
else
{
//Moving to different table
if (resultTableID != newClickedSlot.tableID)
{
if (craftTableID != newClickedSlot.tableID)
{
if (newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
{
Item slotItem = FindItem(selectedItemSlot.itemSprite);
if (slotItem.stackable)
{
//Item is the same and is stackable, remove item from previous position and add its count to a new position
selectedItemSlot.itemSprite = null;
newClickedSlot.itemCount += selectedItemSlot.itemCount;
selectedItemSlot.itemCount = 0;
}
else
{
swapPositions = true;
}
}
else
{
swapPositions = true;
}
}
else
{
if (newClickedSlot.itemSprite == null || newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
{
//Add 1 item from selectedItemSlot
newClickedSlot.itemSprite = selectedItemSlot.itemSprite;
newClickedSlot.itemCount++;
selectedItemSlot.itemCount--;
if (selectedItemSlot.itemCount <= 0)
{
//We placed the last item
selectedItemSlot.itemSprite = null;
}
else
{
releaseClick = false;
}
}
else
{
swapPositions = true;
}
}
}
}
}
if (swapPositions)
{
//Swap items
Sprite previousItemSprite = selectedItemSlot.itemSprite;
int previousItemConunt = selectedItemSlot.itemCount;
selectedItemSlot.itemSprite = newClickedSlot.itemSprite;
selectedItemSlot.itemCount = newClickedSlot.itemCount;
newClickedSlot.itemSprite = previousItemSprite;
newClickedSlot.itemCount = previousItemConunt;
}
if (releaseClick)
{
//Release click
selectedItemSlot.slot.count.color = selectedItemSlot.slot.item.color = Color.white;
selectedItemSlot = null;
}
//Update UI
UpdateItems(playerSlots);
UpdateItems(craftSlots);
UpdateItems(new SlotContainer[] { resultSlot });
}
}
}
SlotContainer GetClickedSlot()
{
for (int i = 0; i < playerSlots.Length; i++)
{
if (playerSlots[i].slot.hasClicked)
{
playerSlots[i].slot.hasClicked = false;
return playerSlots[i];
}
}
for (int i = 0; i < craftSlots.Length; i++)
{
if (craftSlots[i].slot.hasClicked)
{
craftSlots[i].slot.hasClicked = false;
return craftSlots[i];
}
}
if (resultSlot.slot.hasClicked)
{
resultSlot.slot.hasClicked = false;
return resultSlot;
}
return null;
}
void PerformCrafting()
{
string[] combinedItemRecipe = new string[craftSlots.Length];
craftButton.colors = defaultButtonColors;
for (int i = 0; i < craftSlots.Length; i++)
{
Item slotItem = FindItem(craftSlots[i].itemSprite);
if (slotItem != null)
{
combinedItemRecipe[i] = slotItem.itemSprite.name + (craftSlots[i].itemCount > 1 ? "(" + craftSlots[i].itemCount + ")" : "");
}
else
{
combinedItemRecipe[i] = "";
}
}
string combinedRecipe = string.Join(",", combinedItemRecipe);
print(combinedRecipe);
//Search if recipe match any of the item recipe
Item craftedItem = FindItem(combinedRecipe);
if (craftedItem != null)
{
//Clear Craft slots
for (int i = 0; i < craftSlots.Length; i++)
{
craftSlots[i].itemSprite = null;
craftSlots[i].itemCount = 0;
}
resultSlot.itemSprite = craftedItem.itemSprite;
resultSlot.itemCount = 1;
UpdateItems(craftSlots);
UpdateItems(new SlotContainer[] { resultSlot });
}
else
{
ColorBlock colors = craftButton.colors;
colors.selectedColor = colors.pressedColor = new Color(0.8f, 0.55f, 0.55f, 1);
craftButton.colors = colors;
}
}
// Update is called once per frame
void Update()
{
//Slot UI follow mouse position
if (selectedItemSlot != null)
{
if (!slotTemplate.gameObject.activeSelf)
{
slotTemplate.gameObject.SetActive(true);
slotTemplate.container.enabled = false;
//Copy selected item values to slot template
slotTemplate.count.color = selectedItemSlot.slot.count.color;
slotTemplate.item.sprite = selectedItemSlot.slot.item.sprite;
slotTemplate.item.color = selectedItemSlot.slot.item.color;
}
//Make template slot follow mouse position
slotTemplate.container.rectTransform.position = Input.mousePosition;
//Update item count
slotTemplate.count.text = selectedItemSlot.slot.count.text;
slotTemplate.count.enabled = selectedItemSlot.slot.count.enabled;
}
else
{
if (slotTemplate.gameObject.activeSelf)
{
slotTemplate.gameObject.SetActive(false);
}
}
}
}
- Erstellen Sie ein neues Skript, nennen Sie es "SC_SlotTemplate" und fügen Sie dann den folgenden Code ein:
SC_SlotTemplate.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class SC_SlotTemplate : MonoBehaviour, IPointerClickHandler
{
public Image container;
public Image item;
public Text count;
[HideInInspector]
public bool hasClicked = false;
[HideInInspector]
public SC_ItemCrafting craftingController;
//Do this when the mouse is clicked over the selectable object this script is attached to.
public void OnPointerClick(PointerEventData eventData)
{
hasClicked = true;
craftingController.ClickEventRecheck();
}
}
Vorbereiten von Slot-Vorlagen:
- Hängen Sie das SC_SlotTemplate-Skript an das "slot_template"-Objekt an und weisen Sie dessen Variablen zu (Bildkomponente auf demselben Objekt geht an die "Container"-Variable, untergeordnetes "Item"-Bild geht an die "Item"-Variable und ein untergeordnetes "Count" Text geht an die Variable "Count")
- Wiederholen Sie den gleichen Vorgang für das "result_slot_template"-Objekt (hängen Sie das SC_SlotTemplate-Skript daran an und weisen Sie Variablen auf die gleiche Weise zu).
Vorbereitung des Bastelsystems:
- Hängen Sie das SC_ItemCrafting-Skript an das Canvas-Objekt an und weisen Sie dessen Variablen zu. Das Objekt „PlayerSlots“ geht an die Variable "Player Slots Container", das Objekt "CraftingSlots" geht an die Variable "Crafting Slots Container", das Objekt "ResultSlot" geht an die Variable "Result Slot Container" Variable, "CraftButton"-Objekt geht an "Craft Button"-Variable, "slot_template"-Objekt mit angehängtem SC_SlotTemplate-Skript geht an "Slot Template"-Variable und "result_slot_template"-Objekt mit angehängtem SC_SlotTemplate-Skript geht an "Result Slot Template"-Variable):
Wie Sie bereits bemerkt haben, gibt es zwei leere Arrays mit den Namen "Player Slots" und "Items". "Player Slots" enthält die Anzahl der verfügbaren Plätze (mit Gegenstand oder leer) und "Items" enthält alle verfügbaren Gegenstände zusammen mit ihren Rezepten (optional).
Elemente einrichten:
Schauen Sie sich die Sprites unten an (in meinem Fall habe ich 5 Elemente):
(Felsen)
(Diamant)
(Holz)
(Schwert)
(diamond_sword)
- Laden Sie jedes Sprite herunter (Rechtsklick -> Speichern unter...) und importieren Sie es in Ihr Projekt (setzen Sie in den Importeinstellungen den Texturtyp auf "Sprite (2D and UI)" und den Filtermodus auf "Point (no filter)"
- Ändern Sie in SC_ItemCrafting die Items-Größe auf 5 und weisen Sie jedes Sprite der Item-Sprite-Variablen zu.
"Stackable" Die Variable steuert, ob die Gegenstände in einem Steckplatz gestapelt werden können (z. B. möchten Sie möglicherweise nur das Stapeln einfacher Materialien wie Stein, Diamant und Holz zulassen).
"Craft Recipe" Variable steuert, ob dieser Gegenstand hergestellt werden kann (leer bedeutet, dass er nicht hergestellt werden kann)
- Stellen Sie für "Player Slots" die Array-Größe auf 27 ein (am besten geeignet für das aktuelle Crafting-Panel, Sie können jedoch eine beliebige Zahl festlegen).
Wenn Sie auf „Play“ drücken, werden Sie feststellen, dass die Slots korrekt initialisiert sind, aber keine Elemente vorhanden sind:
Um jedem Slot einen Artikel hinzuzufügen, müssen wir der Variablen "Item Sprite" ein Artikel-Sprite zuweisen und "Item Count" auf eine beliebige positive Zahl setzen (alles unter 1 und/oder nicht stapelbare Artikel werden als 1 interpretiert).:
- Weisen Sie das Sprite "rock" Element 0 / "Item Count" 14, das Sprite "wood" Element 1 / "Item Count" 8 und das Sprite "diamond" Element 2 / "Item Count" 8 zu (Stellen Sie sicher, dass die Sprites mit denen identisch sind im "Items" Array, sonst funktioniert es nicht).
Die Gegenstände sollten nun in den Spielerplätzen erscheinen. Sie können ihre Position ändern, indem Sie auf den Gegenstand und dann auf den Platz klicken, in den Sie ihn verschieben möchten.
Bastelrezepte:
Durch das Herstellen von Rezepten können Sie einen Gegenstand erstellen, indem Sie andere Gegenstände in einer bestimmten Reihenfolge kombinieren:
Das Format für das Herstellungsrezept ist wie folgt: [item_sprite_name]([item count])*optional... 9 Mal wiederholt, durch Komma (,) getrennt
Eine einfache Möglichkeit, das Rezept zu entdecken, besteht darin, auf „Play“ zu drücken, dann die Gegenstände in der Reihenfolge zu platzieren, die Sie herstellen möchten, dann auf "Craft" zu drücken und anschließend (Strg + Umschalt + C) zu drücken, um die Unity-Konsole zu öffnen und die zu sehen Neu gedruckte Zeile (Sie können mehrmals auf "Craft" klicken, um die Zeile erneut zu drucken). Die gedruckte Zeile ist das Herstellungsrezept.
Die folgende Kombination entspricht beispielsweise diesem Rezept: rock,,rock,,rock,,rock,,wood (HINWEIS: Es kann für Sie anders sein, wenn Ihre Sprites unterschiedliche Namen haben).
Wir werden das obige Rezept verwenden, um ein Schwert herzustellen.
- Kopieren Sie die gedruckte Zeile und fügen Sie sie im Array "Items" in die Variable "Craft Recipe" unter "sword" ein.
Wenn Sie nun dieselbe Kombination wiederholen, sollten Sie in der Lage sein, ein Schwert herzustellen.
Das Rezept für ein Diamantschwert ist das gleiche, aber statt Stein ist es Diamant:
Das Crafting-System ist jetzt fertig.