Jump to content
Sign in to follow this  
Константин Орлов

Unity: Инвентарь и крафт

Recommended Posts

Два урока по Unity, как сделать инвентарь с крафтом, использованием и одеванием предметов. Первый о том, как установить готовый скрипт, второе видео с разбором кода скриптов.

 

Скачать скрипты и префабы https://yadi.sk/d/Qf1FHNkr3YjZgK или InventoryCraft.rar

 

 

1:36 Установка инвентаря в Unity
2:57 Как спозиционировать предметы в руке (детальней это объяснено в видео от Стрима)
3:47 Как сделать свою поднимаемую вещь

 

 

 

0:18 InventoryPickUp.cs - скрипт для игрока с OnCollisionEnter

1:36 InventoryManager.cs - главный скрипт инвентаря
2:20 переменная с предметами инвентаря и ее методы ("свойства")
3:40 переменные скрипта
8:81 метод Start()
11:43 PickUpOnCollisionEnter() и PuckUpItemRaycastMouse() (да-да, pUckup) - поднимание по соприкосновению и райкасту
18:53 метод Update()
19:15 OpenInventor() и CloseInventory () открытие и закрытие (+ про паузу) 
21:06 FillCellsUIFromList() и ClearCellUI() - заполнение и очищение UI от иконок предметов
25:27 RemoveItemInventory() выкидывание 27:50 DropZone() зона выброса предметов
29:48 UseItemInventory() использовать/одеть предмет
34:02 TakeInHand() взять предмет в руку

38:06 InventoryIcon.cs - иконки в инвентаре
40:40 интерфейсы для UI-элементов
41:35 IBeginDragHandler - OnBeginDrag
44:49 IDragHandler - OnDrag
45:04 IEndDragHandler - OnEndDrag
47:56 IPointerClickHandler - OnPointerClick

49:41 InventoryCell.cs - для ячеек (места под иконки) инвентаря
49:58 интерфейс IDropHandler (OnDrop)

52:39 InventoryItem.cs - для поднимаемых предметов
53:00 enum перечисления типов предметов
54:20 InventoryItemHand.cs - для предметов, которые можно взять в руку

55:00 InventoryCraft.cs - крафт
58:26 UpdateCraft() функция крафта

 

Видеоуроки Стрима по созданию инветаря на Unity с нуля:
Как сделать игру на Unity 5 #16 создание инвентаря - https://www.youtube.com/watch?v=Y-OpWcRhgJ0
Как сделать игру на Unity 5 #17 Инвентарь и предметы - https://www.youtube.com/watch?v=wgl69JPczG0
Unity 5, уроки по заявкам #1 крафт - https://www.youtube.com/watch?v=_syz8dGJ5yk
Эти уроки полезно изучить дополнительно, так как там рассказываются некоторые детали создания UI-инвентаря, которые не раскрыты в двух видео выше. По этим урокам я сделал инвентарь, а потом улучшил его и исправил баги, поэтому чтобы лучше понимать базовые принципы работы инвентаря, стоит посмотреть и их (для новичков).

 

Ознакомление с кодом скриптов (для работы инвентаря в Unity3d нужна еще настройка UI (создание панели с ячейками и т.п.), ее префаб можно скачать выше вместе со скриптами). А тут просто выложен код для ознакомления:

 

InventoryManager.cs
 

Спойлер

 


using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using System; // для работы с событиями и делегатами

public class InventoryManager : MonoBehaviour
{
    //Сделано по уроку https://www.youtube.com/watch?v=Y-OpWcRhgJ0 и https://www.youtube.com/watch?v=wgl69JPczG0

    #region СПИСОК-ИНВЕНТАРЬ и его методы (добавить, удалить и т.п.)
    /// <summary> СПИСОК-ИНВЕНТАРЬ (лист-переменная для хранения предметов инвентаря) </summary>
    private List<InventoryItem> inventoryList; // приватный, доступ через функции

    /// <summary> ДОБАВИТЬ В ЛИСТ-ИНВЕНТАРЬ </summary>
    public void InventoryListAdd(InventoryItem item)
    {
        inventoryList.Add(item);
        // извещаем о изменении количества
        if (OnChangeCountItem != null) OnChangeCountItem();
        if (debugMode) Debug.Log("InventoryListAdd - предмет добавлен в ЛИСТ-ИНВЕНТАРЬ: " + item.prefabForScene);
    }

    /// <summary> УДАЛИТЬ ИЗ ЛИСТ-ИНВЕНТАРЯ </summary>
    public void InventoryListRemove(InventoryItem item)
    {
        inventoryList.Remove(item);
        // извещаем о изменении количества
        if (OnChangeCountItem != null) OnChangeCountItem();
        if (debugMode) Debug.Log("InventoryListRemove - предмет удален из ЛИСТ-ИНВЕНТАРЯ: " + item.prefabForScene);
    }

    /// <summary> ПОЛУЧИТЬ ПРЕДМЕТ ИЗ ЛИСТ-ИНВЕНТАРЯ </summary>
    /// <param name="indexItem">Индекс элемента списка (от 0)</param>
    public InventoryItem InventoryListGetItem(int indexItem)
    {
        return inventoryList[indexItem];
    }

    /// <summary> КОЛИЧЕСТВО ПРЕДМЕТОВ В ЛИСТ-ИНВЕНТАРЕ </summary>
    public int InventoryListCount()
    {
        return inventoryList.Count;
    }
    #endregion

    [Header("Настроить обязательно:")] // для визуального отделения настроек в инспекторе

    /// <summary> ССЫЛКА НА ИГРОКА </summary>
    public GameObject player; // для выбрасывания рядом с ним предметов из инвентаря

    /// <summary> ПОЗИЦИЯ ПРАВОЙ РУКИ ИГРОКА </summary>
    [Tooltip("Перенесите сюда правую руку игрока (родительский элемент пальцев)")]
    public Transform RightHandPosition;

