【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 方法,就可以达到检测人物是否进入到物品可以交互的范围里。

【Unity】 2D 游戏 库存模块实现_第1张图片

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

}

【Unity】 2D 游戏 库存模块实现_第2张图片

(注意看左下角的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");
            }
        }
    }

下一个事件则是由这个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);
    }
}

物品进入到人物库存中

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

在这里插入图片描述

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

【Unity】 2D 游戏 库存模块实现_第3张图片

在我的设计中,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();
    }
}

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

}

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

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

效果展示

你可能感兴趣的:(Unity,unity,游戏,游戏引擎)