Unity-Profiler性能分析器的使用

长话短说,我整理了一下mac版Unity3D
pro下的Profiler连接Android的使用。注意是专业版的Unity3D。在Window下会有Profiler

这个工具的意义是,游戏在客户端跑然后UnityProfile测试一些参数

连接手机有两种方式。

1.wifi:让手机和电脑痛处于同一局域网内。

2.数据线连接(下好各种驱动),一般只要Unity中 Build And Run 时游戏能在客户端跑 就基本连接没问题

1.File->Build Setting

注意勾选 Development Build

点击 Player Settings 找到ProductName 记录下来

2.数据线连接手机,File->Build And Run

3.打开终端,找到adb的文件

cd /Applications/android/adt-bundle-mac-x86_64-20140702/sdk/platform-tools

每个mac 不一样

./adb forward tcp:54999 localabstract:Unity-<包名>

4.window->profiler 同时手机运行游戏

如果用wifi连接 先Enter IP 输入手机在局域网中ip即会成为2

如果是数据线 直接选一

ok,随玩游戏随测,数据线连接的话 ,注意不要断开连接。

Unity图集的理解和使用

图集的好处:

1.减少draw call: 多张图片需要多次draw call,合成了一张大图则只需要一次draw call。

2.减少内存占用:OpenGL
ES中每张贴图都需要设置成2的n次方才能使用。比如你有一张宽高为100x100和一张宽高为10x10的图片,如果不合成大贴图,那么需要使用128x128和16x16的两张图片(分别是2的7次方和2的4次方),但如果使用一张大图的话,可以把100x100和10x10的图片放到128x128的大图中,这样就用一张图片。

UGUI的图集打包与工作原理,整整看了一天多,终于看明白了~晕~还是记录一下我研究的成果,也希望大家在下面给我留言我们一起讨论一下。

先说说UGUI的Atlas和NGUI的Atlas的区别,NGUI是必须先打出图集然后才能开始做界面。这一点很烦,因为始终都要去考虑你的UI图集。比如图集会不会超1024
,图集该如何来规划等等。而UGUI的原理则是,让开发者彻底模糊图集的概念,让开发者不要去关心自己的图集。做界面的时候只用小图,而在最终打包的时候unity才会把你的小图和并在一张大的图集里面。然而这一切一切都是自动完成的,开发者不需要去care它。

如下图所示,Editor->Project Settings 下面有sprite packer的模式。Disabled表示不启用它,Enabled For
Builds 表示只有打包的时候才会启用它,Always Enabled 表示永远启用它。 这里的启用它就表示是否将小图自动打成图集。

我的选项是Always Enabled 。因为开发的时候我们需要清楚的看到现在是几个Draw
Call,从而才能优化小图。在最终打包的时候unity会自动构建大的图集,可是我开发的时候就想看图集会占几个Draw
Call,这怎么办呢?如下图所示,首先将你的图片拖入unity中,将同一图集的所有图片的packing tag设置成一个名子即可。

注意你的图片不能放在Resources文件夹下面,Resources文件夹下的资源将不会被打入图集,切记(也就是在这里混淆了我很久)。然后在Windows->Sprite
Packer 里,点击packer
在这里你就可以预览到你的图集信息。图集的大小还有图集的格式等等很多参数我们都是可以控制的,也可以通过脚本来设置。我在下一篇文章里详细说这个(请期待嘿嘿)。

图集的预览紧紧是让你看看你的图集大概张什么样子。那么我们的图集的这张图片保存在了哪里呢?它保存在和Assets文件夹同级的目录,Libary/AtlasCache里面。你不用管它,也不要删除它,就算你删除了也没用因为只要你打包,它就会生成并且会打到包中。

此时在Hierarchy视图中创建两个Image对象。如下图所示,我们可以清楚的看到此时我的draw call已经被合并成了1 。

这两个图片是我是在Editor模式下预先拖入Hierarchy视图中的,可是如果我想运行时根据图片的名子来动态创建精灵该如何?可是unity根本没有提供加载图集的方法,也没有提供加载图集上某个图片的方法。
因为UGUI就不像让开发者有图集的这个概念,可是我们肯定是要实现这个需求的。。怎么办呢?