    /// <summary> ССЫЛКА НА СКРИПТ ИНВЕНТАРЯ НА ИГРОКЕ </summary>
    InventoryPickUp playerInventoryScript;

    /// <summary> СОБЫТИЕ ИЗМЕНЕНИЯ КОЛИЧЕСТВА ПРЕДМЕТОВ (чтобы при изменении в UI-тексте писать новое число) </summary>
    public event Action OnChangeCountItem;

    [Header("Можно не трогать:")] // для визуального отделения настроек в инспекторе

    /// <summary> КНОПКА ОТКРЫТИЯ И ЗАКРЫТИЯ ИНВЕНТАРЯ </summary>
    [Tooltip("Какой кнопкой открывать/закрывать инвентарь?")]
    public KeyCode keyOpenClose = KeyCode.I;

    /// <summary> UI-ПАНЕЛЬ-ИНВЕНТАРЬ, содержащий ячейки для отображения инвентаря </summary>
    public GameObject inventoryPanelUI;

    /// <summary> ССЫЛКА НА ПАНЕЛЬ КРАФТА (для вкл/выкл вместе с инвентарем) </summary>
    public InventoryCraft сraftPanel;

    /// <summary> РОДИТЕЛЬСКИЙ ОБЪЕКТ ПАНЕЛИ ИНВЕНТАРЯ И КРАФТА (для совместного вкл/выкл) </summary>
    public GameObject Panels;

    /// <summary> ИКОНКА-ПРЕФАБ ДЛЯ ЯЧЕЙКИ (представление предметов в инвентаре) </summary>
    public GameObject iconPrefab;

    /// <summary> МАКСИМАЛЬНАЯ ВМЕСТИМОСТЬ ИНВЕНТАРЯ (количество детей у панели со слотами) </summary>
    public int MaxSlotInventoryPanelUI { get { return maxSlotInventoryPanelUI; } private set { maxSlotInventoryPanelUI = value; } }
    private int maxSlotInventoryPanelUI;
    // определяем кол-во ячеек в Start(), так как при перетаскивании предмета, он меняет свой transform на панель со слотом,
    // и если в этот момент код запросит inventoryPanelUI.transform.childCount, то получит на 1 больше, чем реально есть ячеек

    /// <summary> ПОДНИМАТЬ ВЕЩИ ПРИ СТОЛКНОВЕНИИ с коллайдером? </summary>
    [Tooltip("Поднимать ли предметы при столкновении?")]
    public bool puckUpOnCollision = true;

    /// <summary> ПОДНИМАТЬ ВЕЩИ ПРИ КЛИКЕ? </summary>
    [Tooltip("Поднимать ли предметы при клике мышкой?")]
    public bool puckUpOnClick = true;

    /// <summary> ДИСТАНЦИЯ РАЙКАСТА ДЛЯ ПОДНИМАНИЯ МЫШКОЙ (от игрока до вещи) </summary>
    [Tooltip("Максимальная дистанция до объекта от игрока, когда вещь можно поднять по клику на нее")]
    public float maxDistanceClick = 3;

    /// <summary> ВКЛЮЧАТЬ ПАУЗУ ПРИ ОТКРЫТИИ ИНВЕНТАРЯ? </summary>
    [Tooltip("Активировать ли паузу при открытии инвентаря?")]
    public bool pauseOnOpen = true;

    /// <summary> Показывает сколько занято слотов в инвентаре </summary>
    public Text TextCountSlots;

    /// <summary> РЕЖИМ ДЕБАГА (если вкл, то выводит в консоль сообщения, что удалено/добавлено и т.п.) </summary>
    public bool debugMode = false; // если выключено, то выводятся только сообщения об ошибках

    void Start()
    {
        // инициализируем СПИСОК-ИНВЕНТАРЬ
        inventoryList = new List<InventoryItem>();
        // определяеся МАКСИМАЛЬНАЯ ВМЕСТИМОСТЬ ИНВЕНТАРЯ по кол-ву ячеек в UI
        MaxSlotInventoryPanelUI = inventoryPanelUI.transform.childCount;
        if (debugMode) Debug.Log("Максимальная вместимость инвентаря :" + MaxSlotInventoryPanelUI);

        //  NULL-ПРОВЕРКИ
        if (inventoryPanelUI == null) Debug.LogWarning("UI-ПАНЕЛЬ-ИНВЕНТАРЬ: ссылка потеряна! Перетяните в инспекторе!");
        if (iconPrefab == null) Debug.LogWarning("КАРТИНКА-ПРЕФАБ ДЛЯ ЯЧЕЙКИ: ссылка потеряна! Перетяните в инспекторе!");
        if raftPanel == null) Debug.LogWarning("ССЫЛКА НА ПАНЕЛЬ КРАФТА: ссылка потеряна! Перетяните в инспекторе!");

        // ПОЛОЖЕНИЕ ПРАВОЙ РУКИ игрока, проверка на NULL
        if (RightHandPosition == null) Debug.LogWarning("НУЖЕН TRANSFORM РУКИ ИГРОКА! Перетяните в инспекторе в RightHandPosition!");

        // ТЕКСТ о КОЛ-ВЕ СЛОТОВ
        if (TextCountSlots == null) Debug.LogWarning("Потерян UI-текст для вывода кол-ва занятых слотов, перетащите в инспекторе");
        // задаем изначальное значение до первого обновления
        else TextCountSlots.text = "Slots: " + InventoryListCount() + " / " + MaxSlotInventoryPanelUI;
        // при изменении количества предметов в инвентаре обновляется цифра предметов в тексте
        OnChangeCountItem += delegate { TextCountSlots.text = "Slots: " + InventoryListCount() + " / " + MaxSlotInventoryPanelUI; };

