干货预警!!!

一、资源加载与使用

1】所有Assets目录下的资源都可以被AssetDatabase类访问并加载。
资源分为两类:一类时引擎可识别的资源,prefab,声音,视频,动画,UI等;第二类是第三方导入的,通过第三方工具解析成引擎可识别的资源。
一些常用操作的API

AssetDatabase.LoadAssetAtPath("相对于Assets的相对路径");
AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Cube.prefab");
AssetDatabase.DeleteAsset(path);  //删除某个资源文件
//加载自定义的资源
AnimInfoList rList = AssetDatabase.LoadAssetAtPath(animInfoFilePath, typeof(AnimInfoList)) as AnimInfoList;
rList = GameObject.Instantiate(rList);  //实例化资源
AssetDatabase.CreateAsset(rList, animInfoFilePath);  //创建资源
AssetDatabase.SaveAssets();  //保存资源
AssetDatabase.Refresh(); //刷新Project视图目录

//获取所有依赖
string[] deps = AssetDatabase.GetDependencies(uiFilePath)
//获取子文件夹
string[] subFolders = AssetDatabase.GetSubFolders(searchPath_)
//获取资源路径
AssetDatabase.GetAssetPath(obj)

2】编辑模式下可以这样实例化prefab(FBX是一种特殊的引用关系的资源,也可以这样实例化)

//这样实例化会保持原有的引用关系,并非克隆一份新的
Gameobject go = PrefabUtility.InstantiatePrefab(prefab) as Gameobject; 
//设置父物体的时候,如果传入false,则表示不被父坐标影响,会归zero。
go.transform.SetParent(Selection.activeTransform, false)  

3】编辑模式下判断当前选择的是否是prefab

Object prefab = PrefabUtility.GetPrefabParent(Selection.activeGameObject);
if(prefab)
{
    //是
}

对prefab的相关操作都在PrefabUtility类中。

4】编辑模式卸载资源

// 第二个参数true:卸载游戏对象引用的资源,默认是false
GameObject.DestroyImmediate(Object obj, bool unloadAssets)

5】Unity为了避免IO阻塞,当调用GameObject.Destroy()和GameObject.DestroyImmediate()的时候,只卸载它的对象,它身上的引用的贴图和mesh还在内存中。
为了解决这个问题,unity提供了Resources.UnloadUnusedAssets() 方法去卸载没有引用的资源。
编辑模式使用EditorUtility.UnloadUnusedAssetsImmediate().
6】GameObject.Destroy()删除对象是等1帧后再删除,避免当前帧后面还在使用,建议使用它进行删除。

二、版本管理

1】unity工程只需要将Assets和ProjectSetting文件夹下的所有文件以及meta文件上传管理就行。
2】.meta文件是记录游戏资源在引擎中的一些设置信息,资源最终呈现是根据设置进行优化的。
最重要的就是每个meta里的guid了。用来关联资源与游戏对象的引用的。
例如场景中一个模型用到了一个贴图,则模型对应的prefab会记录引用这个贴图的guid。如果模型不是prefab,则场景文件会记录。
3】meta文件一定要上传,不然别人更新到这个资源的时候,无法用它本来的设置。
4】程序跟美术维护版本方法
4.1】方法1、程序的工程下,添加一个美术的svn外链,版本同步。
4.2】方法2、美术只在主分支提交,程序需要从主分支合并。
4.3】美术上传的资源要通过程序工具整合后在resources目录下供后续出包使用,不能直接用。

三、Unity打包资源

1】Unity会将被引用的资源打进包内,如果某个scene被添加到scenes in build中的话,scene里面所有引用到的资源都会打进包内。
2】Resources目录下文件无论是否有被引用,unity都会自动将他们合并在一起,压缩后打进包内。在代码中动态读取这些资源并加载。
特别注意:resources目录下的资源尽量不要直接引用在场景中,否则这个资源会被场景和resources打成两份进包。
3】unity打包时会将streamingAssets(无压缩)以及Resources目录下(压缩)打进包内。

四、资源卸载

1】先GC还是先UnloadUnusedAssets() ,当然最好是执行两遍。
2】个人理解,有一些临时对象引用着资源,所以如果先卸载资源,会卸载不掉,如果先GC会将临时对象GC掉,然后卸载资源才更有效。
那有人说要先卸载没用的资源,才能更好的GC掉没有引用的变量。 那这些资源都能被卸载了,就说明他们是没有被某些变量引用着的,你卸载不卸载跟能不能更好的GC没关系。人家本身就没有被变量引用,就算你不卸载资源,该GC还是被GC。 个人偏向先GC再卸载。有些项目是先卸载资源再GC,这个看情况吧,影响有,但不大。最好还是执行两遍,或者合适的时机主动处理。

