Jump to content

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

}

 

 

 

 

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

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

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

Link to post
Share on other sites
 

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Powered by Invision Community
Поддержка Invision Community в России

×
  • Create New...