游戏中的排行榜,商城等可滚动显示的界面通常要显示海量的数据,如果直接把所有数据对应的item实例化到Grid列表下面,作为一名游戏开发者,这么做是极为不明智的,通常用的技巧就是重用item。打个比方:如果有千条数据需要显示在界面中,但界面只能一次显示3条数据信息,那么重用item的思想只需要最多最开始实例化出4个item加入带Grid列表下面即可,而不是实例化出千个item,无疑大大提高了游戏性能。
接下来我说一下重用item的基本思想:当滚动列表向上移动时(也就是鼠标或触屏向上移动,数据向下显示),就把超出屏幕上部的item移动到列表末尾位置并重置数据。当滚动列表向下移动时(也就是鼠标或触屏向下移动,数据向上显示),就把超出屏幕下部的item移动到列表开头。思想说完了,接下来开始正式开发Demo了。
一、制作一个scrollview滚动界面
这里我是比着NGUI的官方Demo(下载NGUI插件并导入untiy,然后会有一个Example7-Scroll View(Panel).unity)制作的。如下图所示,官方Demo是一个可以横向滚动的滚动列表,而我要做成一个竖向滚动的列表(一般排行榜、商城都是竖向显示数据的哈哈哈,再来自己动手做一遍效果比较好)。
制作完scrollview后,在grid下创建一个我们想要的Item,不要忘记添加BosCollider和UIDragScrollView,BosCollider要勾选IsTrigger。并制作成预制体。制作完后运行场景,可以看到一个item,并且可以在ScrollView范围内拖拽.。
下面是我做的效果图:
二、编写脚本
2.1 编写数据类脚本ItemData,代码如下:
///
/// item数据类
///
public class ItemData {
///
/// item的索引
///
public int index;
//item的价格
public string money;
public ItemData()
{
}
public ItemData(int index,string money)
{
this.index = index;
this.money = money;
}
}
这个类记录了列表中每个item的初始数据,成员字段有两个,一个是索引index,一个是物品显示的价格money(item的价格,为了方便展示效果)。
2.2编写脚本Item,代码如下:
///
/// 该类挂载再item预设上,用于显示每条item的数据
///
public class Item : MonoBehaviour {
[SerializeField]
private UILabel label;
public ItemData data;
// Use this for initialization
void Start () {
//label = transform.Find("MoneyLabel").GetComponent();
}
// Update is called once per frame
void Update () {
}
///
/// 显示该item的价钱
///
///
public void setMoney(ItemData itemData)
{
this.data = itemData;
label.text = data.money;
}
}
这里声明了一个ItemData类的引用,记录当前的item显示哪条数据,label显示该数据的价格数据。这个脚本要放在item预设体上。
2.3编写核心脚本ScrollViewManager,代码如下:
public class ScrollViewManager : MonoBehaviour {
//保存所有的item物体以及列表中数据
List itemDataList = new List();
List- itemList = new List
- ();
UIScrollView sv;
UIGrid grid;
//用于判断scrollView的移动方向,记录下位置
float svLastPos = 0;
//最大和最小y坐标,判断item移出屏幕的最大和最小距离
float maxHeight;
float minHeight;
// Use this for initialization
void Start () {
sv = transform.GetComponent
();
grid = transform.Find("Grid").GetComponent();
sv.transform.localPosition = new Vector3(0,0,0);
//100个数据初始化
for(int i = 0; i < 100; i++)
{
itemDataList.Add(new ItemData(i, string.Format("${0}", i+1)));
}
//计算要生成多少个item对象,用整个ScrollView的高度除以每个item的高度得到屏幕中一共能显示多少个item,然后加上1个以避免item不足的情况出现。
//面板的大小——可以是剪切矩形,也可以是屏幕尺寸。
Vector2 viewsize = transform.GetComponent().GetViewSize();
//设置item数量,数量为屏幕最多可显示item数量+1
int count = (int)(viewsize.y / grid.cellHeight + 1);
Debug.Log(count);
//for循环:实例化出item+1数量的item实体,初始化数据,并设置数据后放在Grid下边
for(int i = 0; i < count; i++)
{
//终止条件:如果itemData总数小于i,不在实例化item
if (itemDataList.Count <= i)
break;
GameObject go = Resources.Load("Prefab/Item") as GameObject;
GameObject obj = NGUITools.AddChild(grid.gameObject, go);
Item itemAdd = obj.GetComponent- ();
itemAdd.setMoney(itemDataList[i]);
itemList.Add(itemAdd);
}
//在下一次更新中重新定位子节点。
grid.repositionNow = true;
//重新计算网格中所有元素的位置,按字母顺序排序。
grid.Reposition();
svLastPos = grid.transform.localPosition.y;
//计算屏幕的最大高度和最小高度(这里的计算随count+1或+2有所改变)
//maxHeight = maxHeight = viewsize.y / 2 + grid.cellHeight / 2;
maxHeight = grid.cellHeight;
minHeight = -maxHeight;
Debug.Log(sv.transform.localPosition.y);
}
// Update is called once per frame
void Update () {
//判断列表是否移动,用现在的位置减之前记录的位置,如果移动了,重置item
float moveDis = sv.transform.localPosition.y - svLastPos;
Debug.Log(sv.transform.localPosition.y);
if (Mathf.Abs(moveDis) > 0.05f)
{
bool isup = moveDis > 0;
//如果面板向上滚动
if (isup)
{
//判断item列表中第0个元素是否超出坐标的最大范围
//还要判断数据列表中是否超出范围,如果超出范围说明没有数据了,不需要重置item位置了。
//itemList[0].transform.localPosition.y必须加上,不然就会直接拖到底了。这个参数在while循环最后y轴每次减去一个item的高度值,所以一直呈负数增长
while (itemList[0].transform.localPosition.y + sv.transform.localPosition.y > maxHeight &&
itemList[itemList.Count-1].data.index < itemDataList.Count-1)
{
//移动item,首先将item中的数据设置为目标数据,这里直接取item列表最后一个元素获取到数据的index并且+1就是目标数据
Item item = itemList[0];
item.setMoney(itemDataList[itemList[itemList.Count-1].data.index + 1]);
//将当前item添加到列表末尾同时删除列表第一个元素,最后设置item的坐标。
itemList.Add(item);
itemList.RemoveAt(0);
//这要注意!!!!!!是-2
item.transform.localPosition = itemList[itemList.Count-2].transform.localPosition -
new Vector3(0, grid.cellHeight, 0);
}
}
//如果面板向下滚动
else
{
//判断item列表中最后一个元素是否低于坐标的最小范围
//还要判断数据列表中是否超出范围,如果超出范围说明没有数据了,不需要重置item位置了。
while (itemList[itemList.Count-1].transform.localPosition.y + sv.transform.localPosition.y < minHeight &&
itemList[0].data.index >0)
{
// //移动item,首先将item中的数据设置为目标数据,这里直接取item列表第一个元素获取到数据的index并且-1就是目标数据
Item item = itemList[itemList.Count-1];
item.setMoney(itemDataList[itemList[0].data.index-1]);
//将当前item插入到列表开头同时删除列表最后一个元素,最后设置item的坐标。
itemList.Insert(0, item);
itemList.RemoveAt(itemList.Count-1);
item.transform.localPosition = itemList[1].transform.localPosition +
new Vector3(0, grid.cellHeight, 0);
}
}
}
svLastPos = sv.transform.localPosition.y;
}
}
虽然我在代码中写了大量详细的注释,但是我还是要写一下具体过程,这块是最核心的代码。
1.首先定义了两个集合List,分别存储item物体(要实例化到Grid列表下的,如果界面一次最多显示3个item,那么就最多实例化出4个即可)和列表中所有要显示的数据(有100条就存100条)。
2.定义了变量svLastPos用来记录最后一次scrollview的y轴相对位置坐标(上下拖动会发生变化),这是为了后面判断移动方向考虑的,UIScrollView的API中并没有判断移动方向的相关接口。maxHeight和minHeight用来判断移出屏幕的最大值和最小值距离。
3.Start方法中:
(1)首先初始化数据,假设有100条数据要显示。并把数据对象赋值后添加到集合中。
(2)计算要生成多少个item对象,用整个ScrollView的高度除以每个Item的高度得到界面中最多能显示几个item,然后加上1个作为显示用的预设,这样做才不会拖动显示时显得太突兀或不符合常理。
(3)for循环:实例化出item+1个实体,初始化数据,添加到itemList集合中,并作为Grid列表的子对象。注意终止条件,如果界面可显示数据量为3,但是实际只有两条数据,那么实例化完成后就直接跳出循环了。
(4)计算屏幕大小的最大和最小高度。高度为面板界面高度的一半加上单个item的一半。
4.Update方法中:
(1)首先判断列表是否移动了,用scrollview现在的位置减去之前记录的最后的scrollview位置,如果移动了,并且移动距离的绝对值大于0.05,就进行重置item的逻辑。
(2)接下来判断移动方向,如果面板向上滚动,判断item列表中第0个元素是否超出坐标的最大范围。还要判断数据列表中是否超出范围,如果超出范围说明没有数据了,不需要重置item位置了。itemList[0].transform.localPosition.y必须加上,不然就会直接拖到底了。这个参数在while循环最后y轴每次减去一个item的高度值,所以一直呈负数增长。如果要移动item,首先将item中的数据设置为目标数据,这里直接取item列表最后一个元素获取到数据的index并且+1就是目标数据,然后将当前item添加到列表末尾同时删除列表第一个元素,最后设置item的坐标,这里要注意,是itemList[itemList.Count-2]的局部位置,不是itemList[itemList.Count-1,因为已经把该item添加到集合末尾了。向上移动和向下移动的逻辑正好相反,这里不再复述。
至此,优化海量数据的方案已经完成,希望能够给一些爱好者提供一点思路和帮助。