Unity项目优化方案2023

每年整个新活,每年出个手游项目。又到了项目收尾的季节,也是最掉头发的时候。这两周开启漫漫的优化之路。

老方法,先按住Ctrl+7,打开profiler性能分析工具,找到性能占用的大头。不用看也能猜到,Batches是优化的重头戏。

一,降低Batches

完成这一步,其实已经做了70%以上的优化工作。但说起来简单,模型、ui、特效七七八八的算下来,都是疑难杂症的点。我们来一个个解决。

1,合理使用静态合批,降低场景模型的batches

在某些场景中,我们的背景,只需要一个模型即可。这里最好的做法,是美术将其合并为一个整的模型;或者像某些游戏一样,用一张超大的背景图作为背景。

但我们美术是外包的,公司的美术人少又比较忙,所以这个工作暂时只能由自己来做了。对于这些场景,由于不是常驻的,所以采用空间换时间的策略,勾选静态static属性。优化结果示例如下:

由上图可见,在某些重复元素比较多的场景中,静态合批无疑是很划算的。

但对于某些重复元素比较少的场景,静态合批的效果就没那么明显,这时候反而会多占用内存。所以我们要分情况合理的使用。

2,合理使用Animation Instancing 技术合并动作模型

由于战斗场景中存在大量的、重复模型的敌人,上限甚至能达到几百个。这个时候不合批肯定会卡死,所以我们需要采用Animation Instancing技术来合并批次。

在理解这项技术之前,我们先来补充点关于模型合批的基础知识,unity基本所有的模型合批都绕不开GPU instancing,它属于unity官方提供的一种合批方案。由于这种方案不支持skinned mesh renderer皮肤网格渲染器,而animation instancing大致原理也是将动画模型转为固定的mesh renderer(此处如果理解有误,请在评论区指出)。

当然我们项目用的是公司优化方案,也是大致思路,将皮肤网格转化为面片,优化结果如下:

3,合理的使用UGUI的图集

对于场景中存在大量重复UI图素的情况下,通常UI会给你自动合批;但当中间被插入图素的时候,就会打断该次合批。

这是的做法有三种,其一为将这些图素合为一张大图并放到一个父节点下面;其二是老版图集或者第三方合图工具(比如TexturePackerGUI);其三是使用UGUI自带的图集功能。我这里使用的是第三种,先打开设置:

然后点击右键创建图集,这里有个问题是新版unity插件都是独立的,所以找不到创建图集的话就先去package中导入相应的插件:

1,相同功能的合为一个图集,通用的合为一个图集;

2,中间插了图素的单独分为上下两个图集

测试方法:

1,空场景观察合并图集前后批次变化

2,综上所述,游戏中观察

4,合理使用面片来代替大批量重复的粒子特效

在我们游戏后期,子弹的射速是非常快的,相对来说子弹的量级也是翻倍增长。这个时候再用粒子就有些不划算了,因为通过测试半透明粒子不会合批,而半透明面片会自动合批。

面片合批效果如下,这是100个面片炮弹自动合批的情况,再多就会生成新的批次。不过总体来说,面片的批次=种类;而粒子特效的批次=数量*种类。

当然面片的效果不如粒子,如果非得用粒子的话,可以参考这个帖子,写shader自己合批。

5,动态合批

这个不需要我们去设置,但要想尽可能的利用动态合批,基本条件为重复物体并且中间不会被插入别的物体。比如4中的特效合批,不过建议还是自己写shader。

二,代码优化

同样是使用profiler性能分析工具,其中可以通过曲线来分析代码占用,通过Memory来检查内存占用。

1,内存泄漏问题

这个也是大致分为资源和代码两个方面去定位。

资源泄漏比较好定位,直接观察场景中的物体变化即可观察到,通常导致资源内存泄漏的原因都是对象池回收不对导致的,多注意这方面的变化即可。

代码泄漏查找起来比较麻烦,目前我只能一步步分析。不过可以通过脚本帮助我们分析定位,示例代码如下:

  DebugGame.LogError("开始分析场上所有出现的物体");
                        var _dic = new Dictionary<string, int>();
                        var _obj = GameObject.Find("Main");
                        DebugAllObj(_obj.transform, _dic);
                        var _str = "";
                        foreach (var idx in _dic) if (idx.Value > 10) _str += idx.Key + "," + idx.Value + "\n";
                        System.IO.Directory.CreateDirectory("Assets/TablesOther");
                        using (System.IO.StreamWriter writer = System.IO.File.CreateText("Assets/TablesOther/物体长度.csv"))
                            writer.Write(_str);
                        UnityEditor.AssetDatabase.Refresh();

 private void DebugAllObj(Transform _tran,Dictionary<string,int> _dic)
    {
        if (_dic.ContainsKey(_tran.name)) _dic[_tran.name]++;
        else _dic.Add(_tran.name, 1);

        if (_tran.childCount > 0)
            foreach (Transform idx in _tran) DebugAllObj(idx, _dic);           
    }

而且这种问题一般长时间挂机才能发现,算是比较痛苦的过程,当然解决问题的那一刻必然是喜悦的。

另外还可以通过功能区分不同的模块,通过模块测试来定位泄漏及其它问题。

2,代码占用问题

我们可以通过观察内存CPU的占用情况,找到性能占用比较厉害的桢。比如下图中占用忽然几十倍增长,明显是不正常的。

        UnityEngine.Profiling.Profiler.BeginSample("001Script");
        #region 中间是需要分析的代码段,注意不能有return,否则可能会定位问题有误

        #endregion
        UnityEngine.Profiling.Profiler.EndSample();

加上分析代码之后,我们可以点开分析器,找到我们写的脚本,从而具体定位到CPU占用比较厉害的代码段,想办法挪到别的地方或者直接优化掉。

3,其它优化

安卓手机可以根据手机刷新帧率适当设置帧率上限设置,也就是常说的锁桢,不过这个需要关闭垂直同步,可能造成部分不必要的麻烦。

 //这里因为某些安卓手机的刷新率在120及以上,所以刷新显示锁到一定帧率
        if (Screen.currentResolution.refreshRate > 90)
        {
            Application.targetFrameRate = 90;
            QualitySettings.vSyncCount = 0;           
        }
        else
            Application.targetFrameRate = 60;

另外比如说多用加减乘来代替除法、合理使用数值类型和引用类型等,都是些日常用到的小技巧,就不再一一总结。

猜你喜欢

转载自blog.csdn.net/Tel17610887670/article/details/129364360