Fremstilling af lager- og varefremstillingssystem i enhed

I denne tutorial vil jeg vise, hvordan man laver et Minecraft-stil lager- og varefremstillingssystem i Unity.

Item crafting i videospil er en proces, hvor man kombinerer specifikke (normalt enklere) elementer til mere komplekse elementer med nye og forbedrede egenskaber. For eksempel at kombinere træ og sten til en hakke, eller at kombinere metalplade og træ til et sværd.

Håndværkssystemet nedenfor er mobilvenligt og fuldt automatiseret, hvilket betyder, at det vil fungere med ethvert UI-layout og med muligheden for at skabe brugerdefinerede håndværksopskrifter.

Sharp Coder Videoafspiller

Trin 1: Konfigurer Crafting UI

Vi begynder med at konfigurere håndværksbrugergrænsefladen:

  • Opret et nyt lærred (Unity Øverste proceslinje: GameObject -> UI -> Canvas)
  • Opret et nyt billede ved at højreklikke på Canvas Object -> UI -> Billede
  • Omdøb billedobjektet til "CraftingPanel" og skift dets kildebillede til standard "UISprite"
  • Skift "CraftingPanel" RectTransform værdier til (Pos X: 0 Pos Y: 0 Bredde: 410 Højde: 365)

  • Opret to objekter inde i "CraftingPanel" (højreklik på CraftingPanel -> Create Empty, 2 gange)
  • Omdøb det første objekt til "CraftingSlots" og skift dets RectTransform-værdier til ("Top Venstre align" Pivot X: 0 Pivot Y: 1 Pos X: 50 Pos Y: -35 Bredde: 140 Højde: 140). Dette objekt vil indeholde crafting slots.
  • Omdøb det andet objekt til "PlayerSlots" og skift dets RectTransform-værdier til ("Topstræk horisontalt" Pivot X: 0,5 Pivot Y: 1 Venstre: 0 Pos Y: -222 Højre: 0 Højde: 100). Dette objekt vil indeholde spiller slots.

Sektionstitel:

  • Opret ny tekst ved at højreklikke på "PlayerSlots" Objekt -> UI -> Tekst og omdøbe den til "SectionTitle"
  • Skift "SectionTitle" RectTransform værdier til ("Top Venstre align" Pivot X: 0 Pivot Y: 0 Pos X: 5 Pos Y: 0 Bredde: 160 Højde: 30)
  • Skift "SectionTitle"-teksten til "Inventory" og indstil dens skriftstørrelse til 18, Justering til venstre i midten og farve til (0,2, 0,2, 0,2, 1)
  • Dupliker "SectionTitle"-objektet, skift dets tekst til "Crafting" og flyt det under "CraftingSlots"-objektet, og indstil derefter de samme RectTransform-værdier som den forrige "SectionTitle".

Håndværksplads:

Håndværkspladsen vil bestå af et baggrundsbillede, et elementbillede og en optællingstekst:

  • Opret et nyt billede ved at højreklikke på Canvas Object -> UI -> Billede
  • Omdøb det nye billede til "slot_template", indstil dets RectTransform-værdier til (Post X: 0 Pos Y: 0 Bredde: 40 Højde: 40), og skift dets farve til (0,32, 0,32, 0,32, 0,8)
  • Dupliker "slot_template" og omdøb det til "Item", flyt det ind i "slot_template" objekt, skift dets RectTransform-dimensioner til (Bredde: 30 Højde: 30) og farve til (1, 1, 1, 1)
  • Opret ny tekst ved at højreklikke på "slot_template" Objekt -> UI -> Tekst og omdøb den til "Count"
  • Skift "Count" RectTransform-værdier til ("Bottom Right align" Pivot X: 1 Pivot Y: 0 Pos X: 0 Pos Y: 0 Bredde: 30 Højde: 30)
  • Indstil "Count" Tekst til et tilfældigt tal (f.eks. 12), skrifttype til fed, skriftstørrelse til 14, justering til højre bund og farve til (1, 1, 1, 1)
  • Tilføj Shadow-komponent til "Count" Tekst og indstil Effektfarve til (0, 0, 0, 0,5)

Slutresultatet skulle se sådan ud:

Resultatplads (som vil blive brugt til at lave resultater):

  • Dupliker "slot_template"-objektet og omdøb det til "result_slot_template"
  • Skift bredden og højden af ​​"result_slot_template" til 50

Håndværksknap og yderligere grafik:

  • Opret en ny knap ved at højreklikke på "CraftingSlots" Objekt -> UI -> Knap og omdøb den til "CraftButton"
  • Indstil "CraftButton" RectTransform værdier til ("Middle Left align" Pivot X: 1 Pivot Y: 0,5 Pos X: 0 Pos Y: 0 Bredde: 40 Højde: 40)
  • Ændr tekst af "CraftButton" til "Craft"

  • Opret et nyt billede ved at højreklikke på "CraftingSlots" Objekt -> UI -> Billede og omdøb det til "Arrow"
  • Indstil "Arrow" RectTransform værdier til ("Middle Right align" Pivot X: 0 Pivot Y: 0,5 Pos X: 10 Pos Y: 0 Bredde: 30 Højde: 30)

Til kildebilledet kan du bruge billedet nedenfor (højreklik -> Gem som.. for at downloade det). Efter import indstilles dens teksturtype til "Sprite (2D and UI)" og filtertilstand til "Point (no filter)"

