Unity 简易背包系统:可数物品,数值化储存

本文详细介绍Unity中实现简易背包系统的步骤及脚本编写方法,涵盖可数物品的拖动、储存和回退等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

入门背包系统的实现

系列文章
1.Unity 简易背包系统:简单的拖动,储存和回退
2.Unity 简易背包系统:物品展示框
3.Unity 简易背包系统:工作台合成和配方
4.Unity 简易背包系统:可数物品,数值化储存

〇. 实现原理

  • 演示视频
    请添加图片描述
      可数型拖动单元:负责储存单元数值的信息,继承自数据型拖动单元
      可数型储存单元:负责储存和管理可数型拖动单元,继承自数据型储存单元,能够对可数型拖动单元实现与 “ 数据型储存单元数据型拖动单元 ” 相同的基本操作。
      可数型单元管理者:不仅实现对可数型储存单元基本管理操作,还实现了对可数型储存单元局限的多物体管理,继承自单元管理者
      可数型单元容器板:除了继承自单元容器板,实现对可数型储存单元基本操作外,还实现了只对可数型储存单元有效的操作(分堆,集块等等)。

一. 具体实现步骤

  • 总体都是在分堆堆叠方法上修改

1. 储存单元(修改)

  • 添加储存单元消耗一个拖动单元的虚拟方法。
/// <summary>
/// 消耗一个 '拖动单元'
/// </summary>
public virtual void Consume()
{
	Destory();
}

2. 单元工作台(修改)

  • 将消耗材料方法的销毁材料改成消耗一个材料
/// <summary>
/// 工作台消耗合成材料
/// </summary>
protected virtual void ConsumeMaterial()
{
    foreach (UIStoreSlot slot in slots)
        if (!slot.IsEmpty)
            slot.Consume();
}

3. 可数型拖动单元

  • 添加分堆(一半一半地分),多重分堆(我的世界鼠标左键滑动分堆),叠加(普通物体堆叠),消耗(-1)函数。
public class UIDragSlot_Countable : UIDragSlot_Data
{
    [Header("显示相关")]
    [SerializeField] private Text m_QuantityDisplayText;

    [Header("数值相关")]
    [SerializeField] private int quantity;

    public bool IsEmpty => quantity == 0;
    public bool IsOnlyOne => quantity == 1;
    public bool IsFull => quantity == UISlotSettings.settings.UltimateStack;
    public int Quantity => quantity;


    /// <summary>
    /// 拆分物体给 another
    /// </summary>
    /// <param name="another"></param>
    public void SplitFor(UIDragSlot_Countable another)
    {
        another.quantity = Split();

        this.DisplayQuantity();
        another.DisplayQuantity();
    }
    /// <summary>
    /// 拆分物体分给多个 another
    /// <para> ==!注意!== 只会在自己的 quantity 基础上进行拆分 </para>
    /// </summary>
    /// <param name="anothers"></param>
    public void SplitFor(params UIDragSlot_Countable[] anothers)
    {
        int quantityLeft = this.quantity;
        int eachQuantity = Split(anothers.Length);
        this.DisplayQuantity();

        foreach (UIDragSlot_Countable dragSlot in anothers)
        {
            if (quantityLeft > this.quantity)
            {
                dragSlot.quantity = eachQuantity;

                dragSlot.DisplayQuantity();

                quantityLeft -= eachQuantity;
            }
            else
                break;
        }
    }


    /// <summary>
    /// 只改变 quantity 数值,将 quantity 除半(向上进位),并返回失去的值
    /// </summary>
    /// <returns> 失去的值(另一半,向下进位) </returns>
    private int Split()
    {
        if (quantity == 1)
            return 0;

        int rightQuantity = quantity / 2;
        int leftQuantity = quantity - rightQuantity;
        quantity = leftQuantity;

        return rightQuantity;
    }
    /// <summary>
    /// 只改变 quantity 数值,将 quantity 除 totalSlotCount(向上进位),返回每个 anotherSlot 可以获得的 quantity
    /// </summary>
    /// <returns> 每个 anotherSlot 可以获得的 quantity </returns>
    public int Split(int totalSlotCount)
    {
        if (quantity < totalSlotCount)
        {
            quantity = 0;

            return 1;
        }
        else
        {
            int eachQuantity = quantity / totalSlotCount;
            int left = quantity - eachQuantity * totalSlotCount;

            quantity = left;

            return eachQuantity;
        }
    }
    /// <summary>
    /// 只改变 quantity 数值,追加数值,如果超出,则归还超出部分
    /// <para> ==不安全== 如果自身和自身交换,则结果为 0 </para>
    /// <para> -- 不处理 Destory -- </para>
    /// </summary>
    /// <param name="other"></param>
    /// <returns> 如果超出最大界限(不包含相等),则返回 false </returns>
    public void Append(UIDragSlot_Countable other)
    {
        // 1.先把 other 的所有 quantity 赋予给 this
        this.quantity += other.quantity;
        other.quantity = 0;

        // 2.检验是否超出容量,如果超出容量则返还超出部分给 other
        int ultimateStack = UISlotSettings.settings.UltimateStack;
        if (this.quantity > ultimateStack)
        {
            other.quantity += this.quantity - ultimateStack;
            this.quantity = ultimateStack;
        }

        // ==!== 这一步在 Store 里面进行
        // 3.如果 other 容量为 0,则销毁
    }
    /// <summary>
    /// 消耗一个
    /// ==非常不安全== 只进行 --quantity,没有检验
    /// </summary>
    public void Consume()
    {
    	 --quantity;
         this.DisplayQuantity();
	}