        // ПОЛУЧАЕМ ССЫЛКУ НА СПЕЦИАЛЬНЫЙ СКРИПТ ИНВЕНТАРЯ НА ИГРОКЕ
        if (player.GetComponent<InventoryPickUp>()) playerInventoryScript = player.GetComponent<InventoryPickUp>();
        else Debug.LogError("На игроке не найден скрипт InventoryPickUp");
        playerInventoryScript.OnItemCollision += PickUpOnCollisionEnter;
    }

    /// <summary> ИНВЕНТАРЬ НЕ ПОЛНЫЙ (return true, если есть слоты, и return false, если полный) </summary>
    bool InventoryNotFull()
    {
        // если в СПИСКЕ-ИНВЕНТАРЕ элементов меньше, чем ячеек в UI-ПАНЕЛИ-ИНВЕНТАРЯ
        if (InventoryListCount() < MaxSlotInventoryPanelUI) return true; else return false;
    }

    /// <summary> ПОДНЯТЬ ПРИ СОПРИКОСНОВЕНИИ вещи с коллайдером игрока</summary>
    /// <param name="collision">Объект, с которым столкнулись</param>
    void PickUpOnCollisionEnter(Collision collision)
    {
        // получаем ссылку на объект с которым столкнулись
        InventoryItem itemFromCollision = collision.collider.GetComponent<InventoryItem>();
        // если мы столкнулись не с тем, что можно поднимать (не с InventoryItem)
        if (itemFromCollision == null) return; // выход из функции

        // если вкл. режим поднимания по соприкосновению и инвентарь не полон
        if (puckUpOnCollision & InventoryNotFull())
        {
            if (debugMode) Debug.Log("PickUpOnCollisionEnter - поднимаем: " + itemFromCollision.name + " [" + TextCountSlots.text + "]");
            // помещаем ее в СПИСОК-ИНВЕНТАРЬ
            InventoryListAdd(itemFromCollision);
            // удаляем ее со сцены
            Destroy(collision.collider.gameObject);
        }
        // если включен дебагмод, инвентарь полон и поднимание по соприкосновению включено
        else if (debugMode & InventoryNotFull() == false & puckUpOnCollision)
            Debug.Log("PickUpOnCollisionEnter - инвентарь полон. " + itemFromCollision.name + " [" + TextCountSlots.text + "]");
    }

    /// <summary> РАЙКАСТ ДЛЯ ПОДНИМАНИЯ ПРЕДМЕТА </summary>
    void PuckUpItemRaycastMouse()
    {
        if (!puckUpOnClick) return; // если не включено поднимает по клику, то выход из функции
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // пускаем райкаст с позиции мыши
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit)) // если райкаст куда-то попал
        {
            // пытаемся получить с объекта скрипт итема для инвентаря
            InventoryItem itemFromRaycast = hit.collider.GetComponent<InventoryItem>();
            if (itemFromRaycast != null) // если попытка не оказалась неудачной
            {
                // если расстояние до объекта меньше максимального
                if (Vector3.Distance(itemFromRaycast.transform.position, player.transform.position) < maxDistanceClick)
                {
                    if (InventoryNotFull()) // если в инвентаре есть место
                    {
                        if (debugMode) Debug.Log("PuckUpItemRaycastMouse - поднимаем: " + itemFromRaycast.name + " [" + TextCountSlots.text + "]");

                        InventoryListAdd(itemFromRaycast); // добавляем в инвентарь (List) итем
                        Destroy(hit.collider.gameObject); // удаляем со сцены модельку объекта
                        if (Panels.activeSelf) // если инвентарь открыт
                        {
                            CloseInventory(); // закрываем и открываем инвентарь для обновления иконок
                            OpenInventory();
                            // ClearCellUI(); и FillCellsUIFromList(); не используем, так как нужно
                            // еще и с крафтового поля убрать предметы, иначе при заполнении ячеек
                            // UI-инвентаря предметы, находящиеся в крафте, заново появятся в инвентаре
                        }
                    }
                    else Debug.Log("PuckUpItemRaycastMouse - в инвентаре нет места");
                }
                else Debug.Log("PuckUpItemRaycastMouse - предмет слишком далеко (" + hit.distance + "), maxDistanceClick: " + maxDistanceClick);
            }
        }
    }

    void Update()
    {
        // получение предмета по райкасту
        if (Input.GetMouseButtonUp(0)) { PuckUpItemRaycastMouse(); }

        // ВКЛ/ВЫКЛ ИНВЕНТАРЯ ПО НАЖАТИЮ keyOpenClose
        if (Input.GetKeyDown(keyOpenClose)) { if (Panels.activeSelf) { CloseInventory(); } else { OpenInventory(); } }
    }

    /// <summary> ОТКРЫТЬ ИНВЕНТАРЬ </summary>
    public void OpenInventory()
    {
        Panels.SetActive(true); // активируем главную панель (там кнопки)
        сraftPanel.ActivateCraftPanel(true); // активируем панель крафта
        inventoryPanelUI.SetActive(true); // активируем инвентарь-панель на экране
        FillCellsUIFromList(); // вызываем функцию обновления (вывода) содержимого на экранном инвентаре, которая покажет, что у нас есть в инвентаре
        if (pauseOnOpen) Time.timeScale = 0; // если галочка стоит, ставим на паузу игру
        if (debugMode) Debug.Log("OpenInventory - инвентарь открыт. Time.timeScale: " + Time.timeScale);
    }

    /// <summary> ЗАКРЫТЬ ИНВЕНТАРЬ </summary>
    public void CloseInventory()
    {
        Time.timeScale = 1;
        сraftPanel.ActivateCraftPanel(false); // деактивируем панель крафта
        inventoryPanelUI.SetActive(false); // деактивируем инвентарь
        Panels.SetActive(false); // дактивируем главную панель (там кнопки)
        ClearCellUI(); // очищаем дисплейный инвентарь
        if (debugMode) Debug.Log("CloseInventory - инвентарь закрыт. Time.timeScale: " + Time.timeScale);
    }

    /// <summary> ЗАПОЛНИТЬ ячейки UI-ПАНЕЛИ-ИНВЕНТАРЯ предметами из СПИСКА-ИНВЕНТАРЯ (вызывается при активации UI-ПАНЕЛИ-ИНВЕНТАРЯ) </summary>
    void FillCellsUIFromList()
    {
        for (int i = 0; i < InventoryListCount(); i++) // берем каждый предмет из СПИСКА-ИНВЕНТАРЯ
        {
            // если в UI-ПАНЕЛИ-ИНВЕНТАРЯ слотов больше, чем текущий предмет из СПИСКА-ИНВЕНТАРЯ
            if (MaxSlotInventoryPanelUI >= i)
            {
                // создаем картинку под отображение конкретного предмета в инвентаре из префаба container (на нем висит iconForItem.cs)
                GameObject icon = Instantiate<GameObject>(iconPrefab);
                // делаем картинку дочерней от слота в инвентаре
                icon.transform.SetParent(inventoryPanelUI.transform.GetChild(i).transform);
                // устанавливаем картинке изображение той вещи, которая там должна лежать
                icon.GetComponent<Image>().sprite = InventoryListGetItem(i).spriteIcon;
                // в иконку передаем сам предмет
                icon.GetComponent<InventoryIcon>().item = InventoryListGetItem(i);
            }
            else
            {
                Debug.LogWarning("Предметов у СПИСКА-ИНВЕНТАРЯ оказалось больше, чем слотов в UI-ПАНЕЛИ-ИНВЕНТАРЯ. Как так, блэт?" +
                    "В UI-ПАНЕЛЬ-ИНВЕНТАРЯ добавлены только те, что поместились, остальные проигнорированы.");
                break;
            }
        }
        if (debugMode) Debug.Log("FillCellsUIFromList - ячейки UI-ПАНЕЛИ-ИНВЕНТАРЯ заполнены иконками.");
    }

    /// <summary> УДАЛИТЬ ИКОНКИ ИЗ UI-ПАНЕЛИ-ИНВЕНТАРЯ (вызывается перед деактивацией UI-ПАНЕЛИ-ИНВЕНТАРЯ) </summary>
    void ClearCellUI()
    {
        for (int i = 0; i < MaxSlotInventoryPanelUI; i++) // для каждой ячейки UI-ПАНЕЛИ-ИНВЕНТАРЯ
        {
            // если в ячейке что-то лежит (количество детей больше 0)
            if (inventoryPanelUI.transform.GetChild(i).transform.childCount > 0)
            {
                // удаляем КАРТИНКУ-ПРЕФАБ ДЛЯ ЯЧЕЙКИ (iconPrefab)
                Destroy(inventoryPanelUI.transform.GetChild(i).transform.GetChild(0).gameObject);
            }
        }
        if (debugMode) Debug.Log("ClearCellUI - ячейки UI-ПАНЕЛИ-ИНВЕНТАРЯ очищены от иконок");
    }

    /// <summary> ВЫКИНУТЬ ВЕЩЬ НА СЦЕНУ </summary> <param name="icon">Иконка с InventoryIcon.cs</param>
    void RemoveItemInventory(InventoryIcon icon) // (вызывается кликом на иконке с помощью скрипта InventoryIcon.cs)
    {
        // проверка есть ли ссылка на префаб выкидываемой вещи
        if (Resources.Load<GameObject>(icon.item.prefabForScene) == null)
        {
            Debug.LogError("Невозможно выкинуть вещь (создать префаб)! Проверьте его ссылку: " + icon.item.prefabForScene);
            return;
        }

        if (debugMode) Debug.Log("RemoveItemInventory - предмет выкинут на сцену: " + icon.item.prefabForScene);

        // создаем на сцене объект из префаба, на который ссылается ячейка
        GameObject droppedItem = Instantiate(Resources.Load<GameObject>(icon.item.prefabForScene));
        // задаем позицию появления на сцене
        droppedItem.transform.position = DropZone();
        // удаляем из UI-ПАНЕЛИ-ИНВЕНТАРЯ картинку-ячейку
        Destroy(icon.gameObject);
        // удаляем из СПИСКА-ИНВЕНТАРЯ предмет
        InventoryListRemove(icon.item);
    }

    /// <summary> КУДА ВЫКИДЫВАТЬ ВЕЩИ ИЗ ИНВЕНТАРЯ </summary>
    Vector3 DropZone()
    {
        // берем позицию игрока + случайные координаты в диапазоне
        return player.transform.position + new Vector3(
               UnityEngine.Random.Range(-0.5f, 0.5f),   // диапазон разброса по x
               UnityEngine.Random.Range(-0.9f, 0.9f),   // диапазон разброса по y
               UnityEngine.Random.Range(-0.5f, 0.5f)    // диапазон разброса по z
               );
    }

    /// <summary> ИСПОЛЬЗОВАТЬ/ОДЕТЬ ПРЕДМЕТ </summary>
    /// <param name="icon">Иконка предмета с InventoryIcon.cs</param>
    void UseItemInventory(InventoryIcon icon) // Вызывается из InventoryIcon.cs через "player.BroadcastMessage("UseItemInventory", this);"
    {
        if (icon.item.type == InventoryItem.usageType.food) // если тип предмета food (задается в префабе)
        {
            if (debugMode) Debug.Log("UseItemInventory - Использование предмета обозначено: " + icon.item.prefabForScene + ", тип: " + icon.item.type); // пропишите тут код использования
            InventoryListRemove(icon.item); // удалить из ЛИСТ-ИНВЕНТАРЯ предмет
            Destroy(icon.gameObject); // удалить из UI иконку предмета
        }
        else if (icon.item.type == InventoryItem.usageType.itemRightHand) // если тип предмета такой, что его можно брать в руку
        {
            if (debugMode) Debug.Log("UseItemInventory - одеваем предмет: " + icon.item.prefabForScene + ", тип: " + icon.item.type);
            // вызываем в мир префаб предмета
            InventoryItemHand myitem = Instantiate(Resources.Load<GameObject>(icon.item.prefabForScene)).GetComponent<InventoryItemHand>();
            // даем в руку персонажу предмет
            TakeInHand(myitem);
            // удаляем из ЛИСТ-ИНВЕНТАРЯ предмет
            InventoryListRemove(icon.item);
            // удаляем из UI иконку предмета
            Destroy(icon.gameObject);
        }
        else Debug.LogWarning("UseItemInventory - В скрипте не прописано, как использовать тип предмета '" + icon.item.type + "' (" + icon.item.prefabForScene + ")");
    }

    /// <summary> ЛЕЖАЩАЯ В ПРАВОЙ РУКЕ ВЕЩЬ </summary>
    private InventoryItemHand currentItemInRightHand;

    /// <summary> ВЗЯТЬ В РУКУ </summary>
    public void TakeInHand(InventoryItemHand item)
    {
        // если в руке уже есть другой предмет
        if (currentItemInRightHand != null)
        {
            if (debugMode) Debug.Log("TakeInHand - В руке уже был предмет: " + currentItemInRightHand.prefabForScene + ", он выброшен на сцену");
            // убираем ему родителя, чтобы вещь оказалась в глобальных, а не в локальных координатах
            currentItemInRightHand.transform.SetParent(null);
            // выкидываем на сцену
            currentItemInRightHand.transform.position = DropZone();
            //добавляем вещи Rigidbody, удаленный при одевании
            currentItemInRightHand.gameObject.AddComponent<Rigidbody>();
        }

        // устанавливаем вещи родителя правую руку, чтобы она привязалась к ней
        item.transform.SetParent(RightHandPosition);
        // берем из префаба вещи точную позицию, как позиционировать модельку в руке
        item.transform.localPosition = item.positionInHand;
        // также берем и вращение
        item.transform.localRotation = Quaternion.Euler(item.rotationInHand);
        // убираем Rigidbody, чтобы вещь не притягивалась к земле
        Destroy(item.GetComponent<Rigidbody>());
        // храним в переменной вещь
        currentItemInRightHand = item;
        if (debugMode) Debug.Log("TakeInHand - Предмет " + item.prefabForScene + " взят в руку");
    }


}

 

 

 

 

