首页 > 其他分享 >Unity-背包系统

Unity-背包系统

时间:2023-11-14 19:27:00浏览次数:30  
标签:背包 itemData 系统 itemUI Unity UI 物品 public

Unity-背包系统

简介

​ 背包是每个成功游戏中不可缺少的,玩家获取的装备与道具将会放入背包,需要时再拿出来使用。如果没有背包来储存玩家在游戏中获得的武器和道具,或许游戏将会变得十分单一枯燥,出招方式一成不变。

​ 有了背包系统,玩家才可以使用不同的武器,搭配不同的道具,使出不同的攻击搭配,从而提高游戏的多样性。

简单构思

​ 首先我们思考一下背包系统的简单逻辑,玩家拾取物品后,背包中出现该物品,点击该物品之后又可以使用。

​ 上述步骤的实现,需要我们完成两个层面的工作,一个是 背包的数据库 ,一个是 背包的UI

背包的数据库

数据库逻辑

​ 现在需要构思如何拾取物品后记录数据。这里可以使用 ScriptableObject 来记录各个物品以及背包的数据,当玩家拾取物品后,将物品的信息传入背包的数据库进行记录。

创建数据库的步骤及细节

​ 根据上述简单逻辑,可以得到下列创建数据库的步骤:

  1. 需要给 每个物品每个背包 都创建自己的 ScriptableObject 数据
  2. 物品数据中包含自身的各项参数;背包数据需要有将物品数据存进背包的函数
  3. 同时需要创建 MonoBehavior脚本,并将其挂载在 物品和背包 上用于 控制自身读取数据
  4. 物品脚本需要获得物品数据,并拥有将该数据放入背包的函数;背包脚本只需要拥有背包数据即可
  5. Player在触碰到物体后,物体本身的脚本将会触发,并通过函数将自己的数据加入背包中

