C#初中级实战—3DFlipBird(四)缓存池与性能分析(UPR+夜神模拟器)
缓存池概念:
缓存池,关键在于缓存二字,如何缓存呢,即保存到某个地方,每次需要创造某个物体时先从缓存池里看看有没有,有就从缓存池里拿出来用,不进行创造。用完了就放到缓存池里面、
制作方法:
我们每次创建障碍物应该先从缓存池里面找有没有障碍物,有的话就直接从缓存池里面拿出来,又因为我们有很多不同的障碍物,所以我们使用字典,查找对应缓存池是否存在,没有就创建一个,有就放进去。
缓存池字典
public Dictionary<string, PoolDate> poolDic = new Dictionary<string, PoolDate>();
缓存池类:我们找一个父物体,将池子里的东西都放到对应的池子下面
public class PoolDate
{
public GameObject fatherObj;//缓存对象的父节点
public List<GameObject> poolList;//对象的容器
public PoolDate(GameObject obj, GameObject poolObj)
{
//给缓存池创造一个父对象,并且将其作为总pool的子物体
fatherObj = new GameObject(obj.name);
fatherObj.transform.parent = poolObj.transform;
poolList = new List<GameObject>() { };
PushObj(obj);//创建池子时将第一个物体放进去
}
/// <summary>
/// 存物体
/// </summary>
/// <param name="obj"></param>
public void PushObj(GameObject obj)
{
obj.SetActive(false);//失活,隐藏
//存起来
poolList.Add(obj);
//设置父对象
obj.transform.parent = fatherObj.transform;
}
/// <summary>
/// 取物体
/// </summary>
/// <param name="name"></param>
/// <param name="transform"></param>
/// <returns></returns>
public GameObject GetObj()
{
GameObject obj = null;
obj = poolList[0];
poolList.RemoveAt(0);
obj.SetActive(true);
//激活显示
obj.transform.parent = null;//断开父子关系
return obj;
}
}
写一个创造方法 替代之前的Instantiate方法
我们将物体名字传进去,并且将要创造的物体和位置传进去(如果没有池子,就根据这个创建物体)
传名字是为了给物体改名(一个物体第一次肯定不在池子里,且名字会带clone,目的就是改掉clone,方便存储和取出)
public GameObject GetObj(string name,GameObject gameObject,Transform transform)
{
//有池子,有东西
if (poolDic.ContainsKey(name) && poolDic[name].poolList.Count > 0)
{
return (poolDic[name].GetObj());
}
else
{
Debug.Log("物体的名字"+name);
Debug.Log("物体/池子不存在");
var reNameObj = GameObject.Instantiate(gameObject, transform);//
Debug.Log("实例化的物体的名字" + reNameObj.name);
reNameObj.name = name;
return null;
}
}
我们在物体出现一定时间后,不是将物体销毁而是放到对应的池子里。用Push方法替代Destroy方法。
在ObstacleBase脚本里,创建Push方法,传物体自己的名字和自己
在PoolManager中定义存的方法,判断池子和其中是否有物体,存到对应名字的池子里,并且隐藏物体。(拿出来的时候激活物体)
public void PushObj(string name, GameObject obj)
{//里面有池子
if (poolObj == null)
{
poolObj = new GameObject("pool");
}
obj.SetActive(false);//失活,隐藏
obj.transform.parent = poolObj.transform;//设置父对象
if (poolDic.ContainsKey(name))
{
poolDic[name].PushObj(obj);
}else//里面没有,加入一个
{
poolDic.Add(name, new PoolDate(obj, poolObj));
}
}
效果:
可以看到 Hierarchy窗口出现Pool,里面是不同障碍物对应的池子,每当物体定时时间到了,就会进入缓存池,而另一边创造物体 会优先从缓存池拿物体,当物体不够再进行创造
最后在游戏结束(血量清零)清除缓存池(否则重新开始游戏可能会出现问题),每次开始游戏也可进行缓存池的清除。
性能测试:
下面进行性能测试,这里用UPR+夜神模拟器进行测评
(目前UPR已经不支持直接在编辑器模式下测试了,要么打包测,要么profiler)
测评方法:
下载upr windows,下载upr package 加载进项目里面
项目打包
下载夜神模拟器
URP添加测试
开启模拟器同时开启UPR windows,在urp官网登录-我的项目-创建项目-新建测试
这里测试4次,两次中质量打包,两次最高质量打包(增加模拟器的渲染压力),其中2次使用缓存池,2次不使用缓存池
将建立好的测试复制Session id到 Windows中,然后选择ADB。此时模拟器应该是打开状态且安装了对应的app,点击之后Start,就开始测试了,测试完点击Stop即可完成测试(官网有视频教程)
结果分析:
标准手机打包(中质量)
先看标准手机打包(中质量)下,缓存池对于性能的影响
缓存池
无缓存池
可以看到,缓存池对于性能影响不高,GC的次数居然还多了(可能是测试时间不一样长),GC的峰值时间变长了,帧数基本变化不大,不过使用了缓存池的CPU运行比较平稳,帧数更加稳定。
最高质量打包
下面看如果渲染压力比较高的情况下,缓存池是否会有效果:
缓存池
内存占用
GC
无缓存池
内存占用
GC
可以看到,在高渲染压力情况下,帧数直接掉一半,使用了缓存池的帧数还略低一点,内存情况是,使用了缓存池的内存确实用的更多,而且峰值GC时间和 普通画质下的情况一样,都 比不使用缓存池要高。还有一点情况和普通画质一样的是,使用了缓存池的CPU运行时间比不使用缓存池平稳运行时间更长(毛刺更少),说明帧数更稳定。
不过使用了缓存池的GC数量却比不使用更多,这一点比较困惑,也许需要更多测试样本。
对于此项目来说,画质的优化是重头,cpu优化反而不那么重要,两次画质的不同直接导致帧数掉了一半,所以如果要优化,应该往模型面数,贴图尺寸等画面表现来进行(粒子,shader之类的),应该看一下相关参数,不过此次重点是GC对于性能的影响,别的指标就下次再讨论了。(UPR之初体验)
本文地址:https://blog.csdn.net/euphorias/article/details/109241201