五、关于AssetBundle

1】将指定一些资源构建成AssetBundle文件,以便下载更新。
2】资源的右下角选择abName以及资源的后缀名(可默认)
3】使用unity的打AssetBundle接口最终会输出到streamingAssets目录下的,因为别的目录无法读取。这是一个相对路径,在不同平台下的路径不同,可以使用Application.streamingAssetsPath查看。
4】会被多引用的资源一定要设置ab,否则会被打成多份。
5】打ab的压缩方式有3种,高压缩慢加载,中压缩中加载,不压缩高加载。项目中推荐使用这种:ChunkBaseConpression
6】加载的流程:
6.1】加载AssetBundleManifest获取ab的依赖关系

manifest = manifestAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

6.2】加载依赖的ab

LoadFromFile(path);

6.3】加载本身的ab
6.4】从ab中加载资源

assetbundle.LoadAsset<GameObject>("cube");

6.5】实例化资源。
7】下载的ab资源会首先下载到Application.persistentDataPath(持久化数据路径,玩家就不会退出游戏就没了,跟持久化数据的保存一样)
8】如果场景是从ab中加载的,那么场景是不需要添加到scenes in build中的,这样会减少包体大小。

SceneManager.LoadScene("scene-ab");

9】图解ab加载及内存占用(重要)
在这里插入图片描述
图片来源
加载与释放过程
更新:
1、图中AssetBundle.Load 在untiy5.0版本后被AssetBundle.LoadAsset(name)取代
2、途中从硬盘中加载AssetBundle文件的方法被AssetBundle.LoadFromFile()取代
3、在手机(安卓和ios)系统上,加载出来的AssetBundle镜像文件是头文件,包含简略信息,只有当AssetBundle.Load的时候才实例化某个Asset。而在pc端,是直接将整个AssetBundle文件全部加载进内存的。
4、除了GameObject其他四种资源(shader,网格,材质,贴图)都是引用,即使实例化很多个GameObject,身上的这四种资源只是引用,修改一个,其他的也会修改,当销毁GameObject的时候只是减少引用。
5、当从AssetBundle中加载出来以后,这些Asset就归Resources管,会自动维护。
6、关于UnloadUnusedAssets方法: unity这个方法是个option,也是归preloadManager管,但是是同步的,不能并行执行其他的option,避免load的同时被unload。也正是因为同步的,所以会造成卡顿。
unity在切换场景的时候会自动调用一次这个方法(加载场景的方式非Add的时候)
7、AssetBundle.Unload
一个是AssetBundle.Unload(true) : 将这个ab,以及从这个ab上加载出来的assets全部卸载掉。
一个是AssetBundle.Unload(false):只是将这个ab卸载掉,从ab上加载出来的asset不会被卸载,但是当下次再加载ab的时候,从ab上加载出来的assets会有两份,因为当只卸载ab的时候,ab与assets已经断开连接了,unity是不知道这个asset是从这个ab上加载出来的。所以再次加载会存在两份。

六、游戏对象

1】安全的删除所有子物体,建议使用GameObject.Destroy()并且从后往前删。最好不要while循环一直删除第0个元素。
2】获取游戏对象

GameObject g1 = GameObject.Find("");  //根据scene中完整路径获取游戏对象
GameObject g2 = g1.transform.Find("");  //根据相对路径获取某游戏对象下的子对象

3】获取组件

GetComponentInChildren<>();  //包括自身  InParent同样
GameObject.FindObjectsOfType<Camera>(); //获取场景内所有camera组件

七、特殊文件夹

1】EditorDefaultResorces:编辑模式下的资源可以放这里
2】Plugins:此文件夹下的代码会优先编译成dll文件
3】StandardAssets:导入Unity标准的package文件夹,这里的脚本执行顺序会设置的靠前
4】~ 隐藏文件夹不进入包体 比如将Resources文件夹改成Resources~ 则不会打进包体,

八、推荐资源加载策略

1】从application.persistentDataPath目录下查找读写目录下是否有我要加载的ab
2】如果1没找到,接着去Application.steamingAssetsPath目录下查找本地是否有
3】如果2没找到,接着去resources目录下加载
上述策略使用的同时需要考虑ab的卸载时机

