背包系统一

前置知识

ScriptableObject

ScriptableObject是一个可独立于类实例来保存大量数据的数据容器。ScriptableObject的一个主要用例就是通过避免重复值来减少项目的内存使用量。如果使用Editor,可以在编辑时和运行时将数据保存到ScriptableObject

ScriptableObject的主要用例为:

  • 在Editor会话期间保存和存储数据
  • 将数据保存为项目中的资源,以便在运行时使用

官方教程:https://docs.unity.cn/cn/2019.4/Manual/class-ScriptableObject.html

C#结构体与类

在C#中,结构体是值类型,他使得单一变量可以存储各种数据类型的相关数据。struct 关键字用于创建结构体

类的对象是存储在堆内存中,结构体存储在栈中。对空间大,但访问速度较慢,栈较小,访问速度相对更快。所以,当我们秒速一个轻量级对象的时候,结构体可以提高效率,成本更低。但也要从需求出发,假如我们在传值的时候希望传递的是引用而不是值,就应该使用类了

特点:

  • 与类不同,结构不能继承其他的结构或者类
  • 结构可以实现一个或者多个接口
  • 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化
  • 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用

代码分类

配置数据

ScriptableItem定义道具的ScriptableObject脚本

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

[CreateAssetMenu(menuName = "Create Scriptable/General", order = 999)]
public partial class ScriptableItem : ScriptableObject
{
    /// <summary>
    /// 道具最大堆叠数
    /// </summary>
    public int maxStack;
    /// <summary>
    /// 道具名
    /// </summary>
    public string itemName;
    /// <summary>
    /// 道具编号,不可重复
    /// </summary>
    public int itemID;

    [Tooltip("耐久性仅适用于不可堆叠的项目(如MaxStack为1)")]
    public int maxDurability = 0; // disabled by default
    public long buyPrice;
    public long sellPrice;
    public long itemMallPrice;
    public bool sellable;
    public bool tradable;
    public bool destroyable;

    [SerializeField, TextArea(1, 30)] protected string toolTip;
    public Sprite image;

    public virtual string ToolTip()
    {
        StringBuilder tip = new StringBuilder(toolTip);
        tip.Replace("{NAME}", name);
        tip.Replace("{ItemName}", itemName);
        tip.Replace("{DESTROYABLE}", (destroyable ? "Yes" : "No"));
        tip.Replace("{SELLABLE}", (sellable ? "Yes" : "No"));
        tip.Replace("{TRADABLE}", (tradable ? "Yes" : "No"));
        tip.Replace("{BUYPRICE}", buyPrice.ToString());
        tip.Replace("{SELLPRICE}", sellPrice.ToString());
        return tip.ToString();
    }
}

点击菜单Assets/Create/Create Scriptable/General,会生成ScriptableObject资源,此脚本就像是一个模板工厂,会创建具有相同属性的资源,然后可以根据需求配置不同的数据,生成的资源如下:

image-20210110163800162

定义数据获取数据的管理器ScriptableItemMgr,此脚本可以将生成的ScriptableObject资源转成Dictionary,方便其他代码调用

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

public class ScriptableItemMgr
{
    static Dictionary<int, ScriptableItem> cache;
    public static Dictionary<int, ScriptableItem> dict
    {
        get
        {
            if (cache == null)
            {
                ScriptableItem[] items = Resources.LoadAll<ScriptableItem>("Items");

                var duplicates = items.ToList().FindDuplicates(item => item.name);
                if (duplicates.Count == 0)
                {
                    cache = items.ToDictionary(item => item.itemID, item => item);
                }
                else
                {
                    foreach (string duplicate in duplicates)
                        Debug.LogError("资源文件夹包含多个名为" + duplicate + "的ScriptableItem");
                }
            }
            return cache;
        }
    }
}

ItemInfo道具信息结构体,一般在创建道具的时候使用,可以根据此ItemInfo提供的信息来创建对应Item道具,使用方式可以参考PlayerInventory

// 道具信息
[Serializable]
public struct ItemInfo
{
    public int id { get; set; }
    public string name { get; set; }
    public int amount { get; set; }
    public int durability { get; set; }
    public int index { get; set; }
}

游戏类

Item是道具条目,Item是对ScriptableItem数据的封装,提供了一些对外的方法

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

[SerializeField]
public partial struct Item
{
    public int id;
    /// <summary>
    /// 耐久
    /// </summary>
    public int durability;

    public Item(ScriptableItem data)
    {
        id = data.itemID;
        durability = data.maxDurability;
    }

    public ScriptableItem data
    {
        get
        {

            if (!ScriptableItem.dict.ContainsKey(id))
                throw new KeyNotFoundException("没有hash=" + id + "的ScriptableItem。");
            return ScriptableItem.dict[id];
        }
    }

