写一个unity加载资源管理器

版权声明:转载请注明 https://blog.csdn.net/hl1991825/article/details/84327622

需求:

1.能切换不同加载模式 

   开发阶段编辑器运行直接加载资源无需打ab包 

   测试或正式发布阶段通过ab包加载资源

2.缓存机制 能定时清理长时间未使用的资源内存

3.既有同步加载 也有异步加载

思路:

加载模式可以使用一个全局枚举变量控制

资源管理器中 包含不同的加载器 每个加载器都有同步和异步加载方法 要加载资源时调用资源管理器,管理器查询资源是否在缓存中,不在根据当前加载模式选择对应的加载器加载资源,

画张导图

实现:

首先定义加载模式枚举

LoadMode.cs

public enum LoadMode{
    Editor,    //编辑器模式(该模式直接加载资源)
    AssetBundle,    //ab包模式(该模式所有资源使用ab方式加载)
}

定义资源管理器类,它是一个单例

AssetManager.cs

public class AssetManager : MonoSingleton<AssetManager>{
	[Header("当前资源加载模式:")]
	public AssetLoadMode LoadMode = AssetLoadMode.Editor;
	[Header("定时清理缓存间隔(秒):")]
	public float ClearCacheDuration;
	[Header("缓存数据驻留时间(秒)")]
	public float CacheDataStayTime;


	private IAssetLoader editorLoader;
	private IAssetLoader abLoader;
	private float cacheTimeTemp;

	//缓冲区[key 为绝对路径]
	private Dictionary<string, CacheDataInfo> cacheDataDic = new Dictionary<string, CacheDataInfo>();


	public void InitMode(AssetLoadMode mode, float duration = 10f, float cacheStayTime = 9f) {
		Debug.LogFormat("[AssetManager]初始化 当前加载模式:{0} 定时清理缓冲间隔:{1}s", mode, duration);
		LoadMode = mode;
		ClearCacheDuration = duration;
		CacheDataStayTime = cacheStayTime;
		editorLoader = new EditorAssetLoader();
		abLoader = new AssetBundleLoader(GameConfigs.StreamingAssetABRootPath, GameConfigs.StreamingAssetManifestPath);
	}

	//同步加载
	public T LoadAsset<T>(string path) where T : Object {
		CacheDataInfo info = queryCache(path);
		if (info != null) {
			info.UpdateTick();
			return info.CacheObj as T;
		} else {
			switch (LoadMode) {
			case AssetLoadMode.Editor:
				return editorLoader.LoadAsset<T>(path);
			case AssetLoadMode.AssetBundler:
				return abLoader.LoadAsset<T>(path);
			}
			return null;
		}
	}

	//异步加载
	public void LoadAssetAsync<T>(string path, UnityAction<T> onLoadComplate) where T : Object {

		CacheDataInfo info = queryCache(path);
		if (info != null) {
			info.UpdateTick();
			if (onLoadComplate != null) {
				onLoadComplate(info.CacheObj as T);
			}
		} else {
			switch (LoadMode) {
				case AssetLoadMode.Editor:
					StartCoroutine(editorLoader.LoadAssetAsync<T>(path, onLoadComplate));
					break;
				case AssetLoadMode.AssetBundler:
					StartCoroutine(abLoader.LoadAssetAsync<T>(path, onLoadComplate));
					break;
			}

		}
	}

	//检测缓冲区
	private CacheDataInfo queryCache(string path) {
		if (cacheDataDic.ContainsKey(path)) {
			return cacheDataDic[path];
		}
		return null;
	}

	//加入缓冲区
	public void pushCache(string path, Object obj) {
		Debug.Log("[AssetManager]加入缓存:" + path);

		lock (cacheDataDic) {
			if (cacheDataDic.ContainsKey(path)) {
				cacheDataDic[path].UpdateTick();
			} else {
				CacheDataInfo info = new CacheDataInfo(path, obj);
				cacheDataDic.Add(path, info);
				info.UpdateTick();
			}
		}
	}

