本质
对于背包系统,我们实际上是可以细分为 Item
和 Inventory
。
关于 Item
:首先是有一个 id
,其应该有很多属性,比如 name
, icon
, description
这些固有信息,这种不变的属性就可以存储在数据库当中。而类似于 count
, unlock
这种属性,其是会随着玩家的游玩而改变的,这些就可以存储在存档当中
关于 Inventory
:其本质就一个是一个 Item List
或者 Item Array
。再在这个数据结构上面,对其支持一些操作。例如:Add
, Remove
等等。
数据
首先我们需要 IBag
和 IBagItem
两个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public interface IBagItem : IEquatable<IBagItem> { string Id { get; set; } string Name { get; set; } int Index { get; set; } bool Stackable { get; set; } int StackLimit { get; set; } int StackCount { get; set; } bool CheckLegal<T>(IBag<T> bag) where T : IBagItem; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public interface IBag<T> where T: IBagItem { event Action OnItemAdd; event Action OnItemRemove; event Action OnItemTakeOut; int Count { get; } int MaxCapacity { get; set; } void Add(T item); void TakeOut(int index, int takeoutCount); void RemoveById(string id); void RemoveByIndex(int index); void Clear(); T GetItemByIndex(int index); List<T> GetItemsById(string id); T GetCanStackItemById(string id); List<T> GetAllItems(); void Sort(Func<T, object> sortByProperty); Bag<T> Select(Func<T, bool> filter); bool CheckItemPropertyLegal(IBagItem item, Func<IBagItem, IComparable> checkByProperty); }
|
此时,我们就基本上抽象出了背包和背包物品的属性和行为。对于 IBag
此处使用一个泛型,是为了保证类型安全。
接着我们给出以上两个接口的一种实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| public enum BagItemType { Weapon, Coin, Sword, Shield, Bow, Gun, Pao, Dao }
public class BagItem : IBagItem { public string Id { get; set; } public string Name { get; set; } public int Index { get; set; } public bool Stackable { get; set; } public int StackLimit { get; set; } public int StackCount { get; set; } public BagItemType BagItemType { get; set; } public int Grade { get; set; } public int Star { get; set; }
public BagItem(string id, string name, BagItemType itemType, int stackCount, int grade, int star) { Id = id; Name = name; BagItemType = itemType; Grade = grade; Star = star; if (stackCount > 0) { Stackable = true; StackLimit = 99; StackCount = stackCount; } }
public BagItem(BagItem item) { Id = item.Id; Name = item.Name; BagItemType = item.BagItemType; Stackable = item.Stackable; StackLimit = item.StackLimit; StackCount = item.StackCount; Grade = item.Grade; Star = item.Star; }
public bool CheckLegal<T>(IBag<T> bag) where T : IBagItem { bool result = true; result = bag.CheckItemPropertyLegal(this, item => item.Stackable) && result; result = bag.CheckItemPropertyLegal(this, item => item.Name) && result; result = bag.CheckItemPropertyLegal(this, item => ((BagItem)item).BagItemType) && result; result = bag.CheckItemPropertyLegal(this, item => ((BagItem)item).Grade) && result; result = bag.CheckItemPropertyLegal(this, item => ((BagItem)item).Star) && result; return result; }
public bool Equals(IBagItem other) { return other != null && Id == other.Id; } }
|
关于背包物品接口实现,添加了一些类型,等级,星级之类的属性。关于 CheckLegal
这个函数的实现,主要功能是判断添加进的同 Id
物品(一类的物品),其是否符合规范(规范由首次添加进的物品决定)。
- 比如添加进了一把四星的xx剑,第二次添加相同物品的时候,即
Id
相同,我们就得开始检查两把武器的属性,Name
得一样吧,Type
得一样吧,其实这里 Grade
应该是不用检查的,毕竟同一类武器也有等级不一样的情况。Star
也同理,根据整个游戏,是否有升星系统之类的来判断到底需不需要检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
| public class Bag<T> : IBag<T> where T : IBagItem { private List<T> _bagItems; private readonly Dictionary<string, List<T>> _bagItemMapById; private Bag<T> _selectedItems; public event Action OnItemAdd; public event Action OnItemRemove; public event Action OnItemTakeOut; public int Count => _bagItems.Count; public int MaxCapacity { get; set; }
public Bag(int maxCapacity) { _bagItemMapById = new Dictionary<string, List<T>>(); _bagItems = new List<T>(); MaxCapacity = maxCapacity; }
public Bag(List<T> bagItems) { _bagItemMapById = new Dictionary<string, List<T>>(); _bagItems = bagItems; foreach (var item in _bagItems) { List<T> list; var find = _bagItemMapById.TryGetValue(item.Id, out list); if (find) { list.Add(item); } else { _bagItemMapById.Add(item.Id, new List<T> { item }); } }
MaxCapacity = _bagItems.Count; }
#region CRUD
public void Add(T item) { if (Count >= MaxCapacity) { var sameIdItem = GetCanStackItemById(item.Id); if (sameIdItem != null && item.CheckLegal(this) && sameIdItem.StackCount + item.StackCount <= item.StackLimit) { sameIdItem.StackCount += item.StackCount; OnItemAdd?.Invoke(); } else { Debug.LogError("背包容量不足"); }
return; }
if (!_bagItemMapById.ContainsKey(item.Id)) { RegisterBagItem(item); return; }
if (!item.CheckLegal(this)) { Debug.Log("加入的物品不规范"); return; }
if (!item.Stackable) { RegisterBagItem(item); return; }
var canStackableItem = GetCanStackItemById(item.Id); var mergeCount = item.StackCount + canStackableItem.StackCount; var endCount = mergeCount - item.StackLimit > 0 ? mergeCount - item.StackLimit : mergeCount; if (endCount == mergeCount) { canStackableItem.StackCount = endCount; } else { canStackableItem.StackCount = canStackableItem.StackLimit; item.StackCount = endCount; RegisterBagItem(item); } }
public void TakeOut(int index, int takeoutCount) { var item = GetItemByIndex(index); if (item.StackCount < takeoutCount) { Debug.LogError("此格子物品数量不足"); } else if (item.StackCount == takeoutCount) { _bagItems.Remove(item); var items = GetItemsById(item.Id); items.Remove(item); } else { item.StackCount -= takeoutCount; }
OnItemTakeOut?.Invoke(); }
public void RemoveById(string id) { _bagItemMapById.Remove(id); _bagItems.RemoveAll(item => item.Id == id);
OnItemRemove?.Invoke(); }
public void RemoveByIndex(int index) { if (index < 0 || index >= Count) { return; }
var item = _bagItems[index]; _bagItemMapById[item.Id].Remove(item); _bagItems.Remove(item);
OnItemRemove?.Invoke(); }
public void Clear() { _bagItems.Clear(); }
public T GetItemByIndex(int index) { if (index < 0 || index >= Count) { Debug.LogError($"Index: {index} 数组越界"); }
return _bagItems[index]; }
public List<T> GetItemsById(string id) { List<T> list; _bagItemMapById.TryGetValue(id, out list); return list; }
public T GetCanStackItemById(string id) { var list = GetItemsById(id); if (list == null) { return default(T); }
var findItem = list.Find(item => item.Stackable && item.StackCount < item.StackLimit); if (findItem != null) { return findItem; }
return list.Find(item => item.Stackable && item.StackCount == item.StackLimit); }
public List<T> GetAllItems() { return _bagItems; }
#endregion
public void Sort(Func<T, object> sortByProperty) { _bagItems = _bagItems.OrderBy(sortByProperty).ToList(); RefreshItemIndex(); }
public List<T> SelectAndOutList(Func<T, bool> filter) { return _bagItems.Where(filter).ToList(); }
public Bag<T> Select(Func<T, bool> filter) { return new Bag<T>(SelectAndOutList(filter)); }
public bool CheckItemPropertyLegal(IBagItem item, Func<IBagItem, IComparable> checkByProperty) { var property = checkByProperty(item); var item2 = GetItemsById(item.Id)[0]; var property2 = checkByProperty(item2); return property.Equals(property2); }
private void RegisterBagItem(T item) { item.Index = _bagItems.Count; _bagItems.Add(item); if (_bagItemMapById.ContainsKey(item.Id)) { _bagItemMapById[item.Id].Add(item); } else { _bagItemMapById.Add(item.Id, new List<T> { item }); }
OnItemAdd?.Invoke(); }
public T this[int index] { get => GetItemByIndex(index); set { if (index < 0 || index >= Count) { Debug.LogError($"Index: {index} 数组越界"); } else { _bagItems[index] = value; } } }
private void RefreshItemIndex() { for (int i = 0; i < _bagItems.Count; i++) { _bagItems[i].Index = i; } }
public override string ToString() { string info = "BagInfo: "; for (int i = 0; i < _bagItems.Count; i++) { var item = _bagItems[i]; info += $"Index: {i}, Id: {item.Id}, Name: {item.Name}, Count: {item.StackCount}\n"; }
return info; } }
|
以上就是一个背包数据操作基本的封装。
- 使用
List<T>
来作为保存物品背包的数据结构
- 使用
Dictionary<string, List<T>>
来做一个根据 Id
的快速索引一类物品
- 关于
CheckItemPropertyLegal
,在调用其之前我们其实是保证了背包已有同 Id
物品,因此可以直接在 Id
缓存里面拿。
default(T)
是返回一个类型的默认值,而对于我们的 BagItem
来说,其是一个引用类型,因此默认值就为 null
基本的背包 CRUD
逻辑基本上完成,其余如展示出来背包 ui
之类的,再在这基础上二次封装一下即可。