广东双非一本的大三小白,计科专业,想在制作毕设前夯实基础,毕设做出一款属于自己的游戏!
理论基础:
效果如下:
要用到前面的UI框架和单例
非常简单,背包面板只是一个简单的 ScrowView,做成预制体
当背包里的格子也要做成预制体,格子身上也应该要挂载一个类主要用于初始化信息(后期扩展可以鼠标移上去展示信息),这里的格子只有简单的数字当下标
格子主要放在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);
}
要明确:
现在开始实现格子类
BagItem.cs
using UnityEngine.UI;
///
/// 格子类对象 他是放在背包里的一个一个的道具格子
/// 主要用来显示 单组道具信息的
///
public class BagItem : BasePanel, IItemBase<Item> {
///
/// 这个方法 是用于初始化 道具格子信息
///
public void InitInfo(Item info)
{
//读取道具表 根据表中数据 来更新信息 更新图标 更新名字
//更新道具数量
GetControl<Text>("txtNum").text = info.num.ToString();
}
}
格子有了,现在做背包,但是不只是背包,其实很多地方都有需要用到
所以先抽象出通用的一个 类(除了背包,计分板也有可能要用到)
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 脚本拖拽到对应的预制体上即可使用