资源更新:
1】将cdn上的资源下载并保存在Application.persistentDataPath目录中,然后按上面的加载顺序加载。 自己要维护一个列表,判断是否需要更新
2】AssetBundle不能信任MD5的,要使用Unity提供的散列值来确保一致性

AssetBundleManifest.GetAssetBundleHash()

所以在构建ab的时候要设置 BuildAssetBundleOptions.DeterministicAssetBundle参数才行

九、查找资源引用关系

1】Unity提供了查找这个资源在当前场景中什么地方引用
Find References in Scene
2】Unity提供了查找这个资源应用了哪些资源
Select Dependencies
但是没有提供这个资源被哪些资源引用,这个百度很多,自己copy一份到自己工程就行。

十、其他资源的规范和注意事项

1】场景内尽量使用prefab,节省内存,并且加载速度变快。
场景1:三个cube,非prefab
场景2:三个cube,同一个prefab
在场景2序列化的时候,会先序列化prefab,并且三个cube都指向同一块内存。而场景1要序列化三次,占用三块内存。

2】慎重勾选min mip的贴图(一般ui界面用的可以不勾选)
如果勾选,会在编辑器里生成多种分辨率的纹理,GPU会根据摄像机透视投影下的面积决定用哪个,但相同的打出来的纹理会增加33%大小。

3】避免导入非正方形或者非2的幂的纹理,否则unity还要进行缩放切割处理,增大工作量和内存。

4】关于图集的一些内容
同一图集下的texture用的shader一定要一样的。
图集经常使用在ui界面。同一个功能模块下的打到一个图集里。
并不是将一个图集打满才好。合理分布。
打图集是为了减少dc,降低cpu的负载,内存占用基本相同。

5】我们可能会有很多一次性音效,如对话片段,它们没有需 要长久保存在内存中。创建Audio Sources并分配一个AudioClip如果只是这样即使在游戏中只有一次使用也会导致内存消耗过剩,我们应该利用Resources.Load()和Resources.UnloadAsset()来保持需要播放的音频数据在内存中,一旦它不在需要就立即释放它。

6】profiler中PersistentManager.Remapper
Remapper负责跟踪内存中的对象与它们在磁盘上的表示之间的关系,如果您的项目正在加载大量的资产包,则REAPPER将在AssetBundle卸载之前将数据存储在内存中。REAPPER本身的实现使用内存池。为了释放内存,您需要在加载一定数量的AssetBundles之后执行卸载操作。

7】如果脚本引用了GameObject,那转换场景的时候脚本和GameObject都没了,还会产生堆内存的吗?
如果脚本是MonoBehaviour,而且在切换场景后所挂的Game Object被释放了,那么这个脚本对象所引用的堆内存就会在GC的时候被释放。 但有一种例外,如果是通过Static变量引用的堆内存,那么依然是释放不掉的,除非手动解开引用,比如变量置Null,数组Clear等等。

8】对于cpu Usage 这个界面的信息解读
在这里插入图片描述

Total ms:当前任务在当前帧内的耗时总时间。
Self ms:当前任务自身(不包含内部的子任务)时间消耗。
Total:当前任务的时间消耗占当前帧cpu消耗的时间比例。
Self:任务自己时间消耗占当前帧cpu消耗的时间比例。
Call:当前任务在当前帧内被调用的次数。 (如果是当前任务是GC.alloc)就相当于执行了N次的内存分配
GC Alloc:当前任务在当前帧内进行过内存回收和分配的次数。

9】资源存在两份的几个原因:
9.1】Resources目录下的资源尽量不要直接引用在场景中,不然这个资源会被场景和Resources打成两份。
9.2】AssetBundle.Unload
一个是AssetBundle.Unload(true) : 将这个ab,以及从这个ab上加载出来的assets全部卸载掉。
一个是AssetBundle.Unload(false):只是将这个ab卸载掉,从ab上加载出来的asset不会被卸载,但是当下次再加载ab的时候,从ab上加载出来的assets会有两份,因为当只卸载ab的时候,ab与assets已经断开连接了,unity是不知道这个asset是从这个ab上加载出来的。所以再次加载会存在两份。
9.3】资源未打成ab, 被多个地方引用
9.4】贴图设置为read,如果write的话,内存里会有一份拷贝

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