一个简单的吃鸡背包编写——1
Posted CZandQZ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个简单的吃鸡背包编写——1相关的知识,希望对你有一定的参考价值。
好久都没写博客。是时候来更新一篇一直想写的一个demo,前段时间网易出一个荒野行动。听说还是unity做的,所以自己没事做的时候就来写的玩玩。由于是个人开发所以网络端用的是一个Photon View插件来实现简单的联网同步操作。关于Photon View插件的运用,我会在接下来的几篇博客来介绍一下,今天首先写一个类似荒野行动的背包。之前给大家介绍了MVC及MVVM框架的中心思想,所以这里还是用MVC和响应式编程的思想来实现这样一个背包。首先截一下背包的图来讲解背包该如何实现。
首先角色靠近地上的物体。附近栏中会显示地上的物体对应的UI icon。当打开背包button的时候 附近栏中同时会出现地上物体对应的UI icon,当玩家拾取的时候角色会执行对应的动画。今天我们只需要明白UI之间交互及UI与玩家数据的交互。按照响应式编程的思想。这里应该有一个数据类来驱动UI的显示工作,所以最开始写背包的时候,我们应该可以用简单的GUI来显示背包数据,及对应的数据类,即在没有UI素材的时候我们可以通过GUI来检测我们的背包数据逻辑的正确性。这种做法在早期的一些格子类型游戏及卡牌(例如扎金花,斗地主之类的),所以我们得先把背包数据的逻辑跑通。在逻辑数据跑通之前我们的需要知道每种物体对应的编号,一般直接用一个数字来代替,如果我们只有10种类别的游戏物体。每种种类只有10个左右的话,那么我们可以这样编号,11开始一直到19这样代表一种装备,21一直到29这样代表另外一种装备,依次类推一直到91一直到99.如果种类有100种的时候这个而每个种类有100种的时候,那么总种类就应该是1000*100。这里不多赘述这个问题,这个只是一个简单的并且合理的分配一些ID而已。这里我们尽量让每种种类分配更多的数量,例如1001代表鞋子的一种,那么第二个种类应该就是1002依次类推一直到1999,2001代表头盔的一种,那么依次类推就是2001到2999。首先建一个基本的数据类,将其定义为:InventoryItem,首先它有几个基本的字段:它的id(int),它的名字(string),它的属性(是否可以被使用,是否可以被装备)。它的类别(是武器,还是靴子,或者是背包等等),它的重量。它的ui预设。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Inventory
/// <summary>
/// 基本的游戏背包
/// </summary>
///
[Serializable]
public class InventoryItem
/// <summary>
/// ItemId is uniqid of the Inventory name
/// </summary>
public int ItemId;
/// <summary>
/// describe this InventoryItem can be used
/// </summary>
public bool Usable;
/// <summary>
/// describe this InventoryItem can be used
/// </summary>
public bool Equipable;
/// <summary>
/// The name of current InventoryItem
/// </summary>
public string ItemName;
/// <summary>
/// This is Descripe The InventoryItem Type
/// </summary>
public InventoryItemType InventoryItemType;
/// <summary>
/// InventoryItem UI Prefab
/// </summary>
public GameObject UiPrefab;
/// <summary>
/// InventoryItem Weight
/// </summary>
public int Weight;
public bool CanMoveObject = true;
public bool CanSwapObject = true;
public InventoryItem(int id)
ItemId = id;
var item = InventoryConfigTable.Instance.GetItemById(id);
UiPrefab = item.UiPrefab;
ItemName = item.ItemName;
InventoryItemType = item.InventoryItemType;
Usable = item.Usable;
Equipable = item.Equipable;
Weight = item.Weight;
public InventoryItem()
public void Pick()
public void Use()
Debug.LogError("使用了" + ItemName);
//send ItemId message to Receiver
public void Equip()
Debug.LogError("装备了" + ItemName);
//send ItemId message to Receiver
public void UnEquip()
Debug.LogError("卸掉了" + ItemName);
//send ItemId message to Receiver
public virtual void Swap()
public void Drop()
对应得枚举类型为InventoryItemType,定义如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Inventory
public enum InventoryItemType
None = 0,
Shoes, // 鞋子
Helmet, // 头盔
Bodyarmor, // 防弹衣
Waistcoat, // 背心
Pants, // 裤子
Bandage, // 绷带
BloodPack, // 血包
Rifle, // 步枪
Pistol, // 手枪
Ammo, // 弹药
Bag, // 背包
Clip // 弹夹
接下来就是写一个关于基本的数据存储类,如果在excel中理解的话,前面写的就算是一行数据,那么现在写的就是一个数据的集合即很多行数据。所以这里最起码得有一个数组或者字典的东西。这里我用的字典,这样便于查询方便。所以这里基本操作基本上就有添加,删除,查询这些了。同样这个类是作为基类存在的。在吃鸡里面背包项的添加存在几种情况。例如鞋子只能添加一种,如果再添加另外一种就会被覆盖原先的一种,但是子弹不会,弹夹不会。还有枪械只能添加2种等不同的情况,所以这些数据的添加和移除方式可能不一样,所以我们需要将他们分开来处理。所以这里的基类是很有必要的。
using System;
using System.Collections.Generic;
using Event;
using UnityEngine;
namespace Inventory
public abstract class InventoryBaseData
protected Dictionary<int, InventoryItem> Cache;
protected Dictionary<int, bool> CacheBools;
protected int TotalCount;
protected InventoryType InventoryType;
protected InventoryBaseData(int totalCount, InventoryType inventoryType)
TotalCount = totalCount;
InventoryType = inventoryType;
Cache = new Dictionary<int, InventoryItem>();
CacheBools = new Dictionary<int, bool>();
if (TotalCount > 0)
for (int i = 0; i < TotalCount; i++)
CacheBools.Add(i, false);
public virtual void AddToCache(InventoryItem item,ref int origin)
public virtual void RemoveFromCache(int index)
public virtual int GetItemFromCache(int index)
return 0;
/// <summary>
/// Test GUI
/// </summary>
public virtual void ShowGui(Vector2 originVector2, GUIStyle fontStyle)
int index = 0;
Vector2 v2 = originVector2;
foreach (var item in Cache)
var v1 = new Vector2(v2.x, v2.y + index * 30);
GUI.Label(new Rect(v1.x, v1.y, 200, 30), String.Format("位置:0,名字:1", item.Key, item.Value.ItemName), fontStyle);
index++;
protected int GetEmptyIndex(bool isLimit)
if (isLimit)
for (int i = 0; i < TotalCount; i++)
bool b = CacheBools[i];
if (!b)
return i;
return -1;
int index = 0;
while (true)
if (!CacheBools.ContainsKey(index))
CacheBools.Add(index, true);
return index;
bool b = CacheBools[index];
if (!b)
return index;
index++;
protected int GetIndexByitemType(InventoryItemType itemItemType)
foreach (var item in Cache)
if (item.Value.InventoryItemType == itemItemType)
return item.Key;
return -1;
这里的枚举类型为:
public enum InventoryType
None = -1,
ArticleInventory,
ClothInventory,
EquipInventory,
GunInventory
这里我简单把背包数据分为2类,一类是枪械,那么剩下的就是另外一种类别了。
首先分析一些枪械这个类别,第一一个玩家只能存在2个枪械,这个是不分种类,可以2把同样的枪械,第二,枪械只能被激活一把。所以需要重写AddToCache方法,另外需要重写添加一个SwitchItem方法。
public override void AddToCache(InventoryItem item,ref int origin)
int index = 0;
int count = Cache.Count;
if (count <= 1)
index = GetEmptyIndex(true);
if (index >= 0)
Cache.Add(index, item);
CacheBools[index] = true;
if (count == 0)
GunIndex = 0;
CurrentGunInventoryItem = item;
else if (count == 2)
origin -= Cache[GunIndex].Weight;
Cache.Remove(GunIndex);
Cache.Add(GunIndex, item);
CacheBools[GunIndex] = true;
GunIndex = 0;
index = GunIndex;
CurrentGunInventoryItem = item;
EventManager.TriggerEvent(new IInventoryUIAddEvent(item, InventoryType, index));
首先判断枪械背包中存在几把枪械,如果<=1的时候,就直接添加就行了,如果==2的时候就替换已经激活的那把枪械。
EventManager.TriggerEvent(new IInventoryUIAddEvent(item, InventoryType, index));
这句的意思是数据更新之后通知ui及时更新,就是一个简单的消息处理,这种消息处理方式相比之前写的扩展性更高,这个在背包之后我会继续给出来这种消息处理的方式,其实和之前写的那种相差不大,只是以前使用字符串来实现消息的传递现在换成了结构体。
public void SwitchItem(int preIndex,int nextIndex)
if (Cache.ContainsKey(preIndex)&& Cache.ContainsKey(nextIndex))
var preItem = Cache[preIndex];
var nextItem = Cache[nextIndex];
Cache[preIndex] = nextItem;
Cache[nextIndex] = preItem;
CurrentGunInventoryItem = Cache[preIndex];
枪械之间的交换就是字典中value的值的指针的交换而已。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Event;
using Tool;
using UI;
using UnityEngine;
namespace Inventory
public class InventoryGunData : InventoryBaseData
protected int GunIndex;
protected InventoryItem CurrentGunInventoryItem;
public InventoryGunData(int totalCount, InventoryType inventoryType) : base(totalCount, inventoryType)
GunIndex = -1;
CurrentGunInventoryItem = null;
public override void AddToCache(InventoryItem item,ref int origin)
int index = 0;
int count = Cache.Count;
if (count <= 1)
index = GetEmptyIndex(true);
if (index >= 0)
Cache.Add(index, item);
CacheBools[index] = true;
if (count == 0)
GunIndex = 0;
CurrentGunInventoryItem = item;
else if (count == 2)
origin -= Cache[GunIndex].Weight;
Cache.Remove(GunIndex);
Cache.Add(GunIndex, item);
CacheBools[GunIndex] = true;
GunIndex = 0;
index = GunIndex;
CurrentGunInventoryItem = item;
EventManager.TriggerEvent(new IInventoryUIAddEvent(item, InventoryType, index));
public override void ShowGui(Vector2 originVector2, GUIStyle fontStyle)
base.ShowGui(originVector2, fontStyle);
if (CurrentGunInventoryItem != null)
GUI.Label(new Rect(1150, 0, 300, 100), String.Format("Pos:0,Name:1", GunIndex, CurrentGunInventoryItem.ItemName));
public void SwitchItem(int preIndex,int nextIndex)
if (Cache.ContainsKey(preIndex)&& Cache.ContainsKey(nextIndex))
var preItem = Cache[preIndex];
var nextItem = Cache[nextIndex];
Cache[preIndex] = nextItem;
Cache[nextIndex] = preItem;
CurrentGunInventoryItem = Cache[preIndex];
接下来就是编写除了枪械之外的数据背包类,将其定义为InventoryNormalData,同样它需继承InventoryBaseData这样一个基类的方法。这里继承了AddToCache、RemoveFromCache和GetItemFromCache这3个方法。AddToCache存在是否忽略同一类型的物品只能添加一种这样的情况。
public override void AddToCache(InventoryItem item,ref int origin)
var itemType = item.InventoryItemType;
int index = 0;
if (!_ignoreSameType)//不忽略同一类型(即同一类型的物品只能存在一个)
if (!_inventoryCount.ContainsKey(itemType))
_inventoryCount.Add(itemType, 1);
index = GetEmptyIndex(_isLimit);
if (index >= 0)
Cache.Add(index, item);
CacheBools[index] = true;
else
if (_inventoryCount[itemType] >= 1)
index = GetIndexByitemType(itemType);
if (index >= 0)
origin -= Cache[index].Weight;
Cache.Remove(index);
Cache.Add(index, item);
CacheBools[index] = true;
else
if (!_inventoryCount.ContainsKey(itemType))
_inventoryCount.Add(itemType, 1);
index = GetEmptyIndex(_isLimit);
if (index >= 0)
Cache.Add(index, item);
CacheBools[index] = true;
else
index = GetEmptyIndex(_isLimit);
if (index >= 0)
Cache.Add(index, item);
CacheBools[index] = true;
EventManager.TriggerEvent(new IInventoryUIAddEvent(item, InventoryType, index));
EventManager.TriggerEvent(new IInventoryUIAddEvent(item, InventoryType, index));
数据更新及时通知ui发生更新,把这句去掉之后代码也不会有大问题,所以前期这里测试数据逻辑的时候ui的更新就没必要了。
public override void RemoveFromCache(int index)
if (!Cache.ContainsKey(index))
return;
InventoryItemType itemType = Cache[index].InventoryItemType;
Cache.Remove(index);
CacheBools[index] = false;
_inventoryCount[itemType] -= 1;
if (_inventoryCount[itemType] <= 0)
_inventoryCount.Remove(itemType);
RemoveFromCache只是简单的从字典里面移除一个背包项。具体的代码实现如下:
using System.Collections.Generic;
using Event;
using Inventory;
using Tool;
using UI;
namespace Inventory
public class InventoryNormalData: InventoryBaseData
private bool _ignoreSameType;
private Dictionary<InventoryItemType, int> _inventoryCount;
private bool _isLimit;
public InventoryNormalData(int totalCount, bool ignoreSameType,bool isLimit, InventoryType inventoryType) : base(totalCount, inventoryType)
_isLimit = isLimit;
_ignoreSameType = ignoreSameType;
_inventoryCount = new Dictionary<InventoryItemType, int>();
public override void AddToCache(InventoryItem item,ref int origin)
var itemType = item.InventoryItemType;
int index = 0;
if (!_ignoreSameType)//不忽略同一类型(即同一类型的物品只能存在一个)
if (!_inventoryCount.ContainsKey(itemType))
_inventoryCount.Add(itemType, 1);
index = GetEmptyIndex(_isLimit);
if (index >= 0)
Cache.Add(index, item);
CacheBools[index] = true;
else
if (_inventoryCount[itemType] >= 1)
index = GetIndexByitemType(itemType);
if (index >= 0)
origin -= Cache[index].Weight;
Cache.Remove(index);
Cache.Add(index, item);
CacheBools[index] = true;
else
if (!_inventoryCount.ContainsKey(itemType))
_inventoryCount.Add(itemType, 1);
index = GetEmptyIndex(_isLimit);
if (index >= 0)
Cache.Add(index, item);
CacheBools[index] = true;
else
index = GetEmptyIndex(_isLimit);
if (index >= 0)
Cache.Add(index, item);
CacheBools[index] = true;
EventManager.TriggerEvent(new IInventoryUIAddEvent(item, InventoryType, index));
public override void RemoveFromCache(int index)
if (!Cache.ContainsKey(index))
return;
InventoryItemType itemType = Cache[index].InventoryItemType;
Cache.Remove(index);
CacheBools[index] = false;
_inventoryCount[itemType] -= 1;
if (_inventoryCount[itemType] <= 0)
_inventoryCount.Remove(itemType);
public override int GetItemFromCache(int index)
if (Cache.ContainsKey(index))
return Cache[index].ItemId;
return -1;
定义完了一些小背包项之后就需要写一个管理大背包项的类了,将其定义为:InventoryModel。这个总的背包管理类中首先定义几个小的背包项字段:
private InventoryBaseData _clothBaseData;
private InventoryBaseData _articleBaseData;
private InventoryBaseData _equipBaseData;
private InventoryBaseData _gunBaseData;
然后初始化的方法,定义为Load
public static InventoryModel Load()
return new InventoryModel(3,5,2);
这里Load将其定义为静态的,因为有时候我们需要从本地读取数据,所以这里暂时写成new一个新的数据实体类了。所以构造函数里面就是对其进行new 构造而已。
public InventoryModel(int clothCount,int articleCount,int gunCount)
_clothBaseData = new InventoryNormalData(clothCount, false, true, InventoryType.ClothInventory);
_articleBaseData = new InventoryNormalData(articleCount, false, true, InventoryType.ArticleInventory);
_equipBaseData = new InventoryNormalData(default(int), true, false, InventoryType.EquipInventory);
_gunBaseData = new InventoryGunData(gunCount, InventoryType.GunInventory);
_currentWeight = 0;
_totalWeight = 120;
这个类的职责就是简单来说就是增删改查了。所以会存在四个方法。所有的数据类基本都存在这四个方法。AddItem,RemoveItem,GetItem,SwitchGunItem。
public void AddItem(int id)
InventoryItem item = new InventoryItem(id);
OnHandlerAddItem(item);
Add方法参数是传进来一个id之后然后对其进行判断,这个判断操作在OnHandlerAddItem里面进行了。
public void RemoveItem(InventoryType inventoryType, int index)
OnHandlerDropItem(inventoryType, index);
RemoveItem方法第一个参数是小背包的类型,第二个参数是在小背包类型中位置的索引。
public int GetItem(InventoryType inventoryType,int index)
switch (inventoryType)
case InventoryType.ClothInventory:
return _clothBaseData.GetItemFromCache(index);
case InventoryType.ArticleInventory:
return _articleBaseData.GetItemFromCache(index);
case InventoryType.EquipInventory:
return _equipBaseData.GetItemFromCache(index);
return -1;
GetItem方法主要获得对应小背包中索引为index的背包项的id。
public void SwitchGunItem(int preIndex, int nextIndex)
OnHandlerSwitchGunItem(preIndex,nextIndex);
SwitchGunItem方法主要是交换索引为preindex和nextindex上面的枪械。一些私有方法就不一一赘述了,整个类如下:
using System;
using Assets.Scripts.Tool;
using BaseTable;
using Event;
using Tool;
using UI;
using UnityEngine;
namespace Inventory
/// <summary>
/// 背包模型层
/// </summary>
public class InventoryModel
private InventoryBaseData _clothBaseData;
private InventoryBaseData _articleBaseData;
private InventoryBaseData _equipBaseData;
private InventoryBaseData _gunBaseData;
private int _totalWeight;
private int _currentWeight;
public Action<int> TotalWeightCallBack set; get;
public Action<int> CurrentWeightCallBack set; get;
public static InventoryModel Load()
return new InventoryModel(3,5,2);
public InventoryModel(int clothCount,int articleCount,int gunCount)
_clothBaseData = new InventoryNormalData(clothCount, false, true, InventoryType.ClothInventory);
_articleBaseData = new InventoryNormalData(articleCount, false, true, InventoryType.ArticleInventory);
_equipBaseData = new InventoryNormalData(default(int), true, false, InventoryType.EquipInventory);
_gunBaseData = new InventoryGunData(gunCount, InventoryType.GunInventory);
_currentWeight = 0;
_totalWeight = 120;
public void AddItem(int id)
InventoryItem item = new InventoryItem(id);
OnHandlerAddItem(item);
public void RemoveItem(InventoryType inventoryType, int index)
OnHandlerDropItem(inventoryType, index);
public int GetItem(InventoryType inventoryType,int index)
switch (inventoryType)
case InventoryType.ClothInventory:
return _clothBaseData.GetItemFromCache(index);
case InventoryType.ArticleInventory:
return _articleBaseData.GetItemFromCache(index);
case InventoryType.EquipInventory:
return _equipBaseData.GetItemFromCache(index);
return -1;
public void SwitchGunItem(int preIndex, int nextIndex)
OnHandlerSwitchGunItem(preIndex,nextIndex);
private void AddTotalLoadCount(int value)
_totalWeight += value;
if (TotalWeightCallBack != null)
TotalWeightCallBack.Invoke(_totalWeight);
private bool TryAddCurrentLoadCount(int value)
if (_currentWeight < _totalWeight)
_currentWeight += value;
if (CurrentWeightCallBack != null)
CurrentWeightCallBack.Invoke(_currentWeight);
return true;
return false;
private void MinusCurrentLoadCount(int value)
_currentWeight -= value;
if (CurrentWeightCallBack != null)
CurrentWeightCallBack.Invoke(_currentWeight);
public void ShowGui(GUIStyle fontStyle)
_articleBaseData.ShowGui(new Vector2(0, 0), fontStyle);
_clothBaseData.ShowGui(new Vector2(400, 0), fontStyle);
_equipBaseData.ShowGui(new Vector2(600, 0), fontStyle);
_gunBaseData.ShowGui(new Vector2(800, 0), fontStyle);
private void OnHandlerSwitchGunItem(int preIndex, int nextIndex)
var inventoryGunData = _gunBaseData as InventoryGunData;
if (inventoryGunData != null)
inventoryGunData.SwitchItem(preIndex, nextIndex);
EventManager.TriggerEvent(new IGunInventoryUISwitchEvent(preIndex,nextIndex));
EventManager.TriggerEvent(new ICharacterSwapGun(preIndex, nextIndex));
private void OnHandlerAddItem(InventoryItem item)
var itemtype = item.InventoryItemType;
if (IsCloth(itemtype) && TryAddCurrentLoadCount(item.Weight))
_clothBaseData.AddToCache(item, ref _currentWeight);
var value = ToolHelp.GetSkinPairByInventoryItem(item);
EventManager.TriggerEvent(new ICharacterChangeSkin(value.Key,value.Value));
return;
if (IsArticle(itemtype))
if (IsBag(itemtype))
var assetItem = BagDatabase.Instance.GetAssetByBagId(item.ItemId);
if (assetItem != null)
_articleBaseData.AddToCache(item, ref _currentWeight);
AddTotalLoadCount(assetItem.Capacity);
EventManager.TriggerEvent(new ICharacterPickUpBag(assetItem.BagId % 10));
else
if (TryAddCurrentLoadCount(item.Weight))
_articleBaseData.AddToCache(item, ref _currentWeight);
var value = ToolHelp.GetSkinPairByInventoryItem(item);
EventManager.TriggerEvent(new ICharacterChangeSkin(value.Key, value.Value));
return;
if (IsEquip(itemtype)&& TryAddCurrentLoadCount(item.Weight))
_equipBaseData.AddToCache(item, ref _currentWeight);
return;
if (IsGun(itemtype) && TryAddCurrentLoadCount(item.Weight))
_gunBaseData.AddToCache(item, ref _currentWeight);
EventManager.TriggerEvent(new ICharacterPickupGun(item.ItemId));
private void OnHandlerDropItem(InventoryType inventoryType,int index)
switch (inventoryType)
case InventoryType.ClothInventory:
_clothBaseData.RemoveFromCache(index);
break;
case InventoryType.ArticleInventory:
_articleBaseData.RemoveFromCache(index);
break;
case InventoryType.EquipInventory:
_equipBaseData.RemoveFromCache(index);
break;
private bool IsCloth(InventoryItemType itemtype)
return (itemtype == InventoryItemType.Shoes) || (itemtype == InventoryItemType.Waistcoat) ||
(itemtype == InventoryItemType.Pants);
private bool IsArticle(InventoryItemType itemtype)
return (itemtype == InventoryItemType.Helmet) || (itemtype == InventoryItemType.Bodyarmor)|| (itemtype == InventoryItemType.Bag);
private bool IsEquip(InventoryItemType itemtype)
return (itemtype == InventoryItemType.Ammo) || (itemtype == InventoryItemType.BloodPack) ||
(itemtype == InventoryItemType.Bandage) || (itemtype == InventoryItemType.Clip);
private bool IsGun(InventoryItemType itemType)
return (itemType == InventoryItemType.Pistol) || (itemType == InventoryItemType.Rifle);
private bool IsBag(InventoryItemType itemType)
return itemType == InventoryItemType.Bag;
这个里面可能存在一些通知事件,在测试数据的时候可以将它注释掉,因为数据在发生变化的时候肯定会引起ui或者角色属性的变化,这是必然的。这就存在2个模块甚至代码之间在发生数据的传输或者交换。这里采用消息处理是为了避免2个模块之间交互的时候耦合性太强。一般耦合性太强的时候都会想到用第三个类来中间搭桥来同时持有这2个类的引用,从而来减低耦合性。测试这个背包的逻辑的时候可以直接建一个简单的类,然后调用这个数据实体类中的方法即可,直接用GUI来测试逻辑的正确性。下一节编写一个简单的数据查询类,这里以ScriptableObject类为基础来编写编辑器功能。如有疑问可以联系我。qq:1850761495
以上是关于一个简单的吃鸡背包编写——1的主要内容,如果未能解决你的问题,请参考以下文章