InventoryPickUp.cs

Спойлер

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System; // для работы с делегатами и событиями

/// <summary> СКРИПТ ИНВЕНТАРЯ НА ИГРОКЕ (отслеживает коллизии с вещами и т.п.) </summary>
public class InventoryPickUp : MonoBehaviour
{
    // КОГДА ИГРОК КАСАЕТСЯ ОБЪЕКТА (у обоих должны быть коллайдеры)
    void OnCollisionEnter(Collision collision)
    {
        // если столкнулись с поднимаемой вещью
        if (collision.collider.GetComponent<InventoryItem>() != null)
        {
            // и если на событие есть подписчик, то сообщаем о нем
            if (OnItemCollision != null) OnItemCollision(collision);
        }
    }

    /// <summary> ИГРОК ДОТРОНУЛСЯ ДО ПОДНИМАЕОМОГО ПРЕДМЕТА </summary>
    public event Action<Collision> OnItemCollision;
    // (используется в менеджере инвентаря для поднимания предмета по соприкосновении с ним)
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

 

 

 

InventoryItem.cs
 

Спойлер

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary> ПРЕДМЕТ ДЛЯ ИНВЕНТАРЯ </summary>
public class InventoryItem : MonoBehaviour
{
    // скрипт вешается на все предметы, которые необходимо сделать подбираемыми
    // содержит в себе картинку для иконки инвентаря и ссылку на префаб, который будет
    // инстанциироваться в мире когда предмет выкидывается из инвентаря

