【unity】AssetBundle共享资源及依赖资源打包

AssetBundle系列――资源的加载、简易的资源管理器

每个需要进行资源管理的类都继承自IAssetManager,该类维护它所使用到的所有资源的一个资源列表。并且每个资源管理类可以重写其资源引用接口和解引用接口。

每个管理器有自己的管理策略,比如SceneManager对场景背景图可以保留最近使用的几张,使用LRU算法维护当前内存中的贴图张数等...

复制代码

using UnityEngine; System.Collections; System.Collections.Generic;// 和资源有关的管理器都将继承自此类public class IAssetManager {  // 管理器所管理的资源列表,实际上是引用列表
  protected List<string> lstRefAsset = new List<string>();  // 增加引用的资源
  virtual void RefAsset(string name)  {}  // 以一定的策略卸载资源
  bool UnloadAsset()  { return true; } }

复制代码

资源管理器类,UnloadUnusedAsset函数保证只有在真正有资源需要卸载的时候才调用Resources.UnloadUnusedAssets();

有一点需要注意的地方就是,在资源解压完成后,一定要记得要释放压缩包内存,即:

www.assetBundle.LoadAsync(GetAssetName(name),type);

www.assetBundle.Unload(false);

因为,www压缩包数据只能手动卸载,跳转场景都不会删除,所以加载完立即unload是一个好习惯。

还有一点需要注意的www.assetBundle.mainAsset是一个同步加载的操作,也就是说调用此函数时会卡。using UnityEngine;using System.Collections;

