Lage inventar og vareutformingssystem i enhet

I denne opplæringen vil jeg vise hvordan du lager et Minecraft-stil inventar og varelagingssystem i Unity.

Vareutforming i videospill er en prosess for å kombinere spesifikke (vanligvis enklere) gjenstander til mer komplekse gjenstander, med nye og forbedrede egenskaper. For eksempel å kombinere tre og stein til en hakke, eller kombinere metallplate og tre til et sverd.

Håndverkssystemet nedenfor er mobilvennlig og helautomatisert, noe som betyr at det vil fungere med alle UI-layout og med muligheten til å lage tilpassede håndverksoppskrifter.

Sharp Coder Videospiller

Trinn 1: Sett opp Crafting UI

Vi begynner med å sette opp håndverksgrensesnittet:

  • Lag et nytt lerret (Unity Toppoppgavelinje: GameObject -> UI -> Canvas)
  • Lag et nytt bilde ved å høyreklikke på Canvas Object -> UI -> Image
  • Gi bildeobjektet nytt navn til "CraftingPanel" og endre kildebildet til standard "UISprite"
  • Endre "CraftingPanel" RectTransform-verdier til (Pos X: 0 Pos Y: 0 Bredde: 410 Høyde: 365)

  • Lag to objekter i "CraftingPanel" (Høyreklikk på CraftingPanel -> Create Empty, 2 ganger)
  • Gi nytt navn til det første objektet til "CraftingSlots" og endre dets RectTransform-verdier til ("Topp venstrejustering" Pivot X: 0 Pivot Y: 1 Pos X: 50 Pos Y: -35 Bredde: 140 Høyde: 140). Dette objektet vil inneholde spilleautomater.
  • Gi nytt navn til det andre objektet til "PlayerSlots" og endre dets RectTransform-verdier til ("Toppstrekk horisontalt" Pivot X: 0,5 Pivot Y: 1 Venstre: 0 Pos Y: -222 Høyre: 0 Høyde: 100). Dette objektet vil inneholde spilleautomater.

Seksjonstittel:

  • Lag ny tekst ved å høyreklikke på "PlayerSlots" Objekt -> UI -> Tekst og gi den nytt navn til "SectionTitle"
  • Endre "SectionTitle" RectTransform-verdier til ("Topp venstrejustering" Pivot X: 0 Pivot Y: 0 Pos X: 5 Pos Y: 0 Bredde: 160 Høyde: 30)
  • Endre "SectionTitle"-teksten til "Inventory" og sett dens skriftstørrelse til 18, justering til venstre midt og farge til (0.2, 0.2, 0.2, 1)
  • Dupliser "SectionTitle"-objektet, endre teksten til "Crafting" og flytt det under "CraftingSlots"-objektet, og sett deretter de samme RectTransform-verdiene som forrige "SectionTitle".

Håndverksspor:

Håndverkssporet vil bestå av et bakgrunnsbilde, et elementbilde og en telletekst:

  • Lag et nytt bilde ved å høyreklikke på Canvas Object -> UI -> Image
  • Gi nytt navn til det nye bildet til "slot_template", sett RectTransform-verdiene til (Post X: 0 Pos Y: 0 Bredde: 40 Høyde: 40), og endre fargen til (0.32, 0.32, 0.32, 0.8)
  • Dupliser "slot_template" og gi det nytt navn til "Item", flytt det inn i "slot_template"-objektet, endre dets RectTransform-dimensjoner til (Bredde: 30 Høyde: 30) og Farge til (1, 1, 1, 1)
  • Lag ny tekst ved å høyreklikke på "slot_template" Objekt -> UI -> Tekst og gi det nytt navn til "Count"
  • Endre "Count" RectTransform-verdier til ("Bottom Right align" Pivot X: 1 Pivot Y: 0 Pos X: 0 Pos Y: 0 Bredde: 30 Høyde: 30)
  • Sett "Count" tekst til et tilfeldig tall (f.eks. 12), skriftstil til fet skrift, skriftstørrelse til 14, justering til høyre bunn og farge til (1, 1, 1, 1)
  • Legg til Shadow-komponent til "Count" Tekst og sett Effektfarge til (0, 0, 0, 0,5)

Sluttresultatet skal se slik ut:

Resultatspor (som vil bli brukt til å lage resultater):

  • Dupliser "slot_template"-objektet og gi det nytt navn til "result_slot_template"
  • Endre bredden og høyden på "result_slot_template" til 50

Lageknapp og tilleggsgrafikk:

  • Opprett en ny knapp ved å høyreklikke på "CraftingSlots" Objekt -> UI -> Knapp og gi den nytt navn til "CraftButton"
  • Sett "CraftButton" RectTransform-verdier til ("Midt venstrejustering" Pivot X: 1 Pivot Y: 0,5 Pos X: 0 Pos Y: 0 Bredde: 40 Høyde: 40)
  • Endre tekst av "CraftButton" til "Craft"

  • Lag et nytt bilde ved å høyreklikke på "CraftingSlots" Objekt -> UI -> Bilde og gi det nytt navn til "Arrow"
  • Sett "Arrow" RectTransform-verdier til ("Middle Right align" Pivot X: 0 Pivot Y: 0,5 Pos X: 10 Pos Y: 0 Bredde: 30 Høyde: 30)

For kildebildet kan du bruke bildet nedenfor (Høyreklikk -> Lagre som.. for å laste det ned). Etter import, sett teksturtypen til "Sprite (2D and UI)" og filtermodus til "Point (no filter)"