第一个设想,先把散=小图打包成图集,然后再把所有散图拷贝在Resources文件夹下,这样运行时就能用Resources.load了。

第二个设想,还是先把小图打成图集,然后把所有小图关联在prefab上,拷贝在Resources文件夹下,这样运行时也能用Resources.load了。到底那个靠谱呢?
给大家看一个图大家就知道答案了。

如下图所示,打成图集的图片如果在放在Resources那么资源就变成双份了。。
所以我们只能把小图关联在Prefab上,把所有的Prefab放在Resources下面,这样就不占用多余的空间了。

好了,现在方法我们已经掌握,那么就开始写工具吧。如下图所示可以按文件夹分,每一个文件夹就是一个图集。然后每一张小图创建一个Prefab,Prefab的名子就起小图的名子,文件关联在Resources下面。

代码比较简单,我就不注释了。

[MenuItem (“MyMenu/AtlasMaker”)]

static private void MakeAtlas()

{

string spriteDir = Application.dataPath +”/Resources/Sprite”;

if(!Directory.Exists(spriteDir)){

Directory.CreateDirectory(spriteDir);

}

DirectoryInfo rootDirInfo = new DirectoryInfo (Application.dataPath
+”/Atlas”);

foreach (DirectoryInfo dirInfo in rootDirInfo.GetDirectories()) {

foreach (FileInfo pngFile in
dirInfo.GetFiles(“*.png”,SearchOption.AllDirectories)) {

string allPath = pngFile.FullName;

string assetPath = allPath.Substring(allPath.IndexOf(“Assets”));

Sprite sprite = Resources.LoadAssetAtPath(assetPath);

GameObject go = new GameObject(sprite.name);

go.AddComponent().sprite = sprite;

allPath = spriteDir+”/“+sprite.name+”.prefab”;

string prefabPath = allPath.Substring(allPath.IndexOf(“Assets”));

PrefabUtility.CreatePrefab(prefabPath,go);

GameObject.DestroyImmediate(go);

}

}

}

然后是运行时的代码。

using UnityEngine;

using System.Collections;

using UnityEngine.UI;

public class UIMain : MonoBehaviour {

void Start ()

{

CreatImage(loadSprite(“image0”));

CreatImage(loadSprite(“image1”));

}

private void CreatImage(Sprite sprite ){

GameObject go = new GameObject(sprite.name);

go.layer = LayerMask.NameToLayer(“UI”);

go.transform.parent = transform;

go.transform.localScale= Vector3.one;

Image image = go.AddComponent();

image.sprite = sprite;

image.SetNativeSize();

}

private Sprite loadSprite(string spriteName){

return Resources.Load(“Sprite/“ +
spriteName).GetComponent().sprite;

}

}

因为这两个图是在同一个图集上,所以drawcall就是1了。这样我们就可以根据图片的名子来运行时加载图片了。

接下来就是Assetbundle了,如果我们的图集需要在线更新那该怎么办呢?
其实Assetbundle比Resources要更简单一些,无论如何我们要先开始打图集。

[MenuItem (“MyMenu/Build Assetbundle”)]

static private void BuildAssetBundle()

{

string dir = Application.dataPath +”/StreamingAssets”;

if(!Directory.Exists(dir)){

Directory.CreateDirectory(dir);

}

DirectoryInfo rootDirInfo = new DirectoryInfo (Application.dataPath
+”/Atlas”);

foreach (DirectoryInfo dirInfo in rootDirInfo.GetDirectories()) {

List assets = new List();

string path = dir +”/“+dirInfo.Name+”.assetbundle”;

foreach (FileInfo pngFile in
dirInfo.GetFiles(“*.png”,SearchOption.AllDirectories))

{

string allPath = pngFile.FullName;

string assetPath = allPath.Substring(allPath.IndexOf(“Assets”));

assets.Add(Resources.LoadAssetAtPath(assetPath));

}

if(BuildPipeline.BuildAssetBundle(null, assets.ToArray(),
path,BuildAssetBundleOptions.UncompressedAssetBundle|
BuildAssetBundleOptions.CollectDependencies, GetBuildTarget())){

}

}

}