	//清空缓冲区
	public void RemoveCache() {
            cacheDataDic.Clear();
	}

	//清理缓冲区
	private void updateCache() {
		Debug.Log("[AssetManager]清理缓存");
		foreach (var iter in cacheDataDic.ToList()) {
			if (iter.Value.StartTick + CacheDataStayTime >= Time.realtimeSinceStartup) {
				Debug.Log("过期清理:" + iter.Value.CacheName);
				cacheDataDic.Remove(iter.Key);
			}
		}
	}


	private void Update() {
		if (ClearCacheDuration < 0) return;

		cacheTimeTemp += Time.deltaTime;

		if (cacheTimeTemp >= ClearCacheDuration) {
			updateCache();
			cacheTimeTemp -= ClearCacheDuration;
		}
	}

}

定义加载器接口

IAssetLoader.cs

public interface IAssetLoader{
    //异步加载
    IEnumerator LoadAssetAsync<T>(string path, UnityAction<T> callback) where T : class;
    //同步加载
    T LoadAsset<T>(string path) where T : class;
}

编辑器模式下加载器实现类, 使用UnityEditor.AssetDatabase.LoadAssetAtPath函数加载资源

EditorAssetLoader.cs

public class EditorAssetLoader : IAssetLoader
    {
        public T LoadAsset<T>(string path) where T : class {
            return load<T>(path);
        }
        public IEnumerator LoadAssetAsync<T>(string path, UnityAction<T> callback) where T : class {
            if (callback != null) {
                callback(load<T>(path));
            }
            yield return null;
        }
        T load<T>(string path) where T : class {
#if UNITY_EDITOR
            string absolutepath = path;
            //绝对路径转为相对Assets文件夹的相对路径
            path = PathUtils.GetRelativePath(path, Application.dataPath);
            Debug.Log("[LoadAsset(Editor)]: " + path);
            Object obj = UnityEditor.AssetDatabase.LoadAssetAtPath(path, typeof(T));
            if (obj == null) {
                Debug.LogError("Asset not found - path:" + path);
            }
            AssetManager.Instance.pushCache(absolutepath, obj);
            return obj as T;
#endif
            return null;
        }
    }

assetbundle模式下加载器实现类

AssetBundleLoader.cs

