写在前面
在少数业务需求和很多优化需求中,不免会产生对unity资源文件进行大批次同类型修改,目前接触的相关需求包括多语言翻译、model类型规范、texture mipmap资源查找和处理等等。为了同步制作资源编辑类脚本的方法论和交流更快更好的可行性方案,写了这么一篇分享文章。
找到目标群体与目标值
unity记录资源信息主要分为两种:prefab等unity自创资源和meta。
prefab/.asset等unity资源
prefab本身即序列化文件,在对预设做处理的时候一般是直接修改prefab里的信息即可。
举个text component批量修改的例子,找到某个prefab内部带有text的组件,
深入到prefab序列化文件中其实就是对这些字段进行操作。
scriptObject.asset这种序列化文件对于我们来说稍显陌生,但是和prefab类似,是直接在其文件本身做信息记录的,参考下面的两个框,第一个框内表示序列化的是哪个类,第二个框内表示各个字段的key和value。
代码:
public class KitchenObject : ScriptableObject
{
public string objectName;
public GameObject prefab;
public Sprite sprite;
}
类似的还有spriteAtlas等,它们的信息都是直接序列化在原文件的,而非在meta文件中序列化。
插句题外话,正因为spriteAtlas只保存了需要pack在一起的sprite信息,并没有图集打出来之后的大小信息(1024*1024 or other),所以没法用工具搜索出不合规范的大图集信息。
.meta
对于texture、image、material和model这种源文件无法序列化的(换句话说,是图片、视频、模型等外部资源类型,并非unity自身创造的文件类型),则会在meta文件中进行绝大部分数据的保存,比如对texture检测是否是mip map enabled:
资源修改与保存
对于两种类型资源自然有两种不同的修改和保存操作。
AssetDataBase.SaveAssets
对于prefab这类文件,只是分为FindAssetsPath->LoadAssetAtPath->Find(找到对应组件、信息)->Change(直接赋值即可)->Save。
比如下方这段代码,背景是需要根据已经翻译后的excel文件内容来找各个prefab内部符合匹配条件的text并且更新新的text。
static void ChangetTextInProjec()
{
List<TextInfo> listTextInfo = new List<TextInfo>();
string text = System.IO.File.ReadAllText(Application.dataPath + @"\TextInfo.txt");
string[] infos = text.Split('\n');
foreach (string info in infos)
{
string[] values = info.Split('\t');
if (values.Length < 4 || string.IsNullOrEmpty(values[1]))
{
continue;
}
TextInfo TextInfo = new TextInfo();
TextInfo.prefabGUID = values[0];
TextInfo.fileID = long.Parse(values[1]);
TextInfo.value = values[2];
listTextInfo.Add(TextInfo);
}
foreach (TextInfo TextInfo in listTextInfo)
{
if (string.IsNullOrEmpty(AssetDatabase.GUIDToAssetPath(TextInfo.prefabGUID)))
{
continue;
}
string path = AssetDatabase.GUIDToAssetPath(TextInfo.prefabGUID);
GameObject target = AssetDatabase.LoadAssetAtPath<GameObject>(path);
Text[] texts = target.GetComponentsInChildren<Text>(true);
for (int i = 0; i < texts.Length; i++)
{
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(texts[i], out string id, out long fileID);
if (fileID == TextInfo.fileID)
{
texts[i].text = TextInfo.value;
EditorUtility.SetDirty(target);
AssetDatabase.SaveAssets();
EditorUtility.ClearDirty(target);
}
}
}
}
AssetImporter
对于外部原文件的修改,则需要修改一步:
FindAssetsPath->AssetImport.GetAtPath->Find(找到对应组件、信息)->Change(要根据assetimporter提供的接口来更改)->Save。
比如下方修改项目内所有texture的mipmap为关闭状态的时候,需要遍历所有texture并且通过TextureImporter读取,修改后直接走AssetImporter.SaveAndReimport接口。
static void FindMipmapTextureInProject()
{
string filter = "t:texture";
string[] fileGUIDs = AssetDatabase.FindAssets(filter);
foreach (string guid in fileGUIDs)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
TextureImporter t = TextureImporter.GetAtPath(assetPath) as TextureImporter;
if (t.mipmapEnabled == false)
continue;
t.mipmapEnabled = false;
t.SaveAndReimport();
AssetDatabase.ImportAsset(assetPath);
}
}
model的所有变化都通过modelImporter实现,比如保证importMode正确、保证mat引用数在大于等于1的时候引用的都是项目内shader而非unity自带的standard shader。
static void ChangeModel(ModelImporter target)
{
//保证格式正确
if(target.materialImportMode == ModelImporterMaterialImportMode.None)
{
target.materialImportMode = ModelImporterMaterialImportMode.ImportStandard;
}
//保证mat引用数
if (target.GetExternalObjectMap().Count <= 0)
{
return;
}
//保证引用的都是项目内mat
Object[] objects = AssetDatabase.LoadAllAssetsAtPath(target.assetPath);
for (int i = 0; i < objects.Length; i++)
{
if (objects[i].GetType() != typeof(Material))
{
continue;
}
Material mat = objects[i] as Material;
if (mat != null && mat.shader.name == "Standard")
{
target.AddRemap(new AssetImporter.SourceAssetIdentifier(mat), m_matDefault);
}
}
}
AssetDataBase.StartAssetEditing/StopAssetEditing
Unity官方文档中有预留这两个接口,目的是将多个资源导入组合成一个更大的导入,需要成对出现。
组合成大的导入之后不会看见每一个导入的进度条,同时不会像之前一样串行导入,确实是能提升效率,类似合批操作。
csdn传送门
资源信息的收集/提取
如果需要收集信息而非第一时间修改它,收集完信息后或分析(如)或交给第三方,则需要FileStream写入:
FileStream fsTotalFile = new FileStream(Application.dataPath + @"\TextInfo.txt", FileMode.Create);
for (int i = 0; i < listTextInfo.Count; i++)
{
TextInfo info = listTextInfo[i];
byte[] bytes = System.Text.Encoding.Default.GetBytes(info.prefabGUID + "\t" + info.fileID + "\t" + info.value + "\n");
fsTotalFile.Write(bytes, 0, bytes.Length);
}
fsTotalFile.Close();
提取信息则ReadAllText即可:
string text = System.IO.File.ReadAllText(Application.dataPath + @"\TextInfo.txt");
string[] infos = text.Split('\n');
foreach (string info in infos)
{
string[] values = info.Split('\t');
TextInfo textInfo = new TextInfo();
textInfo.prefabGUID = values[0];
textInfo.fileID = long.Parse(values[1]);
textInfo.value = values[2];
}