    /// <summary>
    /// 耐久百分比
    /// </summary>
    /// <returns></returns>
    public float DurabilityPercent()
    {
        return (durability != 0 && MaxDurability != 0) ? (float)durability / (float)MaxDurability : 0;
    }

    /// <summary>
    /// 检查耐久度是否有效
    /// </summary>
    public bool CheckDurability()
    {
        return MaxDurability == 0 || durability > 0;
    }

    public string ToolTip()
    {
        // 执行ScriptableItem资源对象的ToolTip()方法
        StringBuilder tip = new StringBuilder(data.ToolTip());

        // 只有当物品具有耐久性时才显示耐久性
        if (MaxDurability > 0)
            tip.Replace("{DURABILITY}", (DurabilityPercent() * 100).ToString("F0"));

        return tip.ToString();
    }

    #region 属性
    public string Name => data.name;
    /// <summary>
    /// 数量的上限
    /// </summary>
    public int MaxStack => data.maxStack;
    public int MaxDurability => data.maxDurability;
    public long BuyPrice => data.buyPrice;
    public long SellPrice => data.sellPrice;
    /// <summary>
    /// 商城价格
    /// </summary>
    public long ItemMallPrice => data.itemMallPrice;
    /// <summary>
    /// 可出售
    /// </summary>
    public bool Sellable => data.sellable;
    /// <summary>
    /// 可交易
    /// </summary>
    public bool Tradable => data.tradable;
    /// <summary>
    /// 可销毁
    /// </summary>
    public bool Destroyable => data.destroyable;
    public Sprite Image => data.image;
    # endregion
}

ItemSlot是道具槽,主要负责对当前道具槽内Item道具进行维护和管理。持有Item的引用,并且维护了一个amount属性,即为当前道具槽内的道具数量

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

[SerializeField]
public partial struct ItemSlot
{
    /// <summary>
    /// 格子中道具
    /// </summary>
    public Item item;
    /// <summary>
    /// 格子中道具的数量
    /// </summary>
    public int amount;

    public ItemSlot(Item item, int amount = 1)
    {
        this.item = item;
        this.amount = amount;
    }

    /// <summary>
    /// 减少数量
    /// </summary>
    /// <param name="reduceBy"></param>
    /// <returns></returns>
    public int DecreaseAmount(int reduceBy)
    {
        int limit = Mathf.Clamp(reduceBy, 0, amount);
        amount -= limit;
        return limit;
    }

    /// <summary>
    /// 增加数量
    /// </summary>
    /// <param name="increaseBy"></param>
    /// <returns></returns>
    public int IncreaseAmount(int increaseBy)
    {
        int limit = Mathf.Clamp(increaseBy, 0, item.MaxStack - amount);
        amount += limit;
        return limit;
    }

    public string ToolTip()
    {
        if (amount == 0) return "";

        // 执行Item属性方法结构体的ToolTip()方法
        StringBuilder tip = new StringBuilder(item.ToolTip());
        tip.Replace("{AMOUNT}", amount.ToString());
        return tip.ToString();
    }
}

Inventory背包管理,维护所有道具槽的正确运行,提供了对道具的增删改功能,方便道具能够正确放到指定道具槽中

using System;
using UnityEngine;
public class Inventory : ItemContainer
{
    public List<ItemSlot> slots = new List<ItemSlot>();
    
    public ItemSlot CreateSlot(int itemID, int amount = 1)
    {
        Item item = new Item(ScriptableItem.dict[itemID]);
        return new ItemSlot(item, amount);
    }

    public Item CreateItem(int itemID)
    {
        return new Item(ScriptableItem.dict[itemID]);
    }
    
    public int FreeSlots()
    {
        int free = 0;
        foreach (ItemSlot slot in slots)
            if (slot.amount == 0)
                ++free;
        return free;
    }

    /// <summary>
    /// 计算背包有多少被道具占用的格子
    /// </summary>
    /// <returns></returns>
    public int SlotsOccipied()
    {
        int occupied = 0;
        foreach (ItemSlot slot in slots)
            if (slot.amount > 0)
                ++occupied;
        return occupied;
    }

    // 计算背包中某种道具的总数量
    public int Count(Item item)
    {
        int amount = 0;
        foreach (ItemSlot slot in slots)
            if (slot.amount > 0 && slot.item.Equals(item))
                amount += slot.amount;
        return amount;
    }

    // 从背包中移除指定数量某种道具
    public bool Remove(Item item, int amount)
    {
        for (int i = 0; i < slots.Count; ++i)
        {
            ItemSlot slot = slots[i];
            if (slot.amount > 0 && slot.item.Equals(item))
            {
                // 从包含某种道具的格子尽量移除此种道具
                amount -= slot.DecreaseAmount(amount);
                slots[i] = slot;

                // 达到指定数量
                if (amount == 0) return true;
            }
        }

        // 背包中没有更多此种道具,达不到指定数量
        return false;
    }