public class AssetBundleLoader : IAssetLoader
    {
        private string assetRootPath;
        private string mainfastPath;
        private static AssetBundleManifest manifest;
        public AssetBundleLoader(string assetPath,string mainfast) {
            assetRootPath = assetPath;
            mainfastPath = mainfast;
        }
        public T LoadAsset<T>(string path) where T : class {
            string absolutepath = path;
            path = PathUtils.NormalizePath(path);
          
            Debug.Log("[LoadAsset]: " + path);
            //打的ab包都资源名称和文件名都是小写的
            string assetBundleName = PathUtils.GetAssetBundleNameWithPath(path, assetRootPath);
            //加载Manifest文件
            LoadManifest();
            //获取文件依赖列表
            string[] dependencies = manifest.GetAllDependencies(assetBundleName);
            //加载依赖资源
            List<AssetBundle> assetbundleList = new List<AssetBundle>();
            foreach (string fileName in dependencies) {
                string dependencyPath = assetRootPath + "/" + fileName;
                Debug.Log("[AssetBundle]加载依赖资源: " + dependencyPath);
                assetbundleList.Add(AssetBundle.LoadFromFile(dependencyPath));
            }
            //4加载目标资源
            AssetBundle assetBundle = null;
            Debug.Log("[AssetBundle]加载目标资源: " + path);
            assetBundle = AssetBundle.LoadFromFile(path);
            assetbundleList.Insert(0, assetBundle);
            Object obj = assetBundle.LoadAsset(Path.GetFileNameWithoutExtension(path), typeof(T));
            //释放依赖资源
            UnloadAssetbundle(assetbundleList);
            //加入缓存
            AssetManager.Instance.pushCache(absolutepath, obj);
            return obj as T;
        }
        public IEnumerator LoadAssetAsync<T>(string path, UnityAction<T> callback) where T : class {
            string absolutepath = path;
            path = PathUtils.NormalizePath(path);
            Debug.Log("[LoadAssetAsync]: " + path);
            //打的ab包都资源名称和文件名都是小写的
            string assetBundleName = PathUtils.GetAssetBundleNameWithPath(path, assetRootPath);
            //加载Manifest文件
            LoadManifest();
            //获取文件依赖列表
            string[] dependencies = manifest.GetAllDependencies(assetBundleName);
            //加载依赖资源
            AssetBundleCreateRequest createRequest;
            List<AssetBundle> assetbundleList = new List<AssetBundle>();
            foreach (string fileName in dependencies) {
                string dependencyPath = assetRootPath + "/" + fileName;
                Debug.Log("[AssetBundle]加载依赖资源: " + dependencyPath);
                createRequest = AssetBundle.LoadFromFileAsync(dependencyPath);
                yield return createRequest;
                if (createRequest.isDone) {
                    assetbundleList.Add(createRequest.assetBundle);
                } else {
                    Debug.LogError("[AssetBundle]加载依赖资源出错");
                }
            }
            //加载目标资源
            AssetBundle assetBundle = null;
            Debug.Log("[AssetBundle]加载目标资源: " + path);
            createRequest = AssetBundle.LoadFromFileAsync(path);
            yield return createRequest;
            if (createRequest.isDone) {
                assetBundle = createRequest.assetBundle;
                //释放目标资源
                assetbundleList.Insert(0, assetBundle);
            }
            AssetBundleRequest abr = assetBundle.LoadAssetAsync(Path.GetFileNameWithoutExtension(path), typeof(T));
            yield return abr;
            Object obj = abr.asset;
            //加入缓存
            AssetManager.Instance.pushCache(absolutepath, obj);
            callback(obj as T);
            //释放依赖资源
            UnloadAssetbundle(assetbundleList);
        }
        // 加载 manifest
        private void LoadManifest() {
            if (manifest == null) {
                string path = mainfastPath;
                Debug.Log("[AssetBundle]加载manifest:" + path);
                AssetBundle manifestAB = AssetBundle.LoadFromFile(path);
                manifest = manifestAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
                manifestAB.Unload(false);
            }
        }
      
        private void UnloadAssetbundle(List<AssetBundle> list) {
            for (int i = 0; i < list.Count; i++) {
                list[i].Unload(false);
            }
            list.Clear();
        }
    }

在使用前还需要准备做几件事情

1.使用工具把项目资源打成ab包(请自行百度谷歌,有很多文章介绍,项目代码里也写有一个简单的打包工具。◕ᴗ◕。)

2.指定项目资源存放的文件夹路径,指定ab包路径,这里写一个全局配置类GameConfig.cs