Pil højre ikon Pixel

  • Højreklik på "CraftingSlots" -> Opret tom og omdøb den til "ResultSlot", dette objekt vil indeholde resultatspalten
  • Indstil "ResultSlot" RectTransform værdier til ("Middle Right align" Pivot X: 0 Pivot Y: 0,5 Pos X: 50 Pos Y: 0 Bredde: 50 Højde: 50)

UI-opsætningen er klar.

Trin 2: Program Crafting System

Dette håndværkssystem vil bestå af 2 scripts, SC_ItemCrafting.cs og SC_SlotTemplate.cs

  • Opret et nyt script, navngiv det "SC_ItemCrafting" og indsæt derefter 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);
            }
        }
    }
}
  • Opret et nyt script, navngiv det "SC_SlotTemplate" og indsæt derefter 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();
    }
}

Forberedelse af slot skabeloner:

  • Vedhæft SC_SlotTemplate-scriptet til "slot_template"-objektet, og tildel dets variabler (Billedkomponenten på det samme objekt går til "Container"-variablen, underordnet "Item" Billedet går til "Item"-variablen og et underordnet "Count" Tekst går til "Count" variabel)
  • Gentag den samme proces for "result_slot_template"-objektet (vedhæft SC_SlotTemplate-scriptet til det, og tildel variabler på samme måde).

Forberedelse af håndværkssystem:

  • Vedhæft SC_ItemCrafting-scriptet til Canvas-objektet, og tildel dets variabler ("PlayerSlots"-objekt går til "Player Slots Container"-variablen, "CraftingSlots" Objekt går til "Crafting Slots Container"-variablen, "ResultSlot"-objekt går til "Result Slot Container" variabel, "CraftButton" Objekt går til "Craft Button" variabel, "slot_template" Objekt med SC_SlotTemplate script vedhæftet går til "Slot Template" variabel og "result_slot_template" Objekt med SC_SlotTemplate script vedhæftet går til "Result Slot Template" variabel):

Som du allerede har bemærket, er der to tomme arrays ved navn "Player Slots" og "Items". "Player Slots" vil indeholde antallet af ledige pladser (med vare eller tom), og "Items" vil indeholde alle de tilgængelige varer sammen med deres opskrifter (valgfrit).

Opsætning af elementer:

Tjek sprites nedenfor (i mit tilfælde vil jeg have 5 genstande):

Rock Genstand (klippe)

Diamant vare (diamant)

Træ vare (træ)

Sværd Genstand (sværd)

Diamant sværd (diamant_sword)

  • Download hver sprite (højreklik -> Gem som...) og importer dem til dit projekt (i Importindstillinger sæt deres teksturtype til "Sprite (2D and UI)" og filtertilstand til "Point (no filter)"

  • I SC_ItemCrafting ændres Items Size til 5 og tildel hver sprite til Item Sprite-variablen.

"Stackable" variabel styrer, om emnerne kan stables sammen i én spalte (f.eks. vil du måske kun tillade stabling for simple materialer såsom sten, diamant og træ).

"Craft Recipe" variable kontroller, hvis dette element kan fremstilles (tom betyder, at det ikke kan fremstilles)

  • For "Player Slots" sæt Array Size til 27 (passer bedst til det aktuelle Crafting Panel, men du kan indstille et hvilket som helst tal).

Når du trykker på Play, vil du bemærke, at slots er initialiseret korrekt, men der er ingen elementer:

For at tilføje et element til hver plads skal vi tildele et element Sprite til "Item Sprite"-variablen og indstille "Item Count" til et hvilket som helst positivt tal (alt under 1, og/eller ikke-stabelbare elementer vil blive fortolket som 1):

  • Tildel "rock"-spriten til Element 0 / "Item Count" 14, "wood"-spriten til Element 1 / "Item Count" 8, "diamond"-spriten til Element 2 / "Item Count" 8 (sørg for, at spritene er de samme som i "Items" Array, ellers virker det ikke).

Elementerne skulle nu vises i Player Slots, du kan ændre deres placering ved at klikke på elementet og derefter klikke på det slot, du vil flytte det til.

Håndværksopskrifter:

At lave opskrifter giver dig mulighed for at oprette en vare ved at kombinere andre varer i en bestemt rækkefølge:

Formatet til at lave opskriften er som følger: [item_sprite_name]([item count])*valgfrit... gentaget 9 gange, adskilt af komma (,)

En nem måde at finde opskriften på er ved at trykke på Afspil, derefter placere emnerne i den rækkefølge, du vil lave, og derefter trykke på "Craft", derefter trykke på (Ctrl + Shift + C) for at åbne Unity-konsollen og se nytrykt linje (Du kan klikke på "Craft" flere gange for at genudskrive linjen), den trykte linje er håndværksopskriften.

For eksempel svarer kombinationen nedenfor til denne opskrift: rock,,rock,,rock,,rock,,wood (BEMÆRK: det kan være anderledes for dig, hvis dine sprites har forskellige navne).

Sword Item Crafting Opskrift

Vi vil bruge opskriften ovenfor til at lave et sværd.

  • Kopiér den udskrevne linje, og indsæt den i "Items"-arrayet i "Craft Recipe"-variablen under "sword"-elementet:

Når du nu gentager den samme kombination, burde du være i stand til at lave et sværd.

En opskrift på et diamantsværd er den samme, men i stedet for sten er det diamant:

Diamond Item Sword Recipe Unity Inspector

Unity Inventory System og Item Crafting

Håndværkssystemet er nu klar.