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

В этом уроке я покажу, как создать в стиле Minecraft систему инвентаря и создания предметов в Unity.

Создание предметов в видеоиграх — это процесс объединения определенных (обычно более простых) предметов в более сложные предметы с новыми и улучшенными свойствами. Например, объединить дерево и камень в кирку или объединить металлический лист и дерево в меч.

Приведенная ниже система крафта удобна для мобильных устройств и полностью автоматизирована, то есть она будет работать с любым макетом пользовательского интерфейса и с возможностью создания собственных рецептов крафта.

Sharp Coder Видео проигрыватель

Шаг 1. Настройте пользовательский интерфейс крафта

Начнем с настройки пользовательского интерфейса крафта:

  • Создайте новый холст (Unity Верхняя панель задач: GameObject -> UI -> Canvas)
  • Создайте новое изображение, щелкнув правой кнопкой мыши объект Canvas -> Пользовательский интерфейс -> Изображение.
  • Переименуйте объект изображения в "CraftingPanel" и измените его исходное изображение на значение по умолчанию. "UISprite"
  • Измените значения "CraftingPanel" RectTransform на (Pos X: 0 Pos Y: 0 Ширина: 410 Высота: 365)

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

Название раздела:

  • Создайте новый текст, щелкнув правой кнопкой мыши объект "PlayerSlots" -> UI -> Text и переименовав его в "SectionTitle"
  • Измените значения "SectionTitle" RectTransform на («Выровнять по левому краю» Pivot X: 0 Pivot Y: 0 Pos X: 5 Pos Y: 0 Ширина: 160 Высота: 30)
  • Измените текст "SectionTitle" на "Inventory" и установите размер шрифта на 18, выравнивание по левому краю и цвет на (0,2, 0,2, 0,2, 1).
  • Дублируйте объект "SectionTitle", измените его текст на "Crafting" и переместите его под объект "CraftingSlots", затем установите те же значения RectTransform, что и для предыдущего "SectionTitle".

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

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

  • Создайте новое изображение, щелкнув правой кнопкой мыши объект Canvas -> Пользовательский интерфейс -> Изображение.
  • Переименуйте новое изображение в "slot_template", установите его значения RectTransform на (Post X: 0 Pos Y: 0 Ширина: 40 Высота: 40) и измените его цвет на (0,32, 0,32, 0,32, 0,8).
  • Дублируйте "slot_template" и переименуйте его в "Item", переместите его внутрь объекта "slot_template", измените его размеры RectTransform на (Ширина: 30 Высота: 30) и Цвет на (1, 1, 1, 1).
  • Создайте новый текст, щелкнув правой кнопкой мыши объект "slot_template" -> UI -> Text и переименовав его в "Count"
  • Измените значения "Count" RectTransform на («Выравнивание по нижнему правому краю» Pivot X: 1 Pivot Y: 0 Pos X: 0 Pos 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" -> UI -> Кнопка и переименуйте ее в "CraftButton"
  • Установите для "CraftButton" значения RectTransform на («Выравнивание по среднему левому краю» Pivot X: 1 Pivot Y: 0,5 Pos X: 0 Pos Y: 0 Ширина: 40 Высота: 40)
  • Измените текст "CraftButton" на "Craft"

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

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

Пиксель значка стрелки вправо

  • Щелкните правой кнопкой мыши "CraftingSlots" -> Создать пустой и переименуйте его в "ResultSlot", этот объект будет содержать слот результата.
  • Установите значения "ResultSlot" RectTransform на («Выравнивание по центру справа» Pivot X: 0 Pivot Y: 0,5 Pos X: 50 Pos Y: 0 Ширина: 50 Высота: 50)

Настройка пользовательского интерфейса готова.

Шаг 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 элементов):

Каменный предмет (камень)

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

Деревянный предмет (древесина)

Меч Предмет (меч)

Алмазный меч (бриллиант_меч)

  • Загрузите каждый спрайт (щелкните правой кнопкой мыши -> Сохранить как...) и импортируйте их в свой проект (в настройках импорта установите тип текстуры на "Sprite (2D and UI)" и режим фильтра на "Point (no filter)"

  • В SC_ItemCrafting измените Размер элемента на 5 и назначьте каждый спрайт переменной Item Sprite.

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

"Craft Recipe" переменные элементы управления, можно ли создать этот предмет (пусто означает, что его нельзя создать)

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

Когда вы нажмете Play, вы заметите, что слоты инициализируются правильно, но предметов нет:

Чтобы добавить предмет в каждый слот, нам нужно присвоить спрайт предмета переменной "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]([количество предметов])*необязательно... повторяется 9 раз, через запятую (,)

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

Например, приведенная ниже комбинация соответствует этому рецепту: rock,,rock,,rock,,rock,,wood (ПРИМЕЧАНИЕ: для вас это может быть по-другому, если ваши спрайты имеют разные имена).

Рецепт изготовления предмета меча

Мы воспользуемся рецептом, приведенным выше, для изготовления меча.

  • Скопируйте напечатанную строку и вставьте ее в массив "Items" в переменную "Craft Recipe" под элементом "sword":

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

Рецепт алмазного меча тот же, но вместо камня здесь алмаз:

Алмазный предмет Рецепт меча Unity Inspector

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

Система крафта готова.

Источник
📁ItemCrafting.unitypackage36.13 KB
Рекомендуемые статьи
Кодирование простой системы инвентаризации с помощью перетаскивания пользовательского интерфейса в Unity
Введение в конечный автомат в Unity
Как запустить кат-сцену в Unity
Создание игры в стиле Pac-Man в Unity
Создание предметов коллекционирования и усилений в Unity
Система выбора и сброса без инвентаря в Unity
Создание игры-головоломки в Unity