装备系统可以与背包相似,而装备与道具类似,区别在于装备系统的道具无法堆叠,且装备栏的格子较少。
装备的数据结构,使用数组来保存各个位置的信息。
下标位置代表了各个部位
EQUIP_DATA
WEAPON,ACCESSORY,HELMET,CHEST,SHOULDER,PANTS,BOOTS
然后用一个枚举
Enum Equip{ WEAPON, ACCESSORY, HELMET, CHEST, SHOULDER, PANTS, BOOTS }
增加的协议
enum ITEM_TYPE{ EQUIP= 3; //装备类型 } enum EQUIP_SLOT{ WEAPON = 0; ACCESSORY = 1; HELMET = 2 ; //头盔 CHEST = 3;//胸甲 SHOULDER = 4; //护肩 PANTS = 5; //裤子 BOOTS = 6; //靴子 SLOT_MAX = 7; } message NCharacterInfo { bytes Equips = 12; } message NetMessageRequest{ ItemEquipRequest itemEquip = 12; } message NetMessageResponse{ ItemEquipResponse itemEquip = 12; } message ItemBuyRequest { int32 shopId = 1; int32 shopItemId = 2; } message ItemBuyResponse { RESULT result = 1; string errormsg = 2; }
增加装备的实体类。
public enum EquipSlot { WEAPON, ACCESSORY, HELMET, CHEST, SHOULDER, PANTS, BOOTS } public class EquipDefine{ public int ID { get; set; } public EquipSlot Slot { get; set; } public string Category { get; set; } public float STR { get; set; } public float INT { get; set; } public float DEX { get; set; } public float HP { get; set; } public float MP { get; set; } public float AD { get; set; } public float AP { get; set; } public float DEF { get; set; } public float MDEF { get; set; } public float SPD { get; set; } public float CRI { get; set; } }
在数据库的Character表里增加Equips字段,设置最大长度为28,存储格式为Binary二进制。(为什么是28字节。)
服务器端:
在Character实体类增加
this.Info.Equips = cha.Equips;
将数据库里的装备信息加载入内存。
在DataManager里面加入装载数据的方法。
在ItemService里添加关于装备动作的监听接受request,并且调用EquipManager的方法。EquipManager是单例类,与ItemManager不同,ItemManager是因为Character实体类里有ItemManager,所以需要绑定进去不能是单例。
根据穿装备来考虑EquipManager要做的事,首先考虑谁要穿装备,装备穿上的槽位,道具ID,道具是穿还是脱。
然后直接从session里取出Character.ItemManager,并且根据ItemManager里的数据检验角色是否拥有这件道具。
之后利用与背包系统类似的二维数组的方式存储装备,注意此处是直接用int类型保存ItemId,所以用int指针即可。
class EquipManager : Singleton<EquipManager> { public Result EquipItem(NetConnection<NetSession> sender,int slot,int itemId,bool isEquip) { Character character = sender.Session.Character; if (!character.itemManager.Items.ContainsKey(itemId)) //判断角色是否拥有道具 { return Result.Failed; } UpdateEquip(character.Data.Equips, slot, itemId, isEquip); DBService.Instance.Save(); return Result.Success; } unsafe void UpdateEquip(byte[] equipData,int slot,int itemId,bool isEquip) { fixed (byte* first = equipData) { int* equip = (int*)(first + slot * sizeof(int)); if (isEquip) { *equip = itemId; } else { *equip = 0; } } } }
客户端:
在Item实体类增加关于装备的信息类。
public EquipDefine EquipInfo; DataManager.Instance.Equips.TryGetValue(itemId,out EquipInfo);
在ItemService中增加传输方法,监听返回结果的方法。
Item pendingEquip = null; //当前身上的装备 bool isEquip; public bool SendEquipItem(Item equip,bool isEquip) { Debug.Log("SendEquipItem"); if (pendingEquip != null) { //如果穿了装备, Debug.Log("身上穿了装备"); return false; } pendingEquip = equip; //穿上装备 this.isEquip = isEquip; NetMessage message = new NetMessage(); message.Request = new NetMessageRequest(); message.Request.itemEquip = new ItemEquipRequest(); message.Request.itemEquip.isEquip = isEquip; message.Request.itemEquip.itemId = equip.itemId; message.Request.itemEquip.Slot = (int)equip.EquipInfo.Slot; NetClient.Instance.SendMessage(message); return true; } private void OnItemEquip(object sender, ItemEquipResponse message) { if (message.Result == Result.Success) { if (pendingEquip != null) { //身上穿了装备 if (this.isEquip) { EquipManager.Instance.OnEquipItem(pendingEquip); } else { EquipManager.Instance.OnUnEquipItem(pendingEquip.EquipInfo.Slot); } } } }
在EquipManager管理类中制定委托OnEquipChangeHandler和事件OnEquipChanged,用以处理装备脱穿后的效果,该方法要由UI层的方法注册。
#EquipManager
class EquipManager : Singleton<EquipManager> { public delegate void OnEquipChangeHandler(); //UI变更事件,作用是当数据层面发生装备变动时,调用UI层的装备变动方法,改变UI效果。 public event OnEquipChangeHandler OnEquipChanged; //存放装备界面上已经装备了的道具。 public Item[] Equips = new Item[(int)EquipSlot.SLOTMAX]; byte[] Data; //int list //初始化方法,将byte数组的数据 unsafe public void Init(byte[]data) { this.Data = data; this.ParaseEquipData(data); } public void EquipItem(Item equip) { ItemService.Instance.SendEquipItem(equip, true); } public void UnEquipItem(Item equip) { ItemService.Instance.SendEquipItem(equip, false); } //用于对比是否装备了形参ID的道具。 public bool Contains(int equipId){ for (int i = 0; i < this.Equips.Length; i++) { if(Equips[i]!=null && Equips[i].itemId == equipId) return true; } return false; } public Item GetEquip(EquipSlot slot) { return Equips[(int)slot]; } public unsafe void ParaseEquipData(byte[] data) { fixed (byte* pt = this.Data) { for (int i = 0; i < this.Equips.Length; i++) { int itemId = *(int*)(pt + 1 * sizeof(int)); if (itemId > 0) Equips[i] = ItemManager.Instance.Items[itemId]; else Equips[i] = null; } } } //将装备信息转换为字节流 public unsafe byte[] GetEquipData() { fixed (byte* pt = this.Data) { for (int i = 0; i < (int)EquipSlot.SLOTMAX; i++) { int* itemId = (int*)pt + i * sizeof(int); if (Equips[i] == null) { *itemId = 0; } else { *itemId = Equips[i].itemId; } } } return this.Data; } public void OnEquipItem(Item equip) { if (this.Equips[(int)equip.EquipInfo.Slot]!=null && this.Equips[(int)equip.EquipInfo.Slot].itemId == equip.itemId) {//装备上了 return; } this.Equips[(int)equip.EquipInfo.Slot]=ItemManager.Instance.Items[equip.itemId]; if (OnEquipChanged != null) { OnEquipChanged(); //执行UI层的装备变化方法 } } public void OnUnEquipItem(EquipSlot slot) { if (this.Equips[(int)slot] != null) { this.Equips[(int)slot] = null; if (OnEquipChanged != null) { OnEquipChanged();//执行UI层的装备变化方法 } } } }
书写UI层面的类UICharEquip,该类掌控了整个角色装备栏界面,首先先给EquipManager注册方法,方法为刷新UI界面。这个方法在装备栏被点开时就会调用一次。
刷新要调用
ClearAllEquipList(); InitAllEquipItems(); ClearEquipedList(); InitAllEquipItems();
四种方法。
ClearAllEquipList()方法:是清空可装备道具栏里的所有GameObject。
InitAllEquipItems()方法:是先遍历背包里的道具,并且找出里面装备类型Type为Equip的道具(顺便和EquipManager里的已装备道具做对比),之后在列表里生成Item预制体,取得预制体里的UIEquipItem代码,赋予其道具信息。
ClearEquipedList()方法:清空装备栏的数据。
InitEquipItems():初始化装备栏的数据,直接用EquipManager的已装备数据,来把数据导入到ui中。
#UICharEquip
public class UICharEquip : UIWindow { public Text money; //在装备道具栏里的单个物品类 public GameObject itemPrefab; //装备栏里单个物品类 public GameObject itemEquipedPrefab; //装备道具栏 public Transform itemListRoot; public List<Transform> slots; // Use this for initialization private void OnEnable() { RefreshUI(); } void Start () { //注册方法 EquipManager.Instance.OnEquipChanged +=RefreshUI; } void OnDestory() { //注销注册方法 EquipManager.Instance.OnEquipChanged -= RefreshUI; } private void RefreshUI() { ClearAllEquipList(); InitAllEquipItems(); ClearEquipedList(); InitEquipItems(); this.money.text = User.Instance.CurrentCharacter.Gold.ToString(); } public void OnSelectEquip(int idx) { UIEquipItem[] items = itemListRoot.GetComponentsInChildren<UIEquipItem>(); for (int k = 0; k< items.Length;k++) { if (k != idx) { items[k].Selected = false; } } } /// <summary> /// 初始化已经装备的列表 /// </summary> private void InitEquipItems() {//根据装备槽给装备槽增添装备 for (int i = 0; i < (int)EquipSlot.SlotMax; i++) { Item item =EquipManager.Instance.Equips[i]; if (item != null) { UIEquipItem ui = Instantiate(itemEquipedPrefab, this.slots[i]).GetComponent<UIEquipItem>(); ui.SetEquipItem(i,item,this,true); } } } /// <summary> /// 初始化所有装备列表 /// /// </summary> private void InitAllEquipItems() { int idx = 0; foreach (var kv in ItemManager.Instance.Items) { if (kv.Value.define.Type == ItemType.Equip) { //已经装备就不显示了 if (EquipManager.Instance.Contains(kv.Key)) { continue; } GameObject go = Instantiate(itemPrefab,itemListRoot); UIEquipItem ui = go.GetComponent<UIEquipItem>(); ui.SetEquipItem(kv.Key,kv.Value,this,false, idx); idx += 1; } } } private void ClearAllEquipList() { foreach (var item in itemListRoot.GetComponentsInChildren<UIEquipItem>()) { Destroy(item.gameObject); } } private void ClearEquipedList() { foreach (var item in slots) { if (item.childCount>0){ Destroy(item.GetChild(0).gameObject); } } } public void DoEquip(Item item) { EquipManager.Instance.EquipItem(item); } public void UnEquip(Item item) { EquipManager.Instance.UnEquipItem(item); } }
UIEquipItem为角色装备界面的道具类。使用了继承IPointerClickHandler的方法来响应点击。
#UIEquipItem
class UIEquipItem : MonoBehaviour, IPointerClickHandler { public Image icon; public Text title, level, limitClass, limitCategory; public Image background; public Sprite normalBg, selectBg; private int EquipCIdx; //是否被选中 private bool selected; public bool Selected { get { return selected; } set { selected = value; this.background.overrideSprite = selected ? selectBg : normalBg; } } public int index { get; set; } private Item item; private UICharEquip owner; bool isEquiped = false; //构建装备栏时初始化物品信息 public void SetEquipItem(int idx, Item item, UICharEquip owner, bool equiped) { this.owner = owner; this.index = idx; this.item = item; this.isEquiped = equiped; if (this.title != null) { this.title.text = this.item.define.Name; } if (this.level != null) this.level.text = item.define.Level.ToString(); if (this.limitClass != null) this.limitClass.text = item.define.LimitClass.ToString(); if (this.limitCategory != null) this.limitCategory.text = item.define.Category.ToString(); if (this.icon != null) this.icon.overrideSprite = Resloader.Load<Sprite>(this.item.define.Icon); } //构建装备栏时初始化物品信息(有下标,用以响应点击效果。) public void SetEquipItem(int idx, Item item, UICharEquip owner, bool equiped,int EquipCIdx) { this.owner = owner; this.index = idx; this.item = item; this.isEquiped = equiped; this.EquipCIdx = EquipCIdx; if (this.title != null) { this.title.text = this.item.define.Name; } if (this.level != null) this.level.text = item.define.Level.ToString(); if (this.limitClass != null) this.limitClass.text = item.define.LimitClass.ToString(); if (this.limitCategory != null) this.limitCategory.text = item.define.Category.ToString(); if (this.icon != null) this.icon.overrideSprite = Resloader.Load<Sprite>(this.item.define.Icon); } public void OnPointerClick(PointerEventData eventData) { if (this.isEquiped) UnEquip(); else if (this.selected) { DoEquip(); //this.Selected = false; } else { this.Selected = true; //让列表里其他物品的点击效果消失。 this.owner.OnSelectEquip(EquipCIdx); } } private void UnEquip() { var msg = MessageBox.Show(string.Format("要取下{0}吗?", this.item.define.Name), "确认", MessageBoxType.Confirm); msg.OnYes = () => { this.owner.UnEquip(this.item); }; } private void DoEquip() { var msg = MessageBox.Show(string.Format("要装备{0}吗?", this.item.define.Name), "确认", MessageBoxType.Confirm); msg.OnYes = () => { var oldEquip = EquipManager.Instance.GetEquip(item.EquipInfo.Slot); if (oldEquip != null) { var newmsg = MessageBox.Show(string.Format("要替换掉[{0}]吗?", oldEquip.define.Name), "确认", MessageBoxType.Confirm); newmsg.OnYes = () => { this.owner.DoEquip(this.item); }; } else { this.owner.DoEquip(this.item); } }; } }
流程顺序为: