Tips:ET7.2框架游戏背包系统后端学习笔记,纯后端!!!
背包系统的构成
通过ECS架构编写背包系统:
层级关系:BagComponent(背包组件)挂载在Unit上,Item(实体组件)挂载在BagComponent上,EquipInfoComponent(装备组件)挂载在Item上,AttributeEntry(属性词条组件)挂载在EquipInfoComponent上。
BagComponent——背包
Item——背包中的物品
ItemType——物品项类型: 武器、 防具、 戒指、 道具
ItemOp——物品操作指令: 增加物品、 移除物品
ItemContainerType——物品容器类型: 背包容器、 游戏角色装配容器
物品详情: 属性词条 ——>最大生命值、 暴击率、 闪避、 伤害 ——> 每个武器的属性词条不一样
评分、 出售价格
背包物品的操作:穿戴、 售卖
背包系统的后端实现
背包组件的编写
using System.Collections.Generic;
using MongoDB.Bson.Serialization.Attributes;
//IDeserialize --> 反序列化
//ITransfer --> 可以随着Unit进行传送
namespace ET
{
[ComponentOf(typeof(Unit))]
public class BagComponent : Entity,IAwake,IDestroy,IDeserialize,ITransfer
{
//字典类型存放背包中的物体实体Item
[BsonIgnore]
public Dictionary<long, Item> ItemsDict = new Dictionary<long, Item>();
//MultiMap类型中的Item实体表示背包中页签的物品列表实体
[BsonIgnore]
public MultiMap<int, Item> ItemsMap = new MultiMap<int, Item>();
[BsonIgnore]
public M2C_ItemUpdateOpInfo message = new M2C_ItemUpdateOpInfo() {ContainerType = (int)ItemContainerType.Bag};
}
}
消息的定义:
message ItemInfo
{
int64 ItemUid = 1;
int32 ItemConfigId = 2;
int32 ItemQuality = 3;
EquipInfoProto EquipInfo = 4;
}
message M2C_AllItemsList //IActorMessage
{
int32 RpcId = 90;
repeated ItemInfo ItemInfoList = 1;
int32 ContainerType = 2;
}
//物品更新信息操作的消息
message M2C_ItemUpdateOpInfo // IActorMessage
{
int32 RpcId = 90;
ItemInfo ItemInfo = 1;
int32 Op = 2;
int32 ContainerType = 3;
}
//ResponseType M2C_SellItem
message C2M_SellItem // IActorLocationRequest
{
int32 RpcId = 1;
int64 ItemUid = 2;
}
message M2C_SellItem // IActorLocationResponse
{
int32 RpcId = 90;
int32 Error = 91;
string Message = 92;
}
message EquipInfoProto
{
int64 Id = 1;
int32 Score = 2;
repeated AttributeEntryProto AttributeEntryProtoList = 3;
}
//ResponseType M2C_EquipItem
message C2M_EquipItem // IActorLocationRequest
{
int32 RpcId = 1;
int64 ItemUid = 2;
}
创建出背包组件后要挂载到Unit上,UnitFactory:unit.AddComponent<BagComponent>();
实体Item的编写
using MongoDB.Bson.Serialization.Attributes;
namespace ET
{
[ChildOf(typeof(BagComponent))]
public class Item : Entity,IAwake<int>,IDestroy,ISerializeToEntity
{
//物品配置ID
public int ConfigId = 0;
/// <summary>
/// 物品品质
/// </summary>
public int Quality = 0;
[BsonIgnore]
//物品配置数据 来自Excel文件 ItemConfig@cs.xlsx
public ItemConfig Config => ItemConfigCategory.Instance.Get(ConfigId);
}
}
我的理解是配置表中存储背包物品的基本信息,ET框架会根据Excel配置表生成配置类,后端通过配置类生成背包中的物品数据传给前端展示。
Excel配置文件在ET7.2中的存放位置: PS:ET6的Excel就在根目录下,7.2藏的这么深 -_-
ItemType
namespace ET
{
/// <summary>
/// 物品项类型
/// </summary>
public enum ItemType
{
Weapon = 0, //武器
Armor = 1, //防具
Ring = 2, //戒指
Prop = 3, //道具
}
/// <summary>
/// 物品操作指令
/// </summary>
public enum ItemOp
{
Add = 0, //增加物品
Remove = 1 //移除物品
}
/// <summary>
/// 物品容器类型
/// </summary>
public enum ItemContainerType
{
Bag = 0, //背包容器
RoleInfo = 1, //游戏角色装配容器
}
}
BagHelper实现增加背包物品
增加背包物品需要做各种判断和逻辑处理,比如物品是否可以生成、背包是否满了、物品的创建、生成物品是否重复等等,这些判断和逻辑处理在多个类中实现。
1.通过随机帮助类BagHelper发放背包物品
//背包测试代码 随机下发闯关成功的奖励
for(int i = 0; i < 30; i++)
{
if( !BagHelper.AddItemByConfigId(unit,RandomHelper.RandomNumber(1002,1018)))
{
Log.Error("增加背包物品失败");
}
}
2.BagHelper的具体实现
namespace ET
{
public static class BagHelper
{
public static bool AddItemByConfigId(Unit unit, int configId)
{
BagComponent bagComponent = unit.GetComponent<BagComponent>();
if ( bagComponent == null)
{
return false;
}
//使用配置Id创建Item实体
//判断是否可以生成背包物品
if (!bagComponent.IsCanAddItemByConfigId(configId))
{
return false;
}
//正式增加游戏物品
return bagComponent.AddItemByConfigId(configId);
}
}
}
BagComponentSystem
using System.Collections.Generic;
namespace ET
{
public class BagComponentDestroySystem: DestroySystem<BagComponent>
{
protected override void Destroy(BagComponent self)
{
foreach (var item in self.ItemsDict.Values)
{
item?.Dispose();
}
self.ItemsDict.Clear();
self.ItemsMap.Clear();
}
}
//反序列化的生命周期函数
public class BagComponentDeserializeSystem: DeserializeSystem<BagComponent>
{
protected override void Deserialize(BagComponent self)
{
foreach (Entity entity in self.Children.Values)
{
self.AddContainer(entity as Item);
}
}
}
[FriendOf(typeof(Item))]
[FriendOf(typeof(BagComponent))]
public static class BagComponentSystem
{
/// <summary>
/// 是否达到最大负载
/// </summary>
/// <param name="self"></param>
/// <returns></returns>
public static bool IsMaxLoad(this BagComponent self)
{
return self.ItemsDict.Count == self.GetParent<Unit>().GetComponent<NumericComponent>()[NumericType.MaxBagCapacity];
}
public static bool AddContainer(this BagComponent self, Item item)
{
//在字典中是否存在Id,存在则代表有重复物品
if (self.ItemsDict.ContainsKey(item.Id))
{
return false;
}
self.ItemsDict.Add(item.Id, item);
self.ItemsMap.Add(item.Config.Type, item);
return true;
}
public static void RemoveContainer(this BagComponent self, Item item)
{
self.ItemsDict.Remove(item.Id);
self.ItemsMap.Remove(item.Config.Type, item);
}
//创建Item物品,加入到容器中并同步给客户端
public static bool AddItemByConfigId(this BagComponent self, int configId, int count = 1)
{
if ( !ItemConfigCategory.Instance.Contain(configId))
{
return false;
}
//count默认添加物品数量为1
if ( count <= 0 )
{
return false;
}
for ( int i = 0; i < count; i++ )
{
//创建出Item实体
Item newItem = ItemFactory.Create(self, configId);
if (!self.AddItem(newItem))
{
Log.Error("添加物品失败!");
newItem?.Dispose();
return false;
}
}
return true;
}
public static void GetItemListByConfigId(this BagComponent self, int configID, List<Item> list)
{
ItemConfig itemConfig = ItemConfigCategory.Instance.Get(configID);
foreach (Item goods in self.ItemsMap[itemConfig.Type])
{
if (goods.ConfigId == configID)
{
list.Add(goods);
}
}
}
public static bool IsCanAddItem(this BagComponent self, Item item)
{
if (item == null || item.IsDisposed)
{
return false;
}
if ( !ItemConfigCategory.Instance.Contain(item.ConfigId))
{
return false;
}
if (self.IsMaxLoad())
{
return false;
}
if (self.ItemsDict.ContainsKey(item.Id))
{
return false;
}
if (item.Parent == self)
{
return false;
}
return true;
}
public static bool IsCanAddItemByConfigId(this BagComponent self, int configID)
{
if (!ItemConfigCategory.Instance.Contain(configID))
{
return false;
}
//背包最大容量
if (self.IsMaxLoad())
{
return false;
}
return true;
}
public static bool IsCanAddItemList(this BagComponent self, List<Item> goodsList)
{
if (goodsList.Count <= 0)
{
return false;
}
if (self.ItemsDict.Count + goodsList.Count > self.GetParent<Unit>().GetComponent<NumericComponent>()[NumericType.MaxBagCapacity])
{
return false;
}
foreach (var item in goodsList)
{
if (item == null || item.IsDisposed)
{
return false;
}
}
return true;
}
public static bool AddItemList(this BagComponent self, List<Item> itemsList)
{
if (itemsList.Count <= 0)
{
return true;
}
foreach ( Item newItem in itemsList )
{
if (!self.AddItem(newItem) )
{
newItem?.Dispose();
return false;
}
}
return true;
}
public static bool AddItem(this BagComponent self, Item item )
{
//是否为空是否释放掉
if (item == null || item.IsDisposed)
{
Log.Error("item is null!");
return false;
}
//容量是否达到最大值
if (self.IsMaxLoad())
{
Log.Error("bag is IsMaxLoad!");
return false;
}
//装载Item
if ( !self.AddContainer(item) )
{
Log.Error("Add Container is Error!");
return false;
}
//Item的父实体是否为BagComponent
if (item.Parent != self)
{
self.AddChild(item);
}
//同步客户端背包的游戏物品
ItemUpdateNoticeHelper.SyncAddItem(self.GetParent<Unit>(), item,self.message);
return true;
}
public static void RemoveItem(this BagComponent self, Item item)
{
self.RemoveContainer(item);
ItemUpdateNoticeHelper.SyncRemoveItem(self.GetParent<Unit>(), item,self.message);
item.Dispose();
}
public static Item RemoveItemNoDispose(this BagComponent self, Item item)
{
self.RemoveContainer(item);
ItemUpdateNoticeHelper.SyncRemoveItem(self.GetParent<Unit>(), item,self.message);
return item;
}
public static bool IsItemExist(this BagComponent self, long itemId)
{
self.ItemsDict.TryGetValue(itemId, out Item item);
return item != null && !item.IsDisposed;
}
public static Item GetItemById(this BagComponent self, long itemId)
{
//获取字典中Item的实体
self.ItemsDict.TryGetValue(itemId, out Item item);
return item;
}
}
}
通过ItemFactory创建背包物品
using System;
namespace ET
{
[FriendOf(typeof(Item))]
public static class ItemFactory
{
public static Item Create(Entity parent, int configId)
{
//判断配置Id是否存在
if ( !ItemConfigCategory.Instance.Contain(configId))
{
Log.Error($"当前所创建的物品id 不存在: {configId}");
return null;
}
BagComponent bg = (BagComponent)parent;
Item item = bg.AddChild<Item, int>(configId);
//随机物品品质
item.RandomQuality();
AddComponentByItemType(item);
return item;
}
public static void AddComponentByItemType(Item item)
{
//根据物品类型加上自定义组件
switch ((ItemType)item.Config.Type)
{
case ItemType.Weapon: //武器
case ItemType.Armor: //防具
case ItemType.Ring: //戒指
{
//装备信息组件
item.AddComponent<EquipInfoComponent>();
}
break;
case ItemType.Prop:
{
}
break;
}
}
}
}
ItemSystem
namespace ET
{
public class ItemAwakeSystem: AwakeSystem<Item,int>
{
protected override void Awake(Item self,int configID)
{
self.ConfigId = configID;
}
}
public class ItemDestorySystem: DestroySystem<Item>
{
protected override void Destroy(Item self)
{
self.Quality = 0;
self.ConfigId = 0;
}
}
[FriendOf(typeof(Item))]
public static class ItemSystem
{
//将Item实体信息和身上的组件信息转化为网络消息同步给客户端
public static ItemInfo ToMessage(this Item self,bool isAllInfo = true)
{
ItemInfo itemInfo = new ItemInfo();
itemInfo.ItemUid = self.Id;
itemInfo.ItemConfigId = self.ConfigId;
itemInfo.ItemQuality = self.Quality;
if (!isAllInfo)
{
return itemInfo;
}
EquipInfoComponent equipInfoComponent = self.GetComponent<EquipInfoComponent>();
if (equipInfoComponent != null)
{
//EquipInfo填充数据,就可以和客户端同步装备信息
itemInfo.EquipInfo = equipInfoComponent.ToMessage();
}
return itemInfo;
}
}
}
ItemHelp生成对应物品的品质
namespace ET
{
[FriendOf(typeof(Item))]
public static class ItemHelper
{
//生成物品相对应的品质
public static void RandomQuality(this Item item)
{
int rate = RandomGenerator.RandomNumber(0, 10000);
if (rate< 4000)
{
item.Quality = (int)ItemQuality.General;
}
else if (rate < 7000)
{
item.Quality = (int)ItemQuality.Good;
}
else if (rate < 8500)
{
item.Quality = (int)ItemQuality.Excellent;
}
else if (rate < 9500)
{
item.Quality = (int)ItemQuality.Epic;
}
else if (rate < 10000)
{
item.Quality = (int)ItemQuality.Legend;
}
}
}
}
ItemQuality物品品质的定义
namespace ET
{
/// <summary>
/// 物品品质等级
/// </summary>
public enum ItemQuality
{
General = 0, //普通
Good = 1, //良好
Excellent = 2, //精良
Epic = 3, //史诗
Legend = 4, //传奇
}
}
ItemUpdateNoticeHelper同步客户端背包的游戏物品
using ET.Server;
namespace ET
{
[FriendOf(typeof(EquipmentsComponent))]
[FriendOf(typeof(BagComponent))]
[FriendOf(typeof(Item))]
public static class ItemUpdateNoticeHelper
{
public static void SyncAddItem(Unit unit, Item item, M2C_ItemUpdateOpInfo message)
{
//
message.ItemInfo = item.ToMessage();
//物品增加的操作指令
message.Op =(int)ItemOp.Add;
MessageHelper.SendToClient(unit, message);
}
public static void SyncRemoveItem(Unit unit, Item item,M2C_ItemUpdateOpInfo message)
{
message.ItemInfo = item.ToMessage(false);
message.Op = (int)ItemOp.Remove;
MessageHelper.SendToClient(unit, message);
}
public static void SyncAllBagItems(Unit unit)
{
//Map游戏逻辑服到游戏客户端的消息,用于同步一系列物品的信息
M2C_AllItemsList m2CAllItemsList = new M2C_AllItemsList(){ContainerType = (int)ItemContainerType.Bag};
BagComponent bagComponent = unit.GetComponent<BagComponent>();
foreach (var item in bagComponent.ItemsDict.Values)
{
m2CAllItemsList.ItemInfoList.Add(item.ToMessage());
}
MessageHelper.SendToClient(unit, m2CAllItemsList);
}
public static void SyncAllEquipItems(Unit unit)
{
//Map游戏逻辑服到游戏客户端的消息,用于同步一系列物品的信息
M2C_AllItemsList m2CAllItemsList = new M2C_AllItemsList(){ContainerType = (int)ItemContainerType.RoleInfo};;
EquipmentsComponent equipmentsComponent = unit.GetComponent<EquipmentsComponent>();
foreach (var item in equipmentsComponent.EquipItems.Values)
{
m2CAllItemsList.ItemInfoList.Add(item.ToMessage());
}
MessageHelper.SendToClient(unit, m2CAllItemsList);
}
}
}
Gate网关服务器传送Unit实体到Map游戏逻辑服
using System;
using Unity.Mathematics;
namespace ET.Server
{
//Gate网关服务器传送Unit实体到Map游戏逻辑服
[ActorMessageHandler(SceneType.Map)]
public class M2M_UnitTransferRequestHandler : AMActorRpcHandler<Scene, M2M_UnitTransferRequest, M2M_UnitTransferResponse>
{
protected override async ETTask Run(Scene scene, M2M_UnitTransferRequest request, M2M_UnitTransferResponse response)
{
UnitComponent unitComponent = scene.GetComponent<UnitComponent>();
Unit unit = MongoHelper.Deserialize<Unit>(request.Unit);
unitComponent.AddChild(unit);
unitComponent.Add(unit);
foreach (byte[] bytes in request.Entitys)
{
Entity entity = MongoHelper.Deserialize<Entity>(bytes);
unit.AddComponent(entity);
}
unit.AddComponent<MoveComponent>();
unit.AddComponent<PathfindingComponent, string>(scene.Name);
unit.Position = new float3(-10, 0, -10);
unit.AddComponent<MailBoxComponent>();
// 通知客户端开始切场景
M2C_StartSceneChange m2CStartSceneChange = new M2C_StartSceneChange() {SceneInstanceId = scene.InstanceId, SceneName = scene.Name};
MessageHelper.SendToClient(unit, m2CStartSceneChange);
// 通知客户端创建My Unit
M2C_CreateMyUnit m2CCreateUnits = new M2C_CreateMyUnit();
m2CCreateUnits.Unit = UnitHelper.CreateUnitInfo(unit);
MessageHelper.SendToClient(unit, m2CCreateUnits);
//通知客户端同步背包信息
ItemUpdateNoticeHelper.SyncAllBagItems(unit);
ItemUpdateNoticeHelper.SyncAllEquipItems(unit);
// 加入aoi
unit.AddComponent<AOIEntity, int, float3>(9 * 1000, unit.Position);
// 解锁location,可以接收发给Unit的消息
await LocationProxyComponent.Instance.UnLock(unit.Id, request.OldInstanceId, unit.InstanceId);
}
}
}
增加物品、装备物品、卸下物品、售卖物品
SessionPlayerComponent sessionPlayerComponent = session.GetComponent<SessionPlayerComponent>();
if (sessionPlayerComponent == null)
{
response.Error = ErrorCode.ERR_SessionPlayerError;
return;
}
Player player = session.GetComponent<SessionPlayerComponent>().GetMyPlayer();
Log.Debug("playerplayerplayerplayerplayer:"+player);
(bool isNewPlayer, Unit unit) = await UnitHelper.LoadUnit(player);
unit.AddComponent<UnitGateComponent, long>(player.InstanceId);
//玩家Unit上线后的初始化操作
await UnitHelper.InitUnit(unit, isNewPlayer);
#region 背包测试代码
unit.GetComponent<BagComponent>().countMax = 100;
if( !BagHelper.AddItemByConfigId(unit,RandomGenerator.RandomNumber(1002,1018)))
{
Log.Error("增加背包物品失败");
}
//装备背包物品
//---------------------------------------------------------------------------------
BagComponent bagComponent = unit.GetComponent<BagComponent>();
EquipmentsComponent equipmentsComponent = unit.GetComponent<EquipmentsComponent>();
long itemId = 0;
foreach (var item in bagComponent.ItemsDict)
{
itemId = item.Key;
Log.Debug("key:"+item.Key+",value:"+item.Value);
}
//判断所请求的背包物品是否存在
if (!bagComponent.IsItemExist(itemId))
{
response.Error = ErrorCode.ERR_ItemNotExist;
return;
}
Item bagItem = bagComponent.GetItemById(itemId);
Log.Debug("bagItem::::::::::"+bagItem);
//获取装配位置
var equipPosition = (EquipPosition)bagItem.Config.EquipPosition;
Log.Debug("equipPosition::::"+equipPosition);
//在背包组件中移除所要装备的Item
bagItem = bagComponent.RemoveItemNoDispose(bagItem);
Log.Debug("bagItem::::::::::"+bagItem);
//获取对应装配位置上面的已经装配好的一个物品的实体
Item equipItem = equipmentsComponent.GetEquipItemByPosition(equipPosition);
Log.Debug("equipItem::::::::"+equipItem);
//equipItem不为空代表当前位置已经装配了一个物品
//对于已经装配好的物品进行一个卸下操作,然后放回背包中
if (equipItem != null)
{
//传入已经装配好的物品,判断是否满足放入背包的条件
if ( !bagComponent.IsCanAddItem(equipItem) )
{
bagComponent.AddItem(bagItem);
response.Error = ErrorCode.ERR_AddBagItemError;
return;
}
//卸下对应位置的Item实体
equipItem = equipmentsComponent.UnloadEquipItemByPosition(equipPosition);
bagComponent.AddItem(equipItem);
}
//装配Item
if (!equipmentsComponent.EquipItem(bagItem))
{
response.Error = ErrorCode.ERR_EquipItemError;
return;
}
Log.Debug("装配物品成功");
//---------------------------------------------------------------------------------
//卸下背包物品
//---------------------------------------------------------------------------------
//判断背包是否装满
if (bagComponent.IsMaxLoad())
{
response.Error = ErrorCode.ERR_BagMaxLoad;
return;
}
Log.Debug("背包没有装满");
//所要卸下装配对应位置处是否有装配Item
if (!equipmentsComponent.IsEquipItemByPosition(equipPosition))
{
response.Error = ErrorCode.ERR_ItemNotExist;
return;
}
Log.Debug("所要卸下装配对应位置已经装配Item");
//传入装配的位置
//获取对应装配位置上面的已经装配好的一个物品的实体
Item unequipItem = equipmentsComponent.GetEquipItemByPosition(equipPosition);
Log.Debug("unequipItem::::::"+unequipItem);
//Item是否可以放回背包中
if ( !bagComponent.IsCanAddItem(unequipItem))
{
response.Error = ErrorCode.ERR_AddBagItemError;
return;
}
Log.Debug("Item可以放回背包中");
unequipItem = equipmentsComponent.UnloadEquipItemByPosition(equipPosition);
//把卸下的Item放入背包中
bagComponent.AddItem(unequipItem);
Log.Debug("卸下装备成功");
//---------------------------------------------------------------------------------
//售卖背包物品
//---------------------------------------------------------------------------------
foreach (var item in bagComponent.ItemsDict)
{
itemId = item.Key;
Log.Debug("key:"+item.Key+",value:"+item.Value);
}
//判定所请求需要删除的Item是否存在
if (!bagComponent.IsItemExist(itemId))
{
response.Error = ErrorCode.ERR_ItemNotExist;
return;
}
//获取对应物品实体
Item sellBagItem = bagComponent.GetItemById(itemId);
Log.Debug("sellBagItem:"+sellBagItem);
//拿到物品实体售价
int addGold = sellBagItem.Config.SellBasePrice;
Log.Debug("物品实体的售卖价格:" + addGold + "$");
//移除物品
bagComponent.RemoveItem(sellBagItem);
//售卖之前玩家的金币数量
Log.Debug("售卖之前玩家的金币数量:"+unit.GetComponent<NumericComponent>()[NumericType.Gold] + "$");
//移除后增加金币的数量
unit.GetComponent<NumericComponent>()[NumericType.Gold] += addGold;
//---------------------------------------------------------------------------------
Log.Debug("要放进缓存服中的Unit:"+unit);
//把Unit放进缓存服和服务器中
Log.Debug("售卖之后玩家的金币数量:" + unit.GetComponent<NumericComponent>()[NumericType.Gold] + "$");
UnitCacheHelper.AddOrUpdateUnitAllCache(unit);
#endregion