【U3D/UGUI】5.优化滚动panel(10w个物品)并泛化

自我介绍

广东双非一本的大三小白,计科专业,想在制作毕设前夯实基础,毕设做出一款属于自己的游戏!

优化滚动panel

理论基础:

  • 背包中显示(可视范围内)有多少个物品就有多少个gameobject
  • 利用位置关系计算并显示出当前格子信息

效果如下:

【U3D/UGUI】5.优化滚动panel(10w个物品)并泛化_第1张图片

要用到前面的UI框架和单例

搭建场景

非常简单,背包面板只是一个简单的 ScrowView,做成预制体

【U3D/UGUI】5.优化滚动panel(10w个物品)并泛化_第2张图片

当背包里的格子也要做成预制体,格子身上也应该要挂载一个类主要用于初始化信息(后期扩展可以鼠标移上去展示信息),这里的格子只有简单的数字当下标

格子主要放在Content里面

处理格子

每个格子都有基本的信息,我们抽象出一个简单的model

Item.cs

public class Item
{
        // 道具信息
    public int id;
    public int num;
}

格子信息有了,怎么让他展示在UI上呢,我们先抽象出一个接口供给UIItem必须继承的(抽象出接口是以后有用的)

IItemBase.cs

public interface IItemBase<T>
{
        // 该接口 作为 格子对象 必须继承的类 它用于实现初始化格子的方法
    void InitInfo(T info);
}

要明确:

  • 每个格子都是一个UI,所以要继承前面UI框架里的 BasePanel
  • 每个格子必须实现接口 IItemBase

现在开始实现格子类

BagItem.cs

using UnityEngine.UI;

/// 
/// 格子类对象  他是放在背包里的一个一个的道具格子
/// 主要用来显示 单组道具信息的
/// 
public class BagItem : BasePanel, IItemBase<Item> {
     

    /// 
    /// 这个方法 是用于初始化 道具格子信息
    /// 
	public void InitInfo(Item info)
    {
     
        //读取道具表 根据表中数据 来更新信息 更新图标 更新名字
        //更新道具数量
        GetControl<Text>("txtNum").text = info.num.ToString();
    }
}

ScrowView通用类

格子有了,现在做背包,但是不只是背包,其实很多地方都有需要用到

所以先抽象出通用的一个 类(除了背包,计分板也有可能要用到)

CustomSV.cs

using System.Collections.Generic;
using UnityEngine;

/// 
/// 自定义sv类 用于节约性能 通过缓存池创建复用对象
/// 
/// 代表的 数据来源类
/// 代表的 格子类
public class CustomSV<T, K> where K : IItemBase<T>
{
     
    private RectTransform content;      //履带对象  需要通过他得到可视范围的位置  还要把动态创建的格子设置为他的子对象
    private int viewPortH;              //可视范围高 

    private Dictionary<int, GameObject> nowShowItems = new Dictionary<int, GameObject>();   //当前显示着的格子对象

    private List<T> items;      //数据来源

    //记录上一次显示的索引范围
    private int oldMinIndex = -1;
    private int oldMaxIndex = -1;

    //格子的间隔宽高
    private int itemW;
    private int itemH;

    private int col;    //格子的列数

    private string itemResName;     //预设体资源的路径

    //初始化工作可以放到构造函数内,不过要注意调用顺序

    // 初始化格子资源路径
    public void InitItemResName(string name)
    {
     
        itemResName = name;
    }

    /// 
    /// 初始化Content父对象 以及 我们可视范围的高
    /// 
    public void InitContentAndSVH(RectTransform trans, int h)
    {
     
        this.content = trans;
        this.viewPortH = h;
    }

    /// 
    /// 初始化数据来源 并且把content的高初始化
    /// 
    public void InitInfos(List<T> items)
    {
     
        this.items = items;
        content.sizeDelta = new Vector2(0, Mathf.CeilToInt(items.Count / col) * itemH);     //应该要初始化履带的长度content的高
    }

    /// 
    /// 初始化格子间隔大小 以及 一行几列
    /// 
    public void InitItemSizeAndCol(int w, int h, int col)
    {
     
        this.itemW = w;
        this.itemH = h;
        this.col = col;
    }

