Створення інвентарю та система створення предметів в Unity

У цьому підручнику я покажу, як створити Minecraft систему інвентарю та створення предметів у Unity.

Виготовлення предметів у відеоіграх — це процес поєднання конкретних (зазвичай простіших) предметів у складніші з новими та покращеними властивостями. Наприклад, поєднання дерева та каменю в кирку або поєднання металевого листа та дерева в меч.

Наведена нижче система крафта зручна для мобільних пристроїв і повністю автоматизована, тобто вона працюватиме з будь-якою інтерфейсом інтерфейсу та з можливістю створювати спеціальні рецепти крафта.

Sharp Coder Відеоплеєр

Крок 1. Налаштуйте інтерфейс користувача Crafting

Ми починаємо з налаштування інтерфейсу користувача:

  • Створіть новий Canvas (Unity Верхня панель завдань: GameObject -> UI -> Canvas)
  • Створіть нове зображення, клацнувши правою кнопкою миші на Canvas Object -> UI -> Image
  • Перейменуйте об’єкт зображення на "CraftingPanel" і змініть його вихідне зображення на стандартне "UISprite"
  • Змініть значення "CraftingPanel" RectTransform на (Поз X: 0 Поз Y: 0 Ширина: 410 Висота: 365)

  • Створіть два об’єкти всередині "CraftingPanel" (клацніть правою кнопкою миші на CraftingPanel -> Create Empty, 2 рази)
  • Перейменуйте перший об’єкт на "CraftingSlots" і змініть його значення RectTransform на («Вирівнювання вгорі по лівому краю» Поворот X: 0 Поворот Y: 1 Поз X: 50 Поз Y: -35 Ширина: 140 Висота: 140). Цей об’єкт міститиме слоти для крафта.
  • Перейменуйте другий об’єкт на "PlayerSlots" і змініть його значення RectTransform на («Розтягнення верху по горизонталі» Поворот X: 0,5 Поворот Y: 1 Ліворуч: 0 Позиція Y: -222 Справа: 0 Висота: 100). Цей об’єкт міститиме слоти для гравців.

Назва розділу:

  • Створіть новий текст, клацнувши правою кнопкою миші на "PlayerSlots" Object -> UI -> Text і перейменуйте його на "SectionTitle"
  • Змініть значення "SectionTitle" RectTransform на ("Вирівнювання вгорі по лівому краю" Поворот X: 0 Поворот Y: 0 Поз X: 5 Поз Y: 0 Ширина: 160 Висота: 30)
  • Змініть текст "SectionTitle" на "Inventory" і встановіть його розмір шрифту на 18, вирівнювання на значення Left Middle і колір на (0,2, 0,2, 0,2, 1)
  • Скопіюйте об’єкт "SectionTitle", змініть його текст на "Crafting" і перемістіть його під об’єкт "CraftingSlots", а потім встановіть ті самі значення RectTransform, що й у попередньому "SectionTitle".

Слот для крафта:

Слот для створення складатиметься з фонового зображення, зображення предмета та тексту підрахунку:

  • Створіть нове зображення, клацнувши правою кнопкою миші на Canvas Object -> UI -> Image
  • Перейменуйте нове зображення на "slot_template", встановіть його значення RectTransform на (Post X: 0 Pos Y: 0 Width: 40 Height: 40) і змініть його колір на (0,32, 0,32, 0,32, 0,8)
  • Скопіюйте "slot_template" і перейменуйте його на "Item", перемістіть його всередину об’єкта "slot_template", змініть його розміри RectTransform на (Ширина: 30 Висота: 30) і колір на (1, 1, 1, 1)
  • Створіть новий текст, клацнувши правою кнопкою миші на "slot_template" Object -> UI -> Text і перейменуйте його на "Count"
  • Змініть значення "Count" RectTransform на («Вирівнювання за нижнім правим краєм» Поворот X: 1 Поворот Y: 0 Поз X: 0 Поз Y: 0 Ширина: 30 Висота: 30)
  • Установіть "Count" Текст на випадкове число (наприклад, 12), Стиль шрифту на Жирний, Розмір шрифту на 14, Вирівнювання на правий нижній край і Колір на (1, 1, 1, 1)
  • Додайте компонент «Тінь» до тексту "Count" і встановіть «Колір ефекту» на (0, 0, 0, 0,5)