static private BuildTarget GetBuildTarget()

{

BuildTarget target = BuildTarget.WebPlayer;

#if UNITY_STANDALONE

target = BuildTarget.StandaloneWindows;

#elif UNITY_IPHONE

target = BuildTarget.iPhone;

#elif UNITY_ANDROID

target = BuildTarget.Android;

#endif

return target;

}

如下图所示,我的assetbundle已经打出来了。

然后把UIMain.cs在改一改。

using UnityEngine;

using System.Collections;

using UnityEngine.UI;

public class UIMain : MonoBehaviour {

AssetBundle assetbundle = null;

void Start ()

{

CreatImage(loadSprite(“image0”));

CreatImage(loadSprite(“image1”));

}

private void CreatImage(Sprite sprite ){

GameObject go = new GameObject(sprite.name);

go.layer = LayerMask.NameToLayer(“UI”);

go.transform.parent = transform;

go.transform.localScale= Vector3.one;

Image image = go.AddComponent();

image.sprite = sprite;

image.SetNativeSize();

}

private Sprite loadSprite(string spriteName){

#if USE_ASSETBUNDLE

if(assetbundle == null)

assetbundle = AssetBundle.CreateFromFile(Application.streamingAssetsPath
+”/Main.assetbundle”);

return assetbundle.Load(spriteName) as Sprite;

#else

return Resources.Load(“Sprite/“ +
spriteName).GetComponent().sprite;

#endif

}

}

如下图所示,依然还是一个drawcall。

衷心希望有经验的朋友在留言处给我提提意见, 或者大家一起讨论讨论。。 我们共同为把NGUI干掉的目标而奋斗,嘻嘻。

unity性能优化内存篇

资源内存占用

在一个较为复杂的大中型项目中,资源的内存占用往往占据了总体内存的70%以上。因此,资源使用是否恰当直接决定了项目的内存占用情况。一般来说,一款游戏项目的资源主要可分为如下几种:纹理(Texture)、网格(Mesh)、动画片段(AnimationClip)、音频片段(AudioClip)、材质(Material)、着色器(Shader)、字体资源(Font)以及文本资源(Text
Asset)等等。其中,纹理、网格、动画片段和音频片段则是最容易造成较大内存开销的资源。

纹理资源可以说是几乎所有游戏项目中占据最大内存开销的资源。一个6万面片的场景,网格资源最大才不过10MB,但一个2048x2048的纹理,可能直接就达到16MB。因此,项目中纹理资源的使用是否得当会极大地影响项目的内存占用。

那么,纹理资源在使用时应该注意哪些地方呢?

(1) 纹理格式

纹理格式是研发团队最需要关注的纹理属性。因为它不仅影响着纹理的内存占用,同时还决定了纹理的加载效率。一般来说,我们建议开发团队尽可能根据硬件的种类选择硬件支持的纹理格式,比如Android平台的ETC、iOS平台的PVRTC、Windows
PC上的DXT等等。 因此,我们在UWA测评报告中,将纹理格式进行详细罗列,以便开发团队进行快速查找,一步定位。

在使用硬件支持的纹理格式时,你可能会遇到以下几个问题:

色阶问题

由于ETC、PVRTC等格式均为有损压缩,因此,当纹理色差范围跨度较大时,均不可避免地造成不同程度的“阶梯”状的色阶问题。因此,很多研发团队使用RGBA32/ARGB32格式来实现更好的效果。但是,这种做法将造成很大的内存占用。比如,同样一张1024x1024的纹理,如果不开启Mipmap,并且为PVRTC格式,则其内存占用为512KB,而如果转换为RGBA32位,则很可能占用达到4MB。所以,研发团队在使用RGBA32或ARGB32格式的纹理时,一定要慎重考虑,
更为明智的选择是尽量减少纹理的色差范围,使其尽可能使用硬件支持的压缩格式进行储存。

ETC1 不支持透明通道问题