    public bool CanAdd(Item item, int amount)
    {
        //TODO:优化返回索引
        for (int i = 0; i < slots.Count; i++)
        {
            // 当前格子没有物品
            if (slots[i].amount == 0)
            {
                amount -= item.MaxStack;
            }
            //相同类型的物品是否能够装下
            else if (slots[i].item.Equals(item))
            {
                amount -= (item.MaxStack - slots[i].amount);
            }
            if (amount <= 0)
                return true;
        }
        return false;
    }

    public bool Add(Item item, int amount)
    {
        if (CanAdd(item, amount))
        {
            // 格子中已有此物品
            for (int i = 0; i < slots.Count; i++)
            {
                if (slots[i].amount > 0 && slots[i].item.Equals(item))
                {
                    ItemSlot temp = slots[i];
                    amount -= temp.IncreaseAmount(amount);
                    slots[i] = temp;
                }

                if (amount < 0) return true;
            }

            // 未达到指定数量,寻找空格子加入此物品
            for (int i = 0; i < slots.Count; i++)
            {
                if (slots[i].amount == 0)
                {
                    int add = Mathf.Min(amount, item.MaxStack);
                    slots[i] = new ItemSlot(item, add);
                    amount -= add;
                }

                if (amount <= 0) return true;
            }
            if (amount != 0) Debug.LogError("背包空间不够!添加道具到背包失败: " + item.Name + " " + amount);
        }
        return false;
    }
}

PlayerInventory负责读取背包数据,并根据数据初始化背包

using UnityEngine;

public class PlayerInventory : Inventory
{
    [Header("Components")]
    public Player player;

    [Header("Inventory")]
    public int size = 30;

    public KeyCode[] splitKeys = { KeyCode.LeftShift, KeyCode.RightShift };

    void Awake()
    {
        // 测试数据
        ItemInfo[] itemsDefault = new ItemInfo[4]{
            new ItemInfo{id = 12001,amount = 30},
            new ItemInfo{id = 12002,amount = 15},
            new ItemInfo{id = 12004,amount = 10},
            new ItemInfo{id = 12005,amount = 6}
        };

        LoadPlayerInventory(itemsDefault);
    }

    public void LoadPlayerInventory(ItemInfo[] itemInfos)
    {
        // 创建玩家背包的格子
        for (int i = 0; i < player.Inventory.size; i++)
        {
            player.Inventory.slots.Add(new ItemSlot());
        }
        print(player.Inventory.slots.Count);

        // 添加格子中的道具
        for (int i = 0; i < itemInfos.Length; i++)
        {
            ItemInfo item = itemInfos[i];
            if (item.index > 0) // 物品指定了格子位置
            {
                //用带道具的格子替换指定位置的空格子
                Insert(CreateSlot(item.id, item.amount), item.index);
            }
            else
            {
                //没有指定位置,将道具添加到下一个空格子
                Add(CreateItem(item.id), item.amount);
            }
        }
    }

    /// <summary>
    /// 添加道具到背包的指定格子
    /// </summary>
    public void Insert(ItemSlot slot, int index)
    {
        if (player.Inventory.slots[index].item.id == 0)
            player.Inventory.slots[index] = slot;
        else
            Debug.LogError("背包中有此物品:" + slot.item.Name);
    }
}

总结

配置数据

  • ScriptableItem
  • ScriptableItemMgr
  • ItemInfo 道具信息

游戏对象

  • Item 道具条目
  • ItemSlot 道具槽
  • Inventory 背包管理
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity背包系统是一个游戏中常用的组件,用于管理玩家在游戏中所获得的道具。一个完整的背包系统通常由数据、逻辑和UI三部分组成。在设计过程中,我们首先进行UI设计,包括背包的标题、关闭键和背包内的格子容器。 背包系统的数据部分主要负责管理物品的信息,例如物品的名称、图片、数量等。逻辑部分负责实现将物品放置进背包、对背包内物品进行管理以及使用背包内物品等功能。实际上,当我们获取或移动物品时,我们会直接修改背包数据库中的物品信息。修改完成后,我们通过背包中的UI元素来获取背包数据库中相应索引的物品信息,并将物品栏中的UI信息更新为数据库中对应序号的物品的图片和数量。 通过将背包系统分解为数据、逻辑和UI三部分,我们可以有效地管理背包系统的复杂逻辑关系,并使代码更加清晰和易于维护。这种设计模式有助于提高游戏开发的效率和代码质量。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Unity游戏开发:背包系统的实现](https://blog.csdn.net/float_freedom/article/details/126243888)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Unity3D RPG实现 2 —— 背包系统](https://blog.csdn.net/weixin_43757333/article/details/123187025)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值