Кінцевий результат повинен виглядати так:

Слот для результатів (який буде використовуватися для створення результатів):

  • Скопіюйте об’єкт "slot_template" і перейменуйте його на "result_slot_template"
  • Змініть ширину та висоту "result_slot_template" на 50

Кнопка створення та додаткова графіка:

  • Створіть нову кнопку, клацнувши правою кнопкою миші на "CraftingSlots" Object -> UI -> Button і перейменуйте її на "CraftButton"
  • Установіть значення "CraftButton" RectTransform («Вирівнювання посередині по лівому краю» Поворот X: 1 Поворот Y: 0,5 Поз X: 0 Поз Y: 0 Ширина: 40 Висота: 40)
  • Змінити текст "CraftButton" на "Craft"

  • Створіть нове зображення, клацнувши правою кнопкою миші на "CraftingSlots" Object -> UI -> Image і перейменуйте його на "Arrow"
  • Установіть значення "Arrow" RectTransform («Вирівнювання посередині по правому краю» Поворот X: 0 Поворот Y: 0,5 Поз X: 10 Поз Y: 0 Ширина: 30 Висота: 30)

Для вихідного зображення ви можете використати зображення нижче (клацніть правою кнопкою миші -> Зберегти як..., щоб завантажити його). Після імпорту встановіть тип текстури "Sprite (2D and UI)" і режим фільтра "Point (no filter)"

Піксель піктограми зі стрілкою вправо

  • Клацніть правою кнопкою миші на "CraftingSlots" -> Create Empty та перейменуйте його на "ResultSlot", цей об’єкт міститиме слот результату
  • Встановіть значення "ResultSlot" RectTransform ("Вирівнювання посередині по правому краю" Поворот X: 0 Поворот Y: 0,5 Поз X: 50 Поз Y: 0 Ширина: 50 Висота: 50)

Налаштування інтерфейсу користувача готове.

Крок 2: Система створення програм

Ця система створення складатиметься з 2 скриптів SC_ItemCrafting.cs і SC_SlotTemplate.cs

  • Створіть новий сценарій, назвіть його "SC_ItemCrafting" і вставте в нього наведений нижче код:

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);
            }
        }
    }
}
  • Створіть новий сценарій, назвіть його "SC_SlotTemplate" і вставте в нього наведений нижче код:

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

Підготовка шаблонів слотів:

  • Приєднайте сценарій SC_SlotTemplate до об’єкта "slot_template" і призначте його змінні (компонент «Зображення» того самого об’єкта переходить до змінної "Container", дочірнє зображення "Item" — до змінної "Item", а дочірній "Count" Текст переходить до змінної "Count")
  • Повторіть той самий процес для об’єкта "result_slot_template" (приєднайте до нього сценарій SC_SlotTemplate і призначте змінні таким же чином).

Підготовка крафтової системи:

  • Приєднайте сценарій SC_ItemCrafting до об’єкта Canvas і призначте його змінні (об’єкт «PlayerSlots» переходить до змінної "Player Slots Container", об’єкт "CraftingSlots" переходить до змінної "Crafting Slots Container", об’єкт "ResultSlot" переходить до "Result Slot Container" змінна, "CraftButton" об’єкт переходить до змінної "Craft Button", "slot_template" об’єкт із приєднаним сценарієм SC_SlotTemplate переходить до змінної "Slot Template", а об’єкт "result_slot_template" із приєднаним сценарієм SC_SlotTemplate переходить до змінної "Result Slot Template"):