Pil høyre ikon Pixel

  • Høyreklikk på "CraftingSlots" -> Opprett tom og gi det nytt navn til "ResultSlot", dette objektet vil inneholde resultatsporet
  • Sett "ResultSlot" RectTransform-verdier til ("Middle Right align" Pivot X: 0 Pivot Y: 0,5 Pos X: 50 Pos Y: 0 Bredde: 50 Høyde: 50)

UI-oppsettet er klart.

Trinn 2: Program Crafting System

Dette håndverkssystemet vil bestå av 2 skript, SC_ItemCrafting.cs og SC_SlotTemplate.cs

  • Lag et nytt skript, navngi det "SC_ItemCrafting" og lim inn koden nedenfor 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);
            }
        }
    }
}
  • Lag et nytt skript, navngi det "SC_SlotTemplate" og lim inn koden nedenfor 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();
    }
}

Forbereder spormaler:

  • Fest SC_SlotTemplate-skriptet til "slot_template"-objektet, og tilordne dets variabler (Bildekomponenten på det samme objektet går til "Container"-variabelen, underordnet "Item" Bildet går til "Item"-variabelen, og et underordnet "Count" Tekst går til "Count" variabel)
  • Gjenta den samme prosessen for "result_slot_template"-objektet (fest SC_SlotTemplate-skriptet til det og tilordne variabler på samme måte).

Klargjøring av håndverkssystem:

  • Fest SC_ItemCrafting-skriptet til Canvas-objektet, og tilordne dets variabler ("PlayerSlots"-objektet går til "Player Slots Container"-variabelen, "CraftingSlots"-objektet går til "Crafting Slots Container"-variabelen, "ResultSlot"-objektet går til "Result Slot Container" variabel, "CraftButton" Objekt går til "Craft Button" variabel, "slot_template" Objekt med SC_SlotTemplate-skript vedlagt går til "Slot Template" variabel og "result_slot_template" Objekt med SC_SlotTemplate-skript vedlagt går til "Result Slot Template" variabel):

Som du allerede har lagt merke til, er det to tomme arrays kalt "Player Slots" og "Items". "Player Slots" vil inneholde antall tilgjengelige plasser (med element eller tom) og "Items" vil inneholde alle tilgjengelige elementer sammen med oppskriftene deres (valgfritt).

Sette opp elementer:

Sjekk spritene nedenfor (i mitt tilfelle vil jeg ha 5 elementer):

Rock Element (stein)

Diamantgjenstand (diamant)

Trevare (tre)

Sverdelement (sverd)

Diamantsverd (diamant_sverd)

  • Last ned hver sprite (høyreklikk -> Lagre som...) og importer dem til prosjektet ditt (i Importinnstillinger setter deres teksturtype til "Sprite (2D and UI)" og filtermodus til "Point (no filter)"

  • I SC_ItemCrafting endres Items Size til 5 og tilordne hver sprite til Item Sprite-variabelen.

"Stackable" variabel kontrollerer om gjenstandene kan stables sammen i ett spor (f.eks. vil du kanskje bare tillate stabling for enkle materialer som stein, diamant og tre).

"Craft Recipe" variable kontroller hvis dette elementet kan lages (tom betyr at det ikke kan lages)

  • For "Player Slots", sett Array Size til 27 (passer best for gjeldende Crafting Panel, men du kan angi hvilket som helst tall).

Når du trykker på Play, vil du legge merke til at sporene er initialisert riktig, men det er ingen elementer:

For å legge til et element til hvert spor, må vi tilordne et element Sprite til "Item Sprite"-variabelen og sette "Item Count" til et hvilket som helst positivt tall (alt under 1, og/eller ikke-stablebare elementer vil bli tolket som 1):

  • Tilordne "rock"-spriten til Element 0 / "Item Count" 14, "wood"-spriten til Element 1 / "Item Count" 8, "diamond"-spriten til Element 2 / "Item Count" 8 (Pass på at spritene er de samme som i "Items" Array, ellers vil det ikke fungere).

Elementene skal nå vises i spillerautomater, du kan endre deres plassering ved å klikke på elementet, og deretter klikke på sporet du vil flytte det til.

Lage oppskrifter:

Å lage oppskrifter lar deg lage et element ved å kombinere andre elementer i en bestemt rekkefølge:

Formatet for å lage oppskrifter er som følger: [item_sprite_name]([item count])*valgfritt... gjentas 9 ganger, atskilt med komma (,)

En enkel måte å finne oppskriften på er ved å trykke på Play, deretter plassere elementene i den rekkefølgen du vil lage, deretter trykke "Craft", deretter trykker du (Ctrl + Shift + C) for å åpne Unity-konsollen og se nytrykt linje (Du kan klikke "Craft" flere ganger for å skrive ut linjen på nytt), den trykte linjen er håndverksoppskriften.

Kombinasjonen nedenfor tilsvarer for eksempel denne oppskriften: rock,,rock,,rock,,rock,,tre (MERK: det kan være annerledes for deg hvis spritene dine har forskjellige navn).

Oppskrift på å lage sverdgjenstander

Vi vil bruke oppskriften ovenfor for å lage et sverd.

  • Kopier den utskrevne linjen, og i "Items" Array lim den inn i "Craft Recipe" variabelen under "sword" element:

Når du nå gjentar den samme kombinasjonen, bør du være i stand til å lage et sverd.

En oppskrift på et diamantsverd er den samme, men i stedet for stein er det diamant:

Diamond Item Sword Recipe Unity Inspector

Unity Inventory System og vareutforming

Håndverkssystemet er nå klart.