(还需要注意以下细节:

  1. 物品数据中只有自身的个数,无法记录相同物品在背包中的个数,需要 创建一个类来保存物品数据及个数
  2. 不同的物品会需要不同的物品数据,需根据需求获取不同物品独有的 ScriptableObject数据
  3. 背包数据中,存储数据的算法需要做到最简(?)

代码样例_物品数据

public enum ItemType{ Useable, Weapon, Armor }//控制物品类型

[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
    public bool stackable;  //判断物品是否可以堆叠
    public ItemType itemType;   //物品对应类型
    public string itemName;     //物品名字
    public Sprite itemIcon;     //物品图片
    public int itemAmount;      //物品个数

    [TextArea]
    public string description = "";	//用于描述物品信息

    ////////////////////////////////////////////////////////
    /////////////////以下是各个物品的独有数据/////////////////
    ////////////////////////////////////////////////////////

    [Header("Useable")] //使用品
    public UseableItemData_SO useableItemData;

    [Header("Weapon")]  //武器
    public GameObject weaponPrefabs;
    public AttackData_SO attackData;
}

代码样例_背包数据

////////////////////////////////////////////////////////////
////////////////为记录背包中的物品个数创建该类/////////////////
///////////////////////////////////////////////////////////
[System.Serializable]   //为使该类在Unity窗口中显示,需要序列化
public class InventroyItem
{
    public ItemData_SO itemData;  //物品信息
    public int amount;  //物品数量
}
////////////////////////////////////////////////////////////
////////////////为记录背包中的物品个数创建该类/////////////////
///////////////////////////////////////////////////////////

[CreateAssetMenu(fileName = "New Inventory", menuName = "Inventory/Inventory Data")]
public class InventoryData_So : ScriptableObject
{
    public List<InventroyItem> items = new List<InventroyItem>();
    //将物品数据保存进背包
    public void AddItem(ItemData_SO itemData, int amount){
        bool found = false; //判断物品是否被找到
        //如果物品是可堆叠的,则先遍历列表
        if(itemData.stackable){
            foreach (var item in items){
                if(item.itemData == itemData){
                    //如果背包中有同类型道具,则直接增加数量,并标明物品已经找到
                    item.amount += amount;
                    found = true;
                    break;
                }
            }
        }
        if(!found){	//如果物品没有被找到
            for(int i = 0; i < items.Count; i++){
                //则同样遍历整个列表,找到第一个空位并且添加
                if(items[i].itemData == null){
                    items[i].itemData = itemData;
                    items[i].amount = amount;
                    break;
                }
            }
        }
    }
}

代码样例_背包脚本

public class InventroyManager : Singleton<InventroyManager> //继承单例模式
{
    //TODO:最后复制模板保存数据
    [Header("Inventory Data")]	//背包数据
    public InventoryData_So inventoryData;
	//剩余不同背包........

    void Start() 
    {
        inventoryUI.RefreshUI();
        //剩余不同背包.........
    }
    
    //UI部分代码
}

/*该类还有很多尚未完成的部分,未完成的部分会在下面讲述背包UI时完善*/

代码样例_物品脚本

public class ItemPickUp : MonoBehaviour
{
    public ItemData_SO itemData;
    void OnTriggerEnter(Collider other) 
    {
        if(other.CompareTag("Player"))
        {
            //TODO:将物品添加到背包
            InventroyManager.Instance.inventoryData.AddItem(itemData, itemData.itemAmount);
            //........

            //TODO:装备武器
            // GameManager.Instance.playerStats.EquipWeapon(itemData);

            Destroy(gameObject);
        }    
    }
}

背包的UI

​ 完成上述背包的数据库后,还需要根据数据库完成背包的UI部分,让玩家能够更直观的管理背包

背包UI逻辑

​ 要完成背包UI,就需要 从背包的数据库中获取背包中各项物品的各项数据(图片、数量......),然后在背包UI上显示。

创建背包UI的步骤及细节

  1. 编写背包UI的 代码 需要三层,第一层是 物品UI层(ItemUI),第二层是 物品栏(SlotHolder),第三层是背包层(ContainerUI),最后需要将背包层挂再在 InventoryManager 上供其他类调用

  2. 根据背包UI代码所分的三层,背包UI的 GameObject 同样需要分成三层。第一层 挂载ItemUI 作为物品UI层,同时子物体需要有 图片(Image)文本(Text);第二层 挂载SlotHolder 作为物品栏,子物体需要 ItemUI;第三层 挂载Container 作为背包层,子物体需要 n个 SlotHolder

(下面将会通过代码实例更详细的讲述

代码样例_ItemUI

public class ItemUI : MonoBehaviour
{
    public Image icon = null;   //物品图片
    public Text amount = null;  //物品个数
    public InventoryData_So Bag { get; set; }   //物品所属背包,在SlotHolder中赋值
    public int Index { get; set; } = -1;    //物品代号

    public void SetupItemUI(ItemData_SO item, int itemAmount)   //更新图片数据
    {
        if(itemAmount == 0)	//当数量为零时,清除背包中该位置的物品数据
        {
            Bag.items[Index].itemData = null;
            icon.gameObject.SetActive(false);
            return;
        }

        if(item != null)	//如果Item不等于空, 则根据item数据显示物品
        {
            icon.sprite = item.itemIcon;
            amount.text = itemAmount.ToString();
            icon.gameObject.SetActive(true);
        }
        else
        {
            icon.gameObject.SetActive(false);
        }
    }
}

代码样例_SlotHolder

public enum SlotType { BAG, WEAPON, ARMOR, ACTION }	//记录物品栏类型(背包、武器栏、防具栏、道具栏)
public class SlotHolder : MonoBehaviour, IPointerClickHandler
{
    public SlotType slotType;   //背包栏类型
    public ItemUI itemUI;   //外部赋值

	//鼠标点击使用物品函数........

    public void UpdateItem()    //更新物品
    {
        switch(slotType)
        {
            //根据背包栏类型找对应数据库
            case SlotType.BAG:
                itemUI.Bag = InventroyManager.Instance.inventoryData;
                break;
            case SlotType.WEAPON:
                itemUI.Bag = InventroyManager.Instance.equipmentData;
                //需判断武器栏内是否为空,不为空则装备对应武器,2D不需要装备武器
                break;
            case SlotType.ARMOR:
                itemUI.Bag = InventroyManager.Instance.equipmentData;
                break;
            case SlotType.ACTION:
                itemUI.Bag = InventroyManager.Instance.actionData;
                break;
        }

        //根据背包数据库中的信息更改背包UI
        var item = itemUI.Bag.items[itemUI.Index];
        itemUI.SetupItemUI(item.itemData, item.amount);
    }
}

代码样例_ContainerUI

public class ContainerUI : MonoBehaviour
{
    public SlotHolder[] slotHolders;
    
    public void RefreshUI() //刷新UI
    {
        for(int i = 0; i < slotHolders.Length; i++)
        {
            //根据SlotHolder类的数组的序号,刷新各个背包栏的标号
            slotHolders[i].itemUI.Index = i;
            slotHolders[i].UpdateItem();
        }
    }
}

实现拖拽物品

​ 实现物品拖拽,可以直接使用Unity自带的拖拽物品函数,只需要实现以下几个接口:

public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public void OnBeginDrag(PointerEventData eventData){} //开始拖拽
    
    public void OnDrag(PointerEventData eventData){}  //拖拽中

    public void OnEndDrag(PointerEventData eventData){}   //拖拽结束
}

(这几个接口中传入的参数 eventData 包含了关于鼠标的各项参数,有需要的可以查阅Unity官方手册

​ 需要知道的是:

  1. 需要拖拽的物品是挂载有 ItemUI 的 GameObject ,所以该类需要挂载在上述 GameObject 上
  2. 为了防止玩家将物品拖拽进错的位置,需要 记录物品原本的位置 并在其 位置错误之后,将物品复原
  3. 在拖动结束后,需要进行一系列判断,保证物品交换无误(直接交换、堆叠 或 回到原位)

(下面用代码样例进行详细说明

代码样例

[RequireComponent(typeof(ItemUI))]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    ItemUI currentItemUI;
    SlotHolder currentHolder;
    SlotHolder targetHolder;
    void Awake() 
    {
        currentItemUI = GetComponent<ItemUI>();
        currentHolder = GetComponentInParent<SlotHolder>();
    }

  ////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////// 以下各个PointerEventData类会有与鼠标相关的各个数据///////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////


    public void OnBeginDrag(PointerEventData eventData) //开始拖拽
    {
        //记录原始数据
        InventroyManager.Instance.currentDrag = new InventroyManager.DragData();
        InventroyManager.Instance.currentDrag.originalHolder = GetComponentInParent<SlotHolder>();
        InventroyManager.Instance.currentDrag.originalParent = (RectTransform)transform.parent; 
        
        //为了使物品不被物品栏遮挡,将其设置为一个层级更高的画布的子物体
        transform.SetParent(InventroyManager.Instance.DragCanvas.transform, true);
    }

    public void OnDrag(PointerEventData eventData)  //拖拽中
    {
        //跟随鼠标位置
        transform.position = eventData.position;
    }

    public void OnEndDrag(PointerEventData eventData)   //拖拽结束
    {
        //放下物品 交换数据
        if(EventSystem.current.IsPointerOverGameObject())   //判断是否在UI上
        {
           	/*判断是否在对应的物品栏中,因为InventoryManager中包含了各个背包UI的数据
           	所以将判断函数写在InventoryManager中,方便调用*/
            if(InventroyManager.Instance.CheckInActionUI(eventData.position) || InventroyManager.Instance.CheckInEquipmentUI(eventData.position) || 
            InventroyManager.Instance.CheckInInventoryUI(eventData.position))
            {
                //给targetHolder赋值,如果找到了SlotHolder组件,则直接赋值,如果没有找到,则在父级中找
                if(eventData.pointerEnter.gameObject.GetComponent<SlotHolder>())
                {
                    targetHolder = eventData.pointerEnter.gameObject.GetComponent<SlotHolder>();
                }
                else
                {
                	targetHolder = eventData.pointerEnter.gameObject.GetComponentInParent<SlotHolder>();
                }

                switch(targetHolder.slotType)	//判断targetHolder的物品栏类型
                {
                     case SlotType.BAG:
                        SwapItem();
                        break;
                    case SlotType.WEAPON:
                        if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Weapon)
                        {
                            SwapItem();
                        }
                        break;
                    case SlotType.ARMOR:
                        if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Armor)
                        {
                            SwapItem();
                        }
                        break;
                    case SlotType.ACTION:
                        if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Useable)
                        {
                            SwapItem();
                        }
                        break;
                }

                currentHolder.UpdateItem();
                targetHolder.UpdateItem();
            }
        }
        //重置物品的层级关系
        transform.SetParent(InventroyManager.Instance.currentDrag.originalParent);

        RectTransform t = transform as RectTransform;

        //为了防止图片出现堆放位置错误的问题
        t.offsetMax = Vector2.one * 40;
        t.offsetMin = -Vector2.one * 40;
    }

    //交换物品
    public void SwapItem()
    {
        //先获得两个 slotHolder 对应的物品
        var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index];
        var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];

        //判断两个物品是否一样
        bool isSameItem = tempItem.itemData == targetItem.itemData;

        if(isSameItem && targetItem.itemData.stackable)
        {
            targetItem.amount += tempItem.amount;
            tempItem.itemData = null;
            tempItem.amount = 0;
        }
        else
        {
            currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem;
            targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem;
        }
    }
}

