参考 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
目的
直接使用原始文件,比如视频文件,有些播放器需要直接使用文件路径去访问
原理
位于 StreamingAssets 目录下的文件将不进行打包操作,以原始文件路径直接存储
资源配置
在Assets目录下建一目录StreamingAssets,把资源放到该目录下
资源加载
目标机器的StreamingAssets目录 Application.streamingAssetsPath 跟平台有关:
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");
参考 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个选项
资源打包
参考 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);
}
很重要的一点
执行后在目标目录输出
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 字段下就是所有资源路径
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);
}
var 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);
IEnumerator 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 格式
方法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
在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不是同一个对象
结论:
场景文件.unity打包到assetbundle上,使用Application.LoadLevel加载场景,Assets/main.unity均使用main做场景名称
结论:
场景可以打包到AssetBundle中,加载AssetBundle再加载场景可以产生覆盖效果
场景文件和场景中的图片分别打包到scene和image,测试这几个文件分别加载的效果
结论:
静态scene引用的是静态的资源,动态的scene引用的是动态的资源,不会交叉引用
也就是说引用资源除了资源标识外,应该还指明了资源所在包,只有Application.LoadLevel例外
该函数应该是逆向搜索所有加载的资源包,找到相应的场景文件
动态scene中所引用的资源更新,scene文件可不更新
把资源放到Resources中,用prefab替代scene,仍有同样的验证
说明Resources.Load加载的资源也不会引用assetbundle中的资源
场景文件中引用一张图片,分别打包到scene和image
结论:
引用关系的资源分别更新时另一方不会更新
只有打包的资源才能使用打包的资源
要注意如果文件夹设置包名,该文件夹下没设置包名的资源将被打进文件夹所在包名
加载assetbundle文件,并创建assetbundle对象,释放WWW对象,对比内存占用大小,测试用assetbundle未压缩大小8.1m
结论:
为了不释放AssetBundle而又不占用内存,必须采用从文件加载,且最好用Assetbundle.CreateFromFile,这样不用解压资源,效率更高
android平台上将一张2024x1024的jpg打包到assetbundle中,对比生成的文件大小
结论:
尽量让图片压缩并且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中,对比生成的文件大小
结论:
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资源