    #region 显示部分
    public void DisplayQuantity()
    {
        m_QuantityDisplayText.text = quantity.ToString();
    }
    #endregion
}

4. 可数型储存单元

  • 分堆,叠加方法(注解都有详细的解释),都是在可数型拖动单元的方法基础上加以修改。
public class UIStoreSlot_Countable : UIStoreSlot_Data
{
	// 缓存 '可数型' 为了避免每次都转换
    private UIDragSlot_Countable m_CacheSlot;

    public override void Start()
    {
        if (UISlotManager.manager == null)
            UISlotManager.Create<UISlotManager_Countable>();

        if (IsEmpty)
            SetNullDo();
        else
            SetHoldDo();
    }


    /// <summary>
    /// 把 this.hold 分两半,然后把另一半给 another
    /// <para> ==安全== 如果 another 已经存在物体,则不会进行任何操作 </para>
    /// </summary>
    /// <param name="another"></param>
    public void Split(UIStoreSlot_Countable another)
    {
        if (another.IsEmpty && !this.m_CacheSlot.IsOnlyOne)
        {
            UIDragSlot_Countable newSlot = Instantiate(m_CacheSlot);
            another.SetHold(newSlot);

            m_CacheSlot.SplitFor(newSlot);

            another.Reback();
        }
    }
    /// <summary>
    /// 把 this.hold 分 n+1 份,然后把剩下的给 this
    /// <para> ==安全== 如果 another 已经存在物体,则不会进行任何操作 </para>
    /// </summary>
    /// <param name="anothors"></param>
    public void Split(params UIStoreSlot_Countable[] anothors)
    {
        if (m_CacheSlot == null)
            return;

        // 1.把所有被分开的值重新合并起来
        foreach (UIStoreSlot_Countable storeSlot in anothors)
        {
            if (!storeSlot.IsEmpty)
                this.m_CacheSlot.Append(storeSlot.m_CacheSlot);
        }
        int rawQuantity = this.m_CacheSlot.Quantity;

        // 2.创建大小为 Quantity 的数组,并转移数据
        List<UIStoreSlot_Countable> storeSlots = new List<UIStoreSlot_Countable>();
        int minLength = m_CacheSlot.Quantity < anothors.Length ? m_CacheSlot.Quantity : anothors.Length;
        for (int i = 0; i < minLength; ++i)
            storeSlots.Add(anothors[i]);

        // 3.把 storeSlot 全部改成 dragSlot 形式
        List<UIDragSlot_Countable> dragSlots = new List<UIDragSlot_Countable>();
        foreach (UIStoreSlot_Countable storeSlot in storeSlots)
        {
            if (storeSlot.IsEmpty)
            {
                UIDragSlot_Countable newSlot = Instantiate(m_CacheSlot); // 这个操作与后面数值分配无关,所以不用调零
                storeSlot.SetHold(newSlot);

                dragSlots.Add(newSlot);
            }
            else
            {
                dragSlots.Add(storeSlot.m_CacheSlot);
            }
        }

        // 4.分配数据
        this.m_CacheSlot.SplitFor(dragSlots.ToArray());

        // 5.检验是否有空的或 Quantity 为 0 的数据,然后销毁,并重置 dragSlot 的位置
        foreach (UIStoreSlot_Countable storeSlot in storeSlots)
        {
            if (storeSlot.IsEmpty || storeSlot.m_CacheSlot.IsEmpty)
                storeSlot.Destory();
            else
                storeSlot.Reback();
        }
        if (anothors.Length >= rawQuantity)
        {
            this.Destory();

            UISlotManager.manager.End(); //强制结束
        }
    }
    /// <summary>
    /// 把 other.hold 数值加给 this.hold,如果 other.hold 数值为 0,则销毁 hold
    /// <para> ==不安全== 如果 this 移动,则不会吧 this.hold 归位 </para>
    /// </summary>
    /// <param name="other"></param>
    public void Append(UIStoreSlot_Countable other)
    {
        // 数据处理部分
        this.m_CacheSlot.Append(other.m_CacheSlot);

        if (other.m_CacheSlot.IsEmpty)
            other.Destory();
        else
            other.Reback();

        // 显示处理部分
        this.m_CacheSlot.DisplayQuantity();
        other.m_CacheSlot?.DisplayQuantity();
    }
    /// <summary>
    /// 把物体加给 this.hold
    /// </summary>
    /// <param name="dragSlot"></param>
    /// <returns> 加给后,多出来的部分 </returns>
    public int Append(UIDragSlot_Countable dragSlot)
    {
        // 数据处理部分
        m_CacheSlot.Append(dragSlot);

        // 显示处理部分
        if (dragSlot.IsEmpty)
        {
            this.m_CacheSlot.DisplayQuantity();
            dragSlot.Dissolve();

            return 0;
        }
        else
        {
            this.m_CacheSlot.DisplayQuantity();
            dragSlot.DisplayQuantity();

            return dragSlot.ID;
        }
    }


