目录
1.批处理
动态批处理:对于小网格,Unity 在 CPU 上分组和转换顶点,然后一次性绘制它们。注意 :只在有足够低复杂度网格(少于 900 个顶点属性和不超过 300 个顶点)时使用这一方法。动态批处理程序不会对更大的网格进行批处理,如果启用会浪费 CPU 时间在每一帧都去查找要批处理的小网格。
静态批处理 :对于不移动的几何体,Unity 可以减少所有共享相同材质的网格的绘制调用。它比动态批处理更有效,但使用更多内存。
GPU 实例化 :如果有大量相同的对象,这种方法通过图像硬件对它们进行更有效地批处理
SRP 批处理 :在 Advanced 下面的 Universal Render Pipeline Asset 中启用 。这样可以SRP Batcher大幅提高 CPU 渲染速度,具体取决于场景。
2.对象剔除
剔除方法 | 描述 | 实现方法 | 适用场景 |
---|---|---|---|
视锥剔除 | Unity 会自动跳过摄像机视野之外的对象渲染(开箱即用) | 无需额外操作,Unity 引擎自动处理 | 所有场景,减少不在摄像机视野范围内对象的渲染计算 |
遮挡剔除 | 跳过被其他对象遮挡的对象的渲染。Unity 内置的遮挡剔除运行时通过 CPU 进行计算,运行时需要加载遮挡数据,需确保内存充足 | 点击 Window->Rendering->Occlusion Culling 进行相关设置。 Smallest :可以被剔除的物体的最小尺寸,如果物体小于这个尺寸,即使被遮挡了也不会被剔除。 Smallect Hole : 如果物体堆叠起来形成一个孔,或是这个物体本来就带孔,通过这个孔我们可以看到后面的物体,这个参数的作用就是当孔的大小小于这个参数值时就会被忽略这个孔的存在,那孔后的物体就会被剔除。 Backface Threshold : 设置背景剔除的阈值。当值为100时就不剔除背景,当小于100时U3D对背景进行优化甚至去掉背景。 |
适用于有较多遮挡关系的场景,如室内场景、城市建筑群等,减少被遮挡对象的渲染 |
细节层次剔除(LOD) | 根据摄像机与对象的距离,渲染低分辨率模型以减少绘制负担 | 1. 为目标对象添加 LOD 组(LOD Group) 2. 配置不同距离范围的模型(如高、中、低分辨率版本) | 远距离显示复杂模型的项目,如大型开放世界游戏中的树木、角色等,减少远距离高精度模型的渲染压力 |
层级剔除 | 利用 Unity 层级系统,根据摄像机的 Culling Mask 跳过某些层的渲染 | 1. 设置对象的层级(Layer) 2. 修改摄像机的 Culling Mask,仅渲染需要的层 | 隐藏次要或不重要对象,如游戏中的特效层、装饰性元素层等,在特定场景下不渲染某些层的对象 |
层级距离剔除 | 剔除特定层级超出指定距离的对象 | float[] _distance = new float[32] ; _distance[11]=8; //设置第11层的剔除距离为8 GetComponent<Camera>().layerCullDistances = _distance ; |
开放世界场景中的远处细节剔除,减少远距离对游戏体验影响较小的对象的渲染 |
背面剔除 | 剔除朝外法线背向摄像机的三角面 | Unity 默认开启背面剔除(Cull Mode 为 Back),可在材质的渲染模式中配置 | 减少无意义的面片渲染,尤其适用于复杂模型,如角色模型、大型建筑模型等,提高渲染效率 |
阴影剔除 | 剔除不投射阴影或不需要接收阴影的对象 | 1. 在对象的渲染器中关闭投射或接收阴影选项 2. 使用脚本动态调整对象的阴影属性 | 优化复杂光照场景的性能,减少阴影计算对性能的影响,如光照较多、对象复杂的场景 |
3.纹理优化
优化策略 | 描述 | 原因 | 注意事项 |
---|---|---|---|
减小Max Size | 使用能生成视觉上可接受结果的最低设置 | 这是降低纹理内存的非破坏性方式,可快速减少纹理内存占用 | 根据实际视觉效果需求合理调整,避免过度降低影响画面质量 |
使用2的幂(POT) | 移动端纹理压缩格式(PVRCT或ETC)要求采用POT纹理尺寸 | 满足Unity对移动端纹理压缩格式的要求,确保纹理正常压缩和使用 | 在设置纹理尺寸时,将宽高设置为2的幂次方数值,如256、512等 |
制作纹理图集 | 将多个纹理放置到单个纹理中 | 减少绘制调用,加快渲染速度,提升性能 | 可使用Unity精灵图集或第三方Texture Packer制作,注意图集内纹理布局合理,避免浪费空间 |
关闭Read/Write Enabled选项 | 启用此选项会在CPU和GPU可寻址内存中都创建副本,使纹理占用双倍内存 | 减少不必要的内存占用 | 大多数情况保持禁用,若需在运行时生成纹理,通过Texture2D.Apply强制执行,并传入设置为true的makeNoLongerReadable |
禁用不必要的Mip Map | 对于屏幕上大小保持不变的纹理(如2D精灵和UI图形),Mip Map非必需;3D模型与摄像机距离变化时保留启用 | 对于特定类型纹理,关闭Mip Map可减少内存占用,且不影响其视觉表现 | 准确判断纹理类型,合理开启或关闭Mip Map |
压缩纹理 | 对纹理进行压缩处理 | 降低纹理内存占用,减少存储空间和传输带宽 | 选择合适的压缩算法和压缩比,平衡压缩效果与画面质量 |
减少过度绘制和Alpha混合 | 避免绘制不必要的透明或半透明图像 | 过度绘制和Alpha混合极大影响移动平台性能 | 仔细检查场景中透明或半透明图像的必要性,优化绘制逻辑 |
降低分辨率 | 在不影响游戏整体视觉效果和用户体验的前提下,适当降低纹理的分辨率 | 较低的分辨率意味着更少的像素数据,从而减少纹理所占用的内存空间 | 需要在内存优化和画面质量之间进行权衡,不能过度降低分辨率导致画面出现明显的模糊、锯齿等影响体验的问题;同时,要考虑不同设备的屏幕分辨率和显示特性,确保在各种设备上都能有可接受的视觉效果 |
4.模型优化
优化策略 | 描述 | 原因 | 注意事项 |
---|---|---|---|
压缩网格 | 对网格进行高性能压缩 | 可减少网格占用的磁盘空间,方便存储和传输 | 网格量化可能导致不准确,需试验不同压缩级别,找到既满足模型显示需求又能有效压缩的级别;压缩仅针对磁盘空间,对运行时内存无影响 |
禁用Read/Write | 关闭该选项,避免启用时内存中出现重复网格(系统内存和GPU内存各有一个副本) | 减少内存占用,避免资源浪费 | 在Unity 2019.2及更早版本中此选项默认为选中状态,需手动检查并禁用;多数情况下应禁用,但如果有特殊需求(如在运行时需从CPU访问网格数据)则需谨慎考虑 |
禁用骨骼和BlendShape | 若网格无需骨架或BlendShape动画,关闭相应选项 | 去除不必要的动画相关数据,节省内存空间 | 确认网格确实不需要相关动画效果后再禁用,以免影响模型预期表现 |
尽可能禁用法线和切线 | 当确定网格的材质不需要法线或切线时,取消选中相关选项 | 进一步节省内存,去除对材质渲染无用的数据 | 需准确判断材质是否真正不需要法线和切线,否则可能影响材质的光照和渲染效果 |
合并模型 | 将多个较小的模型合并为一个较大的模型 | 减少绘制调用(Draw Call)的次数。每次绘制调用都需要 CPU 向 GPU 发送指令来渲染一个模型,过多的绘制调用会增加 CPU 和 GPU 之间的通信开销 | 合并模型时,要考虑模型的材质和纹理是否一致。如果不一致,可能需要对材质和纹理进行调整,以确保合并后的模型外观正确。同时,也要注意合并后的模型是否会出现碰撞检测等问题 |
合理设置碰撞体 | 根据模型的实际形状和功能,选择合适的碰撞体类型(如盒体碰撞体、球体碰撞体、胶囊体碰撞体等),并调整其大小和位置 | 碰撞检测是游戏中常见的功能,但复杂的碰撞体计算会消耗一定的性能。合理设置碰撞体可以在保证碰撞检测准确性的前提下,减少计算量 | 避免设置过于复杂的碰撞体,如使用精确的网格碰撞体时要谨慎。可以使用多个简单的碰撞体组合来近似模拟复杂的形状,同时要确保碰撞体的位置和大小与模型实际相符,以避免出现碰撞错误 |
减少材质和纹理的使用 | 尽量减少模型上使用的材质和纹理数量,避免不必要的材质切换 | 每个材质和纹理都需要占用一定的内存空间,并且材质切换也会增加渲染的开销。减少材质和纹理的使用可以降低内存占用和渲染成本 | 可以尝试使用纹理图集(Texture Atlas)将多个小纹理合并为一个大纹理,以减少纹理数量。同时,对于相似的材质,可以尽量复用,避免创建过多不同的材质 |
5.动画优化
优化策略 | 描述 | 原因 | 注意事项 |
---|---|---|---|
设置Animator组件的Culling Mode | 可选择"Always Animate"(动画不可见也播放)、"Cull Update Transformations"(动画不可见时不渲染,但会根据播放改变游戏对象位置、旋转、缩放,常用)、"Cull Completely"(完全不播放动画,不渲染且不改变游戏对象位置、旋转、缩放) | 根据不同需求选择合适模式,控制动画在不可见时的播放和对游戏对象的影响,从而优化性能。如选择合适模式可避免不必要的动画计算和渲染 | 根据游戏中对象的实际情况和需求来选择模式。对于一些需要始终保持动画逻辑的对象,可选择"Always Animate";对于大部分仅需在可见时渲染的对象,"Cull Update Transformations"或"Cull Completely"可能更合适 |
禁用SkinMesh Renderer组件的Update When Offscreen | 让角色在不可见时动画不更新 | 减少不可见角色的动画计算量,提升游戏性能 | 确认该设置不会对游戏的逻辑和表现产生负面影响,比如一些特殊效果或场景中,角色不可见时可能仍需要动画逻辑运行 |
使用Animator.StringToHash方法 | 使用Animator.StringToHash获取指定字符串的哈希值,将其作为参数传入Animator型对象的GetXXX方法和SetXXX方法中 | 避免在每次调用Set或Get方法时对字符串进行哈希计算,提高性能。因为字符串比较和哈希计算在频繁调用时会有一定开销 | 确保获取哈希值的字符串与Animator中定义的参数名称准确匹配,否则可能导致动画无法正确设置或获取状态 |
删除不用的Animation组件和Animator组件 | 删除不再使用的Animation组件和Animator组件 | 只要组件存在,就会消耗性能来检测当前状态和过渡条件,删除可减少性能消耗 | 在删除前,仔细确认这些组件确实不再被使用,避免误删导致游戏功能出现问题 |
使用DoTween、iTween等插件实现简单动画 | 对于一些简单动画,使用DoTween、iTween等插件实现,而非都用Animator | Animator组件功能强大但相对复杂,对于简单动画使用插件可简化实现过程,减少性能开销,且插件可能更灵活方便 | 了解插件的使用方法和特性,确保插件与项目的兼容性。同时,对于复杂动画或有特定需求的动画,仍需考虑使用Animator组件 |
动画GPU烘焙 | 利用GPU的并行计算能力,将动画烘焙到模型上,使动画数据直接存储在模型的顶点数据中,由GPU直接处理 | 减轻CPU的负担,将动画计算从CPU转移到GPU,提高渲染效率。尤其对于大量角色或复杂动画场景,能显著提升性能 | 确保项目所使用的硬件设备支持GPU烘焙功能。烘焙前要对动画效果进行充分测试,因为烘焙后的动画数据是固定的,修改难度较大。同时,烘焙可能会增加模型的顶点数据量,要注意控制在合理范围内,避免因数据量过大影响性能 |
6.音频优化
优化策略 | 描述 | 原因 | 注意事项 |
---|---|---|---|
尽量使用单声道声音剪辑 | 创作声音剪辑时,若使用3D空间音频,以单声道形式创作或启用Force To Mono设置 | 运行时定位多声道声音会扁平化为单声道源,增加CPU开销并浪费内存,使用单声道可避免此类问题 | 对于有特殊立体声效果需求的音频,需权衡单声道设置对音频效果的影响 |
使用原始未压缩WAV文件作为源资源 | 以未压缩的WAV文件作为音频源资源,避免使用压缩格式(如MP3或Vorbis) | 使用压缩格式时,Unity会在构建时进行解压和重新压缩,产生两个有损通道,降低最终音频质量 | WAV文件占用磁盘空间较大,需考虑项目的存储空间限制 |
压缩剪辑并降低压缩比特率 | 对音频剪辑进行压缩并降低比特率,大多数声音用Vorbis(不循环声音可用MP3),常用短声音用ADPCM,移动设备音效最高设为22,050 Hz | 通过压缩减小剪辑大小和内存使用量,ADPCM可减小文件大小且播放时解码快,较低采样率对最终质量影响小 | 根据音频类型和使用场景选择合适的压缩格式和比特率,通过实际试听判断音频质量是否满足需求 |
选择正确的加载类型 | 小剪辑(< 200 kb)采用Decompress on Load;中等剪辑(>= 200 kb)保持为Compressed in Memory;大文件(背景音乐)设置为Streaming | 不同加载类型适用于不同大小的音频文件,合理选择可优化CPU开销和内存占用 | 准确评估音频文件大小,根据实际情况选择合适的加载类型,避免不恰当加载方式导致性能问题 |
从内存中卸载静音的音频源 | 实现静音按钮时,销毁AudioSource组件而非仅将音量设为0 | 仅将音量设为0时,AudioSource组件仍占用内存,销毁组件可释放内存,减少播放器频繁切换开关的开销 | 确保在销毁AudioSource组件后,后续需要播放音频时能正确重新创建和初始化该组件 |
7.UGUI
7.1.内存优化
具体优化点 | 优化方法 |
---|---|
图集管理 | 1. 尽量将一个界面的元素集中在一个图集中,公用图片放公用图集,降低内存加载冗余,且图集会自动合批降低Drawcall 2. 关注图集引用关系,通过Profile查看引用个数,避免因少量图片引用而加载整个图集到内存 |
图片压缩 | 1. 选用流行压缩格式,如ETC、PVRTC,ASTC逐渐成为主流,内存占用小且更清晰 2. 非透明图片选不带A通道的压缩格式以减小内存占用 3. Http图片加载或下载时,动态创建Texture选合理压缩格式,谨慎开启MinMap,可限定宽高二次压缩;下载图片进行代码二次压缩 |
资源预加载 | 在1G或512M等低内存设备上,考虑放弃部分资源界面的预加载,释放内存空间 |
资源释放 | 使用完Texture后及时调用Destroy进行释放,防止持续占用内存 |
内存泄漏(Mono) | 1. 避免声明无用静态或非静态变量 2. 置空无用的Instance 3. 合理清理数据集合或资源列表 4. 及时Destory使用完成的Texture 5. 清理集合中无引用的图片资源 |
内存泄漏(资源) | 1. 防止内存中加载多份相同资源 2. 借助Profile查看资源引用情况,清理无引用的资源或图片 3. 场景跳转时合理释放与清理内存,避免前场景资源常驻内存 4. 控制过量的资源或Bundle预加载 |
AssetBundle管理 | 1. 正确处理AssetBundle的加载和释放,避免内存问题 2. 留意加密后Bundle解密过程中的内存拷贝并优化处理 3. 进行AssetBundle的冗余操作,防止相同资源重复打进Bundle造成浪费 |
7.2.包体优化
具体优化点 | 优化方法 |
---|---|
图片格式与图集 | 1. 参考内存优化中关于图集和图片的宽高及压缩格式处理方法,根据图片有无透明通道选合适格式,减少包体 2. 合理使用图集,减少空白像素,避免资源浪费 |
无用资源处理 | 1. 及时删除项目中未使用的图片,可借助清理资源垃圾插件精准识别资源引用情况 2. 删除Resources文件夹下无用的图片及资源,因其无论是否被引用都会打进包体 |
重复资源控制 | 项目开始时将资源放置在公用位置,确保资源唯一性,防止后期资源引用混乱导致包体增大 |
打包压缩设置 | 选择合适的Compression Method,如LZ4或LZ4HC,避免使用Default,防止包体因压缩不当过大 |
7.3.性能优化
具体优化点 | 优化方法 |
---|---|
Canvas管理 | 1. 进行动静分离,拆分Canvas,将频繁移动的元素放置在动态Canvas下,降低静态元素更新频率,减小重建时Mesh大小 2. 避免频繁地销毁和创建界面,减少性能损耗 3. 用CanvasGroup替代频繁的SetActive界面操作,提高性能 |
组件使用优化 | 1. 避免使用Unity自带的OutLine和Shadow组件,因其会显著增加Mesh顶点数量和UI重建开销 2. 谨慎使用Text的Best Fit选项,其代价较高,会增加额外生成时间 3. 将Mask替换为Rect Mask 2D或其他优化后的Mask组件,尽量不使用Mask组件,减少drawcall计算 |
组件属性设置 | 对于不需要RayCast Target功能的Image、Text等组件,不开启该功能,节省点击时遍历判断的性能开销 |
图集与布局 | 1. 一个界面尽量减少使用多个图集中的图片,规范图集使用,避免图文交叉排列,防止Drawcall数量增加 2. 注意无用的Image以及Sprite为空的Image会打断合批,谨慎使用 |
Text组件优化 | 避免使用Text的RichText属性,防止频繁赋值时因正则匹配带来的较大开销 |
界面周期控制 | 当界面不显示时,及时停掉该界面的周期函数(如Update),节省性能资源 |
8.光照
8.1.光照探针
光照探针用于存储场景中空白区域的烘焙光照信息,为场景提供高质量的直接和间接光照。它采用球谐函数计算光照,相比动态光照,计算速度更快。在实际使用中,开发者需合理布置光照探针,确保其能覆盖场景中需要获取光照信息的区域,如角色可能活动的空旷地带。当场景中的物体移动到光照探针覆盖区域时,能实时获取准确的光照效果,避免因光照缺失导致的视觉不协调问题,提升场景光照的真实性和一致性。
8.2.伪实时阴影
- 阴影图片:通过向简单网格应用模糊纹理来模拟阴影效果。这种方式在性能消耗上远低于真实阴影计算,且能在一定程度上呈现出物体的阴影形态。例如在一些对性能要求较高的手机游戏中,对于远处的角色或建筑,使用阴影图片模拟阴影,既保证了场景的光影效果,又不会对设备性能造成过大压力。
- Planar Shadow:即平面阴影,是一种基于平面投影的阴影模拟技术。它通过将物体投影到一个平面上生成阴影,常用于模拟物体在地面等平面上的阴影效果。这种阴影生成方式计算相对简单,性能开销较小,适用于一些对阴影效果要求不是特别高,且物体主要在平面上活动的场景,如2D游戏或部分3D游戏中角色在地面行走时的阴影表现。
- Projector Shadow:投影阴影是利用Projector组件实现的一种阴影效果。它可以将纹理或光照信息投射到场景中的物体上,模拟出特定形状和效果的阴影。例如在模拟聚光灯照射下的阴影时,使用Projector Shadow能够精准地控制阴影的形状、颜色和投影范围,为场景增添更丰富的光影变化,同时相较于实时阴影计算,其性能消耗更低。
8.3.烘焙
烘焙是将场景中的光照信息预先计算并存储到光照贴图中的过程。在烘焙过程中,开发者可以设置不同的参数来控制烘焙效果,如光照的强度、颜色、阴影的柔和度等。烘焙后的光照在运行时直接使用预先计算好的光照信息,无需实时计算,大大减少了运行时的性能开销。这对于静态场景尤为重要,能够在不影响视觉效果的前提下,显著提升游戏的运行效率。同时,合理的烘焙设置可以使场景中的光照看起来更加自然和逼真,增强游戏的沉浸感。
8.4.反射球
反射球通过对周围环境进行采样,将场景中的反射信息记录到一张立方体贴图(Cube Map)中。这张立方体贴图包含了从反射球位置观察到的各个方向的场景图像,就像是一个虚拟的镜子,全方位捕捉了周围的环境。在渲染时,具有反射材质的物体,如金属、玻璃等,会参考反射球中的立方体贴图信息来生成反射效果,从而让这些物体表面呈现出周围环境的反射影像,增强场景的真实感。
9.代码
优化技巧 | 描述 | 原理 | 建议 |
---|---|---|---|
合理计算顺序减少矢量操作 | 调整矢量与浮点数相乘顺序,如vec * (3f * 2f) 代替vec * 3f * 2f |
减少中间临时矢量产生及乘法运算次数 | 编写矢量运算代码时关注运算顺序 |
缓存transform | 在MonoBehavior中先缓存物体的transform指针 | 减少因继承关系导致的获取transform指针的时间 | 在脚本开始时缓存transform |
使用localPosition而非position | 优先使用localPosition,避免获取position时的层级坐标转换计算 | position需根据层级结构向上转换为世界坐标,localPosition直接存储 | 除非需要世界坐标,否则多用localPosition,localRotation同理 |
减少引擎调用 | 缓存transform.localPosition ,修改缓存后再赋值给transform.localPosition |
原理未明确,可能与减少引擎内部操作次数有关 | 对transform.localPosition 等频繁修改时采用缓存策略 |
不使用getter和setter | 直接访问字段,不通过属性访问 | 属性封装导致获取字段值变慢,在mono下有效,il2cpp下相反 | 在mono环境且性能敏感处避免使用属性 |
不使用矢量运算符 | 用vec1.x += vec2.x; vec1.y += vec2.y; vec1.z += vec2.z; 代替vec1 += vec2 |
矢量直接运算会在栈上分配临时Vector3变量,分量运算直接修改分量 | 性能要求高时拆分矢量运算,可封装方法但有少量性能损失 |
缓存Time.deltaTime | 在脚本开始缓存Time.deltaTime |
避免每帧多次获取时重复计算 | 脚本中多次使用Time.deltaTime 时进行缓存 |
不使用foreach循环 | 用for循环代替foreach循环 | foreach曾会产生垃圾回收,现版本已解决,但for循环仍更快 | 优先使用for循环 |
使用Array而非List | 使用数组代替List进行下标访问 | 数组下标访问更快 | 集合初始化后不再添加元素时,将List转换为数组 |
尽量避免使用Update和FixedUpdate | 使用自定义计时回调器替代 | 原因为Unity的Update和FixedUpdate存在性能问题 | 根据需求自定义计时回调逻辑 |
减少 Update 每帧执行逻辑 | 审视 Update 函数,将非必要每帧运行的逻辑移出,设定特定触发条件执行非实时性要求高的逻辑 | 降低单帧计算量,避免资源浪费 | 仔细评估 Update 中的逻辑,只保留实时性要求高的代码 |
采用时间切片技术 | 通过Time.frameCount % interval == 0 控制代码每 n 帧运行一次,将繁重工作负载分布到多个帧 |
避免单帧计算量过大 | 根据具体逻辑确定合适的间隔帧数 |
避免在 Update 创建新对象 | 杜绝在 Update 方法中使用New 关键字创建新对象,提前在Start 或Awake 中创建并重复利用 |
减少内存碎片化和垃圾回收压力 | 提前规划好所需对象,在合适的生命周期函数中创建 |
资源分配前置到 Start 和 Awake | 在Start 和Awake 方法中完成所有必要的资源分配和初始化工作,如创建新实例、寻找游戏对象等 |
避免在 Update 中频繁执行高开销操作 | 将可能在 Update 中用到的对象提前获取和创建 |
删除空事件函数 | 及时删除空的MonoBehaviour 事件函数,如空的Update 、LateUpdate 等 |
减少不必要的资源占用 | 定期检查脚本,移除无用的空函数 |
避免频繁查找对象 | 避免在 Update 中频繁使用GameObject.Find 、GameObject.GetComponent 等方法,在Start 或Awake 中查找并缓存结果 |
减少遍历场景查找对象的开销 | 尽早缓存需要频繁访问的对象引用 |
谨慎使用 Camera.main | 在 Unity 2020.2 之前版本,避免在 Update 中频繁调用Camera.main ,在Start 或Awake 中缓存引用 |
Camera.main 背后执行FindGameObjectsWithTag() ,频繁调用开销大 |
提前缓存Camera.main 引用,避免重复查找 |
使用哈希值替代字符串参数 | 在Animator 、Material 和Shader 等对象的属性操作中,使用整数值方法替代字符串值方法 |
Unity 内部通过属性 ID(哈希值)寻址属性,字符串方法需先哈希处理 | 提前计算哈希值并使用,避免重复哈希处理 |
避免字符串连接 | 使用StringBuilder 替代直接的字符串连接操作,减少垃圾分配 |
字符串连接会创建新的字符串对象,StringBuilder 可避免此问题 |
在需要动态生成字符串时,优先使用StringBuilder |
优先使用 Array 数组 | 当列表长度固定或可计算最大成员数量时,使用数组代替List |
数组内存布局更紧凑,下标访问速度更快 | 根据数据特点选择合适的数据结构,固定长度数据优先用数组 |
谨慎使用 LINQ | 避免在频繁执行的代码中使用 LINQ,采用传统循环和条件判断实现相同功能 | LINQ 在性能和内存分配方面表现不佳 | 在性能关键代码中避免使用 LINQ |
注意装箱过程 | 避免将值类型(如int 、float 、bool 等)隐式转换为Object 类型,定义函数时明确参数类型为值类型 |
装箱过程会生成垃圾 | 明确函数参数类型,避免不必要的装箱 |
对象池技术 | 预先创建一批对象并存储在池中,需要时从池中获取,使用完毕放回池中,避免频繁创建和销毁 | 减少内存开销,提高性能 | 对于频繁创建和销毁的对象,使用对象池技术 |
减少堆内存分配 | 优化代码结构,避免在循环中创建大量临时对象,在循环外部声明临时变量并重复使用 | 降低堆内存分配频率,减少垃圾回收压力 | 仔细审查代码,减少不必要的临时对象创建 |
使用值类型 | 在合适场景下,使用结构体代替类存储数据 | 值类型存储在栈上,减少堆内存分配 | 对于简单的数据结构,优先考虑使用结构体 |
适时调用 GC.Collect | 在场景切换等对性能实时要求较低的情况下,主动调用System.GC.Collect() 触发垃圾回收 |
清理不再使用的对象,释放内存 | 谨慎使用,避免频繁调用增加 CPU 开销 |
及时卸载无用资源 | 对于通过 AssetBundle 加载的资源,在不再使用时及时卸载 | 释放内存,减少资源占用 | 合理规划资源使用周期,及时卸载不再使用的资源 |
优化 AssetBundle 加载策略 | 采用异步加载方式,合理规划加载时机,避免一次性加载过多资源 | 避免阻塞主线程,降低内存压力 | 根据游戏流程和资源需求,合理安排加载和卸载操作 |
使用碰撞检测 NonAlloc 函数 | 在物理碰撞检测中,使用不分配内存的NonAlloc 版本函数,如Physics2D.CircleCastNonAlloc() 替代Physics2D.CircleCast() |
减少内存分配 | 在碰撞检测场景中,优先使用NonAlloc 函数 |
坐标运算使用 LocalPosition | 在涉及坐标运算时,尽量使用LocalPosition 替代Position |
Transform.Position 获取和设置涉及更多计算,LocalPosition 性能更好 |
在不影响功能的前提下,优先使用LocalPosition |
协程使用优化 | 减少协程创建开销,限制在游戏关键时刻调用StartCoroutine() ,合理控制协程数量,必要时手动关闭协程 |
StartCoroutine() 会产生少量内存垃圾,过多协程会增加资源消耗 |
在关键场景中谨慎使用协程,及时清理不再使用的协程 |
删除调试日志语句 | 在发布游戏前,禁用或删除日志语句,可通过定义调试开关控制日志输出 | 日志语句会降低性能 | 在开发阶段开启日志,发布时关闭 |
使用 ScriptableObject | 将不变的值或设置存储在ScriptableObject 中,在MonoBehaviour 中引用 |
避免数据重复,提高数据管理效率 | 对于全局配置数据,使用ScriptableObject 存储 |
避免在运行时添加组件 | 尽量在设计阶段完成组件添加,避免在运行时调用AddComponent |
运行时添加组件有开销,Unity 需要进行检查 | 提前规划好组件使用,减少运行时动态添加操作 |