• 【Unity】 2D 游戏 库存模块实现


    库存模块主要参考了 youtube 上的视频

    BMo 的 Flexible INVENTORY SYSTEM in Unity with Events and Scriptable Objects 和 Simple Inventory UI in Unity With Grid Layouts 这两个视频是一个系列

    还是一个视频也是 BMo的 How To INTERACT with Game Objects using UNITY EVENTS Tutorial

    这三个视频讲述了怎么用Unity Event、delegate、Collider Trigger 的功能去做一个库存系统。

    但我感觉 delegate 代理类注册的写法需要在另外一个类中硬编码会比较不美观,所有换了一种两个事件调用的写法。

    功能点主要有两个:一个是物品从地图进入到库存,另一个就是,反过来,物品从库存中消失。

    总体来说,前者比较复杂,后者比较简单。后者的功能代码仅仅是前者的一部分。

    物品从地图进入到库存中的过程有三个:人物进到物品可以交互的范围里、人物和物品交互、物品进入到人物的库存中。这三个流程可以分别设计成独立的实体。

    人物进到物品可以交互的范围里

    人物可以交互的范围是有限的,不然就会出现人物隔着很远的距离可以和N多个物品交互。

    这块功能的实现主要是用了 Unity Collider 2D 的功能,Collider 2D不仅可以作为实体碰撞的功能,也能检测物体从范围外进入到范围内。通过在 C# 中实现 OnTriggerEnter2D 和 OnTriggerExit2D 方法,就可以达到检测人物是否进入到物品可以交互的范围里。

    在这里插入图片描述

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Events;
    
    public class PickUpControl : MonoBehaviour
    {
        public Boolean isInRange;
        public KeyCode interactKey;
        public UnityEvent interactEvent;
    
        void Update()
        {
            if(isInRange)
            {
                if(Input.GetKeyDown(interactKey))
                {
                    interactEvent.Invoke();
                    UnityEngine.Debug.Log($"{transform.parent.name} Interact Event invoke");
                }
            }
        }
    
        /// 
        /// Sent when another object enters a trigger collider attached to this
        /// object (2D physics only).
        /// 
        /// The other Collider2D involved in this collision.
        void OnTriggerEnter2D(Collider2D other)
        {
            if(other.transform.CompareTag("Player"))
            {
                isInRange = true;
                UnityEngine.Debug.Log($"{transform.parent.name} is in range");
            }
    
        }
    
        /// 
        /// Sent when another object leaves a trigger collider attached to
        /// this object (2D physics only).
        /// 
        /// The other Collider2D involved in this collision.
        void OnTriggerExit2D(Collider2D other)
        {
            if(other.transform.CompareTag("Player"))
            {
                isInRange = false;
                UnityEngine.Debug.Log($"{transform.parent.name} is out of range");
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述

    (注意看左下角的Log 和 Scene 中 花所在位置的 Collider)

    人物和物品交互

    人物和物品的交互主要通过两个UnityEvent 交互完成,一个事件做在了 Prefab里,这个Prefab包含了一个上面说的范围检测,除此之外,还有一份受到键位交互触发事件的代码。

    这段代码的作用是如果玩家已经到了交互范围内,并且按下了交互对应的案件,则会触发一个事件,这个事件是他parent transform 中对应交互的方法,例如图中的 Flower.class 的 bePickUp方法,对应花被捡起。

    public Boolean isInRange;
        public KeyCode interactKey;
        public UnityEvent interactEvent;
    
        void Update()
        {
            if(isInRange)
            {
                if(Input.GetKeyDown(interactKey))
                {
                    interactEvent.Invoke();
                    UnityEngine.Debug.Log($"{transform.parent.name} Interact Event invoke");
                }
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    下一个事件则是由这个Collider中绑定的事件去调用玩家库存的交互函数,并将自己的实体对象作为参数传递进去。

    public class Flower : MonoBehaviour
    {
        public ItemData itemData;
        public int number;
    
        public UnityEvent<ItemData, int> pickUpFunction;
    
        // Start is called before the first frame update
        void Start()
        {
            
        }
    
        // Update is called once per frame
        void Update()
        {
            
        }
    
        public void bePickedUp()
        {
            // 资源销毁
            Destroy(gameObject);
            // 玩家拾起
            // ?. 是检查对应是否为空 的 C# 语法
            pickUpFunction?.Invoke(itemData, number);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    物品进入到人物库存中

    物品进入到人物库存中,需要有一套库存相对应的代码,库存对应的概念可以是背包、仓库或者物品栏,我这里就简单以物品栏为例。

    在这里插入图片描述

    上从图中,可以简单看出物品栏有三个UI元素组成,一个物品栏,物品栏中的每一个格子与加入物品栏的物品元素。这三个都是由Unity Image 组件做的,不过物品组件多了一个 Sprite属性,可以由外界(比较说上面提到花)传入。

    在这里插入图片描述

    在我的设计中,InventoryPanel 对应了一份 InventoryManager代码,用来管理物品栏中的每一个格子。而每一个Slot对应可一份InventorySlot的代码,用来管理每一个格子对应的物品和数量。

    从下而上来说,InventorySlot这份代码中,只需要做一件事,那就是构建物品图标和数量。当有新的物品加入时,就将新物品的Sprite传入到Icon属性中,数量传到 Count中。如果消除,则将这两个的enable属性改为 false,从而让物品不显示(消失)。

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class InventorySlot : MonoBehaviour
    {
        // Start is called before the first frame update
        public Image icon;
        public TextMeshProUGUI displayCount;
        public InventoryItem inventoryItem;
        void Start()
        {
            icon.enabled = false;
            displayCount.enabled = false;
        }
    
        // Update is called once per frame
        void Update()
        {
            
        }
    
        public void ClearSlot()
        {
            icon.enabled = false;
            displayCount.enabled = false;
            inventoryItem = null;
        }
    
        public void DrawData(InventoryItem item)
        {
    
            if(item is null)
            {
                ClearSlot();
                return;
            }
    
            icon.enabled = true;
            displayCount.enabled = true;
    
            inventoryItem = item;
            icon.sprite = item.itemData.icon;
            
            displayCount.text = item.number.ToString();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    再说 InventoryManager,这份代码中需要写一个被交互物品可以调用的函数,这个函数的主要功能就是让物品加入物品栏时,到底是进入那一个格子。他的的入参是一个 物品对象 和 一个 int 的变量代表数量。

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

    public class InventoryPanelManager : MonoBehaviour
    {
    private List inventorySlots = new List(10);

    public 
    // Start is called before the first frame update
    void Start()
    {
        for(int i = 0; i < transform.childCount && i < inventorySlots.Capacity; i++) 
        {
    
            inventorySlots.Add(transform.GetChild(i).GetComponent());
        }
    }
    
    // Update is called once per frame
    void Update()
    {
        
    }
    
    public void add(ItemData itemData, int number)
    {
        InventoryItem inventoryItem = new InventoryItem(itemData, number);
        Boolean sign = false;
        for(int i = 0; i < inventorySlots.Capacity; i++)
        {
            Debug.Log($"{i} && {inventorySlots[i].inventoryItem}");
            // 物品已经存在 就用Slots中已有的 和 添加的 做叠加 并写入到对应Slot中
            if(inventorySlots[i].inventoryItem is not null && inventorySlots[i].inventoryItem.itemData.id.Equals(inventoryItem.itemData.id))
            {
                sign = true;
                InventoryItem sumInventroyItem = new InventoryItem(inventoryItem.itemData, inventoryItem.number + inventorySlots[i].inventoryItem.number);
                inventorySlots[i].DrawData(sumInventroyItem);
                return;
            }
        }
        // 物品不存在 找到第一个空位置 进行写数据
        if(!sign)
        {
            for(int i = 0; i < inventorySlots.Capacity; i++)
            {
                if(inventorySlots[i].inventoryItem is null)
                {
                    inventorySlots[i].DrawData(inventoryItem);
                    return;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    }

    代码在Start()函数中,会将自己的子对象中的InventorySlot类绑定到自己的列表中,供下面的方法进行调用。

    而在 void add(ItemData itemData, int number)函数中,会对列表中的InventorySlot做一个遍历查询,将相同的物品进行归并,如果没有的话,就会找到一个空格子,将物品放进去。

    效果展示

    在这里插入图片描述

  • 相关阅读:
    国内哪个工具可以平替chatgpt?国内有哪些比较好用的大模型gpt?
    C++的map用法
    数据结构之赫曼夫树(哈曼夫树)
    XML外部实体注入漏洞(一)
    怎么设置浏览器默认搜索引擎,设置默认搜索引擎的方法步骤
    【图论C++】树的重心——教父POJ 3107(链式前向星的使用)
    SQL 语法书写准则
    Java反射详解篇--一篇入魂
    【无标题】
    腾讯云轻量应用服务器详细测评:性能、优势、性价比与优惠价格
  • 原文地址:https://blog.csdn.net/yr12Dong/article/details/132796739