在Android平台上,对于使用OpenGL ES
2.0的设备,其纹理格式仅能支持ETC1格式,该格式有个较为严重的问题,即不支持Alpha透明通道,使得透明贴图无法直接通过ETC1格式来进行储存。对此,我们建议研发团队将透明贴图尽可能分拆成两张,即一张RGB24位纹理记录原始纹理的颜色部分和一张Alpha8纹理记录原始纹理的透明通道部分。然后,将这两张贴图分别转化为ETC1格式的纹理,并通过特定的Shader来进行渲染,从而来达到支持透明贴图的效果。
该种方法不仅可以极大程度上逼近RGBA透明贴图的渲染效果,同时还可以降低纹理的内存占用,是我们非常推荐的使用方式。

当然,目前已经有越来越多的设备支持了OpenGL ES
3.0,这样Android平台上你可以进一步使用ETC2甚至ASTC,这些纹理格式均为支持透明通道且压缩比更为理想的纹理格式。如果你的游戏适合人群为中高端设备用户,那么不妨直接使用这两种格式来作为纹理的主要存储格式。

(2)纹理尺寸

一般来说,纹理尺寸越大,则内存占用越大。所以,尽可能降低纹理尺寸,如果512x512的纹理对于显示效果已经够用,那么就不要使用1024x1024的纹理,因为后者的内存占用是前者的四倍。
因此,我们在UWA测评报告中,将纹理的尺寸进行详细展示,以便开发团队进行快速检测。

** **

(3) Mipmap功能

Mipmap旨在有效降低渲染带宽的压力,提升游戏的渲染效率。但是,开启Mipmap会将纹理内存提升1.33倍。对于具有较大纵深感的3D游戏来说,3D场景模型和角色我们一般是建议开启Mipmap功能的,但是在我们的测评项目中,经常会发现部分UI纹理也开启了Mipmap功能。这其实就没有必要的,绝大多数UI均是渲染在屏幕最上层,开启Mipmap并不会提升渲染效率,反倒会增加无谓的内存占用。
因此,建议研发团队在UWA的测评报告中通过Mipmap一项进行排序,详细检测开启Mipmap功能的资源是否为UI资源。

(4) Read & Write

一般情况下,纹理资源的“Read & Write”功能在Unity引擎中是默认关闭的。但是,我们仍然在项目深度优化时发现了不少项目的纹理资源会开启该选项。
对此,我们建议研发团队密切关注纹理资源中该选项的使用,因为开启该选项将会使纹理内存增大一倍。

网格资源在较为复杂的游戏中,往往占据较高的内存。对于网格资源来说,它在使用时应该注意哪些方面呢?

(1) Normal、Color和Tangent

在我们深度优化过的大量项目中,Mesh资源的数据中经常会含有大量的Color数据、Normal数据和Tangent数据。这些数据的存在将大幅度增加Mesh资源的文件体积和内存占用。其中,Color数据和Normal数据主要为3DMax、Maya等建模软件导出时设置所生成,而Tangent一般为导入引擎时生成。

更为麻烦的是,如果项目对Mesh进行Draw Call
Batching操作的话,那么将很有可能进一步增大总体内存的占用。比如,100个Mesh进行拼合,其中99个Mesh均没有Color、Tangent等属性,剩下一个则包含有Color、Normal和Tangent属性,那么Mesh拼合后,CombinedMesh中将为每个Mesh来添加上此三个顶点属性,进而造成很大的内存开销。
正因如此,我们在UWA测评报告中为每个Mesh展示了其Normal、Color和Tangent属性的具体使用情况,研发团队可以直接针对每种属性进行排序查看,直接定位出现冗余数据的资源。

一般来说这些数据主要为Shader所用,来生成较为酷炫的效果。所以,建议研发团队针对项目中的网格资源进行详细检测,查看该模型的渲染Shader中是否需要这些数据进行渲染。

限于篇幅,我们今天只针对纹理和网格资源进行详细介绍,对于动画片段、音频片段等其他资源,建议您直接通过UWA测评报告中进行查看。同时,我们会在后续的资源专题中进行详细讲解,敬请期待。

引擎模块自身占用