标签:背包,itemData,系统,itemUI,Unity,UI,物品,public
From: https://www.cnblogs.com/MMMMrD/p/17832317.html

相关文章

  • Unity-FSM有限状态机
    Unity-FSM有限状态机什么是有限状态机?​ 在编写一些需要判断多个条件的程序时,我们常常会用到if-else语句,这样能够很好的帮我们解决多数问题。但在游戏开发过程中,一个角色的行为不是一成不变的,需要实时的进行修改,此时如果我们使用的是if-else来判断角色所处状态,就需要修改整......
  • Unity-场景的异步加载
    Unity-场景的异步加载为什么需要异步加载​ 在诸多大型游戏里,场景渲染精度都是动态的,随着场景与角色距离的增加,渲染精度也在递减,这样极大的减少了硬件性能的消耗。​ 但如果角色使用了某些传送技能,将自己传送到为渲染的地点,游戏可能就会因为需要瞬间渲染大量的场景而卡顿。此时......
  • Unity-Menu&场景切换
    Unity-Menu&场景切换开始界面1.要创建开始界面,首先要新建一个场景,用于添加游戏开始界面的内容2.新建按钮步骤:UI>画板>Button(按钮)>根据需要设置按钮3.给按钮添加代码,使得按下按钮就可以进入下一关/退出游戏(1)代码内容需要用到usingUnityEngine.SceneManagement的头文件......
  • Unity-敌人(Enemy)
    Unity-敌人(Enemy)引言​ 敌人是每个游戏中不可缺少的部分,设计得好的敌人可以给游戏增添很多乐趣,设计得差的则会非常影响我们的游戏体验。​ 经过这段时间的学习,我们已经接触了非常多的敌人代码的写法,但是就是没有系统的归类,导致每次写敌人,都要从头开始。现在是时候将他们进行一......
  • Unity-Light(含Unity2021-2d项目升级Urp渲染管线)
    Unity-Light(含Unity2021-2d项目升级Urp渲染管线)普通渲染管线(比较老旧的光效升级方式,已舍弃)​ 要使场景和角色拥有光效,那就得让他们先暗下来,给他们添加相应的材质场景材质的添加​ 选中需要添加材质的场景,在右侧框内的“材质”菜单中,选中Default-Diffuse材质人物材质的添加​......
  • Unity-单例模式
    Unity-单例模式前言​ 对于某些特殊的类,我们希望在整个程序的生命周期只创建一个该类的对象,或是希望在其他类没有持有该类的引用,就可以调用该类中的函数,我们就需要将这个类写成单例模式单例的简单实现publicclassTest(){ pubicabstractTestInstance;//创建程序中该......
  • Unity-射线
    Unity-射线前言​ 在游戏开发的过程中,许多功能的实现都需要物理检测,而发射射线是Unity中物理检测的通用方法。例如,我们需要检测玩家(Player)脚下是否是地面(图层为Ground),只需要从脚底发射一条射线,检测Player脚下GameObject的图层是否为Ground即可。​ 射线和物理检测何其重要,......
  • Unity-对象池 & 多对象池
    Unity-对象池&多对象池简介​ 在制作游戏的过程中,人物和boss的设计往往会有释放多个子弹的攻击方式。我们可以用直接创造子弹然后销毁的办法来实现这些技能的效果,但当子弹开始变多,游戏就会不断的消耗我们的内存。为了解决这个问题,开发者们就引入了状态机。普通对象池创建思......
  • Unity-协程
    Unity-协程协程的简单实现​ 一般的程序执行都是线性的,也就是必须一行一行的执行代码。​ 使用Unity提供的协程,就可以类似于开辟另一条线程,调整根据你所写的代码,调整下一行代码执行的时间。项目示例​ 下面的例子是一个U3DDemo中的代码,实现最简单的Enemy追击Player的......
  • Unity3D 如何用unity引擎然后用c#语言搭建自己的服务器
    Unity3D是一款强大的游戏开发引擎,可以用于创建各种类型的游戏。在游戏开发过程中,经常需要与服务器进行通信来实现一些功能,比如保存和加载游戏数据、实现多人游戏等。本文将介绍如何使用Unity引擎和C#语言搭建自己的服务器,并给出技术详解以及代码实现。对惹,这里有一个游戏开发交流......