ResourceManager { // 已解压的Asset列表 [prefabPath,asset] private Dictionary<string,Object> dicAsset = new Dictionary<(); // "正在"加载的资源列表 [prefabPath,www] public Object GetResource( name) { Object obj = null; if (dicAsset.TryGetValue(name,out obj) == false) { Debug.LogWarning("<GetResource Failed> Res not exist,res.Name = " + name); if (dicLoadingReq.ContainsKey(name)) { Debug.LogWarning("<GetResource Failed> The res is still loading"); } } return obj; } // name表示prefabPath,eg:Prefab/Pet/ABC void LoadAsync( name) { LoadAsync(name,typeof(Object)); } name,System.Type type) { // 如果已经下载,则返回 (dicAsset.ContainsKey(name)) // 如果正在下载,则返回 (dicLoadingReq.ContainsKey(name)) // 添加引用 RefAsset(name); // 如果没下载,则开始下载 CoroutineProvider.Instance().StartCoroutine(AsyncLoadCoroutine(name,type)); } private IEnumerator AsyncLoadCoroutine(string assetBundleName = GlobalSetting.ConvertToAssetBundleName(name); string url = GlobalSetting.ConverToFtpPath(assetBundleName); int verNum = GameApp.GetVersionManager().GetVersionNum(assetBundleName); Debug.Log("WWW AsyncLoad name =" + assetBundleName + " versionNum = " + verNum); if (Caching.IsVersionCached(url,verNum) == ) Debug.Log("Version Is not Cached,which will download from net!"); WWW www = WWW.LoadFromCacheOrDownload(url,verNum); dicLoadingReq.Add(name,www); while (www.isDone == ) yield ; AssetBundleRequest req = www.assetBundle.LoadAsync(GetAssetName(name),type); while (req.isDone == ; dicAsset.Add(name,req.asset); dicLoadingReq.Remove(name); www.assetBundle.Unload(); www = // Debug.Log("WWW AsyncLoad Finished " + assetBundleName + " versionNum = " + verNum); } bool IsResLoading( name) { dicLoadingReq.ContainsKey(name); } bool IsResLoaded( dicAsset.ContainsKey(name); } public WWW GetLoadingWWW( name) { WWW www = ; dicLoadingReq.TryGetValue(name,'Microsoft YaHei';font-size:14px;">out www); www; } // 移除Asset资源的引用,name表示prefabPath void UnrefAsset( name) { dicAsset.Remove(name); } private string GetAssetName( ResName) { int index = ResName.LastIndexOf('/'); return ResName.Substring(index + 1,ResName.Length - index - 1); } void UnloadUnusedAsset() { bool effectNeedUnload = GameApp.GetEffectManager().UnloadAsset(); bool worldNeedUnload = GameApp.GetWorldManager().UnloadAsset(); bool sceneNeedUnload = GameApp.GetSceneManager().UnloadAsset(); if (effectNeedUnload || worldNeedUnload || sceneNeedUnload) { Resources.UnloadUnusedAssets(); } } // 根据资源路径添加资源引用,每个管理器管理自己的引用 // 模型之类的 (name.Contains(GlobalSetting.CharacterPath)) GameApp.GetWorldManager().RefAsset(name); // 图片之类的 else (name.Contains(GlobalSetting.TexturePath)) GameApp.GetUIManager().RefPTexture(name);// 特效之类的 (name.Contains(GlobalSetting.EffectPath)) GameApp.GetEffectManager().RefAsset(name);      ......      else Debug.LogWarning("<Res not ref> name = " + name); } }

资源管理的关键在于以下几点:

(1)资源所对应的几块内存的管理,Unity的内存一直是一个相对比较棘手的方面,所以一定要多做尝试找到规律和方法;

(2)资源加载、卸载策略,什么时候加载什么时候卸载需要根据游戏类型来进行定制;

(3)资源打包策略,也就是以什么单位进行什么类型的资源打包,原则上是让同一资源尽量只需要打包一次,比如多个场景都用到了同一棵树,那么最好是对这棵树单独打包等等。

   比如Prefab1和Prefab2同时引用了Fbx1,将2个prefab单独打包时都会分别包含Fbx1,并且解压到内存时,也会有2份独立的Fbx1,这样会造成内存变大,这点一定要注意。

......

Unity内存和资源这一块虽然显得比较拖泥带水,不过只要使用的够规范,一般还是能够保证内存的干净的。

还有一个尚未解决的问题,Unity使用www方式加载资源的时候,不能进行同步加载操作,只能异步,我见到过其他人也遇到过这个问题,很是蛋疼菊紧。

AssetBundle系列――共享资源打包/依赖资源打包

  有人在之前的博客中问我有关共享资源打包的代码,其实这一块很简单,就两个函数:

  BuildPipeline.PushAssetDependencies():依赖资源压栈;

  BuildPipeline.PopAssetDependencies():依赖资源出栈。

  直接看代码,下面为打包示例代码,Prefab1和Prefab2共享贴图资源Tex1,在打包时将Tex1单独打包,而Prefab1和Prefab2对应的assetbundle包中不实际包含Tex1资源,而是记录Tex1资源的引用:

UnityEditor; System.IO; PushAndPop { [MenuItem("Test/BuildAssetBundle")] static Execute() { string SavePath = "C:\\"; BuildAssetBundleOptions buildOp = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle; BuildPipeline.PushAssetDependencies(); // 共享资源Tex1.tga Object sharedAsset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/Test/Tex1.tga"); BuildPipeline.BuildAssetBundle(sharedAsset,'Microsoft YaHei';font-size:14px;">null,SavePath + sharedAsset.name + ".assetbundle",buildOp,BuildTarget.StandaloneWindows); // Prefab1,引用了Tex1.tga BuildPipeline.PushAssetDependencies(); Object p1Asset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/Test/P1.prefab"); BuildPipeline.BuildAssetBundle(p1Asset,SavePath + p1Asset.name + // Prefab2,引用了Tex1.tga BuildPipeline.PushAssetDependencies(); Object p2Asset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/Test/P2.prefab"); BuildPipeline.BuildAssetBundle(p2Asset,SavePath + p2Asset.name + "","Completed",'Microsoft YaHei';font-size:14px;">"OK"); AssetDatabase.Refresh(); } }

  可以看到,Push和Pos都是成对使用,一个Push/Pop对就相当于一个Layer(层),层可以嵌套,内层可以依赖外层的资源。也就是说内层某资源在打包时,如果其引用的某个资源已经在外层加载了,那么内层的这个资源包就会包含该资源的引用而不是资源本身。Push/Pop实际上维持了一个依赖的堆栈。

  那么,在加载依赖资源包时,需要注意的是:先加载依赖的资源,然后加载其他资源,需要确保这个顺序。下面的代码演示如何使用依赖资源包:

NewBehaviourScript : MonoBehaviour { OnGUI() { // 清空本地缓存 if (GUI.Button(new Rect(0f,0f,100f,20f),Caching.spaceOccupied.ToString())) { Caching.CleanCache(); } new Rect(0f,30f,'Microsoft YaHei';font-size:14px;">"Load Share Res")) { StartCoroutine(Load(@"file://C:\Tex1.assetbundle",'Microsoft YaHei';font-size:14px;">)); } "Load And Instantiate Prefab")) { StartCoroutine(LoadAndInstantiate(@"file://C:\P1.assetbundle",'Microsoft YaHei';font-size:14px;">)); StartCoroutine(LoadAndInstantiate(@"file://C:\P2.assetbundle",'Microsoft YaHei';font-size:14px;">)); } } // 加载 IEnumerator Load(string url,'Microsoft YaHei';font-size:14px;">int version) { WWW www =// 加载并实例化 IEnumerator LoadAndInstantiate( www; if (!System.String.IsNullOrEmpty(www.error)) { Debug.Log(www.error); } { Object main = www.assetBundle.mainAsset; GameObject.Instantiate(main); } } }

  先按第二个按钮,然后再按第三个按钮就能够正确显示两个Prefab,如果直接进行第三步操作,则实例化出来的Prefab会缺少贴图。

  另外我们可以从assetbundle包的大小来看一下:

  如果不打依赖包,两个prefab都打完整包,得到的包的大小为:

  P1.assetbundle      56K

  P2.assetbundle      38K

  如果打依赖包,得到的包的大小为:

  Tex1.assetbundle     10k

  P1.assetbundle      47K

  P2.assetbundle      29K

  规律是不是很明显。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结