[Unity背包系统]背包系统学习总结

背包系统-总结

提要

  • 背包系统是本蒟蒻第一个学习完成度比较高的游戏功能模块,是通过老外的视频学习的,但是由于视频发布时间较久远,并且老外的代码水平感觉不咋地(屎山!),所以也经过了比较长时间的整理,下面是一些个人分析的点

数据层

从ItemObject开始

  • 这里作者采用了ItemObject(继承于ScriptableObject)来作为存储物品资源相关数据的类,这些资源相关的数据包括了UI图片,物品类型,是否可堆叠等,还有一个物品数据ItemData,它又是Item类型的public Item itemData(包括ID,该物品所带来的Buff长度等),也就是说这个ItemObject存储了所有的数据,但是作者并不希望在游戏中直接使用这个ItemObject,因为太臃肿了,所以就单独把一些常用到的数据ItemData取出来,也就是Item类,它就只包含了物品名称,物品Id,物品Buff(因为我们希望一些装备类比如剑,每次刷到捡到的都是随机的不一样的数值)。
    public Item()
    {
        Name = "";
        Id = -1;
    }//一个空的物品
    public Item(ItemObject item)
    {
        Name = item.name;
        Id = item.data.Id;
        buffs = new ItemBuff[item.data.buffs.Length];//数组是引用类型

        for(int i = 0; i < item.data.buffs.Length; i ++ )
        {
            buffs[i] = new ItemBuff(item.data.buffs[i].min,item.data.buffs[i].max);
            buffs[i].attribute = item.data.buffs[i].attribute;
        }
    }

ItemDatabaseObject

  • 在ItemDatabaseObject中,我们为每一个物品资源赋予独一无二的Id,其实这里感觉也有些臃肿了,其实写完后发现GetItem这个字典类没啥用...不过这里还是学到了一些东西,比如Unity的自定义序列化:
public class ItemDatabaseObject : ScriptableObject,ISerializationCallbackReceiver
{
    //
    public ItemObject[] Items;
    public Dictionary<int, ItemObject> GetItem = new Dictionary<int, ItemObject>();

    //

    public void OnAfterDeserialize()
    { 
        for (int i = 0; i < Items.Length; i++)
        {
            Items[i].data.Id = i;
            GetItem.Add(i, Items[i]);
        }
    }//在反序列化之后,也就是等到Items数组这些东西都加载到游戏里的时候

    public void OnBeforeSerialize()
    {
        GetItem = new Dictionary<int, ItemObject>();
    }//在序列化之前,字典这个容器比较特殊

    //
}

InventoryObject

  • 这里我觉得作者对Inventory的一些代码编写产生了极大的耦合,和UI层关联性太大,没有做到分离,不过也一起归类到数据层吧,若是以后自己编写的话是会作出严格区分的
  • InventoryObject是高度封装的,背包内容实则是一个个记录在UI上的槽位,然后通过一个ItemDatabaseObject来执行一些背包里增删改查的方法,以及涉及到交换(UI上我们可以通过拖动交换两个槽位的物品):
    public bool AddItem(Item item, int amount)
    {
        if (EmptySlotCount <= 0)
            return false;
        InventorySlot slot = FindItemOnInventory(item);
        if(!Database.GetItem[item.Id].stackable || slot == null )
        {
            SetEmptySlot(item,amount);
            return true;
        }
        slot.AddAmount(amount);
        return true;
    }

    public InventorySlot FindItemOnInventory(Item item)
    {
        for(int i = 0; i < BackPack.Items.Length; i ++ )
        {
            if(BackPack.Items[i].item.Id == item.Id)
            {
                return BackPack.Items[i];
            }
        }
        return null;
    }

    public int EmptySlotCount
    {
        get
        {
            int counter = 0;
            for(int i = 0; i <BackPack.Items.Length; i ++ )
            {
                if (BackPack.Items[i].item.Id <= -1)
                    counter++;
            }
            return counter;
        }
    }

    public InventorySlot SetEmptySlot(Item item, int amount)
    {
        for (int i = 0; i < BackPack.Items.Length; i++)
        {
            if (BackPack.Items[i].item.Id <= -1)
            {
                BackPack.Items[i].UpdateSlot(item, amount);
                return BackPack.Items[i];
            }
        }
        return null;
    }//找到第一个空格,设置为相应的物品 
  public void MoveItem(InventorySlot item1,InventorySlot item2)
    {
        if (item2.CanPlaceInSlot(item1.ItemObject) && item1.CanPlaceInSlot(item2.ItemObject))
        {
            InventorySlot t = new InventorySlot(item2.item, item2.amount);
            item2.UpdateSlot(item1.item, item1.amount);
            item1.UpdateSlot(t.item, t.amount);
        }

    }

    public void RemoveItem(Item item)
    {
        for(int i = 0; i < BackPack.Items.Length; i ++ )
        {
            if(BackPack.Items[i].item == item)
            {
                BackPack.Items[i].UpdateSlot(null,0);//new Item()也可以
            }
        }
    }
  • 感觉这一块业务逻辑代码重复比较多(主要是InventorySlot),这里还学到了一些简单的存储,不过比较拉跨而且保密性很差,想到存档也不是这块的主要内容就没要深挖:

     [ContextMenu("Save")]
      public void Save()
    {
        string saveData = JsonUtility.ToJson(this.BackPack, true);
        PlayerPrefs.SetString("Inventory",saveData);
        PlayerPrefs.Save();
    }

    [ContextMenu("Load")]
    public void Load()
    {
        if (PlayerPrefs.HasKey("Inventory"))
        {
            JsonUtility.FromJsonOverwrite(PlayerPrefs.GetString("Inventory"), this.BackPack);
        }
    }

    [ContextMenu("Clear")]
    public void Clear()
    {
        BackPack.Clear();
    }

UI层

EventTrigger

  • EventTrigger组件是一个集成了所有UI事件监听接口的脚本,可以更方便地为控件添加事件监听
  • 这里为了给每个槽位的按钮(需要实现拖拽交换,移动格子等功能)都添加事件监听,作者直接写了一个AddEvent()方法
    protected void AddEvent(GameObject obj, EventTriggerType type, UnityAction<BaseEventData> action)
    {
        EventTrigger trigger = obj.GetComponent<EventTrigger>();//取得GameObject上的EventTrigger组件
        var eventTrigger = new EventTrigger.Entry();//声明一个需要监听的事件对象
        eventTrigger.eventID = type;//设置要监听的事件类型(点击,拖拽等)
        eventTrigger.callback.AddListener(action);//监听后触发的函数关联
        trigger.triggers.Add(eventTrigger);//把声明好的事件对象加入到刚刚的EventTrigger组件中
    }
  • 然后就可以在游戏开始时,为界面里的槽位等UI设置相应的方法了:
    private void Start()
    {
        for(int i = 0; i < inventory.BackPack.Items.Length; i ++ )
        {
            inventory.BackPack.Items[i].parent = this;
        }//设置每个槽位的父UI

        CreateSlots();
        AddEvent(gameObject, EventTriggerType.PointerEnter, delegate { OnEnterInterface(gameObject); });
        AddEvent(gameObject, EventTriggerType.PointerExit, delegate { OnEixtInterface(gameObject); });
    }

  • 注意:delegate{}是一种Lambda表达式的写法,并且我们省略了委托的参数(<BaseEventData>)
posted @ 2023-06-19 09:28  月牙同学  阅读(617)  评论(0)    收藏  举报