public static class GameConfigs
{
    //资源管理器 加载模式
    public static FoxGame.Asset.AssetLoadMode LoadAssetMode = FoxGame.Asset.AssetLoadMode.AssetBundler;
#if UNITY_ANDROID
       static string curPlatformName = "android";
#elif UNITY_IPHONE
       static string curPlatformName = "iphone";
#else
    static string curPlatformName = "win";
#endif
    //当前平台名
    public static string CurPlatformName { get { return curPlatformName; } }
    //(该文件夹只能读,打包时被一起写入包内,第一次运行游戏把该文件夹数据拷贝到本地ab包路径下)
    public static string StreamingAssetABRootPath = Application.streamingAssetsPath + "/" + curPlatformName;
    //streamingasset目录下的manifest文件路径
    public static string StreamingAssetManifestPath = Application.streamingAssetsPath + "/" + curPlatformName + "/" + curPlatformName;
    //游戏资源文件路径
    public static string GameResPath = Application.dataPath + "/GameRes";
    //打包资源的输出文件夹(导出到streamingaseet文件夹下)
    public static string GameResExportPath = Application.streamingAssetsPath + "/" + curPlatformName;
    #region game res path
    private static string assetRoot {
        get {
            if (LoadAssetMode == FoxGame.Asset.AssetLoadMode.Editor) {
                return GameResPath;
            } else {
                return StreamingAssetABRootPath;
            }
        }
    }
    //ui预制体路径
    public static string GetUIPath(string prefabName) {
        string str = "/Prefabs/UI/" + prefabName;
        if (LoadAssetMode != FoxGame.Asset.AssetLoadMode.Editor) {
            str = str.ToLower();
        } else {
            str = str + ".prefab";
        }
        return assetRoot + str;
    }
    //图集路径
    public static string GetSpriteAtlasPath(string name) {
        string str = "/Atlas/" + name;
        if (LoadAssetMode != FoxGame.Asset.AssetLoadMode.Editor) {
            str = str.ToLower();
        } else {
            str = str + ".spriteatlas";
        }
        return assetRoot + str;
    }
    // todo:  扩展...
   
    #endregion
}

测试调用类Launcher.cs

public class Launcher : MonoBehaviour{
    public Text Content;
    public Image Img_1;
    public Button Btn_1;
    public Image Img_2;
    public Button Btn_2;
    // Use this for initialization
    void Start () {
        AssetManager.Instance.InitMode(GameConfigs.LoadAssetMode);
        Content.text = "资源管理器加载模式:" + GameConfigs.LoadAssetMode;
        Btn_1.onClick.AddListener(onClickedBtn1);
        Btn_2.onClick.AddListener(onClickedBtn2);
    }
    void onClickedBtn1() {
        SpriteAtlas atlas = AssetManager.Instance.LoadAsset<SpriteAtlas>(GameConfigs.GetSpriteAtlasPath("ui_atlas"));
        Img_1.sprite = atlas.GetSprite(string.Format("icon_{0}", Random.Range(0, atlas.spriteCount - 1)));
    }
    void onClickedBtn2() {
        AssetManager.Instance.LoadAssetAsync<SpriteAtlas>(GameConfigs.GetSpriteAtlasPath("ui_atlas"), (SpriteAtlas atlas) => {
            Img_2.sprite = atlas.GetSprite(string.Format("icon_{0}", Random.Range(0, atlas.spriteCount - 1)));
        });
    }
   
}

win端效果(ios和Android等我空了测( ̄▽ ̄)~*):

 

说明:

1.模式可以按需求自定义扩展.

2.MonoSingleton类是一个继承了MonoBehaviour的单例类,谷歌百度也有很多。◕ᴗ◕。

3.ab包所有文件名都是小写!小写!小写!注意了各位.

4.PathUtils类是一个路径操作的工具类

5.管理器缓存的思路: 

    缓存资源用CacheDataInfo类包装,该类包含资源本身和存入缓存的时间tick,当有新资源放入缓存或者缓存队列的资源被重用时, 更新该tick为当前时间;

    管理器定时的遍历缓存队列 检查缓存是否有过期的数据.

6.项目中缓存结构要复杂许多,可能某些资源需要常驻内存,某些资源特定场景才会加载,根据需求而定.

另附参考:

编辑器加载资源函数 AssetDatabase.LoadAssetAtPath  https://docs.unity3d.com/ScriptReference/AssetDatabase.LoadAssetAtPath.html

ab类  AssetBundle  https://docs.unity3d.com/ScriptReference/AssetBundle.html

unity单例   https://blog.csdn.net/ycl295644/article/details/49487361

项目工程(unity2017.4):https://pan.baidu.com/s/1ZRAk2uV1eXtj0DRA33PBHw

提取码:2bex

不对的地方欢迎拍砖

猜你喜欢

转载自blog.csdn.net/hl1991825/article/details/84327622