    #region 继承方法区
    // 都是更新 '缓存'
    protected override void SetHoldDo()
    {
        m_CacheSlot = Hold as UIDragSlot_Countable;
    }
    protected override void SetNullDo()
    {
        m_CacheSlot = null;
    }
    public override bool IsSameHold(UIStoreSlot other)
        => m_CacheSlot.ID == (other as UIStoreSlot_Countable).m_CacheSlot.ID;
    public override void Check()
    {
        if (m_CacheSlot.IsEmpty)
        {
            this.Destory();

            UISlotManager.manager.UnSelect();
        }
    }
    #endregion


    public bool IsSameID(UIDragSlot_Countable dragSlot)
        => m_CacheSlot.ID == dragSlot.ID;
    /// <summary>
    /// 继承方法,消耗一个物品
    /// </summary>
    public override void Consume()
    {
        if (!m_CacheSlot.IsEmpty)
            m_CacheSlot.Consume();
    }
    
    // 这些后来有用
    #region 事件区
    public override void BeginDragDo() { }
    public override void OnDragDo() { }
    public override void EndDragDo() { }
    #endregion
}

5. 可数型单元管理者

  • 继承方法,做小修改,就不做注释了。
public class UISlotManager_Countable : UISlotManager
{
    public virtual void OnEnable()
    {
        SelectMultiDo += OnMultiSelectDo;
    }
    public virtual void OnDisable()
    {
        SelectMultiDo -= OnMultiSelectDo;
    }


    protected override void EndDo()
    {
        SlotsState move = MoveTo(StartSlot, EndSlot);

        switch (move)
        {
            case SlotsState.Empty:
            case SlotsState.Ocupy:
                base.EndDo();
                break;
            case SlotsState.Equal:
                (EndSlot as UIStoreSlot_Countable).Append(StartSlot as UIStoreSlot_Countable);
                break;
            default:
                Debug.LogError("移动空物体!!!");
                break;
        }
    }


    #region 事件区
    public void OnMultiSelectDo()
    {
        UIStoreSlot_Countable selectStoreSlot = SelectShow.SelectSlot as UIStoreSlot_Countable;
        selectStoreSlot.Split(SelectShow.GetSelectSlots<UIStoreSlot_Countable>());
    }
    #endregion
}

6. 可数型单元容器板

  • 由于与普通的单元容器板的传入成员有所不同,并且在拖动单元相同的情况处理也不一样,所以没有重写 TransferTo 方法。
  • 重写 TransferSelf 方法,以应对两个数据型拖动单元ID一样时的情况。