    /// 
    /// 更新格子显示的方法
    /// 
    public void CheckShowOrHide()
    {
     
        //检测哪些格子应该显示出来
        int minIndex = (int)(content.anchoredPosition.y / itemH) * col;
        int maxIndex = (int)((content.anchoredPosition.y + viewPortH) / itemH) * col + col - 1;

        minIndex = minIndex < 0 ? 0 : minIndex;     //最小值判断
        maxIndex = maxIndex >= items.Count ? items.Count - 1 : maxIndex;    //超出道具最大数量

        if (minIndex != oldMinIndex || maxIndex != oldMaxIndex)
        {
     
            //在记录当前索引之前 要做一些事儿  根据上一次索引和这一次新算出来的索引 用来判断 哪些该移除
            //删除上一节溢出
            for (int i = oldMinIndex; i < minIndex; ++i)
            {
     
                if (nowShowItems.ContainsKey(i))
                {
     
                    if (nowShowItems[i] != null)
                        PoolMgr.GetInstance().PushObj(itemResName, nowShowItems[i]);
                    nowShowItems.Remove(i);
                }
            }

            //删除下一节溢出
            for (int i = maxIndex + 1; i <= oldMaxIndex; ++i)
            {
     
                if (nowShowItems.ContainsKey(i))
                {
     
                    if (nowShowItems[i] != null)
                        PoolMgr.GetInstance().PushObj(itemResName, nowShowItems[i]);
                    nowShowItems.Remove(i);
                }
            }
        }

        oldMinIndex = minIndex;
        oldMaxIndex = maxIndex;

        //创建指定索引范围内的格子
        for (int i = minIndex; i <= maxIndex; ++i)
        {
     
            if (nowShowItems.ContainsKey(i))
                continue;
            else
            {
        //根据这个关键索引 用来设置位置 初始化道具信息
                int index = i;
                nowShowItems.Add(index, null);
                PoolMgr.GetInstance().GetObj(itemResName, (obj) =>
                {
     
                    obj.transform.SetParent(content);
                    obj.transform.localScale = Vector3.one;
                    obj.transform.localPosition = new Vector3((index % col) * itemW, -index / col * itemH, 0);
                    obj.GetComponent<K>().InitInfo(items[index]);

                    //判断有没有这个坑
                    if (nowShowItems.ContainsKey(index))
                        nowShowItems[index] = obj;
                    else
                        PoolMgr.GetInstance().PushObj(itemResName, obj);
                });
            }
        }
    }
}

上面最重要的还是CheckShowOrHide方法


背包管理器

下面涉及背包部份,一个背包需要有个背包管理器,主要用来初始化物品信息

BagMgr.cs

using System.Collections.Generic;

/// 
/// 背包管理器 主要管理背包的一些公共数据 和 公共方法
/// 
public class BagMgr : BaseManager<BagMgr> {
     

    public List<Item> items = new List<Item>();

    /// 
    /// 这个方法 是我们模拟获取数据的方法 在实际开发中 数据应该是从服务器 或者 是本地文件中读取出来的
    /// 
    public void InitItemsInfo()
    {
     
        for( int i = 0; i < 100000; ++i )
        {
     
            Item item = new Item();
            item.id = i;
            item.num = i;

            items.Add(item);
        }
    }
}

背包面板

毕竟背包是显示给人看的,所以需要有个背包面板 里面用到上面我们提到的通用类

BagPanel.cs

using System.Collections;
using UnityEngine;

/// 
/// 背包面板 主要是用来更新背包逻辑
/// 
public class BagPanel : BasePanel
{
     
    public RectTransform content;

    CustomSV<Item, BagItem> sv;

    void Start()
    {
     
        sv = new CustomSV<Item, BagItem>();

        sv.InitItemResName("UI/BagItem");       //初始预设体名
        sv.InitItemSizeAndCol(300, 250, 2);     //初始化格子间隔大小 以及 一行几列
        sv.InitContentAndSVH(content, 925);     //初始化COntent父对象以及可视范围
        sv.InitInfos(BagMgr.GetInstance().items);   //初始化数据来源
        sv.CheckShowOrHide();
    }

    void Update()
    {
     
        sv.CheckShowOrHide();
    }
}

如果觉得放到Update里面执行CheckShowOrHide方法有点浪费的话

可以另外暴露一个新方法提供给ScrowView里的监听 OnValueChange 事件

最后只需要把 BagItem 脚本拖拽到 对应的预制体,把 BagPanel 脚本拖拽到对应的预制体上即可使用

你可能感兴趣的:(unity/ugui)