当前位置: 首页 > news >正文

Unity小工具:资源引用的检索和替换

在Unity开发中、后期时可能会面临新老资源混用替换等问题,所以分享自己搞得一个小工具“资源引用面板”。在这个面板中可以轻松查找、替换对应的资源,以及手动修复已丢失的引用。

界面:

代码:

#if UNITY_EDITORusing System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;namespace Engine.Editor
{public class AssetReferencePanel : EditorWindow{private bool _searchFoldout = true, _replaceFoldout;private readonly GUILayoutOption _labelWidth80 = GUILayout.Width(80);private readonly GUILayoutOption _labelWidth40 = GUILayout.Width(40);private readonly GUILayoutOption _toggleMinWidth = GUILayout.MinWidth(40);/// <summary>/// 滚动条位置/// </summary>private Vector2 _scrollV2;/// <summary>/// 当前选择的对象/// </summary>private static Object _curSelectedObj;/// <summary>/// 当前选择对象的GUID/// </summary>private static string _curSelectedGuid;/// <summary>/// 当前选择对象的类型/// </summary>private static Type _curSelectedType;/// <summary>/// 检索类型下标/// </summary>private static int _selectTypeIndex;/// <summary>/// 结果存放处/// </summary>private static readonly List<Object> Results = new();private static readonly List<Object> SceneResults = new();private static readonly List<Object> PrefabResults = new();/// <summary>/// 准备替换的资源对象/// </summary>private static Object _replaceNewObj;/// <summary>/// 准备替换资源对象的GUID/// </summary>private static string _replaceNewGuid;[MenuItem("Tools/打开资源检索替换面板")]private static void ShowPanel(){Init();GetWindow<AssetReferencePanel>("处理资源引用面板");}[MenuItem("Assets/查找引用当前资源项", true)]private static bool FindSelectedCheck(){return Selection.objects[0] != null;}[MenuItem("Assets/查找引用当前资源项")]private static void FindSelected(){Init();_curSelectedObj = Selection.objects[0];if (_curSelectedObj)_curSelectedType = _curSelectedObj.GetType();GetWindow<AssetReferencePanel>("处理资源引用面板");UpdateSelectedGuid();SearchReference();}private static void Init(){_selectTypeIndex = 0;_curSelectedGuid = null;_curSelectedObj = null;_replaceNewObj = null;_replaceNewGuid = null;_curSelectedType = typeof(Object);Results.Clear();SceneResults.Clear();PrefabResults.Clear();}/// <summary>/// 更新已选择的Guid/// </summary>private static void UpdateSelectedGuid(){if (_curSelectedObj == null)return;var assetPath = AssetDatabase.GetAssetPath(_curSelectedObj);_curSelectedGuid = AssetDatabase.AssetPathToGUID(assetPath);_curSelectedType = _curSelectedObj.GetType();}/// <summary>/// 更新准备替换的Guid/// </summary>private static void UpdateReplaceGuid(){if (_replaceNewObj == null)return;if (_curSelectedType != typeof(Object) && _replaceNewObj.GetType() != _curSelectedType){_replaceNewObj = null;}var assetPath = AssetDatabase.GetAssetPath(_replaceNewObj);_replaceNewGuid = AssetDatabase.AssetPathToGUID(assetPath);}private void OnGUI(){// 检索文件SearchGUI();// 替换文件ReplaceGUI();// 文件范围TypeGUI();if (GUILayout.Button("重置")){Init();}_scrollV2 = EditorGUILayout.BeginScrollView(_scrollV2);foreach (var t in Results){EditorGUILayout.ObjectField(t, t.GetType(), true);}EditorGUILayout.EndScrollView();}/// <summary>/// 检索UI布局/// </summary>private void SearchGUI(){_searchFoldout = EditorGUILayout.Foldout(_searchFoldout, "检索文件", true, EditorStyles.foldoutHeader);if (!_searchFoldout) return;EditorGUI.indentLevel++; // 开始缩进EditorGUILayout.BeginHorizontal();EditorGUILayout.BeginVertical();EditorGUILayout.BeginHorizontal();EditorGUILayout.LabelField("当前的选择:", _labelWidth80);_curSelectedObj = EditorGUILayout.ObjectField(_curSelectedObj, _curSelectedType, true);EditorGUILayout.EndHorizontal();EditorGUILayout.BeginHorizontal();EditorGUILayout.LabelField("当前GUID:", _labelWidth80);_curSelectedGuid = EditorGUILayout.TextField(_curSelectedGuid);EditorGUILayout.EndHorizontal();EditorGUILayout.EndVertical();UpdateSelectedGuid();if (GUILayout.Button("查询", GUILayout.Width(40), GUILayout.Height(40))){if (string.IsNullOrEmpty(_curSelectedGuid) || string.IsNullOrWhiteSpace(_curSelectedGuid)){return;}SearchReference();}EditorGUILayout.EndHorizontal();EditorGUI.indentLevel--; // 结束缩进EditorGUILayout.Space();}private void ReplaceGUI(){_replaceFoldout = EditorGUILayout.Foldout(_replaceFoldout, "资源替换", true, EditorStyles.foldoutHeader);if (!_replaceFoldout) return;EditorGUI.indentLevel++; // 开始缩进EditorGUILayout.BeginHorizontal();EditorGUILayout.BeginVertical();EditorGUILayout.BeginHorizontal();EditorGUILayout.LabelField("新的资源:", _labelWidth80);_replaceNewObj = EditorGUILayout.ObjectField(_replaceNewObj, _curSelectedType, true);EditorGUILayout.EndHorizontal();EditorGUILayout.BeginHorizontal();EditorGUILayout.LabelField("新的GUID:", _labelWidth80);_replaceNewGuid = EditorGUILayout.TextField(_replaceNewGuid);EditorGUILayout.EndHorizontal();EditorGUILayout.EndVertical();UpdateReplaceGuid();if (GUILayout.Button("替换", GUILayout.Width(40), GUILayout.Height(40))){if (string.IsNullOrEmpty(_curSelectedGuid) || string.IsNullOrWhiteSpace(_curSelectedGuid)){return;}if (string.IsNullOrEmpty(_replaceNewGuid) || string.IsNullOrWhiteSpace(_replaceNewGuid)){return;}ReplaceReference();}EditorGUILayout.EndHorizontal();EditorGUI.indentLevel--; // 结束缩进EditorGUILayout.Space();}private void TypeGUI(){EditorGUILayout.LabelField("资源范围:", EditorStyles.boldLabel);EditorGUI.indentLevel++; // 开始缩进EditorGUILayout.BeginHorizontal();EditorGUILayout.LabelField("所有", _labelWidth40);if (EditorGUILayout.Toggle(_selectTypeIndex == 0, _toggleMinWidth)){_selectTypeIndex = 0;RefreshList();}EditorGUILayout.LabelField("预制", _labelWidth40);if (EditorGUILayout.Toggle(_selectTypeIndex == 1, _toggleMinWidth)){_selectTypeIndex = 1;RefreshList();}EditorGUILayout.LabelField("场景", _labelWidth40);if (EditorGUILayout.Toggle(_selectTypeIndex == 2, _toggleMinWidth)){_selectTypeIndex = 2;RefreshList();}EditorGUILayout.EndHorizontal();EditorGUI.indentLevel--; // 结束缩进EditorGUILayout.Space();}/// <summary>/// 开始搜索并加入缓存/// </summary>private static void SearchReference(){// 清理缓存Results.Clear();SceneResults.Clear();PrefabResults.Clear();try{var select1 = _selectTypeIndex is 0 or 1;var select2 = _selectTypeIndex is 0 or 2;if (select1 || select2){var searchType = select1 && select2 ? "t:Prefab t:Scene" : select1 ? "t:Prefab" : "t:Scene";var guids = AssetDatabase.FindAssets(searchType, new[] { "Assets/" });float length = guids.Length;for (var i = 0; i < length; i++){if (EditorUtility.DisplayCancelableProgressBar("Searching...", $"Processing {i} of {length}",i / length)){break;}var filePath = AssetDatabase.GUIDToAssetPath(guids[i]);var content = File.ReadAllText(filePath);if (!content.Contains(_curSelectedGuid)) continue;var fileObject = AssetDatabase.LoadAssetAtPath(filePath, typeof(Object));Results.Add(fileObject);if (Path.GetExtension(filePath).ToLower().Contains("prefab")){PrefabResults.Add(fileObject);}else{SceneResults.Add(fileObject);}}}}catch (Exception e){Debug.LogError(e);}EditorUtility.ClearProgressBar();}private void ReplaceReference(){var curResultCount = Results.Count;if (curResultCount == 0){SearchReference();}var regex = new Regex($@"guid:\s*{_curSelectedGuid}");for (var i = 0; i < Results.Count; i++){if (EditorUtility.DisplayCancelableProgressBar("替换中...", $"正在处理第 {i + 1} / {curResultCount} 个文件",(float)i / curResultCount)){break;}var assetPath = AssetDatabase.GetAssetPath(Results[i]);if (!File.Exists(assetPath)) continue;try{var content = File.ReadAllText(assetPath);if (!content.Contains(_curSelectedGuid)) continue;// 替换 GUIDvar updatedContent = regex.Replace(content, $"guid: {_replaceNewGuid}");// 写回文件File.WriteAllText(assetPath, updatedContent);// 刷新资源AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);}catch (Exception e){Debug.LogError($"替换失败:{assetPath}\n错误:{e.Message}");}}// AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);EditorUtility.ClearProgressBar();// 刷新面板结果Repaint();if (EditorUtility.DisplayDialog("完成", "资源引用替换已完成!", "确定")){_selectTypeIndex = 0;SearchReference();}}private void RefreshList(){switch (_selectTypeIndex){case 0:Results.Clear();Results.AddRange(SceneResults);Results.AddRange(PrefabResults);break;case 1:Results.Clear();Results.AddRange(PrefabResults);break;case 2:Results.Clear();Results.AddRange(SceneResults);break;}}}
}
#endif

需要注意的是:替换这一步时直接操作的文件,确保做好备份或使用版本控制(Git,SVN);

最后,小工具只是实现了单文件索引查找替换,可根据自己的项目需求进行额外功能添加,比如:支持撤销(有版本控制的可忽略),预览替换内容(显示哪些会被修改、替换前后对比),日志输出等。

http://www.lryc.cn/news/574646.html

相关文章:

  • 深入研究:小红书笔记详情API接口详解
  • Linux环境下MariaDB如何实现负载均衡
  • 一文了解AI Agent的幕后基础设施
  • 记一次 Kafka 磁盘被写满的排查经历
  • 采用ArcGIS10.8.2 进行插值图绘制
  • macOS - 快速上手使用 YOLO
  • MySQL之SQL性能优化策略
  • 信创建设,如何统一管理异构服务器的认证、密码、权限管理等?
  • React性能优化精髓之一:频繁setState导致滚动卡顿的解决方案
  • 新增MCP接入和AutoAgent,汉得灵猿AI中台1.6版正式发布!
  • 【软考高级系统架构论文】论单元测试方法及应用
  • Linux离线安装mysql
  • 探秘深蓝 “引擎”:解码水下推进器的科技与应用
  • Flask(四) 模板渲染render_template
  • Dify×奇墨科技:开源+本土化,破解企业AI落地难题
  • Chrome MCP Server:AI驱动浏览器自动化测试实战「喂饭教程」
  • iframe窗体默认白色背景去除
  • 重点解析(软件工程)
  • 云电脑,“死”于AI时代前夕 | 数智化观察
  • 基于DE1-SoC的My_First_oneAPI(二)
  • 黑马Day01-03集开始
  • 第24篇:Linux内核深度解析与OpenEuler 24.03实践指南
  • TCP/UDP协议深度解析(一):UDP特性与TCP确认应答以及重传机制
  • 交易期权先从买方开始
  • C8BJWD8BJV美光固态闪存HSA22HSA29
  • android脱糖
  • Kubernetes生命周期管理:深入理解 Pod 生命周期
  • python有哪些常用的GUI(图形用户界面)库及选择指南
  • Unity Text-Mesh Pro无法显示中文的问题
  • Android检测当前进程或者应用是否被调试