目录
[TOC]
一、写在前面
1.使用版本
Unity版本:2018.3.Of2
二、AssetBundle打包管理
1.打包策略
- 设置编辑器工具统一设置AB包名、路径
- 根据依赖关系生成不冗余AB包
- 基于Asset的全路径生成自己的依赖关系表
- 根据自己的依赖关系表加载AB包,编辑器下直接加载资源
优点:
- 不需要在打包编辑器可以直接运行游戏
- 不会产生冗余AB包
- 文件夹或文件AB包设置简单方便容易管理
缺点:
- 长时间未打包的情况下,打包的时候时间较长
2.定义打包配置表
分为两种类型的打包:
-
类型一:基于文件夹下所有单个文件进行打包(用于打包Prefab)
记录该情况下的所有文件夹的路径,遍历文件夹下的Prefab,逐一打包。
-
类型二:基于文件夹进行打包(用于打包Shader、Sound…)
记录文件夹的Info:AB包名、文件夹路径
[CreateAssetMenu(fileName = "ABConfig", menuName = "CreateABConfig", order = 1)]
public class EditABConfig : ScriptableObject
{
//路径:基于Assets下的路径(eg:Assets/xxx/xxx...)
//类型一:基于文件夹下所有单个文件进行打包(用于打包Prefab)
//记录该情况下的所有文件夹的路径
//遍历文件夹下的Prefab,逐一打包
public List<string> PrefabsFolderPathList = new List<string>();
//类型二:基于文件夹进行打包(用于打包Shader、Sound...)
//记录文件夹的Info:AB包名、文件夹路径
public List<FilesFolderInfo> FilesFolderInfoList = new List<FilesFolderInfo>();
[System.Serializable]
public struct FilesFolderInfo
{
public string ABName;
public string Path;
}
}
创建出来的ABConfig如下图:
3.打包
流程思路:
-
Step1.设置AB包配置工具。
AB包打包分为两种类型的AB包:
类型一:基于文件夹下所有单个文件进行打包,一般用于打包Perfab,以Prefab名为AB包名,所以Prefab名需要唯一。
类型二:基于文件夹进行打包,以该文件夹的名称作为AB包名,该文件夹下所有文件打到同一个AB包中,一般用于打包sound、shader等。
-
Step2.读取AB包配置文件,将类型二、类型一的相关信息缓存到两个字典中,通过缓存过滤用的列表来防止冗余AB包
-
Step3.通过Step2中缓存好的两个字典来设置AB包名
-
Step4.打包:
- 缓存需要动态加载的AB包的信息
- 删除多余的AB包
- 生成AB包配置表(以供加载AB包)
- 打包
-
Step5.清除AB包名(防止meta文件变动导致Git需要提交)
-
Step6.刷新编辑器,完成打包
使用打包工具步骤:
-
Step1.创建ABConfig,完成两种类型的AB包路径配置。
类型一:基于文件夹下所有单个文件进行打包,一般用于打包Perfab,以Prefab名为AB包名,所以Prefab名需要唯一。
类型二:基于文件夹进行打包,以该文件夹的名称作为AB包名,该文件夹下所有文件打到同一个AB包中,一般用于打包sound、shader等。
例如: 2.定义打包配置表 中的图所示。
-
Step2.(可选)在
BuildAssetBundleEditor
中设置AB包、配置表路径 -
Step3.打包
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
namespace ResLoadFramework
{
public class BuildAssetBundleEditor
{
#region Config
private const string PATH_ABCONFIG = "Assets/Configs/ABConfig.asset";
private static string PATH_AB = Application.streamingAssetsPath;
private static string PATH_AB_CONFIG_XML = Application.dataPath + "/Configs/AssetBundleConfig.xml";
private static string PATH_AB_CONFIG_BINARY = Application.dataPath + "/Resources/Configs/AssetBundleConfig.bytes";
/// <summary>
/// AB配置表名称(用于打包、加载AB配置表)
/// </summary>
private static string CONFIG_ABNAME = "assetbundleconfig";
#endregion
/// <summary>
/// 缓存 类型二 —— 基于文件夹进行打包的
/// Key:ABName
/// Value:文件夹路径
/// </summary>
private static Dictionary<string, string> mFilesFolderDict = new Dictionary<string, string>();
/// <summary>
/// 缓存 类型一 —— 基于文件夹下所有单个文件进行打包
/// Key:ABName(Prefab名字)
/// Value:依赖项路径(含后缀)
/// </summary>
private static Dictionary<string, List<string>> mPrefabDict = new Dictionary<string, List<string>>();
/// <summary>
/// 过滤用的List(缓存 类型二的文件夹路径、类型一的Prefab的依赖项路径(含后缀))
/// 用于保证AB包无冗余
/// </summary>
private static List<string> mFliterList = new List<string>();
/// <summary>
/// 缓存需要动态加载的AB包全路径
/// 用于优化AB配置表,不需要动态加载的就不用写入表内
/// </summary>
private static List<string> mDynamicLoadABPathList = new List<string>();
[MenuItem("自定义工具/打AB包", false, 0)]
public static void Build()
{
mFilesFolderDict.Clear();//清空缓存
mPrefabDict.Clear();
mFliterList.Clear();
mDynamicLoadABPathList.Clear();
CreateDirectory(PATH_AB);
PrepareDirectory();//为(xml、二进制)配置表创建文件夹
EditABConfig config = LoadAsset<EditABConfig>(PATH_ABCONFIG);//加载配置
//缓存类型二:
foreach (var filesFolderInfo in config.FilesFolderInfoList)
{
if (mFilesFolderDict.ContainsKey(filesFolderInfo.ABName))
{
Debug.LogErrorFormat("AB包名重复!AB包名:{0}", filesFolderInfo.ABName);
}
else
{
mFilesFolderDict.Add(filesFolderInfo.ABName, filesFolderInfo.Path);
//缓存过滤用的List(放入类型二的文件夹路径)
mFliterList.Add(filesFolderInfo.Path);
//类型二的全部需要动态加载
mDynamicLoadABPathList.Add(filesFolderInfo.Path);
}
}
//缓存类型一:
string[] guidArray = AssetDatabase.FindAssets("t:Prefab", config.PrefabsFolderPathList.ToArray());
for (int i = 0; i < guidArray.Length; i++)
{
string prefabPath = AssetDatabase.GUIDToAssetPath(guidArray[i]);//含后缀
EditorUtility.DisplayProgressBar("查找Prefab路径", string.Format("路径:{0}", prefabPath), i * 1.0f / guidArray.Length);
//所有Prefab都需要动态加载
mDynamicLoadABPathList.Add(prefabPath);
//防止:1.类型二中混合有类型一;2.因类型一中Prefab和Prefab依赖项之间存在重复而导致的AB包冗余
if (ContainFliterListPath(prefabPath) == false)
{
//获取Prefab的依赖文件路径(含后缀,含自身)
string[] dependencies = AssetDatabase.GetDependencies(prefabPath);
List<string> dependencyList = new List<string>();//缓存当前Prefab依赖项路径(含后缀)
//剔除 和过滤List中重复的、脚本
for (int j = 0; j < dependencies.Length; j++)
{
if (ContainFliterListPath(dependencies[j]) == false && dependencies[j].EndsWith(".cs") == false)
{
dependencyList.Add(dependencies[j]);
//更新过滤用的List(放入类型一Prefab的依赖项的路径(含后缀))
mFliterList.Add(dependencies[j]);
}
}
GameObject prefabGo = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (mPrefabDict.ContainsKey(prefabGo.name))
{
Debug.LogErrorFormat("存在重复的Prefab名称:{0}", prefabGo.name);
}
else
{
mPrefabDict.Add(prefabGo.name, dependencyList);//缓存 类型一
}
}
}
//mPrefabDict.Keys.ToList().ForEach(key => Debug.Log(key));//打印所有Prefab名
#region 设置AB包名
foreach (var filesFolder in mFilesFolderDict)
{
SetABName(filesFolder.Key, filesFolder.Value);
}
foreach (var prefabPackage in mPrefabDict)
{
SetABName(prefabPackage.Key, prefabPackage.Value);
}
#endregion
//打包
BuildAssetBundle();
//清除AB包名(防止meta文件变动导致Git需要提交)
DeleteABNames();
AssetDatabase.Refresh();
//AB配置表打包
BuildABCfg();
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
Debug.Log("打包完成!");
}
/// <summary>
/// AB配置表打包
/// </summary>
private static void BuildABCfg()
{
//设置AB包名
string path = "Assets" + PATH_AB_CONFIG_BINARY.Replace(Application.dataPath, string.Empty);
SetABName(CONFIG_ABNAME, path);
//打包
//使用LZ4压缩,压缩率不如LZMA;可以加载指定资源,无需一次解压全部资源
BuildPipeline.BuildAssetBundles(PATH_AB, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
//清除AB包名(防止meta文件变动导致Git需要提交)
DeleteABNames();
Debug.LogFormat("{0}打包完成!", CONFIG_ABNAME);
}
/// <summary>
/// 打包
/// </summary>
private static void BuildAssetBundle()
{
string[] allABNames = AssetDatabase.GetAllAssetBundleNames();
//Key:全路径;Value:包名(所有的AB包信息)
Dictionary<string, string> abInfoDict = new Dictionary<string, string>();
for (int i = 0; i < allABNames.Length; i++)
{
string[] paths = AssetDatabase.GetAssetPathsFromAssetBundle(allABNames[i]);
for (int j = 0; j < paths.Length; j++)
{
if (paths[j].EndsWith(".cs"))
continue;
Debug.LogFormat("AB包 {0} 内资源全路径:{1}", allABNames[i], paths[j]);
if (ContainedByDynamicLoadABPathList(paths[j]) == true)
{
abInfoDict.Add(paths[j], allABNames[i]);//需要动态加载的AB包信息
}
}
}
//删除多余的AB包
DeleteRedundantAB();
//写入AB包配置表(以供加载AB包)
WriteABConfig(abInfoDict);
//使用LZ4压缩,压缩率不如LZMA;可以加载指定资源,无需一次解压全部资源
BuildPipeline.BuildAssetBundles(PATH_AB, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
}
/// <summary>
/// 写入AB包配置表
/// </summary>
/// <param Key:全路径;Value:包名="abInfoDict"></param>
private static void WriteABConfig(Dictionary<string, string> abInfoDict)
{
AssetBundleConfig config = new AssetBundleConfig();
config.ABDataList = new List<ABData>();
foreach (var abInfo in abInfoDict)
{
ABData abData = new ABData();
abData.Path = abInfo.Key;
abData.Crc = CRC32.GetCrc32(abInfo.Key);
abData.ABName = abInfo.Value;
//AssetName带后缀
abData.AssetName = abInfo.Key.Remove(0, abInfo.Key.LastIndexOf("/") + 1);
abData.ABDependencies = new List<string>();//依赖项AB包名
//获取当前AB包所有依赖项
string[] dependencies = AssetDatabase.GetDependencies(abInfo.Key);
for (int i = 0; i < dependencies.Length; i++)
{
string dependency = dependencies[i];//依赖项全路径
//AB包的依赖项不包含AB包自身、脚本
if (dependency == abInfo.Key || dependency.EndsWith(".cs"))
continue;
string abName = "";//依赖项AB包名
if (abInfoDict.TryGetValue(dependency, out abName))
{
//AB包的依赖项不包含AB包自身
if (abName == abInfo.Value)
continue;
//防止重复添加依赖项
if (abData.ABDependencies.Contains(abName) == false)
{
abData.ABDependencies.Add(abName);
}
}
}
config.ABDataList.Add(abData);
}
//写入Xml
ToXml(config, PATH_AB_CONFIG_XML);
//写入二进制
//优化:二进制配置表文件不需要Path,加载只需要Crc即可
foreach (ABData abData in config.ABDataList)
{
abData.Path = "";
}
ToBinary(config, PATH_AB_CONFIG_BINARY);
}
private static void ToXml<T>(T obj, string fullPath)
{
CheckFileExist(fullPath);
//Step1:文件流——创建xml文件
FileStream fs = new FileStream(fullPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
//Step2:写入流
StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8);
//Step3:创建xml序列化器
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
//Step4:序列化对象到写入流中
xmlSerializer.Serialize(sw, obj);
//Step5:关闭流
sw.Close();
fs.Close();
}
private static void ToBinary<T>(T obj, string fullPath)
{
CheckFileExist(fullPath);
//Step1:文件流——创建二进制文件
FileStream fs = new FileStream(fullPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
//Step2:创建二进制序列化器
BinaryFormatter binaryFormatter = new BinaryFormatter();
//Step3:序列化对象到文件流中
binaryFormatter.Serialize(fs, obj);
//Step4:关闭流
fs.Close();
}
private static void CheckFileExist(string fullPath)
{
//存在则删除
if (File.Exists(fullPath)) File.Delete(fullPath);
}
/// <summary>
/// 删除多余的AB包
/// </summary>
private static void DeleteRedundantAB()
{
string[] allABNames = AssetDatabase.GetAllAssetBundleNames();
DirectoryInfo directoryInfo = new DirectoryInfo(PATH_AB);
//获取AB文件夹下所有文件的fileInfo
FileInfo[] fileInfos = directoryInfo.GetFiles("*", SearchOption.AllDirectories);
for (int i = 0; i < fileInfos.Length; i++)
{
//meta文件可以不清除
if (ContainedByAB(fileInfos[i].Name, allABNames) || fileInfos[i].Name.EndsWith(".meta"))
continue;
else//可以清除
{
Debug.LogFormat("AB包 {0} 已经删除或改名", fileInfos[i].Name);
if (File.Exists(fileInfos[i].FullName))
{
File.Delete(fileInfos[i].FullName);
}
}
}
}
/// <summary>
/// 当前file文件是否已经包含在AB包文件内
/// 若不是则可以清除
/// </summary>
private static bool ContainedByAB(string fileName, string[] allABNames)
{
foreach (var abName in allABNames)
{
if (fileName == abName)
return true;
}
return false;
}
/// <summary>
/// 设置类型二(基于文件夹进行打包的)的AB包名
/// path:文件夹路径(Assets/xxx...)
/// </summary>
private static void SetABName(string abName, string path)
{
AssetImporter assetImporter = AssetImporter.GetAtPath(path);
if (assetImporter == null)
Debug.LogErrorFormat("AB包路径出错,路径:{0}", path);
else
assetImporter.assetBundleName = abName;
}
/// <summary>
/// 设置类型一(prefab及其依赖项)的AB包名
/// 依赖项是经过类型二剔除后的
/// </summary>
private static void SetABName(string abName, List<string> dependenciesName)
{
for (int i = 0; i < dependenciesName.Count; i++)
{
SetABName(abName, dependenciesName[i]);
}
}
private static T LoadAsset<T>(string path)
where T : ScriptableObject
{
return AssetDatabase.LoadAssetAtPath<T>(path);
}
/// <summary>
/// 是否包含过滤List中的路径
/// 用于剔除冗余AB包
/// </summary>
private static bool ContainFliterListPath(string path)
{
for (int i = 0; i < mFliterList.Count; i++)
{
//path.Replace(mFliterList[i], string.Empty)[0] == '/'是用于避免:
//Assets/GameData 和 Assets/GameDataxxx/atk.prefab
//第二个包含第一个,但第二个和第一个不属于同一个文件夹
if (path == mFliterList[i] || path.Contains(mFliterList[i]) && path.Replace(mFliterList[i], string.Empty)[0] == '/')
return true;
}
return false;
}
/// <summary>
/// 路径是否包含于 需要动态加载的AB路径List
/// 只有需要动态加载的AB的Path才需要写入AB配置表
/// </summary>
private static bool ContainedByDynamicLoadABPathList(string fullPath)
{
for (int i = 0; i < mDynamicLoadABPathList.Count; i++)
{
//类型一为==;类型二为Contains
if (fullPath.Contains(mDynamicLoadABPathList[i]) && (fullPath.Replace(mDynamicLoadABPathList[i], string.Empty) == string.Empty || fullPath.Replace(mDynamicLoadABPathList[i], string.Empty)[0] == '/'))
return true;
}
return false;
}
/// <summary>
/// 清除AB包名(防止meta文件变动导致Git需要提交)
/// </summary>
private static void DeleteABNames()
{
string[] oldABNames = AssetDatabase.GetAllAssetBundleNames();
for (int i = 0; i < oldABNames.Length; i++)
{
AssetDatabase.RemoveAssetBundleName(oldABNames[i], true);
EditorUtility.DisplayProgressBar("清除AB包名", string.Format("AB包名:{0}", oldABNames[i]), i * 1.0f / oldABNames.Length);
}
}
private static void CreateDirectory(string folderFullPath)
{
if (Directory.Exists(folderFullPath) == false)
{
Directory.CreateDirectory(folderFullPath);
Debug.LogFormat("创建文件夹,文件夹路径:{0}", folderFullPath);
}
}
private static void PrepareDirectory()
{
int xmlIndex = PATH_AB_CONFIG_XML.LastIndexOf("/");
CreateDirectory(PATH_AB_CONFIG_XML.Remove(xmlIndex, PATH_AB_CONFIG_XML.Length - xmlIndex));
int binaryIndex = PATH_AB_CONFIG_BINARY.LastIndexOf("/");
CreateDirectory(PATH_AB_CONFIG_BINARY.Remove(binaryIndex, PATH_AB_CONFIG_BINARY.Length - binaryIndex));
}
}
}
补充:
- 设置
mFliterList
的目的是用于防止AB包冗余的:先缓存类型二的,再缓存类型一;在缓存类型一的时候,类型一的依赖项需经过mFliterList
过滤后才可加入缓存中。
4.AB包配置表
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
namespace ResLoadFramework
{
/// <summary>
/// AB包配置
/// </summary>
[Serializable]
public class AssetBundleConfig
{
[XmlElement("ABDataList")]
public List<ABData> ABDataList { get; set; }
}
/// <summary>
/// AB包数据
/// </summary>
[Serializable]
public class ABData
{
[XmlAttribute("Path")]
public string Path { get; set; }
/// <summary>
/// 唯一标识(精度低于MD5)
/// </summary>
[XmlAttribute("Crc")]
public uint Crc { get; set; }
[XmlAttribute("ABName")]
public string ABName { get; set; }
[XmlAttribute("AssetName")]
public string AssetName { get; set; }
[XmlElement("ABDependencies")]
public List<string> ABDependencies { get; set; }
}
}
5.CRC 效验(快速检测算法)
using System;
namespace ResLoadFramework
{
/// <summary>
/// CRC 效验(快速检测算法)
/// </summary>
public class CRC32
{
private static UInt32[] crcTable =
{
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
public static uint GetCrc32(string msg)
{
byte[] bytes = System.Text.Encoding.Default.GetBytes(msg);//解决中文的问题
//byte[] bytes = System.Text.Encoding.UTF8.GetBytes(msg);//只能英文
uint iCount = (uint)bytes.Length;
uint crc = 0xFFFFFFFF;
for (uint i = 0; i < iCount; i++)
{
crc = (crc << 8) ^ crcTable[(crc >> 24) ^ bytes[i]];
}
return crc;
}
}
}
三、对象管理
1.类对象池 ClassObjectPool
- 构造函数
public ClassObjectPool(int maxCount)
:maxCount为类对象池的最大对象个数(<=0表示不限个数) - API:
- (1)
public T Spawn(bool createIfPoolEmpty)
:从类对象池中取类对象(createIfPoolEmpty:若类对象池中的对象用完了 是否创建对象) - (2)
public bool Recycle(T obj)
:回收对象到类对象池(只有对象真的回收到类对象池中 才返回true)
- (1)
using System.Collections.Generic;
namespace ResLoadFramework
{
public class ClassObjectPool<T> where T : class, new()
{
protected Stack<T> mPool = new Stack<T>();
/// <summary>
/// 池的最大对象个数(<=0表示不限个数)
/// </summary>
protected int mMaxCount = 0;
/// <summary>
/// 未回收的对象个数
/// </summary>
protected int mNoRecycleCount = 0;
public ClassObjectPool(int maxCount)
{
mMaxCount = maxCount;
//填满类对象池
for (int i = 0; i < mMaxCount; i++)
{
mPool.Push(new T());
}
}
/// <summary>
/// 从类对象池中取类对象(createIfPoolEmpty:若类对象池中的对象用完了 是否创建对象)
/// </summary>
/// <param 如果类对象池为空是否创建对象="createIfPoolEmpty"></param>
/// <returns></returns>
public T Spawn(bool createIfPoolEmpty)
{
//池中有对象
if (mPool.Count > 0)
{
T ret = mPool.Pop();
//如果池中的对象为空对象=>池已空
if (ret == null)
{
if (createIfPoolEmpty)
{
ret = new T();
}
}
mNoRecycleCount++;
return ret;
}
//池中无对象
else
{
if (createIfPoolEmpty)
{
T ret = new T();
mNoRecycleCount++;
return ret;
}
}
return null;//池空,无对象可取
}
/// <summary>
/// 回收对象到类对象池(只有对象真的回收到类对象池中 才返回true)
/// </summary>
/// <param 待回收的对象="obj"></param>
/// <returns>只有对象真的回收到类对象池中 才返回true</returns>
public bool Recycle(T obj)
{
//空对象不需要回收
if (obj == null)
return false;
//超出池容量部分的对象
if (mPool.Count >= mMaxCount && mMaxCount > 0)
{
//直接置空,等待GC释放内存
obj = null;
mNoRecycleCount--;
return false;
}
//对象真的回收到类对象池中
mPool.Push(obj);
mNoRecycleCount--;
return true;
}
}
}
2.对象管理器 ObjectMgr
- 继承自单例基类
Singleton<ObjectMgr>
- API:
- (1)
public ClassObjectPool<T> GetOrCreateClassObjectPool<T>(int maxCount) where T : class, new()
:获取或创建类对象池(maxCount:类对象池的最大池容量,若小于等于0则不限制容量)
- (1)
using System;
using System.Collections.Generic;
using UnityEngine;
namespace ResLoadFramework
{
public class ObjectMgr : Singleton<ObjectMgr>
{
#region 类对象池的缓存、获取
/// <summary>
/// 缓存所有的类对象池
/// </summary>
protected Dictionary<Type, object> mClassObjectPoolDict = new Dictionary<Type, object>();
/// <summary>
/// 获取或创建类对象池
/// maxCount:类对象池的最大池容量,若小于等于0则不限制容量
/// </summary>
/// <typeparam 类对象的类型="T"></typeparam>
/// <param 类对象池的最大池容量="maxCount"></param>
/// <returns></returns>
public ClassObjectPool<T> GetOrCreateClassObjectPool<T>(int maxCount) where T : class, new()
{
Type type = typeof(T);
object poolObj = null;
//缓存中不存在 或者 存在但是为null
if (mClassObjectPoolDict.TryGetValue(type, out poolObj) == false || poolObj == null)
{
//创建类对象池
ClassObjectPool<T> newPool = new ClassObjectPool<T>(maxCount);
mClassObjectPoolDict[type] = newPool;
return newPool;
}
return poolObj as ClassObjectPool<T>;
}
#endregion
}
}
四、AssetBundle管理
1.AssetBundle管理器 AssetBundleMgr
- API:
- (1)
public bool LoadABConfig()
:加载、缓存AB包配置表。 - (2)
public ResourceItem LoadResourceItem(uint crc)
:根据资源路径的 Crc 加载资源项 ResourceItem。 - (3)
public void ReleaseResourceItem(ResourceItem resourceItem)
:释放资源项 ResourceItem。 - (4)
public ResourceItem FindResourceItem(uint crc)
:根据资源路径的 Crc 从缓存中查找资源项 ResourceItem。
- (1)
2.加载、缓存 AssetBundle 配置表
ResourceItem 资源项数据类:存储 AssetBundle 信息、依赖项AB包信息、资源信息,供加载资源使用。
ResourceItem 包含有引用计数、资源使用时间,供 ResourcesMgr
中缓存使用。
/// <summary>
/// 资源项,存储资源所在AssetBundle信息、依赖项AB包信息、资源信息
/// </summary>
public class ResourceItem
{
//-------------------------------AB包相关-------------------------------//
/// <summary>
/// 资源路径Crc
/// </summary>
public uint Crc;
/// <summary>
/// 资源名称
/// </summary>
public string AssetName;
/// <summary>
/// 资源所在AB包名称
/// </summary>
public string ABName;
/// <summary>
/// 资源所依赖的AB包列表
/// </summary>
public List<string> ABDependencies = null;
/// <summary>
/// 该资源项对应的AB包(加载好的)
/// </summary>
public AssetBundle AssetBundle = null;
//-------------------------------加载资源相关-------------------------------//
/// <summary>
/// 加载的资源对象
/// </summary>
public UnityEngine.Object Obj = null;
/// <summary>
/// 资源 Object 的唯一标识
/// </summary>
public int Guid = 0;
/// <summary>
/// 该资源最后使用的时间
/// </summary>
public float LastUseTime = 0f;
protected int mRefCount = 0;
/// <summary>
/// 引用计数
/// </summary>
public int RefCount
{
get => mRefCount;
set
{
mRefCount = value;
if (value < 0)
{
Debug.LogErrorFormat("ResourceItem 的引用计数 Erroer!value:{0],加载的资源名称:{1}", value, Obj == null ? "null" : Obj.name);
mRefCount = 0;
}
}
}
}
加载、缓存AB包配置表:public bool LoadABConfig()
#region Configs
/// <summary>
/// AB包配置表名称(用于打包、加载AB包配置表)
/// </summary>
private static string CONFIG_ABNAME = "assetbundleconfig";
/// <summary>
/// AB包配置表资源名称(用于从AssetBundle中加载)
/// </summary>
private static string CONFIG_ASSETNAME = "AssetBundleConfig";
#endregion
/// <summary>
/// 缓存从AB包配置表中加载出来的 资源项
/// </summary>
protected Dictionary<uint, ResourceItem> mResourceItemDict = new Dictionary<uint, ResourceItem>();
/// <summary>
/// 加载、缓存AB包配置表
/// </summary>
/// <returns></returns>
public bool LoadABConfig()
{
mResourceItemDict.Clear();
mAssetBundleItemDict.Clear();
//加载AB包配置表
AssetBundle configBundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + CONFIG_ABNAME);
TextAsset textAsset = configBundle.LoadAsset<TextAsset>(CONFIG_ASSETNAME);
if (textAsset == null)
{
Debug.LogError("LoadABConfig Error!AB包配置表不存在");
return false;
}
MemoryStream memoryStream = new MemoryStream(textAsset.bytes);
BinaryFormatter binaryFormatter = new BinaryFormatter();
AssetBundleConfig config = (AssetBundleConfig)binaryFormatter.Deserialize(memoryStream);
memoryStream.Close();
//缓存AB包配置表
for (int i = 0; i < config.ABDataList.Count; i++)
{
ABData abData = config.ABDataList[i];
ResourceItem resItem = new ResourceItem
{
Crc = abData.Crc,
AssetName = abData.AssetName,
ABName = abData.ABName,
ABDependencies = abData.ABDependencies,
};
if (mResourceItemDict.ContainsKey(resItem.Crc))
{
Debug.LogErrorFormat("LoadABConfig Error!Crc重复,AssetName:{0};ABName:{1}", resItem.AssetName, resItem.ABName);
return false;
}
mResourceItemDict.Add(resItem.Crc, resItem);
}
Debug.Log("AB包配置表加载、缓存完成");
return true;
}
3.根据资源路径的 Crc 加载资源项 ResourceItem
AssetBundleItem AssetBundle资源类,包含引用计数
/// <summary>
/// AssetBundle资源
/// </summary>
public class AssetBundleItem
{
public AssetBundle AssetBundle = null;
/// <summary>
/// 引用计数
/// </summary>
public int RefCount = 0;
/// <summary>
/// 重置
/// </summary>
public void Reset()
{
AssetBundle = null;
RefCount = 0;
}
}
根据资源路径的 Crc 加载资源项 ResourceItem:public ResourceItem LoadResourceItem(uint crc)
/// <summary>
/// 根据资源路径的Crc加载资源项ResourceItem
/// </summary>
/// <param 资源路径的Crc="crc"></param>
/// <returns></returns>
public ResourceItem LoadResourceItem(uint crc)
{
ResourceItem resourceItem = null;
if (mResourceItemDict.TryGetValue(crc, out resourceItem) == false || resourceItem == null)
{
Debug.LogErrorFormat("LoadResourceItem Error!AB包配置表中不存在此Crc:{0}", crc.ToString());
return resourceItem;
}
//资源项对应的AB包已加载
if (resourceItem.AssetBundle != null)
{
return resourceItem;
}
//资源项对应的AB包未加载
resourceItem.AssetBundle = LoadAssetBundle(resourceItem.ABName);
//加载资源项中记录的依赖项
if (resourceItem.ABDependencies != null)
{
for (int i = 0; i < resourceItem.ABDependencies.Count; i++)
{
LoadAssetBundle(resourceItem.ABDependencies[i]);
}
}
return resourceItem;
}
/// <summary>
/// 根据ABName加载、缓存AB包
/// </summary>
/// <param AB包名="abName"></param>
/// <returns></returns>
private AssetBundle LoadAssetBundle(string abName)
{
AssetBundleItem assetBundleItem = null;
uint crc = CRC32.GetCrc32(abName);
if (mAssetBundleItemDict.TryGetValue(crc, out assetBundleItem) == false || assetBundleItem == null)
{
string fullPath = Application.streamingAssetsPath + "/" + abName;
AssetBundle assetBundle = null;
if (File.Exists(fullPath))//容错
{
assetBundle = AssetBundle.LoadFromFile(fullPath);//加载AB包
}
if (assetBundle == null)
{
Debug.LogErrorFormat("LoadAssetBundle Error!AB包全路径:{0}", fullPath);
}
assetBundleItem = mAssetBundleItemPool.Spawn(true);//使用类对象池
assetBundleItem.AssetBundle = assetBundle;
assetBundleItem.RefCount += 1;
mAssetBundleItemDict.Add(crc, assetBundleItem);
}
else
{
assetBundleItem.RefCount += 1;
}
return assetBundleItem.AssetBundle;
}
4.释放资源项 ResourceItem
释放资源项 ResourceItem:public void ReleaseResourceItem(ResourceItem resourceItem)
/// <summary>
/// 释放资源项ResourceItem
/// </summary>
/// <param 资源项="resourceItem"></param>
public void ReleaseResourceItem(ResourceItem resourceItem)
{
if (resourceItem == null) return;
//卸载依赖项
if (resourceItem.ABDependencies != null && resourceItem.ABDependencies.Count > 0)
{
for (int i = 0; i < resourceItem.ABDependencies.Count; i++)
{
UnloadAssetBundle(resourceItem.ABDependencies[i]);
}
}
//卸载自身AB包
UnloadAssetBundle(resourceItem.ABName);
resourceItem.AssetBundle = null;
}
/// <summary>
/// 根据ABName卸载AB包
/// </summary>
/// <param AB包名="abName"></param>
private void UnloadAssetBundle(string abName)
{
AssetBundleItem assetBundleItem = null;
uint crc = CRC32.GetCrc32(abName);
if (mAssetBundleItemDict.TryGetValue(crc, out assetBundleItem) == false || assetBundleItem == null)
{
Debug.LogErrorFormat("UnloadAssetBundle Error!{0}AssetBundle尚未加载", abName);
}
else
{
assetBundleItem.RefCount -= 1;//引用计数--
if (assetBundleItem.RefCount <= 0 && assetBundleItem.AssetBundle != null)
{
assetBundleItem.AssetBundle.Unload(true);//卸载AB包
assetBundleItem.Reset();//重置对象
mAssetBundleItemPool.Recycle(assetBundleItem);//类对象池回收
mAssetBundleItemDict.Remove(crc);//从缓存中移除
}
}
}
5.根据资源路径的 Crc 从缓存中查找资源项 ResourceItem
根据资源路径的 Crc 从缓存中查找资源项 ResourceItem:public ResourceItem FindResourceItem(uint crc)
/// <summary>
/// 根据资源路径的Crc从缓存中查找资源项ResourceItem
/// </summary>
/// <param 资源路径的Crc="crc"></param>
/// <returns></returns>
public ResourceItem FindResourceItem(uint crc)
{
return mResourceItemDict[crc];
}
五、Resources 管理器
1.ResourcesMgr
API:
- (1)
public T LoadResource<T>(string path)
:同步加载资源,根据资源路径。仅加载不需要实例化的资源(sprites/audio…) - (2)
public bool ReleaseResource(UnityEngine.Object obj, bool destroyObj = false)
:卸载资源(不需要实例化的资源:sprites/audio…)
2.双向链表
双向链表节点:
/// <summary>
/// 双向链表节点
/// </summary>
public class DoubleLinkedListNode<T> where T : class, new()
{
/// <summary>
/// 前一个节点
/// </summary>
public DoubleLinkedListNode<T> Previous = null;
/// <summary>
/// 当前节点
/// </summary>
public T Current = null;
/// <summary>
/// 后一个节点
/// </summary>
public DoubleLinkedListNode<T> Next = null;
}
双向链表:
API:
- (1)
public DoubleLinkedListNode<T> AddToHead(T t)
:添加到头结点 - (2)
public DoubleLinkedListNode<T> AddToHead(DoubleLinkedListNode<T> node)
:添加到头结点 - (3)
public DoubleLinkedListNode<T> AddToTail(T t)
:添加到尾结点 - (4)
public DoubleLinkedListNode<T> AddToTail(DoubleLinkedListNode<T> node)
:添加到尾结点 - (5)
public void RemoveNode(DoubleLinkedListNode<T> node)
:移除节点 - (6)
public void MoveToHead(DoubleLinkedListNode<T> node)
:将指定节点移动到头结点
/// <summary>
/// 双向链表(容器)
/// </summary>
/// <typeparam name="T"></typeparam>
public class DoubleLinkedList<T> where T : class, new()
{
/// <summary>
/// 表头
/// </summary>
public DoubleLinkedListNode<T> Head = null;
/// <summary>
/// 表尾
/// </summary>
public DoubleLinkedListNode<T> Tail = null;
/// <summary>
/// 双向链表节点的类对象池
/// </summary>
protected ClassObjectPool<DoubleLinkedListNode<T>> mDoubleLinkedNodePool = ObjectMgr.Instance.GetOrCreateClassObjectPool<DoubleLinkedListNode<T>>(500);
protected int mCount;
/// <summary>
/// 容器内节点个数
/// </summary>
public int Count => mCount;
/// <summary>
/// 添加到头结点
/// </summary>
public DoubleLinkedListNode<T> AddToHead(T t)
{
DoubleLinkedListNode<T> node = mDoubleLinkedNodePool.Spawn(true);
node.Previous = null;
node.Next = null;
node.Current = t;
return AddToHead(node);
}
/// <summary>
/// 添加到头结点
/// </summary>
public DoubleLinkedListNode<T> AddToHead(DoubleLinkedListNode<T> node)
{
if (node == null) return null;
node.Previous = null;
if (Head == null)
{
Tail = Head = node;
}
else
{
node.Next = Head;
Head.Previous = node;
Head = node;
}
mCount++;
return node;
}
/// <summary>
/// 添加到尾结点
/// </summary>
public DoubleLinkedListNode<T> AddToTail(T t)
{
DoubleLinkedListNode<T> node = mDoubleLinkedNodePool.Spawn(true);
node.Previous = null;
node.Next = null;
node.Current = t;
return AddToTail(node);
}
/// <summary>
/// 添加到尾结点
/// </summary>
public DoubleLinkedListNode<T> AddToTail(DoubleLinkedListNode<T> node)
{
if (node == null) return null;
node.Next = null;
if (Tail == null)
{
Head = Tail = node;
}
else
{
node.Previous = Tail;
Tail.Next = node;
Tail = node;
}
mCount++;
return node;
}
/// <summary>
/// 移除节点
/// </summary>
public void RemoveNode(DoubleLinkedListNode<T> node)
{
if (node == null) return;
if (node == Head)
Head = node.Next;
if (node == Tail)
Tail = node.Previous;
if (node.Previous != null)
node.Previous.Next = node.Next;
if (node.Next != null)
node.Next.Previous = node.Previous;
node.Previous = node.Next = null;//重置节点
node.Current = null;
mDoubleLinkedNodePool.Recycle(node);//回收节点对象
mCount--;
}
/// <summary>
/// 将指定节点移动到头结点
/// </summary>
public void MoveToHead(DoubleLinkedListNode<T> node)
{
if (node == null || node == Head) return;
if (node.Previous == null && node.Next == null) return;
if (node == Tail)
Tail = node.Previous;
if (node.Previous != null)
node.Previous.Next = node.Next;
if (node.Next != null)
node.Next.Previous = node.Previous;
node.Previous = null;
node.Next = Head;
Head.Previous = node;
Head = node;
//if (Tail == null)//当只有两个节点的时候,此时尾结点为null
//{
// Tail = Head;
//}
}
}
3.封装双向链表
封装后的双向链表会在销毁时调用析构函数中的 Clear()
方法来清空双向链表。
使用双向链表的原因:
- (1)
API:
- (1)
public void Clear()
:清空双向链表 - (2)
public void InsertToHead(T t)
:插入到双向链表的头结点 - (3)
public void Pop()
:从双向链表表尾弹出一个节点 - (4)
public void Remove(T t)
:移除指定节点 - (5)
public T Back()
:获取双向链表的尾结点 - (6)
public int Size()
:获取双向链表中节点个数 - (7)
public bool Find(T t)
:查找指定节点是否存在 - (8)
public bool Refresh(T t)
:刷新指定节点,将该节点移动到双向链表头结点
/// <summary>
/// 封装后的双向链表
/// </summary>
public class CustomMapList<T> where T : class, new()
{
/// <summary>
/// 双向链表
/// </summary>
private DoubleLinkedList<T> mLink = new DoubleLinkedList<T>();
/// <summary>
/// 用于双向链表元素查找的字典
/// </summary>
private Dictionary<T, DoubleLinkedListNode<T>> mDict = new Dictionary<T, DoubleLinkedListNode<T>>();
/// <summary>
/// 析构函数(清除该对象时清空双向链表)
/// </summary>
~CustomMapList()
{
Clear();
}
/// <summary>
/// 清空双向链表
/// </summary>
public void Clear()
{
while (mLink.Tail != null)
{
Remove(mLink.Tail.Current);
}
}
/// <summary>
/// 插入到双向链表的头结点
/// </summary>
public void InsertToHead(T t)
{
DoubleLinkedListNode<T> node = null;
if (mDict.TryGetValue(t, out node) == false || node == null)//字典中没有此node
{
mLink.AddToHead(t);
mDict.Add(t, mLink.Head);
return;
}
//字典中已有此node
mLink.AddToHead(node);
}
/// <summary>
/// 从双向链表表尾弹出一个节点
/// </summary>
public void Pop()
{
if (mLink.Tail != null)
{
Remove(mLink.Tail.Current);
}
}
/// <summary>
/// 移除指定节点
/// </summary>
public void Remove(T t)
{
DoubleLinkedListNode<T> node = null;
if (mDict.TryGetValue(t, out node) == false || node == null) return;
mLink.RemoveNode(node);
mDict.Remove(t);
}
/// <summary>
/// 获取双向链表的尾结点
/// </summary>
public T Back()
{
return mLink.Tail == null ? null : mLink.Tail.Current;
}
/// <summary>
/// 获取双向链表中节点个数
/// </summary>
public int Size()
{
return mDict.Count;
}
/// <summary>
/// 查找指定节点是否存在
/// </summary>
public bool Find(T t)
{
DoubleLinkedListNode<T> node = null;
if (mDict.TryGetValue(t, out node) == false || node == null)
return false;
return true;
}
/// <summary>
///刷新指定节点,将该节点移动到双向链表头结点
/// </summary>
public bool Refresh(T t)
{
DoubleLinkedListNode<T> node = null;
//节点不存在
if (mDict.TryGetValue(t, out node) == false || node == null)
return false;
//节点存在,移动到头结点
mLink.MoveToHead(node);
return true;
}
}
4.同步加载非实例化资源(shader/sprite/sound…)
流程图:
/// <summary>
/// 缓存资源引用计数为 0 的 ResourceItem,达到最大缓存时,释放最早没有使用的资源
/// </summary>
protected CustomMapList<ResourceItem> mNoReferenceMapList = new CustomMapList<ResourceItem>();
/// <summary>
/// 缓存正在使用的资源(ResourceItem 引用计数非 0)
/// </summary>
public Dictionary<uint, ResourceItem> AssetDict { get; set; } = new Dictionary<uint, ResourceItem>();
/// <summary>
/// 是否从AB包中加载资源
/// </summary>
public bool IsLoadFromAssetBundle = true;
/// <summary>
/// 同步加载资源,根据资源路径。仅加载不需要实例化的资源(sprites/audio...)
/// </summary>
/// <typeparam 资源类型="T"></typeparam>
/// <param 资源路径="path"></param>
/// <returns></returns>
public T LoadResource<T>(string path) where T : UnityEngine.Object
{
if (string.IsNullOrEmpty(path)) return null;
uint crc = CRC32.GetCrc32(path);
//从正在使用的资源缓存中获取
ResourceItem resItem = GetResourceItemFormCache(crc);
//正在使用的资源缓存中 存在该资源
if (resItem != null)
{
return resItem.Obj as T;
}
//正在使用的资源缓存中 不存在该资源
else
{
T obj = null;
#if UNITY_EDITOR
//编辑器模式下(非真机运行环境)
if (IsLoadFromAssetBundle == false)
{
//从 AssetBundleMgr 的缓存中获取 ResourceItem 信息
resItem = AssetBundleMgr.Instance.FindResourceItem(crc);
if (resItem.Obj != null)
{
obj = resItem.Obj as T;
}
else
{
obj = LoadAssetByEditor<T>(path);
}
}
#endif
//真机运行环境(使用 AssetBundle 加载资源)
if (obj == null)
{
//从 AssetBundleMgr 中获取 ResourceItem 信息,并完成AB包加载、依赖项加载
resItem = AssetBundleMgr.Instance.LoadResourceItem(crc);
if (resItem != null && resItem.AssetBundle != null)//容错
{
if (resItem.Obj != null)
{
obj = resItem.Obj as T;
}
else
{
//使用 AssetBundle 加载资源
obj = resItem.AssetBundle.LoadAsset<T>(resItem.AssetName);
}
}
}
//缓存 ResourceItem 到 AssetDict
CacheResourceItem(path, ref resItem, obj);
return obj;
}
}
/// <summary>
/// 缓存资源项 ResourceItem ,更新资源唯一标识、最后使用时间、引用计数
/// </summary>
/// <param 资源路径="path"></param>
/// <param 资源项="resItem"></param>
/// <param 加载的资源="obj"></param>
private void CacheResourceItem(string path, ref ResourceItem resItem, UnityEngine.Object obj, int addRefCount = 1)
{
//缓存太多,清除最早没有使用的资源
WashOut();
if (resItem == null || obj == null)
Debug.LogErrorFormat("CacheResourceItem Error!path:{0}", path);
resItem.Obj = obj;
resItem.Guid = obj.GetInstanceID();//资源 Object 的唯一标识
resItem.LastUseTime = Time.realtimeSinceStartup;
resItem.RefCount += addRefCount;
ResourceItem oldResItem = null;
if (AssetDict.TryGetValue(resItem.Crc, out oldResItem))
{
AssetDict[resItem.Crc] = resItem;//替换 ResourceItem,理论上不需要
}
else
{
AssetDict.Add(resItem.Crc, resItem);
}
}
/// <summary>
/// 清除最早没有使用的资源(缓存太多)
/// </summary>
protected void WashOut()
{
////当前内存使用>80%的时候,清除最早没有使用的资源
//if (mNoReferenceMapList.Size() <= 0)
// break;
//ResourceItem resItem = mNoReferenceMapList.Back();//取出尾部资源
//DestroyResourceItem(resItem,true);
//mNoReferenceMapList.Pop();
}
/// <summary>
/// 从 AssetDict 缓存中回收 ResourceItem
/// </summary>
/// <param 资源项="resItem"></param>
/// <param 是否删除缓存="destroyCache"></param>
protected void DestroyResourceItem(ResourceItem resItem, bool destroyCache = false)
{
if (resItem == null || resItem.RefCount > 0) return;
//不删除缓存:
if (destroyCache == false)
{
//将 AssetDict 中的 ResourceItem 添加到 当前没有使用的资源的缓存列表 中
mNoReferenceMapList.InsertToHead(resItem);
return;
}
//删除缓存:
//若该 ResourceItem 不存在于 AssetDict,则返回,不做处理
if (AssetDict.Remove(resItem.Crc) == false) return;
//若该 ResourceItem 存在于 AssetDict 中
//释放 ResourceItem,对应的 AB包、依赖项 根据引用计数情况进行卸载
AssetBundleMgr.Instance.ReleaseResourceItem(resItem);
if (resItem.Obj != null)
resItem.Obj = null;//对Object资源进行引用置空,此后系统会自动进行GC操作
}
#if UNITY_EDITOR
/// <summary>
/// 同步加载资源(编辑器模式)
/// </summary>
/// <typeparam 资源类型="T"></typeparam>
/// <param 资源路径="path"></param>
/// <returns></returns>
protected T LoadAssetByEditor<T>(string path) where T : UnityEngine.Object
{
return UnityEditor.AssetDatabase.LoadAssetAtPath<T>(path);
}
#endif
/// <summary>
/// 根据资源路径的 crc 从正在使用的资源缓存中获取对应资源
/// </summary>
/// <param 资源路径的 crc="crc"></param>
/// <param 增加引用计数的个数="addRefCount"></param>
/// <returns></returns>
private ResourceItem GetResourceItemFormCache(uint crc, int addRefCount = 1)
{
ResourceItem resitem = null;
if (AssetDict.TryGetValue(crc, out resitem) && resitem != null)
{
resitem.RefCount += addRefCount;
resitem.LastUseTime = Time.realtimeSinceStartup;//记录资源使用时间
//if (resitem.RefCount <= 1)//容错(理论上不会进入此分支)
//{
// mNoReferenceMapList.Remove(resitem);
//}
}
return resitem;
}
5.卸载资源
编辑器模式下,不能完全卸载资源,即不能从 AssetDict 缓存中清除资源。
流程图:
/// <summary>
/// 卸载资源(不需要实例化的资源:sprites/audio...)
/// </summary>
/// <param 资源="obj"></param>
/// <param 是否从缓存中清除该资源="destroyObj"></param>
/// <returns></returns>
public bool ReleaseResource(UnityEngine.Object obj, bool destroyObj = false)
{
if (obj == null) return false;
//从 AssetDict 缓存中通过资源 obj 的唯一标识获取对应的 ResourceItem
ResourceItem resItem = null;
foreach (ResourceItem item in AssetDict.Values)
{
if (item.Guid == obj.GetInstanceID())
{
resItem = item;
}
}
if (resItem == null)
{
Debug.LogErrorFormat("ReleaseResource Error!{0}可能释放了多次", obj.name);
return false;
}
resItem.RefCount -= 1;//引用计数--
DestroyResourceItem(resItem, destroyObj);//从 AssetDict 中回收 ResourceItem
return true;
}