需求:
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
不对的地方欢迎拍砖