unity资源加载
打包的资源
- Android
所有资源打包在 unityLibrary\src\main\assets\bin\Data\data.unity3d- 场景存放在 level[n]
- 共享资源存放在 sharedassets[n].asset
- Resources文件夹下的资源存放在 resources.asset
- iOS
- 场景存放在 Data\level[n]
- 共享资源存放在 sharedassets[0].asset
- Resources文件夹下的资源存放在 resources.asset
Resources.Load
-
参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/Scripting with Assets/在运行时加载资源
-
目的
在游戏运行时根据需要动态加载资源,注意资源还是在安装包中 -
原理
根据creator推测其原理是unity内部对每个资源分配uid,打包时是用uid替换文件路径,
如果文件在Resources下则会把uid和原始文件路径的映射关系保存在一个配置文件中,
加载时先把路径映射成uid,再用uid加载文件 -
资源配置
在Project下的任意文件夹中创建的Resources文件夹,其下的文件如果被场景引用,将打包进sharedassets[level].assets
否则所有Resources文件夹下的资源将被打包成resources.assets,
因此不同目录下的Resources文件夹下的资源最好有不同的路径,以免冲突 -
资源加载
在运行时用Resources.Load动态加载,要用相对Resources的路径,且不带扩展名
如:Assets/Resources/a/glass.png或Assets/MyResource/Resources/a/glass.png
在运行时都是用Resources.Load(“a/glass”,typeof(Texture2D) as Texture2D来加载 -
资源卸载
Resources.UnloadAsset不会卸载资源,要用UnloadUnusedAssets -
查找脚本
Resources.FindObjectsOfTypeAll
Streaming Assets
-
目的
直接使用原始文件,比如视频文件,有些播放器需要直接使用文件路径去访问 -
原理
位于 StreamingAssets 目录下的文件将不进行打包操作,以原始文件路径直接存储 -
资源配置
在Assets目录下建一目录StreamingAssets,把资源放到该目录下 -
资源加载
目标机器的StreamingAssets目录 Application.streamingAssetsPath 跟平台有关:- Editor 或 win Application.streamingAssetsPath = Application.dataPath + “/StreamingAssets”;
- ios 平台 Application.streamingAssetsPath = Application.dataPath + “/Raw”
- android 平台 Application.streamingAssetsPath = “jar:file://” + Application.dataPath + “!/assets”
存储在压缩文件中,必须使用 UnityWebRequest 加载文件 - webGL 平台没有权限访问
- 其它平台直接使用:
GameObject cam = GameObject.Find("Main Camera"); videoPlayer = cam.AddComponent<UnityEngine.Video.VideoPlayer>(); // Obtain the location of the video clip. videoPlayer.url = Path.Combine(Application.streamingAssetsPath, "SampleVideo_1280x720_5mb.mp4");
AssetBundle
-
参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle
-
目的
把一些资源打包成一个文件,可以从外部进行加载,外部可以通过从网络下载的方式来实现更新 -
资源配置
参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle/AssetBundle 工作流程
5.x版本对生成assetBundle进行一定的支持,在每个资源的属性窗口右下角 AssetBundle 栏有2个选项- 左边选择该资源所属分组(也就是包名),支持目录结构用"/"
- 右边选择变体名称(也就是包的后缀名,不能是预留的txt rar等,需要才加不需要留空)
包名和后缀名相同的资源会打在同一个包里, - 注意相同包名的要么都有后缀,要么都没有后缀,不能同时存在 aaa 和 aaa.abb
- 注意如果文件夹设置包名,该文件夹下没设置包名的资源将被打进文件夹所在包名
-
资源打包
参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle/构建 AssetBundle-
资源分组后可以执行下面脚本将各资源分组打包并放入目标目录,目标目录是以工程路径(Assets的上一层)为相对目录
// 将这段代码放到 Assets/Editor/XXX.cs 中 // 通过 菜单Assets->Build AssetBundles 执行 [MenuItem("Assets/Build AssetBundles")] static void BuildAllAssetBundles() { // 打包输出目录 string assetBundleDirectory = "Assets/AssetBundleOut"; if (!Directory.Exists(assetBundleDirectory)) { Directory.CreateDirectory(assetBundleDirectory); } // 第3个参数表示 window 平台,根据需要改成对应平台 // 可以通过 EditorUserBuildSettings.activeBuildTarget 获得配置的目标平台 BuildPipeline.BuildAssetBundles(assetBundleDirectory, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows); }
-
很重要的一点
- 打包时会把资源打包成目标平台的格式
但是打包时不会先切换到目标平台,重新编译Editor代码,然后重新导入资源,最后再打包 - 对于大多数资源,在不同平台下导入时,生成的都是同样的对象,所以不存在问题,
也就是在win平台打android的bundle包也能得到正确的结果 - 但是如果有某种资源,如果你在win平台导入时生成的是A对象,在android平台导入时生成的是B对象,
那么你在win平台下执行打包操作,打包android平台的 bundle 包,则 bundle 包里的将是 A 对象
想得到正确的结果,你必须先切换到android平台再打android平台的bundle包 - 还有一点,打包时对着色器的编译跟 GraphicsApi 相关,也就是同样是window平台,
使用 vulkan 和 dx12 打出的包是不一样的,使用 vulkan 打出的ab包在使用 dx12 打出的应用里是运行不了的 - 某个平台打包时,会使用GraphicsApi列表中第一个支持的进行编译,如果是 Auto Graphics ,那就用默认的
- linux不支持dx12打包,有个变通的方式,把dx12相关的shader关联到一个组件上,打包时删除该组件,由包的加载方创建该组件
- 打包时会把资源打包成目标平台的格式
-
执行后在目标目录输出
xxx 打包好的资源包
xxx.manifest 资源包清单文件,包含包中的资源列表和依赖关系,清单文件是方便用户做自己的热更程序
AssetBundleOut 以输出目录命名的清单捆绑包,包含资源包列表和依赖关系
AssetBundleOut.manifest 以输出目录命名的清单捆绑包的清单文件,包含资源包列表和依赖关系 -
只包含图片的AssetBundle,压缩的一般是不压缩的1/5,如不压缩1.3m,压缩后270k,Unity用lzma算法压缩
要注意,图片是Texture类型时,会自动调整到适合该平台可压缩的图片格式,如大小是POT,方形之类
而图片是Sprite类型时,要自己将图片保存成该平台可压缩格式,否则打包成AssetBundle时会很大,上述的不压缩的AssetBundle中图片1.3m
如果没压缩将达到9.7m生成的AssetBundle将达9.6m
Android平台压缩用ETC1(4bit/pixel),成功压缩的要求是POT且不带透明通道,否则将以16bit/pixel的方式压缩保存
Ios平台压缩用PVRTC,成功压缩的要求是POT且方形,否则将以true color(32bit/pixel)不压缩保存
-
-
资源加载
-
参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle/AssetBundle 工作流程
注意这里加载的都是资源包,也就是上面的 xxx
加载资源时传入的路径是资源相对 Assets 目录的路径,比如 Assets/Obj/a.png 打包成成bundle包后,加载时就传入 Assets/Obj/a.png ,也可以直接传入文件名 a.png
AssetBundle中包含的资源可以在生成的 manifest 中查看,Assets 字段下就是所有资源路径- 从内存加载,内存负担较重,不建议使用
跟方法2从文件加载的原理相同IEnumerator LoadFromMemoryAsync(string path) { AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path)); yield return createRequest; AssetBundle bundle = createRequest.assetBundle; var prefab = bundle.LoadAsset<GameObject>("MyObject"); Instantiate(prefab); }
- 从文件加载,常用
注意用文件创建时不要加file://,否则会报奇怪的错误,read bundle header error
从文件创建,支持任何压缩格式,如果是 LZMA 会先解压到内存中,LZ4和未压缩则需要时直接从硬盘读取
资源名称按完整路径,名称加扩展名,名称 三种方式来匹配如Assets/a/glass.png可以用glass或glass.png或Assets/a/glass.pngvar myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle")); if (myLoadedAssetBundle == null) { Debug.Log("Failed to load AssetBundle!"); return; } var prefab = myLoadedAssetBundle.LoadAsset<GameObject>("MyObject"); Instantiate(prefab);
- 从网络下载并加载
默认从网络下载并解压到内存中,如果调用 GetAssetBundle 提供了版本号或哈希值,则下载的文件将重新压缩存放到Cache文件夹
Caching.compressionEnabled=true,则保存成 LZ4 格式,否则保存成未压缩格式
UnityWebRequest 也可用于加载本地文件,如果本地文件是 LZMA 格式,建议用 UnityWebRequest 加载,提供版本号保存成缓存文件
因为使用 AssetBundle.LoadFromFile 加载 LZMA 文件,每次都会解压到内存中
Cache 目录
在windows中是在C:\Users\Administrator\AppData\LocalLow\Unity\WebPlayer\Cache\DefaultCompany_UnityLua\80f87c8847da6c872ce2fc9fec0a98a73ebc578dIEnumerator InstantiateObject() { string uri = "file:///" + Application.dataPath + "/AssetBundleOut/" + assetBundleName; UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0); yield return request.Send(); AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); GameObject cube = bundle.LoadAsset<GameObject>("Cube"); Instantiate(cube); }
可以用 bundle.mainAsset 访问主资源,新版本已经弃用,并且没有替代方法,只能通过名称查找
- 从内存加载,内存负担较重,不建议使用
-
注意事项: 如果 AssetBundleA 依赖 AssetBundleB ,则你要先加载 AssetBundleB 再加载 AssetBundleA,再创建对象,不然引用的
AssetBundleB 中的对象会被置空,如果我们在每个使用的地方都去手动处理依赖,那就太烦琐了,这时候上面打包时生成的
AssetBundleOut 的捆绑包就派上用场了,该捆绑包包含了所有包列表和依赖关系AssetBundle manifesAB = AssetBundle.LoadFromFile("AssetBundleOut/AssetBundleOut"); AssetBundleManifest manifest= manifesAB.LoadAsset<AssetBundleManifest>("AssetBundleOutManifest"); // 获取所有包 foreach (string name in manifest.GetAllAssetBundles()) { print(name); } // 获取并加载依赖包 string []strs=manifest.GetAllDependencies("xxx"); foreach (var name in strs) { AssetBundle.LoadFromFile("AssetBundleOut/"+name); }
-
可以把场景打包到AssetBundle中,当用Application.LoadLevel加载场景时,如果包含该场景的AssetBundle已被加载,
则优先使用AssetBundle中的场景,否则会使用打包的场景 -
把同一个AssetBundle拷贝并改名,先加载原始的,再加载改名的会提示已经存在,说明AssetBundle不是以名称为标识,
而是里面集成了AssetBundle的标识,加载改名的,再加载引用原始AssetBundle资源的AssetBundle,成功 -
Resources其实也是一份AssetBundle而且不会被释放,所以Resources.Load加载的资源,名称一样就是同一个资源
-
同一个AssetBundle加载出来的同一名称的资源,是同一个资源,AssetBundle.Unload后AssetBundle已经被释放,
这时不能再调用LoadAsset,必须重新加载AssetBundle,但是重新加载AssetBundle后再LoadAsset同一个名称的资源,
其实是创建了一份新的资源
-
-
资源使用
- 使用场景
// 判断ab包中是否包含场景 bool CheckContainScene(AssetBundle assetBundle,string sceneName) { if (assetBundle.isStreamedSceneAssetBundle) { string[] scenePaths = assetBundle.GetAllScenePaths(); foreach (string path in scenePaths) { if (path.Contains(sceneName)) { return true; } } } return false; } // 加载场景,跟普通加载场景相同 AsyncOperation loadScene = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
- 使用场景
-
资源压缩
- LZMA
默认使用 LZMA 创建 AssetBundle ,具有较大的压缩率,适合网络传输 - LZ4
块压缩,能单独解压某个对象,默认做为缓存文件,从网络下载 LZMA 的ab包后会解压再用 LZ4 压缩放到 Cache 目录中
后续直接读取 LZ4 文件,加载对象时可快速单独解压该对象的数据 - 未压缩
加载速度快,但文件大,适合做为本地文件,一般是随 StreamingAssets 发布 - 总结:
对大小有要求用 LZMA,对速度有要求用未压缩,LZ4 处于比较均衡的位置
默认打AB包用 LZMA,可使用参数 BuildAssetBundleOptions.ChunkBasedCompression 强制用 LZ4,或 BuildAssetBundleOptions.UncompressedAssetBundle 强制未压缩
UnityWebRequest 默认下载后使用内存缓存,如果你提供了版本号或哈希,则会存储到缓存文件, Caching.compressionEnabled 设为true使用 LZ4 压缩,否则不压缩
- LZMA
-
资源缓存
参考资源压缩 -
资源打包和加载总结:
如果是从网络下载,应该用 LZMA 格式
方法1.使用 UnityWebRequest 并提供版本号,Caching.compressionEnabled=true 使用 LZ4 保存成缓存文件
方法2.自己手动下载包文件,使用 AssetBundle.RecompressAssetBundleAsync 重新压缩成 LZ4 文件,使用 AssetBundle.LoadFromFile 加载文件
如果是本地文件,应该用 LZ4 格式,直接使用 AssetBundle.LoadFromFile 加载
如果是未压缩文件,直接使用 AssetBundle.LoadFromFile加载 -
资源卸载
参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle/本机使用 AssetBundle
在最下面介绍了 Addressable Assets 包来管理资源包的依赖、加载和卸载用AssetBundle加载的asset一样可以用Resources.UnloadUnusedAssets卸载,即使该AssetBundle还未Unload
使用 LoadFromFile 加载AssetBundle时,有没有调用AssetBundle.Unload占用内存相差不大,说明该函数并未将文件加载到内存,
只是读取了文件句柄,需要时才读取内容,用 UnityWebRequest 来加载AssetBundle时,有没有调用AssetBundle.Unload,内存相差很大,说明该函数将整个文件内存加载到
内存中并解压缩,因为Unload后释放的内存跟未压缩的AssetBundle大小差不多 -
资源查看
参考 Unity User Manual (2019.4 LTS)Working in Unity资源工作流程AssetBundleUnity Asset Bundle Browser 工具
unity 提供了一个工具可以查看、修改、打包 assetBundle 的插件
下载地址 https://github.com/Unity-Technologies/AssetBundles-Browser
把 Editor 文件夹拷贝到 Assets 目录即可
菜单 Window->AssetBundle Browser 打开AssetBundles 面板 -
示例
资源创建和加载可以参考CharacterCustomization示例,
Assets/Plugins/Editor/CreateAssetBundles.cs 创建资源
Assets/Plugins/CharacterGenerator.cs 读取资源
还可以参考 ET 框架的 ResourcesComponent
AssetDatabase
在UNITY_EDITOR模式下还可以用AssetDatabase操作资源
一般用assetsBundle加载的资源,在编辑器里可以用AssetDatabase来加载,以提高开发效率
一些问题的试验:
-
prefab打包到assetbundle上,再从assetbundle加载该prefab并实例化
-
删除本地脚本,则提示脚本Missing
保留本地脚本,成功 -
删除本地图片,成功
修改本地图片,显示的依然是修改前的图片 -
图片打包到assetbundle1上
没有加载assetbundle1,图片丢失,本地有没有删除都一样
加载assetbundle1到 AssetBundle bundle = www.assetBundle后再加载assetbundle成功
修改图片重新打包到assetbundle1上,则显示的图片随之改变 -
加载asset时传入的资源名称,原始资源路径Assets/Test/TextObject.prefab
名称传入TextObject,TextObject.prefab,Assets/Test/TextObject.prefab均能找到资源
诸如Test/TextObject,Assets/Test/TextObject均不能找到资源 -
加载assetbundle,LoadAsset获得prefab,Unload该assetbundle并重新加载,再次LoadAsset获得prefab
发现2次获得的prefab不是同一个对象
-
-
结论:
- 脚本被编译成程序集,脚本对象只能使用本地程序集中的脚本对象,打包进assetbundle中的用不了
- 打包进assetbundle中的资源如果使用了其它assetbundle中的资源,则要先加载其它assetbundle,
否则会找不到资源,如果某个资源既在场景中使用,又在assetbundle中使用,则将会被打包成2份,
场景中的使用场景中的资源,assetbundle使用assetbundle中的资源,其实相当于2个对象(脚本除外) - assetbundle查找资源时按完整路径,名称加扩展名,名称 三种方式来匹配
- 为了不重复创建资源,必须把打开的AssetBundle缓存住,这样从AssetBundle加载出的资源才不会重复
-
场景文件.unity打包到assetbundle上,使用Application.LoadLevel加载场景,Assets/main.unity均使用main做场景名称
- 没有加载AssetBundle,直接加载场景,显示打包时的场景
- 先加载AssetBundle再加载场景,显示AssetBundle中的场景
-
结论:
场景可以打包到AssetBundle中,加载AssetBundle再加载场景可以产生覆盖效果 -
场景文件和场景中的图片分别打包到scene和image,测试这几个文件分别加载的效果
- scene和image都不加载,显示打包的场景
- image加载scene不加载,还是显示打包的场景且图片资源没被替换
- image不加载scene加载,显示scene中的场景且图片资源消失(没找到)
- image加载scene加载,显示scene中的场景且图片是image中的图片
- 重新打包image,scene不变,image和scene都加载,显示的是新的image中的图片
-
结论:
静态scene引用的是静态的资源,动态的scene引用的是动态的资源,不会交叉引用
也就是说引用资源除了资源标识外,应该还指明了资源所在包,只有Application.LoadLevel例外
该函数应该是逆向搜索所有加载的资源包,找到相应的场景文件
动态scene中所引用的资源更新,scene文件可不更新
把资源放到Resources中,用prefab替代scene,仍有同样的验证
说明Resources.Load加载的资源也不会引用assetbundle中的资源 -
场景文件中引用一张图片,分别打包到scene和image
- image文件更新后重新打包,scene文件不会被再次打包
- 加入材质,引用图片并打包到material,image和scene不会变
- 再次更改图片后重新打包,material和scene均不会变
- 更改材质后重新打包,image和scene均不会变
-
结论:
引用关系的资源分别更新时另一方不会更新
只有打包的资源才能使用打包的资源
要注意如果文件夹设置包名,该文件夹下没设置包名的资源将被打进文件夹所在包名 -
加载assetbundle文件,并创建assetbundle对象,释放WWW对象,对比内存占用大小,测试用assetbundle未压缩大小8.1m
- 用new WWW加载,内存增大8,1m,说明将assetbundle文件复制到内存区
- 用AssetBundle.CreateFromFile加载,内存无变化,该函数只能加载未压缩文件
- 用WWW.LoadFromCacheOrDownload加载,内存无变化,说明没有加载到内存,但在
C:\Users\Administrator\AppData\LocalLow\Unity\WebPlayer\Cache\DefaultCompany_UnityLua\80f87c8847da6c872ce2fc9fec0a98a73ebc578d
下发现多了一个文件CAB-19aa249d139a2d14f62c5858d4ef7116,大小跟assetbundle未压缩大小一致,
说明该函数将assetbundle解压成未压缩的assetbundle放到这里,然后再用AssetBundle.CreateFromFile加载
-
结论:
为了不释放AssetBundle而又不占用内存,必须采用从文件加载,且最好用Assetbundle.CreateFromFile,这样不用解压资源,效率更高 -
android平台上将一张2024x1024的jpg打包到assetbundle中,对比生成的文件大小
- etc压缩,assetbundle压缩 0.355m
- etc压缩,assetbundle未压缩 1.37m
- 图片未压缩,assetbundle压缩 1.452m
- 图片未压缩,assetbundle未压缩 8.101m
-
结论:
尽量让图片压缩并且assetbundle也压缩
由于为了使用AssetBundle.CreateFromFile,所以assetbundle只能采用不压缩,网络传输时自己再用zip对assetbundle压缩
为了图片压缩,texture格式会自动处理,Sprite格式在
Android平台用ETC要求图片大小POT且不带透明通道,否则将以16bit/pixel的方式压缩保存
Ios平台压缩用PVRTC,成功压缩的要求是POT且方形,否则将以true color(32bit/pixel)不压缩保存 -
android平台上将一张2024x1024的jpg分别打包到assetbundle和Resources中,对比生成的文件大小
- 打包到assetbundle中,未压缩,8.101m
- 打包到assetbundle中,压缩1.452m
- 打包到resources.asset中,8.101m
-
结论:
resources.asset和sharedassets[level].assets其实就是未压缩的Assetbundle -
通过上面的试验,可以得出最好的热更新方案,就是将资源打包成未压缩的AssetBundle,下载过程用外部工具压缩解压,
使用时用AssetBundle.CreateFromFile加载AssetBundle,并且加载后的AssetBundle不用释放(不占内存),这样就不会
重复创建资源,只有在某个阶段比如切换场景时再一次性释放
游戏打包时,有2种方案:-
资源先打包成AssetBundle,再放到StreamingAsset中,读取资源时直接从AssetBundle中读取
缺点:android版本需要将AssetBundle从StreamingAsset解压出来,占用更大的存储空间
如果不解压则占用更大的内存,且要用异步方式 -
资源放到Resources目录下,读取资源时先判断是否在下载的AssetBundle中,是则从AssetBundle中加载,否则从Resources中加载
缺点:AssetBundle a依赖AssetBundle b时,则当b重新打包更新,若a未被更新过,则a也必须更新
因为打包后的资源只能被打包后的资源访问,a若不更新,用Resources加载的a中资源引用的还是Resources中的b资源
-