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

数据层
从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>
)