    /// <summary> ПЕРЕЧИСЛЕНИЕ ТИПОВ ПРЕДМЕТОВ ПО ИСПОЛЬЗОВАНИЮ (одеваемый, съедобный и т.п.) </summary>
    public enum usageType { food, mana, itemRightHand };
    // при клике на предмет InventoryManager в зависимости от типа что-то делает

    /// <summary> ID предмета (используется для крафта, в рецептах требуемые материалы определяются по id)</summary>
    [Tooltip("Введите ID предмета, чтобы по ID задавать в рецептах нужные предметы-материалы для крафта")]
    public int id;

    /// <summary> ТИП ПРЕДМЕТА (еда, оружие и т.п.) </summary>
    [Tooltip("Выберите тип предмета, чтобы инвентарь знал, как его использовать/одевать")]
    public usageType type;

    /// <summary> КАРТИНКА ДЛЯ ИКОНКИ в инвентаре </summary>
    [Tooltip("Картинка для иконки в инвентаре. Перетащите из папки Resources")]
    public Sprite spriteIcon;

    /// <summary> ПРЕФАБ ПРЕДМЕТА (то что появится на сцене, если выкинуть предмет из инвентаря) </summary>
    [Tooltip("Префаб предмета. Перетяните из папки Resources")]
    public string prefabForScene;

}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

 

 

 

InventoryItemHand.cs
 

Спойлер

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary> ПРЕДМЕТ, КОТОРЫЙ МОЖНО ВЗЯТЬ В РУКУ (и положить в инвентарь) </summary>
public class InventoryItemHand : InventoryItem // наследование от простого предмета
{
    //скрипт вешается на предметы, которые можно подбирать в инвентарь и брать в руку

    /// <summary> КООРДИНАТЫ В РУКЕ </summary>
    [Tooltip("Установите точное положение в руке игрока (локальные координаты относительно transform его руки)")]
    public Vector3 positionInHand;

    /// <summary> ПОД КАКИМ УГЛОМ ПОЛОЖИТЬ В РУКУ </summary>
    [Tooltip("Установите повороты")]
    public Vector3 rotationInHand;

