文章目录

  • 缓存池概念:
  • 制作方法:
  • 效果:
  • 性能测试:
    • URP添加测试
  • 结果分析:
    • 标准手机打包(中质量)
      • 缓存池
      • 无缓存池
    • 最高质量打包
      • 缓存池
      • 无缓存池

缓存池概念:

缓存池,关键在于缓存二字,如何缓存呢,即保存到某个地方,每次需要创造某个物体时先从缓存池里看看有没有,有就从缓存池里拿出来用,不进行创造。用完了就放到缓存池里面、

制作方法:

我们每次创建障碍物应该先从缓存池里面找有没有障碍物,有的话就直接从缓存池里面拿出来,又因为我们有很多不同的障碍物,所以我们使用字典,查找对应缓存池是否存在,没有就创建一个,有就放进去。
缓存池字典

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