Fazendo inventário e sistema de criação de itens no Unity

Neste tutorial, mostrarei como fazer um sistema de inventário e criação de itens no estilo Minecraft em Unity.

A criação de itens em videogames é um processo de combinação de itens específicos (geralmente mais simples) em itens mais complexos, com propriedades novas e aprimoradas. Por exemplo, combinar madeira e pedra em uma picareta ou combinar chapa de metal e madeira em uma espada.

O sistema de criação abaixo é compatível com dispositivos móveis e totalmente automatizado, o que significa que funcionará com qualquer layout de UI e com a capacidade de criar receitas de criação personalizadas.

Sharp Coder Reprodutor de vídeo

Etapa 1: configurar a IU de criação

Começamos configurando a IU de criação:

  • Crie um novo Canvas (Unity Barra de Tarefas Superior: GameObject -> UI -> Canvas)
  • Crie uma nova imagem clicando com o botão direito em Canvas Object -> UI -> Image
  • Renomeie o objeto de imagem para "CraftingPanel" e altere sua imagem de origem para padrão "UISprite"
  • Altere os valores "CraftingPanel" RectTransform para (Pos X: 0 Pos Y: 0 Largura: 410 Altura: 365)

  • Crie dois objetos dentro de "CraftingPanel" (clique com o botão direito em CraftingPanel -> Criar Vazio, 2 vezes)
  • Renomeie o primeiro objeto para "CraftingSlots" e altere seus valores RectTransform para ("Alinhamento superior esquerdo" Pivô X: 0 Pivô Y: 1 Pos X: 50 Pos Y: -35 Largura: 140 Altura: 140). Este objeto conterá slots de artesanato.
  • Renomeie o segundo objeto para "PlayerSlots" e altere seus valores RectTransform para ("Top Stretch Horizontally" Pivô X: 0,5 Pivô Y: 1 Esquerda: 0 Pos Y: -222 Direita: 0 Altura: 100). Este objeto conterá slots de jogador.

Título da seção:

  • Crie um novo texto clicando com o botão direito em "PlayerSlots" Object -> UI -> Text e renomeie-o para "SectionTitle"
  • Altere os valores "SectionTitle" RectTransform para ("Alinhamento superior esquerdo" Pivô X: 0 Pivô Y: 0 Pos X: 5 Pos Y: 0 Largura: 160 Altura: 30)
  • Altere o texto "SectionTitle" para "Inventory" e defina o tamanho da fonte para 18, o alinhamento para o meio esquerdo e a cor para (0,2, 0,2, 0,2, 1)
  • Duplique o objeto "SectionTitle", altere seu texto para "Crafting" e mova-o para baixo do objeto "CraftingSlots", depois defina os mesmos valores RectTransform do "SectionTitle" anterior.

Slot de artesanato:

O espaço de criação consistirá em uma imagem de fundo, uma imagem de item e um texto de contagem:

  • Crie uma nova imagem clicando com o botão direito em Canvas Object -> UI -> Image
  • Renomeie a nova imagem para "slot_template", defina seus valores RectTransform para (Post X: 0 Pos Y: 0 Width: 40 Height: 40) e mude sua cor para (0,32, 0,32, 0,32, 0,8)
  • Duplique "slot_template" e renomeie-o para "Item", mova-o para dentro do objeto "slot_template", altere suas dimensões RectTransform para (Largura: 30 Altura: 30) e Cor para (1, 1, 1, 1)
  • Crie um novo texto clicando com o botão direito em "slot_template" Object -> UI -> Text e renomeie-o para "Count"
  • Altere os valores "Count" RectTransform para ("Alinhamento inferior direito" Pivô X: 1 Pivô Y: 0 Pos X: 0 Pos Y: 0 Largura: 30 Altura: 30)
  • Defina "Count" Texto para um número aleatório (ex. 12), Estilo da fonte para Negrito, Tamanho da fonte para 14, Alinhamento para parte inferior direita e Cor para (1, 1, 1, 1)
  • Adicione o componente Shadow ao texto "Count" e defina a cor do efeito como (0, 0, 0, 0,5)

O resultado final deve ficar assim:

Slot de resultado (que será usado para criar resultados):

  • Duplique o objeto "slot_template" e renomeie-o para "result_slot_template"
  • Altere a largura e a altura de "result_slot_template" para 50

Botão de criação e gráficos adicionais:

  • Crie um novo botão clicando com o botão direito em "CraftingSlots" Object -> UI -> Button e renomeie-o para "CraftButton"
  • Defina os valores "CraftButton" RectTransform como ("Alinhamento do meio à esquerda" Pivô X: 1 Pivô Y: 0,5 Pos X: 0 Pos Y: 0 Largura: 40 Altura: 40)
  • Altere o texto de "CraftButton" para "Craft"

  • Crie uma nova imagem clicando com o botão direito em "CraftingSlots" Object -> UI -> Image e renomeie-a para "Arrow"
  • Defina os valores "Arrow" RectTransform como ("Alinhamento do meio à direita" Pivô X: 0 Pivô Y: 0,5 Pos X: 10 Pos Y: 0 Largura: 30 Altura: 30)

Para a imagem de origem, você pode usar a imagem abaixo (clique com o botão direito -> Salvar como.. para fazer o download). Após a importação, defina seu tipo de textura como "Sprite (2D and UI)" e modo de filtro como "Point (no filter)"

Pixel do ícone de seta para a direita

  • Clique com o botão direito em "CraftingSlots" -> Criar Vazio e renomeie-o para "ResultSlot", este objeto conterá o slot de resultado
  • Defina os valores "ResultSlot" RectTransform como ("Alinhamento do meio à direita" Pivô X: 0 Pivô Y: 0,5 Pos X: 50 Pos Y: 0 Largura: 50 Altura: 50)

