栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

背包系统步骤

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

背包系统步骤

数据存储的选择:尽量不要用多个字段存储多个格子,也不要用字符串,存储空间会变大。结论是使用二进制数据存储。

  • 背包系统结构

关于背包 客户端与服务器端需要传输的结构,

//协议
message NBagInfo{
	int32 Unlocked = 1;   --解锁到第几个格子
		bytes Items = 2;    --背包里面有多少道具
}

message BagSaveRequest
{
	NBagInfo BagInfo = 1;
}

message BagSaveResponse
{
RESULT result = 1;
string errormsg = 2;
}

利用unlock参数来决定Items长度。

服务器端

创建背包相关表CharacterBag,

表结构为:

ID | int32

Items | Binary

Unlocked | Int

创建与Charater表的关联,关联关系为1比1,一个角色只能有一个背包。(多个背包页如何管理?)

在Character类初始化背包。

OnGameEnter→AddCharacter→Character→初始化背包。

this.Info.Bag = new NBagInfo();
this.Info.Bag.Items = this.Data.Bag.Items;
this.Info.Bag.Unlocked = this.Data.Bag.Unlocked;

创建背包接收监听,

public class BagService : Singleton
    {
        public BagService (){

            MessageDistributer>.Instance.Subscribe(this.OnBagSave);
        }
    public void Init() { 
        
        }

        void onBagSave(NetConnection sender, BagSaveRequest request) {
            Character character = sender.Session.Character;
            Log.InfoFormat("BagSaveRequest:character:{0},Unlocked{1}", character.Id, request.BagInfo.Unlocked) ;

            if (request.BagInfo != null) {
                character.Data.Bag.Items = request.BagInfo.Items;
                DBService.Instance.Save();
            }
        }

在用户创建角色时给角色增加背包,在UserService→onCreateCharacter 背包初始化。

var bag = new TCharacterBag();
bag.Owner = character;
bag.Items = new byte[0];
bag.Unlocked = 20; //解锁20个格子
character.Bag = DBService.Instance.Entities.CharacterBags.Add(bag);

客户端

给客户端的物品Item的Model类增加配置表类ItemDefine,一旦产生新道具,就把配置赋予他,以便随时能得到新增加的道具的定义。

  • #Item类

    public class Item {
            public int Id;
            public int Count;
            public ItemDefine define;
    
            public Item(NItemInfo item) {
                this.Id = item.Id;
                this.Count = item.Count;
                this.define = DataManager.Instance.Items[item.Id];
            }
        }
    

之后设计BagItem的Model类,思考要如何设计比较好?

  • ~~关于BagItem思考

    考虑到背包有格子这种东西,我本来思索应该会用字典来存储Item,然后Key值为格子的位置,然后应该会用事件来加载人物所持有的数据,在UserService处创建一个网络连接,在登陆时获取到人物背包数据。

    BagItem里面应该就是存储当前登陆人员的信息,物品在背包里的格子位置,物品Item的信息。

首先制作显示用的UI,创建一个TabView来管理背包页面,创建TabButton来管理单个背包按钮。将一个个背包页按钮抽象成TabButton,并且给自己添加按键监听,点击时触发TabButton的方法,下标传递给TabView做切换。

简单来说就是各做各的事情,Button管理自己的触发,和点击后图片的变换,TabView管理各个页面的切换。

TabView采用了IEnumerator Start() {}来实行他的生命周期,关于IEnumerator 。

  • #TabView类

    public class BagTabView : MonoBehaviour {
    
    	public BagTabButton[] ButtomList;
    	public GameObject[] BagViewList;
    
    	public int index = -1;
    	// Use this for initialization
    	IEnumerator Start() {
    		for (int i = 0; i < ButtomList.Length; i++) {
    			ButtomList[i].tabView = this;
    			ButtomList[i].tabIndex = i;
    		}
    
    		yield return new WaitForEndOfframe();
    		SelectTab(0);
    	}
    
    	internal void SelectTab(int idx)
    	{
    		if (index != idx) {
    			for (int i = 0; i < ButtomList.Length; i++) {
    				ButtomList[i].Select(i == idx);			//触发换页图片
    				BagViewList[i].SetActive(i == idx);		//触发换页效果
    
    			}
    		}
    
    	}
    }
    
  • #TabButton类

    public class BagTabButton : MonoBehaviour
     {
        private Image tabImage;
        public Sprite activeImage;
        public Sprite normalImage;
    
        public BagTabView tabView;
    
        public int tabIndex;  //下标
    
        public bool selected = false;
    
        public void Start()
        {
            tabImage = this.GetComponent();
            normalImage = tabImage.sprite;
            this.GetComponent

设计处理格子图片的类UIIconItem类,放置于每个格子的Image组件里。

  • #UIIconItem

    class UIIconItem : MonoBehaviour
    {
        public Image mainImage;
        public Image secondImage;
        public Text mainText;
    
        
        internal void SetMainIcon(string icon, string text)
        {
            this.mainImage.overrideSprite = Resloader.Load(icon);
            this.mainText.text = text;
    
        }
    }
    

构建BagItem实体**(难点),该类主要采用Struct结构体类型,里面使用了结构布局[StructLayout(LayoutKind.Sequential,Pack=1)],**代表了结构体在内存中的存储格式。

  • 结构体排序问题

    [StructLayout(LayoutKind.Sequential) ]

    Sequential :是指顺序布局,比如

    struct S1{
    int a ;
    int b ;
    }
    

    那么默认情况下在内存是先排a,再排b。

    也就是如果能取到a的地址,和b的地址,则相差一个int类型的长度,4字节。

  • 为什么用结构体而不是类?

    使用结构体就是值类型,而类是引用类型。值类型更方便用'='互相赋值。做值的交换。

其中ItemId与Count 使用了ushort,该类型占2个字节,数据范围在0 ~ 65,535。若不够大小存储可换成int类型。

  • #BagItem

    [StructLayout(LayoutKind.Sequential,Pack =1)]
        struct BagItem 
        {
            public ushort ItemId;
            public ushort Count;
    
            public static BagItem zero = new BagItem {ItemId=0,Count=0};
    
            public BagItem(int ItemId,int count) {
                this.ItemId = (ushort)ItemId;
                this.Count = (ushort)count; 
            }
    //使用Operatior == 与Operatior !=重新定义符号的意义。
            public static bool operator ==(BagItem lhs, BagItem rhs) { //
                return lhs.ItemId == rhs.ItemId && lhs.Count == rhs.Count;
            }
            public static bool operator !=(BagItem lhs, BagItem rhs)
            {
                return !(lhs == rhs);
            }
            public override bool Equals(object obj)
            {
                if (obj is BagItem) {
                    return Equals((BagItem)obj);
                } 
                return false;
            }
    
            public bool Equals(BagItem other) {
                return this == other;
            }
            public override int GetHashCode()
            {
                return ItemId.GetHashCode() *(Count.GetHashCode() <<2);
            }
        }
    

创建管理用的BagManager**(难点)**,单例类,初始化方法为Init(),

在UserService→onGameEnter 角色进入游戏时执行,并传入服务器发送过来的道具数据至Init()方法。

初始化 通过客户端接收并传入NBagInfo参数→通过BagItem的构造函数调用Analyze方法将NBagInfo参数解析成结构体数组(该方法是将字节数组解析成结构体数组)

  • Analyze方法解析(将字节流数组转成BagItem[]):

    public BagItem[] Items;
    public unsafe void Analyze(byte[] items)
            {
                fixed (byte* pt = items) //指向items的指针(items数组的首地址)
                {
                    for (int i = 0; i < this.Unlocked; i++)
                    {   //指的是items的首地址+i个BagItem的存储单元的偏移量。
    										//得到的item指针存储了经过i个BagItem存储单元的地址。
    										//可以理解为指向了第i个BagItem,存储了第i个BagItem的首地址。
                        BagItem* item = (BagItem*)(pt + (i * sizeof(BagItem)));
    										//将该值赋予给本地数组Items[i]
                        Items[i] = *item;
                    }
                } 
            }
    

  • GetBagInfo方法解析(构建字节流)

    private NBagInfo info; 
    public unsafe NBagInfo GetBagInfo()
            {
                fixed (byte* pt = info.Items)
                {
                    for (int i = 0; i < this.Unlocked; i++) {
    									//数组的首地址+第i个物件的存储大小=第i个物体在数组中的位置。
                        BagItem* item = (BagItem*)(pt + i * sizeof(BagItem));
    										//将堆里该地址的值改为Items[i]
                        *item = Items[i];
                    }
                }
                return this.info;
            }
    

之后利用Reset()方法对背包内大于物品叠加上限的道具进行拆分。

  • #BagManager

    public class BagManager : Singleton
        {
            public int Unlocked;
            public BagItem[] Items;
            private NBagInfo info;
    
            internal void Init(NBagInfo info)
            {
                this.info = info;
                Unlocked = info.Unlocked;
                Items = new BagItem[Unlocked]; //开辟背包格的存储。
                if (info.Items != null && info.Items.Length >= Unlocked)
                {
                    Analyze(info.Items);
                }
                else
                {
                    unsafe
                    {
                        this.info.Items = new byte[sizeof(BagItem) * Unlocked];
                    }
                    Reset();
                }
            }
            public void Reset()
            {
                int i = 0;
                foreach (var kv in ItemManager.Instance.Items) {
                    if (kv.Value.Count <= kv.Value.define.StackLimit)
                    {
                        this.Items[i].ItemId = (ushort)kv.Key;
                        this.Items[i].ItemId = (ushort)kv.Value.Count;
                    }
                    else {
                        int count = kv.Value.Count;
                        while (count> kv.Value.define.StackLimit) {
                            this.Items[i].ItemId = (ushort)kv.Key;
                            this.Items[i].Count = (ushort)kv.Value.define.StackLimit;
                            i++;
                            count -= kv.Value.define.StackLimit;
                        }
                        this.Items[i].ItemId = (ushort)kv.Key;
                        this.Items[i].Count = (ushort)count;
    
                    }
                    i++;
                }
            }
    
            public unsafe void Analyze(byte[] items)
            {
    
                fixed (byte* pt = items)
                {
                    for (int i = 0; i < this.Unlocked; i++)
                    {
                        BagItem* item = (BagItem*)(pt + (i * sizeof(BagItem)));
                        Items[i] = *item;
                    }
                }
    
            }
    
            public unsafe NBagInfo GetBagInfo()
            {
                fixed (byte* pt = info.Items)
                {
                    for (int i = 0; i < this.Unlocked; i++) {
                        BagItem* item = (BagItem*)(pt + i * sizeof(BagItem));
                        *item = Items[i];
                    }
                }
                return this.info;
            }
    
        }
    

UIBag

而后就是要在UIBag类里进行BagManager的初始化操作,BagManager初始化的操作需要使用协程,将初始化方法InitBags描述成IEnumerator***函数迭代器。***

该初始化的主要目的是将ItemManager里的Items数据逐个取出,并且在背包里Instantiate相应多的背包格子,然后再根据item里的define物品信息加载图标。

  • #UIBag

    public class UIBag : UIWindow {
        public Text money;
    
        public Transform[] pages;//页数
    
        public GameObject bagItem; //格子的数据
    
        List slots;//格子
    
    	// Use this for initialization
    	void Start () {
            if (slots == null) {
                slots = new List();
            }
            for (int page = 0; page < this.pages.Length; page++) {
                slots.AddRange(this.pages[page].GetComponentsInChildren(true)); //获取该页所有active状态的Image组件。
    
            }
            StartCoroutine(InitBags()); //启动背包管理器初始化协程。
        }
    
        public void SetTitle(string title) {
            this.money.text = User.Instance.CurrentCharacter.Id.ToString();
        }
    
        IEnumerator InitBags() {
    
            for (int i = 0; i < BagManager.Instance.Items.Length; i++) {  //根据物品遍历。
                var item = BagManager.Instance.Items[i];
                if (item.ItemId > 0) { //itemId不为空
                    GameObject go = Instantiate(bagItem, slots[i].transform);//在格子上生成物品的图标。
                    var ui = go.GetComponent();
                    var def = ItemManager.Instance.Items[item.ItemId].define;
                    ui.SetMainIcon(def.Icon,item.Count.ToString());
    
                } 
            }
            for (int i = BagManager.Instance.Items.Length; i < slots.Count; i++) { //
                slots[i].color = Color.gray;
            }
            yield return null;
        }
        public void onReset()
        {
            BagManager.Instance.Reset();
        }
    }
    

 而后还有关于背包UI的优化:

        具体有以下几点:
1,使用对象池重复利用背包里的Item对象。

2,动静分离,分离需要滑动的ScrollView至单独的Canvas中。

3,调整背包ScrollView里Content的pivot点,然后不使用Unity的自动调节组件,手动摆放Item,其中:列=该格子的列数*格子宽度,行=该格子的行数*格子高度

4,滚动时,监控content的y轴变动(如果他是往下滚动的),并通过ViewPort的长度和格子的长宽,来获取哪些格子应该被显示,哪些被隐藏。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/288999.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号