Att göra inventering och produkttillverkningssystem i enhet

I den här handledningen kommer jag att visa hur man gör ett Minecraft-stil inventeringssystem och system för att skapa föremål i Unity.

Att skapa föremål i videospel är en process för att kombinera specifika (vanligtvis enklare) föremål till mer komplexa föremål, med nya och förbättrade egenskaper. Till exempel att kombinera trä och sten till en hacka, eller kombinera metallplåt och trä till ett svärd.

Hantverkssystemet nedan är mobilvänligt och helt automatiserat, vilket innebär att det fungerar med vilken UI-layout som helst och med möjligheten att skapa anpassade hantverksrecept.

Sharp Coder Videospelare

Steg 1: Konfigurera Crafting UI

Vi börjar med att ställa in gränssnittet för att skapa:

  • Skapa en ny arbetsyta (Unity Översta aktivitetsfältet: GameObject -> UI -> Canvas)
  • Skapa en ny bild genom att högerklicka på Canvas Object -> UI -> Image
  • Byt namn på bildobjektet till "CraftingPanel" och ändra dess källbild till standard "UISprite"
  • Ändra "CraftingPanel" RectTransform-värden till (Pos X: 0 Pos Y: 0 Bredd: 410 Höjd: 365)

  • Skapa två objekt inuti "CraftingPanel" (Högerklicka på CraftingPanel -> Skapa tomt, 2 gånger)
  • Byt namn på det första objektet till "CraftingSlots" och ändra dess RectTransform-värden till ("Top Left align" Pivot X: 0 Pivot Y: 1 Pos X: 50 Pos Y: -35 Bredd: 140 Höjd: 140). Detta objekt kommer att innehålla crafting slots.
  • Byt namn på det andra objektet till "PlayerSlots" och ändra dess RectTransform-värden till ("Toppsträcka horisontellt" Pivot X: 0,5 Pivot Y: 1 Vänster: 0 Pos Y: -222 Höger: 0 Höjd: 100). Detta objekt kommer att innehålla spelautomater.

Avsnittsrubrik:

  • Skapa ny text genom att högerklicka på "PlayerSlots" Objekt -> UI -> Text och byt namn på den till "SectionTitle"
  • Ändra "SectionTitle" RectTransform-värden till ("Top Left align" Pivot X: 0 Pivot Y: 0 Pos X: 5 Pos Y: 0 Bredd: 160 Höjd: 30)
  • Ändra "SectionTitle"-texten till "Inventory" och ställ in dess teckensnittsstorlek till 18, justering till vänster i mitten och färg till (0,2, 0,2, 0,2, 1)
  • Duplicera "SectionTitle"-objektet, ändra dess text till "Crafting" och flytta det under "CraftingSlots"-objektet, ställ sedan in samma RectTransform-värden som föregående "SectionTitle".

Crafting Slot:

Hantverksfacket kommer att bestå av en bakgrundsbild, en artikelbild och en räknetext:

  • Skapa en ny bild genom att högerklicka på Canvas Object -> UI -> Image
  • Byt namn på den nya bilden till "slot_template", ställ in dess RectTransform-värden till (Post X: 0 Pos Y: 0 Bredd: 40 Höjd: 40) och ändra dess färg till (0,32, 0,32, 0,32, 0,8)
  • Duplicera "slot_template" och byt namn på det till "Item", flytta det inuti "slot_template" Objekt, ändra dess RectTransform-dimensioner till (Bredd: 30 Höjd: 30) och Färg till (1, 1, 1, 1)
  • Skapa ny text genom att högerklicka på "slot_template" Objekt -> UI -> Text och byt namn på den till "Count"
  • Ändra "Count" RectTransform värden till ("Bottom Right align" Pivot X: 1 Pivot Y: 0 Pos X: 0 Pos Y: 0 Bredd: 30 Höjd: 30)
  • Ställ in "Count" Text till ett slumpmässigt tal (ex. 12), Teckensnitt till fetstil, Teckenstorlek till 14, Justering till höger botten och Färg till (1, 1, 1, 1)
  • Lägg till Shadow-komponent till "Count" Text och ställ in Effektfärg till (0, 0, 0, 0,5)

Slutresultatet ska se ut så här:

Resultatplats (som kommer att användas för att skapa resultat):

  • Duplicera "slot_template"-objektet och byt namn på det till "result_slot_template"
  • Ändra bredden och höjden på "result_slot_template" till 50

Hantverksknapp och ytterligare grafik:

  • Skapa en ny knapp genom att högerklicka på "CraftingSlots" Objekt -> UI -> Knapp och byt namn på den till "CraftButton"
  • Ställ in "CraftButton" RectTransform-värden till ("Middle Left align" Pivot X: 1 Pivot Y: 0,5 Pos X: 0 Pos Y: 0 Bredd: 40 Höjd: 40)
  • Ändra text av "CraftButton" till "Craft"

  • Skapa en ny bild genom att högerklicka på "CraftingSlots" Objekt -> UI -> Bild och byt namn på den till "Arrow"
  • Ställ in "Arrow" RectTransform-värden till ("Middle Right align" Pivot X: 0 Pivot Y: 0,5 Pos X: 10 Pos Y: 0 Bredd: 30 Höjd: 30)

För källbilden kan du använda bilden nedan (Högerklicka -> Spara som.. för att ladda ner den). Efter import ställ in dess texturtyp till "Sprite (2D and UI)" och filterläge till "Point (no filter)"

Höger pilikon Pixel

  • Högerklicka på "CraftingSlots" -> Skapa tom och döp om det till "ResultSlot", detta objekt kommer att innehålla resultatluckan
  • Ställ in "ResultSlot" RectTransform-värden till ("Middle Right align" Pivot X: 0 Pivot Y: 0,5 Pos X: 50 Pos Y: 0 Bredd: 50 Höjd: 50)

