游戏开发过程中我们经常要写一些小工具来提升我们的开发效率,最近做了一个基于Odin插件的Prefab批处理小工具,现在跟大家分享一下开发过程。首先看一下成品图。
打开这个窗口的时候可以把Prefab拖动到Prefabs里面,需要处理的时候点击开始批处理即可。
列表的内容会保存到一个json文件里,以便下次打开的时候还原。修改过的资源会记录MD5,如果资源没改动,批处理的时候会自动跳过该资源。
因为处理后会产生新的加后缀的Prefab,所以可以加个开关控制是否使用新资源,在窗口关闭的时候会使用字符串匹配来修改代码进行保存。
代码如下:
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEngine;
public class PrefabCollectorWindow : OdinEditorWindow
{
[MenuItem("Tools/Prefab Collector")]
public static void ShowWindow()
{
var window = GetWindow<PrefabCollectorWindow>();
window.position = GUIHelper.GetEditorWindowRect().AlignCenter(600, 800);
window.titleContent = new GUIContent("Prefab Collector");
window.Initialize();
}
private JObject ReadSettings()
{
string configPath = string.Concat(Application.dataPath, "/Configs/PrefabCollectorConfig.json");
if (!File.Exists(configPath))
{
Debug.LogError("Prefab Collector Config File Missing!");
return null;
}
using (StreamReader file = File.OpenText(configPath))
{
using (JsonTextReader reader = new JsonTextReader(file))
{
JObject o = (JObject)JToken.ReadFrom(reader);
return o;
}
}
}
private void WriteSettings(JObject settings)
{
string configPath = string.Concat(Application.dataPath, "/Configs/PrefabCollectorConfig.json");
if (!File.Exists(configPath))
{
Debug.LogError("Prefab Collector Config File Missing!");
return;
}
File.WriteAllText(configPath, settings.ToString());
}
private void SaveUseEditedResource()
{
var path = Path.Combine(Application.dataPath,"/Configs/PrefabCollector.cs");
var key = "USE_EDITED_RESOURCE";
var value = useEditedResource.ToString().ToLower();
if (File.Exists(path))
{
// 读取所有行
string[] lines = File.ReadAllLines(path);
bool keyFound = false;
string pattern = $@"^{key} = (true|false)$";
Regex regex = new Regex(pattern);
// 遍历所有行,查找并修改目标行
for (int i = 0; i < lines.Length; i++)
{
if (regex.IsMatch(lines[i]))
{
lines[i] = $"{key} = {value}";
keyFound = true;
break;
}
}
if (!keyFound)
{
Debug.LogWarning("USE_EDITED_RESOURCE not found");
}
else
{
File.WriteAllLines(path, lines);
}
Debug.Log("Configuration updated.");
}
else
{
Debug.LogWarning("File not found: " + path);
}
}
[Title("是否使用修改后的资源", titleAlignment: TitleAlignments.Left)]
public bool useEditedResource = false;
[Title("预制体列表", titleAlignment: TitleAlignments.Left)]
[TableList]
[ListDrawerSettings(Expanded = true)]
public List<PrefabEntry> prefabs = new List<PrefabEntry>();
[Button("开始批处理",ButtonSizes.Large)]
private void ProcessPrefabs()
{
foreach (var entry in prefabs)
{
if (entry.Prefab != null)
{
var md5 = Hash.FileToMD5(entry.Path);
if (md5 != entry.Hash)
{
entry.Hash = md5;
// 根据自己的需求进行处理
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
}
SaveSettings();
Debug.Log("全部Prefab处理成功");
}
protected override void OnImGUI()
{
base.OnImGUI();
Event evt = Event.current;
if (evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform)
{
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (Object draggedObject in DragAndDrop.objectReferences)
{
if (draggedObject is GameObject gameObject)
{
string path = AssetDatabase.GetAssetPath(gameObject);
if (!string.IsNullOrEmpty(path))
{
var prefabEntry = new PrefabEntry
{
Prefab = gameObject,
Path = path,
Hash = "?"
};
if (!HasPrefab(gameObject))
{
prefabs.Add(prefabEntry);
}
}
}
}
}
evt.Use();
}
}
private bool HasPrefab(GameObject gameObject)
{
foreach (var entry in prefabs)
{
if (entry.Prefab == gameObject)
return true;
}
return false;
}
private void SaveSettings()
{
var settings = new JArray();
foreach (var entry in prefabs)
{
settings.Add(entry.Path + ":" + entry.Hash);
}
var settingObj = new JObject();
settingObj["prefabs"] = settings;
settingObj["useEditedResource"] = useEditedResource.ToString();
WriteSettings(settingObj);
}
protected override void OnEnable()
{
base.OnEnable();
var settingObj = ReadSettings();
var settings = (JArray)settingObj["prefabs"];
useEditedResource = (string)settingObj["useEditedResource"] == "True";
prefabs.Clear();
foreach (var path in settings)
{
var pathStr = (string)path;
var pathArr = pathStr.Split(':');
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(pathArr[0]);
if (prefab != null)
{
prefabs.Add(new PrefabEntry { Prefab = prefab, Path = pathArr[0], Hash = pathArr[1]});
}
}
}
protected override void OnDestroy()
{
SaveSettings();
SaveUseEditedResource()
base.OnDestroy();
}
[System.Serializable]
public class PrefabEntry
{
[HideInInspector]
public GameObject Prefab;
[HideInInspector]
public string Hash;
[ReadOnly]
public string Path;
}
}