引擎自身中存在内存开销的部分纷繁复杂,可以说是由巨量的“微小”内存所累积起来的,比如GameObject及其各种Component(最大量的Component应该算是Transform了)、ParticleSystem、MonoScript以及各种各样的模块Manager(SceneManager、CanvasManager、PersistentManager等)…

一般情况下,上面所指出的引擎各组成部分的内存开销均比较小,真正占据较大内存开销的是这两处: WebStream
SerializedFile 。其绝大部分的内存分配则是由AssetBundle加载资源所致。简单言之,当您使用new
WWW或CreateFromMemory来加载AssetBundle时,Unity引擎会加载原始数据到内存中并对其进行解压,而WebStream的大小则是AssetBundle原始文件大小

  • 解压后的数据大小 + DecompressionBuffer(0.5MB)。同时,由于Unity

5.3版本之前的AssetBundle文件为LZMA压缩,其压缩比类似于Zip(20%-25%),所以对于一个1MB的原始AssetBundle文件,其加载后WebStream的大小则可能是5~6MB,
因此,当项目中存在通过new
WWW加载多个AssetBundle文件,且AssetBundle又无法及时释放时,WebStream的内存可能会很大,这是研发团队需要时刻关注的。

对于SerializedFile,则是当你使用LoadFromCacheOrDownload、CreateFromFile或new
WWW本地AssetBundle文件时产生的序列化文件。

对于WebStream和SerializedFile,你需要关注以下两点:

是否存在AssetBundle没有被清理干净的情况。开发团队可以通过Unity Profiler直接查看其使用具体的使用情况,并确定Take
Sample时AssetBundle的存在是否合理;

对于占用WebStream较大的AssetBundle文件(如UI
Atlas相关的AssetBundle文件等),建议使用LoadFromCacheOrDownLoad或CreateFromFile来进行替换,即将解压后的AssetBundle数据存储于本地Cache中进行使用。这种做法非常适合于内存特别吃紧的项目,即通过本地的磁盘空间来换取内存空间。

注意:关于AssetBundle的详细管理机制,建议查看我们之前的AssetBundle技术文章

托管堆内存占用

对于目前绝大多数基于Unity引擎开发的项目而言,其托管堆内存是由Mono分配和管理的。“托管”
的本意是Mono可以自动地改变堆的大小来适应你所需要的内存,并且适时地调用垃圾回收(Garbage
Collection)操作来释放已经不需要的内存,从而降低开发人员在代码内存管理方面的门槛。

但是这并不意味着研发团队可以在代码中肆无忌惮地开辟托管堆内存,因为目前Unity所使用的Mono版本存在一个很严重的问题,即:Mono的堆内存一旦分配,就不会返还给系统。这意味着Mono的堆内存是只升不降的。
举个例子,项目运行时,在场景A中开辟了60MB的托管堆内存,而到下一场景B时,只需要使用20MB的托管堆内存,那么Mono中将会存在40MB空闲的堆内存,且不会返还给系统。这是我们非常不愿意看到的现象,因为对于游戏(特别是移动游戏)来说,内存的占用可谓是寸土寸金的,让Mono毫无必要地锁住大量的内存,是一件非常浪费的事情。
所以,我们在UWA测评报告中,为研发团队统计了测试过程中累积的函数堆内存分配量,大家只需要通过查看堆内存分配Top10的函数,即可快速对其底层代码实现进行查看,定位是否有分配不必要堆内存的代码存在。

读到这里,你可能会产生这样的疑问: 我知道了哪些函数的堆内存分配大了,但是我该如何去进一步定位不必要的堆内存呢?

这是我们经常遇到的问题,所以在我们的深度项目优化服务中,我们都会直接进驻到项目团队,现场查看项目代码并对问题代码进行定位。在经过了大量的深度检测后,我们发现用户不必要的堆内存分配主要来自于以下几个方面:

高频率地 New Class/Container/Array等。
研发团队切记不要在Update、FixUpdate或较高调用频率的函数中开辟堆内存,这会对你的项目内存和性能均造成非常大的伤害。
做个简单的计算,假设你的项目中某一函数每一帧只分配100B的堆内存,帧率是1秒30帧,那么1秒钟游戏的堆内存分配则是3KB,1分钟的堆内存分配就是180KB,10分钟后就已经分配了1.8MB。如果你有10个这样的函数,那么10分钟后,堆内存的分配就是18MB,这期间,它可能会造成Mono的堆内存峰值升高,同时又可能引起了多次GC的调用。在我们的测评项目中,一个函数在10分钟内分配上百MB的情况比比皆是,有时候甚至会分配上GB的堆内存。

Log输出。我们发现在大量的项目中,仍然存在大量Log输出的情况。建议研发团队对自身Log的输出进行严格的控制,仅保留关键Log,以避免不必要的堆内存分配。
对此,我们在UWA测评报告中对Log的输出进行了详细的检测,不仅提供详细的性能开销,同时占用Log输出的调用路径。这样,研发团队可直接通过报告定位和控制Log的输出。

UIPanel.LateUpdate。这是NGUI中CPU和堆内存开销最大的函数。它本身只是一个函数,但NGUI的大量使用使它逐渐成为了一个不可忽视规则。该函数的堆内存分配和自身CPU开销,其根源上是一致的,即是由UI网格的重建造成。因此,其对应的优化方法是直接查看CPU篇中的UI模块讲解。

关于代码堆内存分配的注意点还有很多,比如String连接、部分引擎API(GetComponent)的使用等等,这些已经是老生常谈了,鉴于篇幅限制不在此处多作介绍,大家感兴趣可以Google自行搜索。后续也会有专门的代码效率专题讲解,敬请关注。

UWA测评的内存标准

在大家使用过UWA之后,对于UWA推荐的内存标准值提出了很大的疑惑。在这里,我们也分享下UWA内存标准的制定规则。

(1)150MB的总体内存标准主要由以下两个因素得出:

经过了大量的项目优化后总结而得。
其实,对于目前市场主流的Unity游戏来说,其内存占用主要集中在120~200MB。同时,顾及到iPhone4和512MB/768MB等低端Android机型,其应用的自身总体内存占用不可超过200MB(iPhone4的安全线应该在180MB左右),所以我们将Reserved
Total设定在150MB,这是Unity引擎的自身内存分配,以保证App在使用到的系统库后,其OS中的整体内存也在200MB以下。

某些渠道对Android游戏的PSS内存进行了严格的限制。 一般要求游戏的PSS内存在200MB以下。这是我们将Reserved
Total内存设定在150MB的另外一个重要原因。

(2)当总体内存设定为150MB后,我们进一步对其具体分配进行了设定。但需要说明的是,这里的内存分配其实并没有严格的公式来进行论证,仅是我们在大量的项目优化工作中提炼出的经验值。目前,项目较为合理的内存分配如下:

纹理资源: 50 MB

网格资源: 20 MB

动画片段: 15 MB

音频片段: 15 MB

Mono堆内存: 40 MB

其他: 10 MB

需要指出的是,150MB中并没有涵盖较为复杂的字体文件(比如微软雅黑)以及Text Asset,这些需要根据游戏需求而定。

(3)目前的UWA内存标准是较为苛刻的,对于中高端设备而言,其内容允许量其实要比150MB要大得多。但我们坚持认为,在研发过程中,一个严苛的标准对于一个项目来说是一件好事。至少,它可以为大家提个醒,让大家时刻关注自己的问题。据我们了解,目前的三到五线城市,其低端手机的覆盖率还是相当高的。同时,对于中高端移动设备,我们仍在不断试验和研究中。我们希望在不久的将来可以做到针对各种不同档次的机型都给出一个更为合理的推荐值,从而让大家更为简单地对内存进行管理。

以上所说的是游戏项目中主要的内存分配情况,希望读到此处的你,可以更加了解Unity项目的内存开销和潜在问题,并对自己的项目进行更有针对性的检测。

除以上内容外,还有两个更为重要的地方需要研发团队关注: 内存泄露资源冗余
。我们将在下一篇内存优化文章中为您带来相关分享。同时,不同的项目遇到的问题不尽相同。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×