資源管理器,顧名思義,就是管理遊戲中的所有資源,包括加載資源,回收資源,銷燬資源等等。下面這個資源管理器主要提供了對assetbundle異步加載功能,Resources的加載沒有放在裏面。
一.使用方法
1.在進入遊戲前調用Init(),加載一個資源的名稱列表
2.調用AsynGetAsset(string name, Action<UnityEngine.Object> callback)方法(異步)
說明,這裏的資源列表是從bundle_list列表中解析出來的,並且有一個manifest文件用來查找加載依賴的資源,所以,如果要使用這個資源管理器需要將您的資源進行依賴打包assetbundle,可以參考我以前的文章。http://chenshuhb.blog.51cto.com/6087203/1836562
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.IO;
using LitJson;
using UnityEngine.SceneManagement;
/**
* 資源管理器
*
* 功能:
* 1.加載基礎配置數據
* 2.爲各個模塊提供基礎數據接口,供各個模塊調用
* 3. assetbundle,prefab資源的加載
*
**/
public class ResourceManager : Singleton<ResourceManager>
{
//版本文件名
private static readonly string VERSION_FILE = "resource_version";
//assetbundle 路徑
private string BUNDLE_PATH = "";
//本地Resource路徑
private string LOCAL_RES_PATH = "";
private const string assetTail = ".unity3d";
//版本文件
private JsonData jdVersionFile = null;
/// <summary>
/// 資源文件字典
/// key:資源名,value:資源路徑
/// </summary>
private Dictionary<string, string> dicRes = new Dictionary<string, string>();
/// <summary>
/// 資源目錄字典,需要獲取目錄下的所有資源
/// </summary>
private Dictionary<string, string> dicResDir = new Dictionary<string, string>();
/// <summary>
/// 用來存放加載出來的依賴資源的AssetBundle
/// </summary>
Dictionary<string, AssetBundle> dicDepenceAssetBundles = new Dictionary<string, AssetBundle>();
/// <summary>
/// 依賴資源名稱(路徑)list
/// </summary>
List<string> dependenceBundleNames = new List<string>();
/// <summary>
/// bundle資源的總manifest
/// </summary>
private AssetBundleManifest m_manifest = null;
/// <summary>
/// 已經加載完成的依賴資源的數量
/// </summary>
int finishedDependenceCount = 0;
/// <summary>
/// 所有依賴資源的數量
/// </summary>
int allDependenceCount = 0;
#region LoadAssetBundle
/// <summary>
/// 初始化
/// </summary>
public void Init()
{
#if UNITY_EDITOR && UNITY_ANDROID
BUNDLE_PATH = "file:///" + Application.persistentDataPath + "/res/";
#elif UNITY_EDITOR && UNITY_IOS
BUNDLE_PATH = Application.streamingAssetsPath + "/ios/";
#elif UNITY_EDITOR_WIN
BUNDLE_PATH = "file:///" + Application.streamingAssetsPath + "/windows/";
#elif UNITY_ANDROID
BUNDLE_PATH = "file://" + Application.persistentDataPath + "/res/";
#elif UNITY_IOS
BUNDLE_PATH = Application.persistentDataPath + "/res/";
#endif
LOCAL_RES_PATH = Application.persistentDataPath + "/res/";
loadResVersion();
}
/// <summary>
/// 加載資源版本文件
/// </summary>
private void loadResVersion()
{
string version;
if (File.Exists(LOCAL_RES_PATH + VERSION_FILE))
version = File.ReadAllText(LOCAL_RES_PATH + VERSION_FILE);
else
return;
jdVersionFile = JsonMapper.ToObject(version);
if (null == jdVersionFile)
Debug.LogError("Config file is null");
parse(jdVersionFile);
}
#region 加載資源方法
/// <summary>
/// 獲取資源
/// </summary>
/// <param name="name">資源名</param>
/// <param name="callback"></param>
public void AsynGetAsset(string name, Action<UnityEngine.Object> callback)
{
name = name + assetTail;
if (!dicRes.ContainsKey(name))
{
Debug.Log("AsynGetAsset: The asset " + name + " does not exist!");
if (null != callback)
callback(null);
}
string path = dicRes[name];
Game.Instance.StartCoroutine(LoadAssetBundle(path, (obj) =>
{
callback(obj);
}));
}
/// <summary>
/// 釋放依賴資源
/// </summary>
public void UnloadUnusedAssetbundle()
{
foreach (KeyValuePair<string,AssetBundle> kvp in dicDepenceAssetBundles)
{
AssetBundle ab = kvp.Value;
if (null != ab)
ab.Unload(false);
}
dicDepenceAssetBundles.Clear();
}
#endregion
/// <summary>
/// 要加載的資源名稱列表
/// </summary>
List<string> assetNameList = new List<string>();
/// <summary>
/// 資源是否已經加載完成
/// </summary>
bool isOk = true;
/// <summary>
/// 加載目標資源
/// </summary>
/// <param name="name"></param>
/// <param name="callback"></param>
IEnumerator LoadAssetBundle(string name, Action<UnityEngine.Object> callback)
{
assetNameList.Add(name);
Action<Dictionary<string, AssetBundle>> action = (depenceAssetBundles) =>
{
string realName = name;// this.GetRuntimePlatform() + "/" + name;//eg:Windows/ui/panel.unity3d
LoadResReturnWWW(realName, (www) =>
{
int index = realName.LastIndexOf("/");
string assetName = realName.Substring(index + 1);
//去掉".unity3d"後綴
assetName = assetName.Replace(assetTail, "");
//去掉擴展名
//int extIndex = assetName.LastIndexOf(".");
//string extName = assetName.Substring(extIndex);
//assetName = assetName.Replace(extName, "");
AssetBundle assetBundle = www.assetBundle;
//重新給material 的Shader賦值
resetShader(assetBundle);
UnityEngine.Object obj = null;
if (null != assetBundle)
obj = assetBundle.LoadAsset(assetName);//LoadAsset(name),這個name沒有後綴,eg:panel
else
Debug.Log("assetBundle is null assetName is " + assetName);
//重新給material 的Shader賦值
resetShader(obj);
if (null != assetBundle)
assetBundle.Unload(false);//卸載資源內存
//加載目標資源完成的回調
callback(obj);
});
isOk = true;
};
while (assetNameList.Count > 0)
{
if (isOk)
{
isOk = false;
string _name = assetNameList[0];
assetNameList.RemoveAt(0);
LoadDependenceAssets(_name, action);
}
yield return null;
}
}
/// <summary>
/// 加載目標資源的依賴資源
/// </summary>
/// <param name="targetAssetName"></param>
/// <param name="action"></param>
private void LoadDependenceAssets(string targetAssetName, Action<Dictionary<string, AssetBundle>> action)
{
//Debug.Log("LoadDependenceAssets : targetAssetName is " + targetAssetName);
Action<AssetBundleManifest> dependenceAction = (manifest) =>
{
if (null == m_manifest)
m_manifest = manifest;
excuteLoad(targetAssetName, manifest, action);
};
if (null != m_manifest)
excuteLoad(targetAssetName, m_manifest, action);
else
LoadAssetBundleManifest(dependenceAction);
}
/// <summary>
/// 遞歸加載依賴
/// </summary>
void excuteLoad(string targetAssetName, AssetBundleManifest manifest, Action<Dictionary<string, AssetBundle>> action)
{
finishedDependenceCount = 0;
dependenceBundleNames.Clear();
string[] de = manifest.GetAllDependencies(targetAssetName);
allDependenceCount = de.Length;
//如果要加載的資源沒有依賴資源,直接回調返回
if (allDependenceCount == 0)
{
action(null);
}
else
loadSubDependence(targetAssetName, manifest, action, allDependenceCount);//, ref depenceAssetBundles, ref dependenceBundleNames);
}
/// <summary>
/// 加載依賴
/// </summary>
/// <param name="targetAssetName"></param>
/// <param name="manifest"></param>
/// <param name="action"></param>
void loadSubDependence(string targetAssetName, AssetBundleManifest manifest, Action<Dictionary<string, AssetBundle>> action,
int allDependenceCount)//,ref Dictionary<string, AssetBundle> depenceAssetBundles,ref List<string> dependenceBundleNames)
{
string[] dependences = manifest.GetDirectDependencies(targetAssetName);
int length = dependences.Length;
if (length == 0)
{
//沒有依賴
return;
}
else
{
//有依賴,加載所有依賴資源
for (int i = 0; i < length; i++)
{
string dependenceAssetName = dependences[i];
//遞歸
loadSubDependence(dependenceAssetName, manifest, action, allDependenceCount);//,ref depenceAssetBundles,ref dependenceBundleNames);
//判斷資源是否已經被加載過
if (dependenceBundleNames.Contains(dependenceAssetName))
continue;
dependenceBundleNames.Add(dependenceAssetName);
if (!dicDepenceAssetBundles.ContainsKey(dependenceAssetName))
{
//加載
LoadResReturnWWW(dependenceAssetName, (www) =>
{
int index = dependenceAssetName.LastIndexOf("/");
string assetName = dependenceAssetName.Substring(index + 1);
//去掉".unity3d"後綴
assetName = assetName.Replace(assetTail, "");
//去掉擴展名
//int extIndex = assetName.LastIndexOf(".");
//string extName = assetName.Substring(extIndex);
//assetName = assetName.Replace(extName, "");
AssetBundle assetBundle = www.assetBundle;
//重新給material 的Shader賦值
resetShader(assetBundle);
if (null == assetBundle)
Debug.LogError("null == assetBundle : " + dependenceAssetName);
UnityEngine.Object obj = null;
//try
//{
obj = assetBundle.LoadAsset(assetName);
if (dicAtlasObj.ContainsKey(assetName))
{
if (obj == null)
{
#if REALMA_ONGUI_DEBUG
OnGUIDebug.AddMsg("atlas null name is " + assetName);
#endif
Debug.LogError("atlas null name is " + assetName);
}
else
dicAtlasObj[assetName] = obj;
}
//重新給material 的Shader賦值
resetShader(obj);
if (!dicDepenceAssetBundles.ContainsKey(dependenceAssetName))
dicDepenceAssetBundles.Add(dependenceAssetName, assetBundle);
finishedDependenceCount++;
//依賴都加載完了
if (finishedDependenceCount == allDependenceCount)
{
action(dicDepenceAssetBundles);
}
});
}
else
{
finishedDependenceCount++;
//依賴都加載完了
if (finishedDependenceCount == allDependenceCount)
{
action(dicDepenceAssetBundles);
}
}
}
}
}
public UnityEngine.Object GetAtlasObj(string name)
{
if (dicAtlasObj.ContainsKey(name))
return dicAtlasObj[name];
return null;
}
//UnityEditor 在 Android 下打出來的 Android 的 Assetbundle,加載之後 Shader 不能正確的執行!!!
//解決辦法就是,在 Assetbundle Load完之後,遍歷 Material ,把所有的 Shader 都重新 Load 賦值一次。
//通過Assetbundle加載出來的資源有兩種類型需要處理:
//1.如果加載出來的是材質,那麼可以對shader進行賦值
//2.如果加載出來的是GameObject,那麼需要獲取到Renderer組件下的所有材質,再對每個材質的shader賦值
/// </summary>
void resetShader(UnityEngine.Object obj)
{
List<Material> listMat = new List<Material>();
listMat.Clear();
if (obj is Material)
{
Material m = obj as Material;
listMat.Add(m);
}
else if (obj is GameObject)
{
GameObject go = obj as GameObject;
Renderer rend = go.GetComponent<Renderer>();
if (null != rend)
{
Material[] materialsArr = rend.sharedMaterials;
foreach (Material m in materialsArr)
listMat.Add(m);
}
}
for (int i = 0; i < listMat.Count; i++)
{
Material m = listMat[i];
if (null == m)
continue;
var shaderName = m.shader.name;
var newShader = Shader.Find(shaderName);
if (newShader != null)
m.shader = newShader;
else
Debug.LogWarning("unable to refresh shader: " + shaderName + " in material " + m.name);
}
}
/// <summary>
/// 給assetBundle中的材質的shader重新賦值
/// </summary>
/// <param name="assetBundle"></param>
void resetShader(AssetBundle assetBundle)
{
if (null == assetBundle)
return;
var materials = assetBundle.LoadAllAssets(typeof(Material));
foreach (Material m in materials)
{
var shaderName = m.shader.name;
var newShader = Shader.Find(shaderName);
if (newShader != null)
m.shader = newShader;
else
Debug.LogWarning("unable to refresh shader: " + shaderName + " in material " + m.name);
}
}
/// <summary>
/// 加載AssetBundleManifest
/// </summary>
/// <param name="action"></param>
private void LoadAssetBundleManifest(Action<AssetBundleManifest> action)
{
string manifestName = this.GetRuntimePlatform();
LoadResReturnWWW(manifestName, (www) =>
{
AssetBundle assetBundle = www.assetBundle;
UnityEngine.Object obj = assetBundle.LoadAsset("AssetBundleManifest");
assetBundle.Unload(false);
AssetBundleManifest manif = obj as AssetBundleManifest;
action(manif);
});
}
#endregion
#region ExcuteLoader
public void LoadResReturnWWW(string name, Action<WWW> callback)
{
string path = "";
#if UNITY_EDITOR_WIN
path = BUNDLE_PATH + name;
#elif UNITY_ANDROID
path = BUNDLE_PATH + name;
#endif
Game.Instance.StartCoroutine(LoaderRes(path, callback));
}
IEnumerator LoaderRes(string path, Action<WWW> callback)
{
WWW www = new WWW(path);
yield return www;
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log("www.error is " + www.error);
}
if (www.isDone)
{
callback(www);
}
}
#endregion
/// <summary>
/// 解析版本文件
/// </summary>
void parse(JsonData jd)
{
JsonData resInfos = null;
if (jd.Keys.Contains("resource"))
resInfos = jd["resource"];
if (null == resInfos)
{
Debug.LogError("解析資源版本文件出錯");
return;
}
string[] resNames = new string[resInfos.Count];
resInfos.Keys.CopyTo(resNames, 0);
for (int i = 0; i < resNames.Length; i++)
{
string name = resNames[i].Substring(resNames[i].LastIndexOf("/") + 1);
if (!dicRes.ContainsKey(name))
dicRes.Add(name, resNames[i]);
}
}
#region Util
/// <summary>
/// 平臺對應文件夾
/// </summary>
/// <returns></returns>
private string GetRuntimePlatform()
{
string platform = "";
if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor)
{
//platform = "Windows";
platform = "android";
}
else if (Application.platform == RuntimePlatform.Android)
{
platform = "android";
}
else if (Application.platform == RuntimePlatform.IPhonePlayer)
{
platform = "ios";
}
else if (Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXEditor)
{
platform = "osx";
}
return platform;
}
#endregion
}
肯定還有更好的和更完整的資源管理方案,在此跪求大神分享。