public class UISlotContainer_Countable : UISlotContainer
{
    /// <summary>
    /// 将物品转移给另一个容器
    /// </summary>
    /// <param name="otherContainer"> 要转移物品给的容器 </param>
    public virtual void TransferTo(UISlotContainer_Countable otherContainer)
    {
        UISlotManager.manager.UnSelect();

        for (int i = 0, j = 0; i < slots.Length && j < otherContainer.slots.Length;)
        {
            UISlotManager.SlotsState move = UISlotManager.MoveTo(slots[i], otherContainer.slots[j]);
            switch (move)
            {
                case UISlotManager.SlotsState.Empty:
                    slots[i].ImposeTo(otherContainer.slots[j]);

                    j = 0;
                    ++i;

                    break;
                case UISlotManager.SlotsState.Ocupy:
                    ++j;
                    break;
                case UISlotManager.SlotsState.Error:
                    ++i;
                    break;
                case UISlotManager.SlotsState.Equal:
                    (otherContainer.slots[j] as UIStoreSlot_Countable).Append(slots[i] as UIStoreSlot_Countable);

                    if (slots[i].IsEmpty) // 如果 slot 已经把物品都给光了
                    {
                        j = 0;
                        ++i;
                    }
                    else // 如果没有给光,那一定是 otherSlot 满了
                    {
                        ++j;
                    }
                    break;
                default:
                    return;
            }
        }
    }
    public override void TransferSelf()
    {
        UISlotManager.manager.UnSelect();

        for (int i = 0; i < slots.Length; ++i)
        {
            for (int j = i + 1; j < slots.Length; ++j)
            {
                UISlotManager.SlotsState move = UISlotManager.MoveTo(slots[j], slots[i]);
                switch (move)
                {
                    case UISlotManager.SlotsState.Empty:
                        slots[j].ImposeTo(slots[i]);
                        break;
                    case UISlotManager.SlotsState.Ocupy:
                        break;
                    case UISlotManager.SlotsState.Error:
                        continue;
                    case UISlotManager.SlotsState.Equal:
                        (slots[i] as UIStoreSlot_Countable).Append(slots[j] as UIStoreSlot_Countable);
                        break;
                    default:
                        return;
                }
            }
        }
    }
    /// <summary>
    /// 添加物体
    /// </summary>
    /// <param name="dragSlots"></param>
    public void Append(params UIDragSlot_Countable[] dragSlots)
    {
        for (int i = 0; i < dragSlots.Length; ++i)
            Append(dragSlots[i]);
    }
    /// <summary>
    /// 添加物体
    /// </summary>
    /// <param name="dragSlot"></param>
    public void Append(UIDragSlot_Countable dragSlot)
    {
        for (int i = 0; i < slots.Length; ++i)
            if (slots[i].IsEmpty)                       // 如果要放入的格子为空,则直接设置
            {
                slots[i].SetHold(dragSlot);

                break;
            }
            else
            {
                UIStoreSlot_Countable countableSlot = slots[i] as UIStoreSlot_Countable;
                if (countableSlot.IsSameID(dragSlot))   // 如果要放入的格子与 dragSlot.ID 一样,则叠加
                {
                    int left = countableSlot.Append(dragSlot);
                    if (left == 0)
                        break;
                    else                                // 如果还剩一些,则寻找下一个格子
                        continue;
                }                                       // 如果要放入的格子与 dragSlot.ID 不一样,则寻找下一个格子
                else
                    continue;
            }
    }


    /// <summary>
    /// 拆分所选物体(单元管理者选择的拖动单元)
    /// ==安全== 会先检验空间是否足够和所选物体是否为 null
    /// </summary>
    public void SplitSelect()
    {
        if (IsFull || UISlotManager.manager.SelectSlot == null || UISlotManager.manager.SelectSlot.IsEmpty)
            return;

        for (int i = 0; i < slots.Length; ++i)
        {
            if (slots[i].IsEmpty)
            {
                (UISlotManager.manager.SelectSlot as UIStoreSlot_Countable).Split(slots[i] as UIStoreSlot_Countable);
                break;
            }
        }
    }
}
  • 其中 SplitSelect 方法用在按钮事件(按钮点击触发这个方法)。

7. (关于工作台的在下一篇文章讲解)

二. 脚本附着

  • 注意要把普通单元管理者换成可数型单元管理者,把普通的都换成可数型的,包括单元容器板储存单元拖动单元
    请添加图片描述
  • 分堆按钮的 “构造” :
    请添加图片描述
  • 可数型拖动单元的 “构造” :
    请添加图片描述
  • 注意要把可数型拖动单元的数值显示文本(子物体)改成射线无法达到。
    请添加图片描述
  • 关于最大堆叠数的可编辑脚本(如果想每个物体最大堆叠数不一样就不用这一步,直接写在可数型拖动单元里面单做成员)。
    请添加图片描述
  • 演示视频:
    请添加图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值