Як ви вже помітили, є два порожніх масиви з іменами "Player Slots" і "Items". "Player Slots" міститиме кількість доступних слотів (з Предметом або порожні), а "Items" міститиме всі доступні предмети разом із їхніми рецептами (необов’язково).

Налаштування елементів:

Перевірте спрайти нижче (у моєму випадку у мене буде 5 елементів):

Rock Item (скеля)

Алмазний предмет (алмаз)

Дерев'яний предмет (дерево)

Sword Item (меч)

Алмазний меч (алмазний_меч)

  • Завантажте кожен спрайт (клацніть правою кнопкою миші -> Зберегти як...) та імпортуйте їх до свого проекту (у налаштуваннях імпорту встановіть для них тип текстури "Sprite (2D and UI)" і режим фільтра "Point (no filter)"

  • У SC_ItemCrafting змініть Items Size на 5 і призначте кожен спрайт змінній Item Sprite.

"Stackable" змінна визначає, чи можна складати елементи разом в один слот (наприклад, ви можете дозволити стекування лише для простих матеріалів, таких як камінь, алмаз і дерево).

"Craft Recipe" змінна контролює, чи можна створити цей предмет (порожній означає, що його неможливо створити)

  • Для "Player Slots" встановіть розмір масиву на 27 (найкраще підходить для поточної панелі крафту, але ви можете встановити будь-яке число).

Коли ви натиснете «Відтворити», ви помітите, що слоти ініціалізовано правильно, але немає елементів:

Щоб додати предмет до кожного слота, нам потрібно буде призначити спрайт предмета змінній "Item Sprite" і встановити для "Item Count" будь-яке додатне число (усе, що менше 1, і/або елементи, які не можна стекувати, інтерпретуватимуться як 1):

  • Призначте спрайт "rock" до Елемента 0 / "Item Count" 14, спрайт "wood" до Елемента 1 / "Item Count" 8, спрайт "diamond" до Елемента 2 / "Item Count" 8 (Переконайтеся, що спрайти такі самі, як в масиві "Items", інакше він не працюватиме).

Тепер предмети мають з’явитися в слотах для гравців. Ви можете змінити їх положення, клацнувши предмет, а потім клацнувши слот, куди його потрібно перемістити.

Рецепти крафта:

Рецепти крафта дозволяють створити предмет, поєднуючи інші предмети в певному порядку:

Формат для рецепту крафта такий: [item_sprite_name]([item count])*опціонально... повторюється 9 разів, розділених комою (,)

Простий спосіб дізнатися рецепт — натиснути «Відтворити», потім розташувати елементи в порядку, який ви хочете створити, потім натиснути "Craft", після цього натиснути (Ctrl + Shift + C), щоб відкрити Unity консоль і побачити щойно надрукований рядок (Ви можете натиснути "Craft" кілька разів, щоб повторно надрукувати рядок), надрукований рядок є рецептом виготовлення.

Наприклад, наведена нижче комбінація відповідає цьому рецепту: rock,,rock,,rock,,rock,,wood (ПРИМІТКА: для вас це може бути іншим, якщо ваші спрайти мають різні назви).

Рецепт створення предметів меча

Ми скористаємося наведеним вище рецептом, щоб створити меч.

  • Скопіюйте надрукований рядок і вставте його в масив "Items" у змінну "Craft Recipe" під елементом "sword":

Тепер, повторюючи цю саму комбінацію, ви зможете створити меч.

Рецепт алмазного меча той самий, але замість каменю — алмаз:

Diamond Item Sword Recipe Unity Inspector

Система інвентаризації Unity та створення предметів

Тепер система створення готова.

Джерело
📁ItemCrafting.unitypackage36.13 KB
Рекомендовані статті
Вступ до кінцевої машини в Unity
Як запустити катсцену в Unity
Кодування простої системи інвентаризації з інтерфейсом користувача Drag and Drop в Unity
Створення предметів колекціонування та посилень в Unity
Створення турельного контролера в Unity
Створення гри-головоломки в Unity
Створення гри на основі Pac-Man в Unity