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.