Användargränssnittskonfigurationen är klar.

Steg 2: Program Crafting System

Detta hantverkssystem kommer att bestå av 2 skript, SC_ItemCrafting.cs och SC_SlotTemplate.cs

  • Skapa ett nytt skript, namnge det "SC_ItemCrafting" och klistra sedan in koden nedan i det:

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);
            }
        }
    }
}
  • Skapa ett nytt skript, namnge det "SC_SlotTemplate" och klistra sedan in koden nedan i det:

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();
    }
}

Förbereda platsmallar:

  • Bifoga SC_SlotTemplate-skriptet till "slot_template"-objektet och tilldela dess variabler (Bildkomponenten på samma Objekt går till "Container"-variabeln, underordnad "Item" Bilden går till "Item"-variabeln och en underordnad "Count" Text går till "Count" variabel)
  • Upprepa samma process för "result_slot_template"-objektet (bifoga SC_SlotTemplate-skriptet till det och tilldela variabler på samma sätt).

Förbereder hantverkssystem:

  • Bifoga SC_ItemCrafting-skriptet till Canvas-objektet och tilldela dess variabler ("PlayerSlots" Objekt går till "Player Slots Container" variabeln, "CraftingSlots" Objekt går till "Crafting Slots Container" variabeln, "ResultSlot" Objekt går till "Result Slot Container" variabel, "CraftButton" Objekt går till "Craft Button" variabel, "slot_template" Objekt med SC_SlotTemplate-skript bifogat går till "Slot Template" variabel och "result_slot_template" Objekt med SC_SlotTemplate-skript bifogat går till "Result Slot Template" variabel):

Som du redan märkt finns det två tomma arrayer som heter "Player Slots" och "Items". "Player Slots" kommer att innehålla antalet tillgängliga platser (med objekt eller tomma) och "Items" kommer att innehålla alla tillgängliga artiklar tillsammans med deras recept (valfritt).

Ställa in objekt:

Kolla sprites nedan (i mitt fall kommer jag att ha 5 objekt):

Rock objekt (sten)

Diamantobjekt (diamant)

Trä objekt (trä)

Svärd objekt (svärd)

Diamantsvärd (diamant_sword)

  • Ladda ner varje sprite (högerklicka -> Spara som...) och importera dem till ditt projekt (i importinställningar ställ in deras texturtyp till "Sprite (2D and UI)" och filterläge till "Point (no filter)"

  • I SC_ItemCrafting ändra objektstorlek till 5 och tilldela varje sprite till variabeln Item Sprite.

"Stackable" variabel styr om föremålen kan staplas ihop till en plats (t.ex. vill du kanske bara tillåta stapling för enkla material som sten, diamant och trä).

"Craft Recipe" variabel styr om det här objektet kan skapas (tom betyder att det inte kan skapas)

  • För "Player Slots" ställ in Array Size till 27 (passar bäst för den nuvarande Crafting Panel, men du kan ställa in valfritt antal).

När du trycker på Play kommer du att märka att slotsen är korrekt initierade, men det finns inga objekt:

För att lägga till ett objekt till varje plats måste vi tilldela ett objekt Sprite till variabeln "Item Sprite" och ställa in "Item Count" till valfritt positivt tal (allt under 1, och/eller icke-staplingsbara objekt kommer att tolkas som 1):

  • Tilldela "rock"-spriten till Element 0 / "Item Count" 14, "wood"-spriten till Element 1 / "Item Count" 8, "diamond"-spriten till Element 2 / "Item Count" 8 (Se till att spriten är desamma som i "Items" Array, annars fungerar det inte).

Föremålen ska nu visas i Player Slots, du kan ändra deras position genom att klicka på objektet och sedan klicka på den plats du vill flytta den till.

Hantverksrecept:

Genom att skapa recept kan du skapa ett föremål genom att kombinera andra föremål i en specifik ordning:

Formatet för att skapa recept är som följer: [item_sprite_name]([artikelantal])*valfritt... upprepas 9 gånger, avgränsade med kommatecken (,)

Ett enkelt sätt att upptäcka receptet är genom att trycka på Play, sedan placera föremålen i den ordning du vill skapa, sedan trycka på "Craft", efter det, tryck på (Ctrl + Shift + C) för att öppna Unity Console och se nytryckt rad (Du kan klicka på "Craft" flera gånger för att skriva ut raden igen), den utskrivna raden är receptet för hantverk.

Till exempel, kombinationen nedan motsvarar detta recept: rock,,rock,,rock,,rock,,wood (OBS: det kan vara annorlunda för dig om dina sprites har olika namn).

Recept för att tillverka svärdobjekt

Vi kommer att använda receptet ovan för att tillverka ett svärd.

  • Kopiera den utskrivna raden och i "Items" Array klistra in den i "Craft Recipe" variabeln under "sword" Objekt:

När du nu upprepar samma kombination bör du kunna skapa ett svärd.

Ett recept på ett diamantsvärd är detsamma, men istället för rock är det diamant:

Diamond Item Sword Recept Unity Inspector

Unity Inventory System och Item Crafting

Hantverkssystemet är nu klart.

Källa
📁ItemCrafting.unitypackage36.13 KB
Föreslagna artiklar
Hur man triggar en mellansekvens i Unity
Koda ett enkelt inventeringssystem med UI Dra och släpp i Unity
Introduktion till State Machine in Unity
Öppna lådor och skåp med specifika nycklar i Unity
Plocka och släpp-system utan lager i Unity
Att göra turbaserade spel i Unity
Omfattande guide för att transformera rotation i enhet