A configuração da IU está pronta.

Etapa 2: Sistema de elaboração de programas

Este sistema de artesanato consistirá em 2 scripts, SC_ItemCrafting.cs e SC_SlotTemplate.cs

  • Crie um novo script, nomeie-o "SC_ItemCrafting" e cole o código abaixo dentro dele:

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);
            }
        }
    }
}
  • Crie um novo script, nomeie-o "SC_SlotTemplate" e cole o código abaixo dentro dele:

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

Preparando modelos de slots:

  • Anexe o script SC_SlotTemplate ao objeto "slot_template" e atribua suas variáveis ​​(o componente Imagem no mesmo objeto vai para a variável "Container", a imagem filho "Item" vai para a variável "Item" e um filho "Count" O texto vai para a variável "Count")
  • Repita o mesmo processo para o objeto "result_slot_template" (anexe o script SC_SlotTemplate a ele e atribua variáveis ​​da mesma maneira).

Preparando o sistema artesanal:

  • Anexe o script SC_ItemCrafting ao objeto Canvas e atribua suas variáveis ​​(o objeto "PlayerSlots" vai para a variável "Player Slots Container", o objeto "CraftingSlots" vai para a variável "Crafting Slots Container", o objeto "ResultSlot" vai para a variável "Result Slot Container" variável, objeto "CraftButton" vai para variável "Craft Button", objeto "slot_template" com script SC_SlotTemplate anexado vai para variável "Slot Template" e objeto "result_slot_template" com script SC_SlotTemplate anexado vai para variável "Result Slot Template"):

Como você já percebeu, existem dois arrays vazios chamados "Player Slots" e "Items". "Player Slots" conterá o número de slots disponíveis (com Item ou vazios) e "Items" conterá todos os itens disponíveis junto com suas receitas (opcional).

Configurando itens:

Confira os sprites abaixo (no meu caso terei 5 itens):

Item de pedra (pedra)

Item Diamante (diamante)

Item de madeira (madeira)

Item de espada (espada)

Espada de diamante (espada de diamante)

  • Baixe cada sprite (clique com o botão direito -> Salvar como...) e importe-os para o seu projeto (nas configurações de importação defina seu tipo de textura para "Sprite (2D and UI)" e modo de filtro para "Point (no filter)"

  • Em SC_ItemCrafting altere o Tamanho dos Itens para 5 e atribua cada sprite à variável Item Sprite.

"Stackable" variável controla se os itens podem ser empilhados juntos em um slot (por exemplo, você pode querer permitir o empilhamento apenas para materiais simples, como pedra, diamante e madeira).

"Craft Recipe" variável controla se este item pode ser fabricado (vazio significa que não pode ser fabricado)

  • Para o "Player Slots" defina o Array Size como 27 (melhor ajuste para o Crafting Panel atual, mas você pode definir qualquer número).

Ao pressionar Play, você notará que os slots foram inicializados corretamente, mas não há itens:

Para adicionar um item a cada slot, precisaremos atribuir um item Sprite à variável "Item Sprite" e definir "Item Count" para qualquer número positivo (tudo abaixo de 1 e/ou itens não empilháveis ​​serão interpretados como 1):

  • Atribua o sprite "rock" ao Elemento 0 / "Item Count" 14, o sprite "wood" ao Elemento 1 / "Item Count" 8, o sprite "diamond" ao Elemento 2 / "Item Count" 8 (Certifique-se de que os sprites sejam iguais aos no array "Items", caso contrário não funcionará).

Os itens agora devem aparecer nos slots dos jogadores. Você pode alterar sua posição clicando no item e depois clicando no slot para o qual deseja movê-lo.

Receitas de elaboração:

Criar receitas permite que você crie um item combinando outros itens em uma ordem específica:

O formato para elaboração da receita é o seguinte: [item_sprite_name]([item count])*opcional... repetido 9 vezes, separado por vírgula (,)

Uma maneira fácil de descobrir a receita é apertar Play, depois colocar os itens na ordem que deseja fabricar, depois apertar "Craft", depois disso, apertar (Ctrl + Shift + C) para abrir Unity Console e ver o linha recém-impressa (você pode clicar em "Craft" várias vezes para reimprimir a linha), a linha impressa é a receita de elaboração.

Por exemplo, a combinação abaixo corresponde a esta receita: rock,,rock,,rock,,rock,,wood (NOTA: pode ser diferente para você se seus sprites tiverem nomes diferentes).

Receita de confecção de itens de espada

Usaremos a receita acima para fabricar uma espada.

  • Copie a linha impressa, e no Array "Items" cole-a na variável "Craft Recipe" em "sword" Item:

Agora, ao repetir a mesma combinação, você poderá criar uma espada.

A receita para uma espada de diamante é a mesma, mas em vez de pedra é diamante:

Inspetor de Unidade de Receita de Espada de Item de Diamante

Sistema de inventário Unity e criação de itens

O sistema de artesanato agora está pronto.

Fonte
📁ItemCrafting.unitypackage36.13 KB
Artigos sugeridos
Codificando um sistema de inventário simples com arrastar e soltar UI no Unity
Como desencadear uma cena no Unity
Introdução à máquina de estado no Unity
Sistema Pick and Drop sem estoque no Unity
Criando um simulador de tráfego no Unity
Criando um sistema de marcadores 2D simples no Unity
Criando itens colecionáveis ​​e power-ups no Unity