性能优化示例Git仓库:https://github.com/WanKcn/Suntail_Village_Optimization
详细说明见仓库内README。
示例项目《Suntail_Village_Optimization》(以下简称项目SV)
学习地址:B站Metaverse大衍神君老师《Unity性能优化》系列课程
CSDN上的性能优化相关文章与学习笔记均存放在仓库/Documents
目录下
Unity中的资源有哪些?
在Unity中,资产可以是项目中用于创建游戏或者应用程序的任何对象。这些资源大致可以分为两类
上面的截图是Unity官方文档资产工作流程图。每一列代表一个步骤,使用资源的工作流程分为五步:
Unity创建的资源为什么要涉及到导入问题呢?
不管是Unity内部创建还是外部导入的资源,都会涉及到Unity导入问题。这是由于创建资源在不同平台下可能会涉及到导入设置的改变。或创建的资源在不同开发平台的机器上打开,或使用到了其他三方开发包,他们都需要进行资源的导入。在不同平台,合理的资源导入设置与资源规格可以为应用程序带来较高效率,反之运行效率会变差。 性能优化关于对资源的优化从这五个步骤入手。
结合案例SV项目,在《【Unity性能优化】项目优化前需要进行哪些准备工作》一文中,对SV项目的资源分布做了大致的分析。
SV项目中,预制件,音效,材质,模型,纹理资源使用情况较重。可能会存在导入设置不合理,从而带来性能瓶颈,则需要对这些资源进行检查并优化。
上面的截图也看到了,大量的资源。如果对每一个资源进行检查分析,工程量巨大且不太现实。这里借助Unity提供的Asset Checker来完成资源检查工作。
Asset Checker下载地址:https://upr.unity.cn/instructions/assetchecker
下载好了以后根据操作手册进行配置。官方的操作手册是以Win为示例,并没有Mac配置方法,基本都差不多,仔细阅读文档即可。
另外我在B站找到了一个Up主分享的关于Asset Checker for Mac的教程视频作为辅助参考:Unity资源检测工具Asset Checher for Mac使用分享uinty资产检查工具
需要注意的是,Mac下很可能出现非法开发者无法访问的情况,则需要先在终端运行命令sudo spctl --master-disable
之后再跑配置命令assetcheck.exe --project=
。
项目id的申请,需要登录本人UnityID,创建项目,我这里创建为SV_Test。
打开创建的项目,可以在左侧基本信息拿到ProjectID。
在Mac下示例:
mac下解压后的assetcheck路径 --项目路径 --申请的项目id
等待一段时间后(网络较差时,需要多等待一些时间),直到进度为100%。此时刷新UPR首页来到我的项目/SV_Test/资源监测
可以看到检查结果报告。
打开使用Asset Checker获得的资源检查报告
通过上图可以看到,工具检查了84个资源,对其中的75个提出了改进建议。
其中有73份文件建议启用forceMono,以节省内存和包体大小。可以在SV工程中看到建议修改的音频资源Force To Mono选项并没有开启。
为什么建议通过勾选Force To Mono来优化音频呢?
被建议的音频是双声道音频,且左右两声道的音乐完全相同,可以用勾选ForceToMono的方式强制将此音频修改为单声道,内容不丢失的情况下可以减少它的使用内存和大小。特别是在移动平台下几乎听不出任何区别。如果左右声道内容不同,开启ForceToMono会导致听到的声音错误。
另外,需要注意这里的一下音频详细信息,如下图:
红色框出的部分显示的是音频 原始资源大小 、导入压缩后的大小 、和整体压缩比 。
一般情况下,应该尽可能使用未压缩的wav文件作为音频源文件,通过不同平台支持的压缩格式控制压缩比。一般移动平台下,unity下大多数音频文件采用Vorbis压缩方法。如果音乐不循环可以使用MP3格式。一些操作系统对特定的压缩格式有额外的优化,比如在iOS系统上可以使用MP3格式。此外一些简短常用的音效可以使用ADPCM格式。虽然这种格式的压缩比可能不是最好的,但在播放过程中解码速度很快。总之,音频压缩策略需要考虑不同压缩格式在不同平台下的特点,以及音乐音效文件在不同用途下使用不同的压缩格式。
关注音频源文件的采样率
通常在移动平台都会选择对音质影响最小的最低设置,一般移动平台音频采样率经验数据建议设置为22050Hz。可以看到该音频源文件的音频采样率是4.8w赫兹,在移动平台上完全没有必要,只会徒增文件大小和内存占用。
如果要修改默认的音频采样频率,可以通过Sample Rate Setting下拉菜单选择复写采样频率,并修改采样频率为22050Hz,记得点击Apply。与上面的音频信息对比可以发现导入后的大小从原来的16.3kb缩减为8.7kb,越低采样频率生产的音频文件会越小。
回到检测报告,如下图,报告对不同的音频类型,音效和音乐使用不同的加载类型。
对应unity音频inspector窗口的Load Type设置。它影响的是unity的Asset工作流程图中的第五步(加载)。
如上图,加载类型有三种,默认是Decompress On Load,该选项一般对应音频压缩后大小<200kb的音效文件,如果音效文件大于200kb,则推荐第二种类型Compressed In Memory。如果是背景音乐文件,或较大较长的音效文件。则推荐使用Streaming流加载,可以避免载入时卡顿。
此外,当游戏需要静音时,不要将音量设置为0,应该销毁音频audiosource组件把它从内存中完全卸载。 音乐音效一般不会成为性能瓶颈,但优化音乐音效可以减少对内存的使用与包体大小,使用了第三方带有复杂逻辑的库除外。
这里我用python写了一个批处理工具。使用需要注意几个目录的更换和具体优化方法的调用
TextPath:把资源报告中的文件列表复制到文本中。
ProjectDir:工程目录
BackUpDir:修改文件的备份目录
留意代码中的opt_file
方法,数据修改部分根据需求调用不同的方法。
# Author: 文若
# CreateDate: 2022/9/15
"""
Audio_Optimize 优化工具
"""
import os
import os.path
from shutil import copyfile
# Unity工程目录,注意后面有个"/"
ProjectDir = "/Users/wankcn/Desktop/EditorTest/"
# 文本文件目录 需要拷贝性能报告到文本里 我这里命名为audio.txt
TextPath = "/Users/wankcn/Desktop/audio_txt"
# 源文件备份目录
BackUpDir = ProjectDir + "BackUp/"
# 测试文件
TestFile = "Assets/Suntail Village/Audio/Enviroment/Items/Item_Food_Large_2.wav"
def read_file(src_path):
"""全文读取"""
# if os.path.isfile(src_path) and os.path.splitext(src_path)[-1].lower() == '.txt':
if os.path.isfile(src_path):
with open(src_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 需要去掉"\n"
for i in range(len(lines)):
lines[i] = lines[i].rstrip()
return lines
else:
return None
def read_file2(src_path):
"""行读取"""
lines = []
if os.path.isfile(src_path):
with open(src_path, 'r', encoding='utf-8') as f:
while True:
line = f.readline()
if not line:
break
lines.append(line.strip()) # 必要步骤
return lines
else:
return None
def get_audiofile_data(full_src_path):
"""获取的文件内容"""
# 内容存入字典 {name:[索引,前半段,后半段]} 用:分割
dic = {}
files = read_file(full_src_path)
for i in range(len(files)):
strs = files[i].split(':')
key = strs[0].strip()
dic[key] = [i, strs[0], strs[1]]
return dic
def copy_file(source_path):
if not os.path.exists(BackUpDir):
os.makedirs(BackUpDir)
print(">> 指定的备份目录不存在,创建完成 >>")
# 拷贝一份源meta文件到新目录中
new_file_name = source_path.split('/')[-1]
new_path = BackUpDir + new_file_name
if not os.path.exists(new_path):
print(">>【备份完成】 {0}".format(new_path))
copyfile(source_path, new_path)
else:
print(">>【文件已存在】终止操作!!地址为: {1}".format(new_file_name, new_path))
def write_file(file_name, data):
with open(file_name, "w", encoding="utf-8") as f:
for k, v in data.items():
f.write(v[1] + ':' + v[2] + "\n")
print(">>【写入完成】{0}".format(file_name))
def opt_force_to_mono(data):
"""对文本内容进行处理 代码还可以再优化"""
rate = data['sampleRateOverride'][2]
# 修改音频采样率
if int(rate) >= 22050:
data['sampleRateOverride'][2] = " 22050"
data['sampleRateSetting'][2] = " 2"
# 勾选ForeToMono
data['forceToMono'][2] = " 1"
return data
def opt_compressed_in_memory(data):
data['loadType'][2] = " 1"
return data
def opt_streaming(data):
data['loadType'][2] = " 2"
return data
def opt_file(file_name):
# 拼接目录,修改AudioClip.meta
full_src_path = os.path.join(ProjectDir, file_name + ".meta")
# 文件备份
copy_file(full_src_path)
# 数据结构
dic = get_audiofile_data(full_src_path)
# 修改数据
data = opt_force_to_mono(dic)
# data = opt_compressed_in_memory(dic)
# data = opt_streaming(dic)
# 写入数据
write_file(full_src_path, data)
def opt(path):
files = read_file(path)
for f in files:
opt_file(f)
if __name__ == '__main__':
opt(TextPath)
因为我没有安卓手机,所以我这里只对比安卓包体优化前后的大小。
安卓打包后的对比,左边是优化后,右边是优化前,虽然只缩减了0.1MB哈哈,但是包体是有明显减小的。这是因为我在asset下又相比之前加了优化工具和一些编辑器脚本等文件如下图是22个较大的脚本改动。不过结果很直观,优化是有效果的。
如果安卓包不够明显,接下来来看iPhone11的实际效果
1.先来看包体大小,下面第一张是我从上一遍文章中截的图,当时留有包大小的截图。第二张是我刚才新打包后的包体大小截图。非常明显,包体减小了25MB以上。
2.再来看游戏运行时的profiler情况。
第一张图是优化前,第二张图是优化后。真的是看到这个结果我非常的开心!瞬间写了一天的优化工具值了!
优化前 | 优化后 | |
---|---|---|
TotalAudioCpu | 0.6% | 3.0% |
DPSCpu | 0.5% | 0.6% |
StreamingCpu | 0% | 2.3% |
TotalAudioMemory | 76.8M | 7.1M |
SampleSoundMemory | 74.3M | 1.6M |
可以通过图表非常直观看到,开启相同音源情况下,优化后内存的占用比之前要少了近70兆,而Cpu的负载仅提高了2%。这是因为一部分音频loadtype改成了streaming,额外产生了一些cpu开销。与内存方面相比,这个优化是非常值得的,尤其在正式的工程中含有大量的音频文件,这一优化变得非常有价值。