最新文章
Cocos2d-x游戏开发实例详解7:对象释放时机
03-25 13:59
Cocos2d-x游戏开发实例详解6:自动释放池
03-25 13:55
Cocos2d-x游戏开发实例详解5:神奇的自动释放
03-25 13:49
Cocos2d-x游戏开发实例详解4:游戏主循环
03-25 13:44
Cocos2d-x游戏开发实例详解3:无限滚动地图
03-25 13:37
Cocos2d-x游戏开发实例详解2:开始菜单续
03-25 13:32
unity 移动平台优化
在移动平台开发中,Unity的优化至关重要。本文将深入探讨Unity移动平台的优化方法,特别是动态加载机制和内存管理,帮助开发者更好地控制资源使用,提升游戏性能。
动态加载机制
Unity3D中有两种主要的动态加载机制:Resources.Load和AssetBundle。虽然两者功能相似,但仍存在一些差异。
1. Resources.Load
Resources.Load是从默认打进程序包里的AssetBundle中加载资源。这种方式相对简单,但灵活性较低,因为资源必须预先打包到程序中。
2. AssetBundle
AssetBundle允许开发者在运行时动态加载资源,可以指定资源的路径和来源。开发者需要手动创建AssetBundle文件。
加载过程
- AssetBundle运行时加载:
CreateFromFile:这是最快的加载方法,但只能用于Standalone程序。它不会将整个硬盘的AssetBundle文件加载到内存,而是建立一个文件操作句柄和缓冲区,需要时才实时加载,节省资源。CreateFromMemory(byte[]):byte[]可以来自文件读取的缓冲、WWW的下载或其他方式。WWW.assetBundle:内部数据读取完后会自动创建一个AssetBundle。创建完成后,只是将硬盘或网络的文件读到内存的一个区域,形成AssetBundle内存镜像数据块,此时还没有Assets的概念。- Assets加载:
AssetBundle.Load(同Resources.Load):从AssetBundle的内存镜像中读取并创建一个Asset对象,同时分配相应内存用于存放(反序列化)。AssetBundle.LoadAsync:异步读取Asset对象。AssetBundle.LoadAll:一次读取多个Asset对象。- AssetBundle的释放:
AssetBundle.Unload(false):释放AssetBundle文件的内存镜像,但不包含通过Load创建的Asset内存对象。AssetBundle.Unload(true):释放AssetBundle文件的内存镜像,并销毁所有通过Load创建的Asset内存对象。
实例加载与引用关系
当从AssetBundle中加载一个Prefab时,它可能包含Gameobject、transform、mesh、texture、material、shader、script等各种Assets。实例化一个Prefab是一个克隆(Clone)和引用结合的过程:
GameObject和transform是克隆生成的新对象。Texture和TerrainData等是纯引用关系,只是一个简单的指针指向已经加载的Asset对象。Mesh、material、PhysicMaterial等存在引用和复制同时存在的情况。
对于Script Asset,Unity里每个Script都是一个封闭的Class定义,需要克隆一个script asset(相当于new一个class实例),并将其挂到Unity主线程的调用链里,Class实例里的OnUpdate、OnStart等方法才会被执行。
当销毁一个实例时,只会释放克隆对象,不会释放引用对象和克隆的数据源对象。只有当没有任何游戏场景物体使用这些Assets时,它们才成为无用的Assets,可以通过Resources.UnloadUnusedAssets来释放。
内存管理
Unity3D的内存占用问题一直是开发者关注的焦点,特别是在移动平台上。以下是一些有效的内存管理策略:
通用内存管理方法
- 创建时:
- 先建立一个
AssetBundle,可以从www、文件或内存中创建。 - 使用
AssetBundle.load加载需要的asset。 - 加载完成后立即调用
AssetBundle.Unload(false),释放AssetBundle文件本身的内存镜像,但不销毁加载的Asset对象。 - 释放时:
- 对
Instantiate的对象进行Destroy操作。 - 在合适的地方调用
Resources.UnloadUnusedAssets,释放已经没有引用的Asset。 - 如果需要立即释放内存,可以加上
GC.Collect()。
不同加载和初始化方式总结
- AssetBundle.CreateFrom.....:创建一个
AssetBundle内存镜像,注意同一个assetBundle文件在没有Unload之前不能再次使用。 - WWW.AssetBundle:与上述方法类似,需要先
new一个WWW对象,然后使用yield return,最后才能使用。 - AssetBundle.Load(name):从
AssetBundle读取一个指定名称的Asset并生成Asset内存对象,多次加载同名对象,除第一次外都只会返回已经生成的Asset对象。 - Resources.Load(path&name):从默认的位置加载
Asset。 - Instantiate(object):克隆一个对象的完整结构,包括其所有
Component和子物体,是浅拷贝,不复制所有引用类型。可以用于完整拷贝一个引用类型的Asset,但要拷贝的Texture必须设置为Read/Write able。
不同释放方式总结
- Destroy:主要用于销毁克隆对象,也可用于场景内的静态物体,但不会自动释放该对象的所有引用。用于销毁从文件加载的
Asset对象时会销毁相应的资源文件;如果销毁的Asset是复制的或用脚本动态生成的,只会销毁内存对象。 - AssetBundle.Unload(false):释放
AssetBundle文件内存镜像。 - AssetBundle.Unload(true):释放
AssetBundle文件内存镜像,同时销毁所有已经加载的Assets内存对象。 - Resources.UnloadAsset(Object):显式释放已加载的
Asset对象,只能卸载从磁盘文件加载的Asset对象。 - Resources.UnloadUnusedAssets:用于释放所有没有引用的
Asset对象。 - GC.Collect():强制垃圾收集器立即释放内存,Unity的
GC功能不算好,没把握时可以强制调用。
示例分析
示例1
常见错误:从某个AssetBundle里加载一个prefab并克隆,然后只使用Destroy销毁实例,以为就释放干净了。实际上,通过Load加载的所有引用、非引用Assets对象仍在内存中。此时应在Destroy后使用AssetBundle.Unload(true)彻底释放;如果AssetBundle要反复读取,可以在Destroy后使用Resources.UnloadUnusedAssets()。
示例2
从磁盘读取一个.unity3d文件到内存并建立一个AssetBundle对象,从中读取并创建一个Texture Asset,将其赋值给多个对象的主贴图。当卸载AssetBundle时,不同的卸载方式会有不同的结果。通过这个示例可以理解引用对象的管理和内存释放过程。
其他开发者观点
Hog认为Unity内存管理比较复杂,特别是涉及Texture时。使用AssetBundle加载的asset可以用Resources.UnloadUnusedAssets卸载,但必须先调用AssetBundle.Unload,这些对象才会被识别为无用的asset。保险的做法与上述通用内存管理方法一致。
深入理解Unity加载和内存管理机制
动态加载Prefab方式的差异
存在三种加载prefab的方式:
- 静态引用:创建一个
public的变量,在Inspector里将prefab拖上去,使用时进行instantiate操作。 - Resource.Load:加载后进行
instantiate操作。 - AssetBundle.Load:加载后进行
instantiate操作。
前两种方式,引用对象texture在instantiate时加载;而assetBundle.Load会将prefab的全部assets都加载,instantiate时只是生成克隆对象。因此,前两种方式如果不提前加载相关引用对象,第一次instantiate时会包含加载引用类assets的操作,导致第一次加载有延迟。
AssetBundle创建方式的差异
- CreateFromFile:最节省资源,只需要
Asset对象的内存,但只能在PC/Mac Standalone程序中使用。 - CreateFromMemory和www.assetBundle:会将
AssetBundle文件整个镜像到内存中,文件多大就需要多大的内存,加载Asset对象时还需要额外内存。
UnusedAssets的判断
UnusedAssets不仅要没有被实际物体引用,还要没有被生命周期内的变量所引用。例如,创建并销毁一个Prefab实例后,如果相关变量仍然引用着该Prefab,直接调用Resources.UnloadUnusedAssets不会释放内存,需要将变量设为null后再调用。
复杂示例分析
通过一个复杂的代码示例,展示了从加载AssetBundle、Texture、Prefab,到实例化、销毁实例,再到卸载AssetBundle和释放无用资源的整个过程。通过内存分析可以更直观地了解每个步骤的内存变化。
Unity 3D中的内存种类及优化
内存种类
Unity游戏使用的内存主要有三种:
- 程序代码:包括所有的Unity引擎、使用的库以及开发者编写的游戏代码。编译后的运行文件加载到设备中执行,占用一定内存,这部分内存无法管理,只能通过减少使用的库来优化。
- 托管堆(Managed Heap):由Mono使用,用于存放类的实例。Mono应该自动调整堆的大小,并定时进行垃圾回收。但开发者需要及时清除对不再使用的内存的引用,以确保回收机制正常运行。
- 本机堆(Native Heap):Unity引擎进行申请和操作的地方,如贴图、音效、关卡数据等。Unity有自己的内存管理机制,但自动加载资源容易导致大量内存占用。
优化方法
优化程序代码的内存占用
主要通过减少打包时的引用库来实现。在Player Setting面板中,可以调整Api Compatibility Level和Stripping Level,选择合适的库剥离选项。但需要注意,过度剥离可能导致程序崩溃,可使用第三方轻量级类库来解决依赖问题。
托管堆优化
- 尽早去除不需要的引用,让垃圾回收机制及时清理内存。但要注意内存清理可能导致游戏卡顿,尽量选择合适的时间进行大量内存回收。
- 对于大量需要频繁创建和销毁的对象,可以采用对象重用的方法,将不需要的对象隐藏并放入重用数组,需要时再取出使用。
- 尽量减少在游戏进行过程中对
GameObject的Instantiate()和Destroy()调用,可选择在合适的时间批量销毁和回收内存。也可以手动调用System.GC.Collect()建议系统进行垃圾回收。
本机堆的优化
- 减少在
Hierarchy中对资源的直接引用,使用Resource.Load方法在需要时从硬盘读取资源,并及时使用Resource.UnloadAsset()和Resources.UnloadUnusedAssets()卸载资源。 - 避免在
DontDestroyOnLoad的GameObject上挂载包含大量资源的脚本,防止这些资源在场景切换时无法卸载。 - 减少代码的耦合和对其他脚本的依赖,手动对不再使用的引用对象调用
Destroy()或设置为null。
总之,要实现Unity移动平台的优化,需要深入理解其加载和内存管理机制,根据具体情况选择合适的优化方法,并参考Unity3D的技术手册,以达到最佳的优化效果。