问题情景
从根配置出发,读取所有配表,对每个配表的所有资源路径,检查资源或者预制体身上必要的脚本存在。
从总配表读取每个配表路径,要反序列化成不一样的类。我们发现,对于一个不算大的项目,七八个配表,把这些配表全加载,代码都不少。然后就想把这一套操作封装起来,编辑器资源校验、运行时资源校验、游玩加载都用这一套。但是编辑器资源校验是固定用AssetDatabase加载的,运行时校验、游玩加载需要能在AssetDatabase和AB包之间切换。那么还得封装成2个函数。
或者主配表返回一个<string,Type>字典。然后读取配表的配置管理器,也不能用多个字段存每个配表了。
可以定义一个配置基类或接口,里面有校验资源方法。然后发现Assembly CSharp不能访问Assemble CSharp Editor。只有AB包资源校验能这么搞。
配置基类/接口
那么这样吧,配置类继承配置基类,必须实现一个返回一个<string,Type>字典,包含所有要检查的路径和类型。
这里为了对任意配置类检查id不为空,需要往接口里放字段,只好改成抽象类。
这样避免了编辑器工具里为七八十来个配置类专门写校验方法,变成分散在各配置类提交须校验路径和类型的方法,以及一个编辑器里的通用方法。配置类里的提交方法代码较少,省去重复写n个有循环的函数。
省去了定义n次逻辑差不多的函数,但是要调用n次同一个函数,传入不同泛型参数。
cs
public abstract class ConfigBase
{
public string id;
public abstract void CheckResAB();
/// <summary>
/// 返回此配置类要检查的资源路径和类型。预制体则返回它身上
/// 必须的脚本
/// </summary>
/// <returns></returns>
public abstract Dictionary<string, Type> CheckDic();
}
cs
public class PlantConfig:ConfigBase
{
//public string id;
public string name;
public int growthThreshold = 2;
public int growthDelta = 1;
public int wateredGrowthDelta = 2;
public string prefabPath;
public string fruitID;
/// <summary>
/// 采摘一次就消失
/// </summary>
public bool once;
public override Dictionary<string, Type> CheckDic()
{
Dictionary<string, Type> dic = new();
dic.Add(prefabPath, typeof(PlantBehavior));
return dic;
}
public override void CheckResAB()
{
throw new NotImplementedException();
}
}
这个方法用于校验在某配置类中所有需要校验的资源的路径是否有指定的类型(可以是资源类型GameObject、Sprite,也可以是身上的Mono脚本)
cs
void CheckConfigs<T>(Dictionary<string, T> itemDic, string ABName)where T:ConfigBase
{
foreach (var pair in itemDic)
{
if (string.IsNullOrEmpty(pair.Value.id))
{
Debug.LogError("id为空!");
}
var dic = pair.Value.CheckDic();
foreach (var pair2 in dic)
{
CheckRes(pair2.Key, pair2.Value, ABName);
}
}
}
static UnityEngine.Object CheckRes(string path,Type type,string ABName)
{
UnityEngine.Object t = AssetDatabase.LoadAssetAtPath(path,type);
if (t == null)
{
Debug.LogError($"在{path}找不到资源!!!");
}
else
{
Print($"{path}存在。", Color.cyan);
}
var importer = AssetImporter.GetAtPath(path);
if (!string.IsNullOrEmpty(ABName))
{
if (importer.assetBundleName != ABName)
{
importer.assetBundleName = ABName;
Print($"设置了{path}的AB包名为{ABName}", Color.cyan);
}
}
return t;
}
AB包资源校验和编辑器资源校验的区别
AB包资源校验可以通过编辑器窗口执行,也可以玩家在入口界面点击执行。要打包出去,所以放Assembly CSharp.dll。编辑器资源校验放Assembly CSharp Editor.dll。
编辑器资源校验可以控制台打印结果。AB包资源校验玩家看不见控制台,需要UI显示结果,所以传入成功和失败的UnityAction,失败的需要UnityAction<string>显示失败信息。
AssetDatabase是能直接加载预制体上的组件的。AB包把所有资源assetBundle.LoadAsset()很占内存,所以只用assetBundle.Contains()检查资源存在,不再进行组件的检查。