设计

基本实现

数据库中只存储物品id、物品数量;客户端本地二进制式存储cfg,来显示物品详情;服务端本地二进制式存储cfg,来确定物品效果。

整体流程如下:

  1. 客户端登录时,获取到背包信息(物品id、物品数量)。
  2. 客户端打开背包时,根据内存中的背包信息 + 本地cfg,来展示物品。
  3. 客户端使用物品时,发送请求到服务端并让服务端在数据库验证数量。
  4. 如果数量不足,则失败;
  5. 如果数量足够,则根据服务端本地cfg来计算出账号收益,更新数据库后返回结果给客户端。
  6. 客户端获得返回结果,刷新内存数据。

优化

  • 虚拟列表
  • 对象池
  • 异步加载

基础实现

Scroller View

Viewport,遮罩,决定能看到的内容。(视野)

Content,真实内容。(履带)

设置格子锚点到左上角。

数据类型

主要分为3块。

  • BagMgr,用来获取道具信息。
  • BagItem,背包格子类。
  • BagPanel,背包页面逻辑。

显式获取

要计算出 起始显示的格子索引值、结束显示的格子索引值:

=====================================================================

可视范围的起始位置Y / 一个格子的高 = 可视范围中,起始显示的是哪一行(向下取整),

可视范围中,起始显示的是哪一行 * 一行有多少格子 = 起始显示的格子索引值

用同样的方法,根据可视范围的结束位置Y也可以计算出结束显示的格子索引值

=====================================================================

将上面计算出来的索引值取下来记录,放到 Dictionary<int索引,格子对象> 里。这些就是画面正要显示的格子们。

格子位置确定

物品位置确定。通过简单计算获得:

1
2
// index是物品下标, oneRowColumns是一行几个, childWidth是格子宽度, paddingWidth是格子横向间隔
go.transform.localPosition = new Vector3((index % oneRowColumns) * (childWidth + paddingWidth), - (index / oneRowColumns) * (childHeight + paddingHeight),0);

未显示就不加载的虚拟列表

显式获取 + 格子位置确定 就可以实现。

优化

在上面未显示就不加载的虚拟列表的实现基础上,进行优化。

对象池

对物品进行对象池管理,加载从池中取,销毁就返回池。

去掉加载过、现在不显示的物品

在update中调用刷新物品方法,

计算显示下标 => 根据本次下标范围与上次记录下来的下标范围,算出哪些不再显示 => 把他们放回对象池。

异步加载

异步加载的过程放入协程中执行。

简单地说就是一帧不加载完物品,至少是2帧,异步加载成功后触发回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//异步加载协程
private IEnumerator AsyncLoad<T>(string path, UnityAction<T> callBack) where T : Object
{
ResourceRequest rr = Resources.LoadAsync<T>(path);
yield return rr;

if (rr.asset is GameObject)
{
callBack?.Invoke(GameObject.Instantiate<T>(rr.asset as T));
}
else
{
callBack?.Invoke(rr.asset as T);
}
}

解决方案

demo已上传github

项目使用

实际项目使用更为复杂。这里只展示一下实现了什么。

  • 物品数量数据库存储:使用字符串标记,规则物品id:数量类似于1:2#3:1#5:10
  • 物品选择状态显示:简单业务实现。
  • 服务端物品检测:数据处理使用服务端本地cfg读表,避免客户端作弊。
  • 二进制读表:luban 二进制读表,在加载流程中读取物品配置。
  • 优化:虚拟列表 + 对象池。