    // радиус "ручки" предмета, за которую держит его моделька игрока. Нужен, чтобы через аниматор задать уровень
    // сжатия и разжатия кулака в зависимости от толщины ручки предмета (пока не используется)
    /// <summary> РАДИУС РУЧКИ </summary>
    [Tooltip("Радиус ручки")]
    public float radiusHandle;
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

 

 

 

InventoryIcon.cs
 

Спойлер

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class InventoryIcon : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerClickHandler
{
    // скрипт висит на префабе иконки. Позволяет ее перетаскивать и нажимать на нее

    /// <summary> UI-ПАНЕЛЬ-ИНВЕНТАРЬ, содержащая ячейки для отображения инвентаря </summary>
    public Transform inventoryPanelUI;
    /// <summary> ЯЧЕЙКА ДО ПЕРЕТАСКИВАНИЯ </summary>
    public Transform oldCell;
    /// <summary> ссылка на игрока, ищется по тегу в Start </summary>
    private GameObject player;
    /// <summary> ПРЕДМЕТ, ОБОЗНАЧАЕМЫЙ ИКОНКОЙ </summary>
    public InventoryItem item; // задается из скрипта инвентаря при открытии инвентаря и создании иконки в UI-ПАНЕЛИ-ИНВЕНТАРЯ
    /// <summary> UI-ПАНЕЛЬ КРАФТА </summary>
    InventoryCraft craftPanel;

    void Start()
    {
        inventoryPanelUI = GameObject.Find("InventoryPanelUI").transform; //ищем канвас, в котором находится наш инвентарь
        player = GameObject.FindGameObjectWithTag("Player"); //ищем объект с тегом Player
        craftPanel = GameObject.Find("CraftPanel").GetComponent<InventoryCraft>(); // ищем панель с крафтом
    }

    // по началу перетаскивания
    public void OnBeginDrag(PointerEventData eventData)
    {
        // запоминаем исходную ячейку
        oldCell = transform.parent;
        // помещаем иконку в канвас
        transform.SetParent(inventoryPanelUI);
        // отключаем иконке ловлю райкаста, чтобы ячейки под ней могли понять, где мы ее дропнули
        GetComponent<CanvasGroup>().blocksRaycasts = false;

        // если перетаскиваем из результатов крафта, то она добавляется в инвентарь
        if (oldCell.name == "ResultItem") craftPanel.AddResultToInventory(this);
        // если перетаскиваем материал из ячеек для крафта, то обновляем крафт
        if (oldCell.name == "CraftItem") craftPanel.UpdateCraft();
    }

    // в процессе перетаскивания
    public void OnDrag(PointerEventData eventData)
    {
        // иконка движется вместе с мышкой
        transform.position = Input.mousePosition;
    }

    // по завершению перетастиквания
    public void OnEndDrag(PointerEventData eventData)
    {
        // включаем ловлю райкаста иконкой
        GetComponent<CanvasGroup>().blocksRaycasts = true;

        // если родитель это канвас (бывает, когда иконка не брошена в какую-либо ячейку)
        if (transform.parent == inventoryPanelUI)
        {
            // если мы взяли иконку из результата крафта, добавляем ее в UI-ПАНЕЛЬ-ИНВЕНТАРЬ
            if (oldCell.name == "ResultItem") { ToPanelUI(); }
            // в остальных случаях устанавливаем родителя того, кто был изначально до перетаскивания 
            else transform.SetParent(oldCell);
        }
        // обновляем крафт (на случай если мы взяли иконку из материалов крафта (крафт при этом обновляется,
        // ведь иконки уже нет в ячейке материалов), но ни в одну ячейку ее не бросили, и она вернулась обратно
        craftPanel.UpdateCraft();
    }

    /// <summary> ДОБАВИТЬ ИКОНКУ В UI-ПАНЕЛЬ-ИНВЕНТАРЬ в свободный слот </summary>
    void ToPanelUI()
    {
        // пробегаем по всем ячейкам UI-ИНВЕНТАРЯ
        for (int i = 0; i < inventoryPanelUI.transform.childCount; i++)
        {
            // ищем пустую
            if (inventoryPanelUI.GetChild(i).childCount == 0)
                // добавляем в нее скрафченную вещь
                transform.SetParent(inventoryPanelUI.GetChild(i));
        }
    }

    // когда кликнули по иконке
    public void OnPointerClick(PointerEventData eventData)
    {
        // если КЛИК ПО РЕЗУЛЬТАТУ КРАФТА ИЛИ КРАФТ-МАТЕРИАЛУ, то ничего не делаем
        if (transform.parent.name == "ResultItem" || transform.parent.name == "CraftItem") return;

        // если КЛИК В ИНВЕНТАРЕ
        if (transform.parent.name == "InventorySlot")
        {
            // если клик левой кнопкой мыши
            if (eventData.button == PointerEventData.InputButton.Left)
            {
                //посылаем сообщение игроку(найденному по тегу), вызывая функцию use (может быть в любом скрипте на игроке) и передаем туда 
                //этот объект (т.е. предмет из инвентаря)
                SendMessageUpwards("UseItemInventory", this);
            }
            // если клик правой кнопкой мыши
            else if (eventData.button == PointerEventData.InputButton.Right)
            {
                // выкидываем из инвентаря на сцену
                SendMessageUpwards("RemoveItemInventory", this);
            }
        }
    }

}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

 

 

 

InventoryCell.cs
 

Спойлер

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;


public class InventoryCell : MonoBehaviour, IDropHandler
{
    // скрипт висит на ячейках (инвентаря, материалов для крафта, панели быстрого доступа)
    // отвечает за то, что делать, если в ячейку бросили иконку

    private GameObject player; // для получения игрока, ищется по тегу в Start

    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player"); // ищем объект с тегом Player
    }

