前言
我们知道,使用 Resources.Load< T >(path : string) 可以加载 Resources 文件夹下的文件,但是在项目中,需要一次加载多个文件时文件可能分布在多个目录下,我们不方便去简单地加载文件,同时,加载的文件的目录也可能变动,这使得代码中的目录字符串修改起来十分麻烦。
于是我们可以在编辑器中提供一个生成资源映射表的方法,当 Resources 文件夹下文件有变动时,就在编辑器中调用一次方法,重新生成资源映射信息。使程序运行时可以查询资源映射表,通过文件的名字直接取得文件的目录。
由于生成的是 名称(key) - 路径(value) 映射表,该框架不适用于有重名文件的情况,需要提前规范好命名。
生成资源映射信息
我们可以使用 AssetDatabase 来获取文件信息,在编辑器中生成资源映射信息文件,生成的文件放在 StreamingAssets 目录下,该目录中的文件不会被压缩,且在移动端是只读的,配置文件一般会放在此处。
注意: 该类只需要在编辑器中运行,我们需要创建一个Editor 文件夹,把该类放在其中,使该类处于 Assembly-CSharp 程序集中。该程序集不会被打包到发布后的游戏中。
using System.IO;
using UnityEditor;
using UnityEngine;
public class GenerateResourceConfig : Editor
{
//需要映射的文件类型
private static string[] fileTypes = { "prefab", "material" };
//储存上面类型对应的扩展名
private static string[] extensionStr = { "prefab", "mat" };
//显示在Unity菜单工具栏中,方便点击调用
[MenuItem("Tools/Resource/GenerateResourceConfig")]
public static void GenerateConfig()
{
Debug.Log("GenerateConfig");
for(int i = 0; i < fileTypes.Length; ++i)
{
//将映射信息写入到文件中
if(i == 0)
File.WriteAllLines("Assets/StreamingAssets/ConfigMap.txt", GenerateStrInfo(i));
else
File.AppendAllLines("Assets/StreamingAssets/ConfigMap.txt", GenerateStrInfo(i));
}
//刷新Assets
AssetDatabase.Refresh();
}
public static string[] GenerateStrInfo(int type)
{
//获取GUID
string[] resPaths = AssetDatabase.FindAssets("t:" + fileTypes[type], new string[] { "Assets/Resources" });
for (int i = 0; i < resPaths.Length; ++i)
{
//通过GUID获取路径
resPaths[i] = AssetDatabase.GUIDToAssetPath(resPaths[i]);
//获取文件名
string name = Path.GetFileNameWithoutExtension(resPaths[i]);
//获取Resources Load时需要的路径(去掉"Assets/Resources/"和文件扩展名)
string filePath = resPaths[i].Replace("Assets/Resources/", string.Empty).Replace("." + extensionStr[type], string.Empty);
//通过等号分割key value,此处不宜使用空格分割,因为路径或文件名中可能包含空格
resPaths[i] = name + "=" + filePath;
}
return resPaths;
}
}
测试
提前创建好 StreamingAssets 文件夹。
在 Resources 文件夹下创建一些预制件和材质用于测试:
在编辑器中调用 GenerateConfig() 方法:
这时可以看到 StreamingAssets 目录下出现了一个 ConfigMap 的的文件。打开文件,可以看到生成的资源映射信息:
资源管理器
现在有了资源映射信息,我们需要一个资源管理器类,在游戏一开始读取文件,加载到 Dictionary 中。并封装一个加载资源的方法,通过传入的名称返回资源。
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
public class ResourceManager : MonoBehaviour
{
private static Dictionary<string, string> configMap;
private static string fileName = "ConfigMap.txt";
static ResourceManager(){
string fileContent = GetConfigFile();
InitDict(fileContent);
}
/// <summary>
/// 使用 UnityWebRequest 加载config文件
/// </summary>
private static string GetConfigFile()
{
string url;
/*
* 使用Application.streamingAssetsPath拼接的路径偶尔会不正确
* 此处若出错则无法加载资源配置文件
* 导致游戏中所有使用ResourcesManager加载资源的语句异常
* 该处分别判断平台做适配更为保险
*
* 条件编译是最优先处理的,不满足宏条件的语句不会被编译
* 使用该方法效率比if else判断更佳
*/
#if UNITY_EDITOR || UNITY_STANDALONE
url = "file://" + Application.dataPath + "/StreamingAssets/" + fileName;
#elif UNITY_IPHONE
url= "file://" + Application.dataPath + "/Raw/" + fileName;
#elif UNITY_ANDROID
url= "jar:file://" + Application.dataPath + "!/assets/" + fileName;
#endif
UnityWebRequest www = UnityWebRequest.Get(url);
www.SendWebRequest();
if (!string.IsNullOrEmpty(www.error) || www.isHttpError)
{
Debug.LogError("加载资源配置文件失败" + www.error == null ? "" : www.error);
return null;
}
while (true)
{
//等待www下载完成后返回text
if (www.isDone)
{
return www.downloadHandler.text;
}
}
}
/// <summary>
/// 初始化映射表,将映射信息存入映射表中
/// </summary>
private static void InitDict(string fileContent)
{
configMap = new Dictionary<string, string>();
using(StringReader reader = new StringReader(fileContent))
{
string line = null;
while((line = reader.ReadLine()) != null)
{
string[] keyValue = line.Split('=');
if (!configMap.ContainsKey(keyValue[0]))
configMap.Add(keyValue[0], keyValue[1]);
else
Debug.LogError("资源映射出错,存在重名文件:" + keyValue[1] + " 与 " + configMap[keyValue[0]]);
}
}
}
/// <summary>
/// 对外提供的方法,根据资源名字直接加载资源并返回
/// </summary>
public static GameObject Load<T>(string prefabName)
{
return Resources.Load<GameObject>(configMap[prefabName]);
}
}