前言
unity能够跨平台主要是内置了mono虚拟机这也是unity游戏包体大运行卡的原因。
功能的实现必不可缺,当然也要注意程序性能的优化。随着项目越来越大,优化变得至关重要,它能确保你的游戏任然可以快速流畅地运行。如果想知道如何对unity进行性能优化以及如何进行性能测试,那就继续往下看吧。
一、性能分析软件
1、Draw Call
什么是Draw Call
Draw Call是Cpu向GPU发起的一种在屏幕上绘制内容的请求。问题是,准备Unity Draw Call会占用CPU大量的时间和精力。Unity必须将场景内容转换为GPU可以理解的格式。这个过程开销最昂贵的部分是设置正确的渲染参数,例如纹理,着色器,网格等。
具体过程就是:设置颜色–>绘图方式–>顶点坐标–>绘制–>结束,所以在绘制过程中,如果能在一次DrawCall完成所有绘制就会大大提高运行效率,进而达到优化的目的。
如何查看Draw Call数量
可以通过分析器查看Draw Call数量
2、分析帧调试器
3、Statistics统计面板
二、优化手段
1、关于图集、材质、层级的处理,减少DrawCall
点击运行游戏,打开分析帧调试器
然后点击启用工具,逐步查看Unity如何将图像渲染到屏幕上,我们可以看到有五个绘制调用,分别是背景、地板、敌人、玩家和玩家的武器
如果我们可以使用更少的绘制调用来实现相同的结果,它肯定会更快,我们可以使用Sprite Atlas来做到这一点
把所有的资源都拖入,进行打包(注意这里属性和图片差不多,如果你是像素素材同样可以选择点类型,无压缩进行优化)
打包效果
我们再回去分析帧调试器查看,你会看到我们只有三个绘制调用
既然前面打包了,那么为什么不是一个呢,你可以看到它没有将敌人与玩家进行批处理,因为它们具有不同的材质
如果我们没必要使用不同的材质,我们可以选择把所有的材质换成一样的
这样的话,可以看到就仅仅绘制了一次,这明显加快了GPU绘制
2、批处理
Batches:批处理可让引擎尝试将多个对象的渲染组合到一个内存块中以便减少由于资源切换而导致的 CPU 开销。
Saved by batching:合并的批次数。为确保良好的批处理,应尽可能在不同对象之间共享材质
。更改渲染状态会将批次分成具有相同状态的组。
比如我新增3个cube
默认Batches是4,Saved by batching是0
我们可以将3个cube进行静态合批,它们肯定是共用一个材质的
优化后效果,Batches是2,Saved by batching是2
据我了解合批的技术分为4种,我这里只是举例了一种,具体可以查看这篇文章,我觉得写的挺详细的,这里就不再补充了:
关于Unity四种合批技术详解
3、音乐处理
长时间音乐(背景音乐)压缩格式mp3
短时间音乐(音效)非压缩格式wav
4、减少沉余资源和重复资源
- Resources目录下的资源不管否被I用,都会打包进安装包不使用的资源不要放在Resources自录下
- 不同目录下的相同资源文件,如果都被引用,那么都会打包进资源包,造成沉余,保证同一个资源文件在项目中只存放在一个目录位置
5、渲染优化(GPU)
注意:我们可以通过verts和Tris查看视野内的顶点个数
verts
表示摄像机视野内的顶点个数
Tris
是三角面个数
天空盒也占有顶点个数,这里我删除了天空盒和除相机外的其他物体,你可以看到,场景中的Tis数量变为2,verts数量变为了4,这是由于摄像机存在的关系,删掉它,你就会发现Tris和Verts都变为0了。
Lod技术
如果模型一直使用同样的精细模型,会造成性能的浪费。比如下面三个模型
显示网格,查看模型精细程度
如果距离相机比较远的话,看起来其实是一样的,但是精细的模型肯定会更加消耗性能
Lod技术就是实现模型离得远自动使用最粗糙的模型,离得近使用精细的模型
实现,添加组件LOD Group
拖入模型lod0放置最精细的模型,lod2放置最粗糙的模型
随着相机的远近unity会自动切换不同的模型
我们可以拖动模型距离的占比,拉动相机查看演示效果
Occlusion Culling-遮挡剔除
只渲染在视野内的游戏物体,可以很大的优化我们的性能
勾选所有需要遮挡剔除的物体
生成一些遮挡剔除的数据
bake完之后,会生成一些网格,这些网格是进行遮挡剔除计算的,计算某一个物体是否在视野内,在视野内就渲染,不在视野内就不渲染
如果后期增加物体,都要进行bake重新烘培
移动相机查看效果
看batches变化
光照贴图
添加多个点光源,光照的计算是比较消耗性能的
如果我们把光照提前进行计算渲染,做出贴图,就可以很大的节省我们的性能
实现,勾选所有会被光照影响的物体
记得将所有的光源模式选择为bake烘培模式,Intensity强度也可以调小
烘培光照
默认是自动烘培,我们可以选择取消勾选Auto Generate,进行手动烘培
点击后会生成一个光照贴图信息
烘培后就可以禁用之前的光照了
结果
合并Mesh
合并模型我们可以使用模型软件(比如blender)
【blender小技巧】如何拆分模型、合并和删除模型,删除多余骨骼
也可以通过unity书写代码实现
运行效果
紫色是没有材质,指定材质即可
但是取消运行又消失了,我们可以在edit编辑器模式下运行,就可以保存合并的模型了
6、CPU性能优化
代码执行优化
我们经常会有书写这样的代码,比如新增EnemyBehavior.cs脚本挂载在敌人身上,这里在Update中每一帧都调用敌人面向玩家等操作,但是这完全是资源的浪费,在播放模式下没有必要在每帧上都这样做
运行状态下,打开Profiler 分析器
只需单击图表上的任意位置,我们的游戏就会暂停,以便我们可以对其进行分析,可以看到目前EnemyBehavior的更新占用了1.2%CPU使用率
这时候我们可以选择使用脚本委托和定时器控制它的更新频率,比如新增Ticker脚本,没过0.2秒调用TickEvent方法
逻辑不在Update中调用了,注册委托每0.2s调用一次
可以看到敌人是仍然总是面向玩家,但不是每帧都运行该逻辑,运行频率被降低了,但它并没有对游戏中敌人的行为产生明显的影响
7、性能测试
但是当涉及到某些代码片段时,仅用这些王具很难判断出什么实际上性能更高,有时您只需要自己测试一些东西,所以让我向您展示如何设置基准测试,来让您运行自己的测试
新增性能测试的类
using UnityEngine;
using System.Diagnostics;
// 用于性能测试的类
public class BenchMarker : MonoBehaviour
{
[Range(0, 1000000)]
[SerializeField] private float _iterations; // 迭代次数
private BenchMarkTest _benchMarkTest; // 性能测试对象
private Stopwatch sw; // 计时器
// 在启动时获取BenchmarkTest组件
private void Awake()
{
_benchMarkTest = GetComponent<BenchMarkTest>(); // 获取BenchmarkTest组件
}
// 执行测试的方法
public void RunTest()
{
sw = Stopwatch.StartNew(); // 创建并启动计时器
for (int i = 0; i < _iterations; i++)
{
_benchMarkTest.PerformBenchmarkTest(); // 执行性能测试
}
sw.Stop(); // 停止计时器
UnityEngine.Debug.Log(sw.ElapsedMilliseconds + "ms"); // 输出测试时间
}
}
Vector2.Distance 和 sqrMagnitude哪个好?
在Unity中,Vector2.Distance 和 sqrMagnitude 都是用来计算向量之间距离的方法,但它们的性能特性略有不同。
新增脚本,这里距离判断我们使用了Vector2.Distance
,实际它的性能很差,因为它在内部计算中使用了开方运算 Mathf.Sqrt,开方运算通常比较昂贵,尤其是在大量计算时会消耗较多的计算资源。
点击测试
可以看到它平均需要执行390毫秒左右
我们现在换成性能更高的办法.sqrMagnitude
进行距离判断,sqrMagnitude 属性返回向量的平方长度,与Vector2.Distance 方法不同,sqrMagnitude 不进行开方操作,因此它的计算代价更低。
重新测试发现,平均快了几毫秒左右
事实证明,我们可以继续使用Vector2.Distance
进行距离计算,因为它的可读性更高且对性能的影响似乎可以忽略不计
动画切换优化
通常我们都是这样进行动画切换
测试效果
但是其实有更好的办法,其实Unity获取这个动画字符串后会在幕后对其进行哈希处理,所以之后我们可以通过只做一次来节省时间,所以在这里我们可以提前对动画字符串进行哈希处理
可以看到,节约了近30毫秒的性能
shader属性优化
我们也想给改变shader属性来执行相同的操作,比如这里实现更改敌人的材质的颜色
改变颜色
测试结果,可以看到大概需要610ms左右
让我们尝试同样对字符串进行哈希处理
结果,发现速度加快了将近一半
8、URP渲染器资产优化
在URP渲染器资产中,如果你不需要深度纹理(Depth Texture)或不透明纹理(Opaque Texture )可以将其关闭,因为这些纹理最终会根据您的相机所看到的内容绘制纹理的深度或不透明度。如果你关闭它们,最终会优化大量不必要的内存
9、对象池优化
但如果您进行了大量实例化和销毁操作例如,如果您有一个带有大量子弹的游戏,那么您将需要实现个对象池系统,参考:【Unity小技巧】Unity探究自制对象池和官方内置对象池(ObjectPool)的使用
10、 删除没必要的空函数
如果你的脚本中有空函数,请删除它们,每个函数也会有一点开销成本,即使它实际上没有执行任何内容
11、图片、音乐音效、贴图等素材压缩
合理压缩素材会很大的减低我们的内存
比如先看看我的背景没有压缩,它是6.8MB
低质量压缩它是1.1MB
ps
:合理的使用压缩,确保它不会明显降低你的画面质量
12、ScriptableObject优化参数
不同的敌人实例之间更改这些变量,现在当我点击播放时,这些脚本中的每一个都将有自己的实例,并且在这些类实例中的每一个上这些变量将在内存中拥有自己的副本,并且对于这种情况完全是浪费
这里我们可以使用ScriptableObject
配置
这样我们就不会为这些变量的创建数十几个副本存放在内存中,它只是位于ScriptableObject上的一个副本,每个敌人都只指向该ScriptableObject的对象
即使你想要实现不同的敌人配置,你也只需创建另一个ScriptableObject的对象资产,绑定它并修改参数即可
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~