    // КОГДА В ЯЧЕЙКУ БРОСИЛИ ИКОНКУ (не срабатывает в случае, если перетягиваемую иконку 
    // отпустить и она вернется обратно)
    public void OnDrop(PointerEventData eventData)
    {
        // получаем компонент иконки из того, что на бросили
        InventoryIcon iconItem = eventData.pointerDrag.GetComponent<InventoryIcon>();

        // если не удалось получить, прерываемся
        if (iconItem == null) { Debug.LogWarning("На иконке нет скрипта! Обработка OnDrop невозможна!"); return; }

        // если иконка упала в ЯЧЕЙКУ ИНВЕНТАРЯ
        if (transform.name == "InventorySlot")
        {
            if (transform.childCount > 0) // если ячейка занята (если детей больше 0)
            {
                // если иконка пришла из результатов крафта, то выходим (чтобы иконка не наложилась на другую
                // так как поменять местами мы их не можем), и она добавится в свободый слот через InventoryIcon.OnEndDrag
                if (iconItem.oldCell.name == "ResultItem") return;

                // меняем иконки местами:
                transform.GetChild(0).SetParent(iconItem.oldCell); // иконку из инвентаря ставим на место дропнутой
                iconItem.transform.SetParent(transform); // дропнутую ставим в инвентарь
            }
            else iconItem.transform.SetParent(transform); // если слот не занят, просто кладем иконку в ячейку
            return; // выход
        }

        // если иконка упала в ЯЧЕЙКУ МАТЕРИАЛОВ КРАФТА
        if (transform.name == "CraftItem")
        {
            if (transform.childCount > 0) // если ячейка занята (если детей больше 0)
            {
                // меняем иконки местами
                transform.GetChild(0).SetParent(iconItem.oldCell); // иконку из крафт-ячейки ставим на место дропнутой
                iconItem.transform.SetParent(transform); //дропнутую ставим в крафт-ячейки
            }
            else // если ячейка не занята просто кладем иконку в слот
            {
                iconItem.transform.SetParent(transform);
            }
            return; // выход
        }
    }

}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

 

 

 

InventoryCraft.cs
 

Спойлер

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class InventoryCraft : MonoBehaviour
{
    // весит на крафт панели, в инспекторе содержит рецепты, сверяет предметы из ячеек-материалов с рецептами, и крафтит
    // сделано по уроку https://www.youtube.com/watch?v=_syz8dGJ5yk

    /// <summary> МАССИВ РЕЦЕПТОВ (задается в инспекторе) </summary>
    public Reciple[] listReciples;
    /// <summary> ССЫЛКА НА ПАНЕЛЬ КРАФТА CraftCells, у которой детей 9 ячеек </summary>
    public Transform craftCells;
    /// <summary> РЕЗУЛЬТИРУЮЩАЯ КРАФТ ЯЧЕЙКА </summary>
    public Transform resultCraftCell;
    /// <summary> ПРЕФАБ ИКОНКИ ПРЕДМЕТОВ для ячеек </summary>
    public GameObject iconPrefab;
    /// <summary> СКРИПТ ИНВЕНТАРЯ </summary>
    public InventoryManager inventoryCanvas;

    private void Start()
    {
        //  NULL-ПРОВЕРКИ
        if (craftCells == null) Debug.LogWarning("CraftPanel: ссылка на craftCells потеряна! Перетяните в инспекторе!");
        if (resultCraftCell == null) Debug.LogWarning("CraftPanel: ссылка на resultCraftCell потеряна! Перетяните в инспекторе!");
        if (iconPrefab == null) Debug.LogWarning("CraftPanel: ссылка на iconPrefab потеряна! Перетяните в инспекторе!");
        if (inventoryCanvas == null) Debug.LogWarning("CraftPanel: ссылка на inventoryCanvas потеряна! Перетяните в инспекторе!");
    }

    /// <summary> ЭКЗЕМПЛЯР РЕЦЕПТА </summary>
    [System.Serializable]
    public class Reciple
    {
        /// <summary> ЧТО СКРАФТИТСЯ </summary>
        public InventoryItem resultItem;
        /// <summary> ЧТО НУЖНО ДЛЯ КРАФТА </summary> 
        public NeededMaterial material; // id предметов-материалов к рецепту задаются в инспекторе
    }

    /// <summary> КЛАСС МАТЕРИАЛОВ ДЛЯ КРАФТА Нужен чтобы выводить требуемые материалы в виде таблички 3х3 в инспекторе </summary>
    [System.Serializable]
    public class NeededMaterial
    {
        public Vector3 A; public Vector3 B; public Vector3 C;
    }

    /// <summary> ВКЛ/ВЫКЛ ОКНО КРАФТА </summary> 
    public void ActivateCraftPanel(bool isActive) // принимает true/false
    {
        gameObject.SetActive(isActive); // делаем активным/неактивным объект на котором скрипт
        if (inventoryCanvas.debugMode & isActive) Debug.Log("CraftPanel/ActivateCraftPanel - окно крафта открыто");
        else if (inventoryCanvas.debugMode & !isActive) Debug.Log("CraftPanel/ActivateCraftPanel - окно крафта закрыто");

        if (!isActive) // если окно закрыли
        {
            // очищаем ячейку с результатом
            if (resultCraftCell.childCount > 0) Destroy(resultCraftCell.GetChild(0).gameObject);
            // пробегаемся по всем ячейкам материалов
            for (int i = 0; i < craftCells.childCount; i++)
            {
                // если в них что-то есть, то удаляем
                if (craftCells.GetChild(i).childCount > 0) Destroy(craftCells.GetChild(i).GetChild(0).gameObject);
            }
            if (inventoryCanvas.debugMode) Debug.Log("CraftPanel/ActivateCraftPanel - иконки материалов и результата крафта очищены");
        }
    }
    /// <summary> ДОБАВИТЬ РЕЗУЛЬТАТ КРАФТА В ИНВЕНТАРЬ (вызывается при перетягивании вещи из ячейки resultCraftCell)</ summary >
    public void AddResultToInventory(InventoryIcon craftedItem)
    {
        // пробегаемся по всем ячейкам для крафта
        for (int i = 0; i < craftCells.childCount; i++)
        {
            // если в них что-то есть
            if (craftCells.GetChild(i).childCount > 0)
            {
                // получаем ссылку на предмет (материал)
                GameObject g = craftCells.GetChild(i).GetChild(0).gameObject;
                // удаляем предмет-материал из ЛИСТ-ИНВЕНТАРЯ
                inventoryCanvas.InventoryListRemove(g.GetComponent<InventoryIcon>().item);
                // удаляем ИКОНКУ-материала из UI-ПАНЕЛИ-ИНВЕНТАРЯ
                Destroy(g);
            }
        }
        if (inventoryCanvas.debugMode) Debug.Log("CraftPanel/AddResultToInventory - попытка добавить в инвентарь скрафченный предмет: " + craftedItem.item);
        // добавить РЕЗУЛЬТАТ КРАФТА В ЛИСТ-ИНВЕНТАРЬ
        inventoryCanvas.InventoryListAdd(craftedItem.item);
    }

    /// <summary>
    /// ОБНОВИТЬ ОКНО КРАФТА. Вызывается, когда кладется вещь в окно, либо перетаскивается внутри.
    /// Просчитывает можно ли из положенных предметов что-то скрафтить.
    /// </summary>
    public void UpdateCraft()
    {
        if (inventoryCanvas.debugMode) Debug.Log("CraftPanel/UpdateCraft - обновление окна крафта (проверка что можно скрафтить)");

        // очищаем результирующую ячейку (в ней может лежать нескрафченная вещь от прошлой комбинации материалов)
        if (resultCraftCell.childCount > 0) Destroy(resultCraftCell.GetChild(0).gameObject);

        // список всех рецептов, который сверяем с материалами в крафт-панели и зануляем неподходящие рецепты
        List<int[]> listRecForCheck = new List<int[]>();
        //переводим массив векторов в массив типа int
        for (int i = 0; i < listReciples.Length; i++) 
        {
            int[] arr = new int[9]
            {
                (int) listReciples [i].material.A.x, (int) listReciples [i].material.A.y, (int) listReciples [i].material.A.z,
                (int) listReciples [i].material.B.x, (int) listReciples [i].material.B.y, (int) listReciples [i].material.B.z,
                (int) listReciples [i].material.C.x, (int) listReciples [i].material.C.y, (int) listReciples [i].material.C.z
            };
            listRecForCheck.Add(arr); //добавляем полученный выше массив в listRecForCheck
        } //теперь лист содержит массивы со всеми рецептами

        //пробегаемся по всем детям-ячейкам из CraftItems, и проверяем совпадает ли id лежащих в нем предметов с id из листа
        //если есть ребенок, то берем id у ребенка, если нет, то id = 0
        for (int i = 0; i < craftCells.childCount; i++)
        {
            //переменную id будем менять далее в зависимости от того, с каким id лежит предмет в ребенке-ячейке у панели CraftItems
            int id = 0;
            //если у ячейки есть ребенок (т.е. если в ней что-то лежит)
            if (craftCells.GetChild(i).childCount > 0)
                //то добавляем в переменную id индекс предмета, который прописан в его префабе (если ребенка нет, то id остается 0)
                id = craftCells.GetChild(i).GetChild(0).GetComponent<InventoryIcon>().item.id;

            // проходимся по всем рецептам. j - текущий рецепт, i - текущая ячейка
            for (int j = 0; j < listReciples.Length; j++)
            {
                if (listRecForCheck[j] != null) // если рецепт j еще не был отбракован, как неподходящий
                {
                    if (listRecForCheck[j][i] != id) // если материал в рецепте j из ячейки i не совпадает с материалом, лежащим в ячейке i крафтовой панели
                    {
                        listRecForCheck[j] = null; // тогда весь рецепт j помечаем, как неподходящий для крафта
                    }
                } // в итоге останется не null только тот рецепт, в котором id всех элементов совпадут с id вещей, лежащих в крафтовой панели
            }
        }

        //ПРОВЕРКА СПИСКА и КРАФТ ПРЕДМЕТА, если в списке есть элементы не null
        for (int j = 0; j < listReciples.Length; j++) //количество итераций равно количество рецептов в игре
        {
            if (listRecForCheck[j] != null) //если под каким либо индексом у списка будет не Null, тогда крафтим такой предмет
            {
                if (inventoryCanvas.debugMode) Debug.Log("CraftPanel/UpdateCraft - можно скрафтить " + listReciples[j].resultItem + " из рецепта под индексом: " + j);
                // создаем переменную рецепта такого, какой будет под индексом j (индекс берется из listRecForCheck)
                Reciple current = listReciples[j];
                // узнаем из рецепта, какую вещь он крафтит
                InventoryItem item = current.resultItem.GetComponent<InventoryItem>();
                // создаем иконку для предмета
                GameObject iconResultCraft = Instantiate(iconPrefab);
                // помещаем иконку в результат крафта
                iconResultCraft.transform.SetParent(resultCraftCell);
                // устанавливаем иконке картинку соответствующей вещи
                iconResultCraft.GetComponent<Image>().sprite = item.spriteIcon;
                // добавляем в скрипт InventoryIcon, весящий на иконке, информацию, какую вещь она символизирует
                iconResultCraft.GetComponent<InventoryIcon>().item = item;
                break;
            }
        }
    }

}

 

 

 

 

Share this post


Link to post
Share on other sites

здравствуйте. а как видео посмотреть? хотя,бы 2ое.. или всё пропало?

Share this post


Link to post
Share on other sites
В 17.09.2019 в 21:28, mumum сказал:

здравствуйте. а как видео посмотреть? хотя,бы 2ое.. или всё пропало?

Привет! Все пропало, я со временем (если будет время) все выложу, и с видео и скрипты инвентаря нормальные. Когда напишу...)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×