using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Reflection; /// /// 纯视觉 Prefab 生成器:剥掉所有逻辑脚本,只保留表现组件。 /// 用法:Project 窗口选中一个/多个 prefab → Tools → 生成纯视觉Prefab /// 输出:同目录 <原名>_Visual.prefab(不覆盖原文件) /// /// 保留白名单:Unity 内置表现组件、UGUI、TMP、Spine、Animancer、DOTween、URP Volume /// 删除:其余所有 MonoBehaviour(BY_* 游戏逻辑、LuaBehaviour、FMOD、NGUI 等) /// Missing 脚本(原逻辑脚本移除后的残留)同时清理。 /// public static class StripVisualPrefabTool { // 表现类程序集/命名空间白名单 static readonly string[] KeepNamespaces = { "UnityEngine.UI", "TMPro", "Spine", "Spine.Unity", "Animancer", "DG.Tweening", "UnityEngine.Rendering", "UnityEngine.U2D", "UnityEngine", "UnityEngine.Experimental", }; // 表现类程序集名(.dll) static readonly string[] KeepAssemblies = { "UnityEngine.UI", "Unity.TextMeshPro", "Unity.Timeline", "Spine", "Spine.Unity", "Animancer", "DOTween", "DOTweenPro", "Unity.RenderPipelines.Core.Runtime", "Unity.RenderPipelines.Universal.Runtime", "Unity.2D.Animation.Runtime", "Unity.Animator2D", "Assembly-CSharp", // 视觉辅助:UIGradient/TextSpacing/Outline8/Z1_EffectFollow 等 }; [MenuItem("Tools/生成纯视觉Prefab(剥离逻辑脚本)")] public static void StripSelected() { // 收集目标:选中的 prefab + 选中文件夹下递归的所有 prefab var prefabPaths = new HashSet(); foreach (var obj in Selection.objects) { var path = AssetDatabase.GetAssetPath(obj); if (string.IsNullOrEmpty(path)) continue; if (path.EndsWith(".prefab")) prefabPaths.Add(path); else if (System.IO.Directory.Exists(path)) { foreach (var f in System.IO.Directory.GetFiles(path, "*.prefab", System.IO.SearchOption.AllDirectories)) prefabPaths.Add(f.Replace('\\', '/')); } } if (prefabPaths.Count == 0) { Debug.LogError("[纯视觉] 未找到 prefab——请选中 prefab 或包含 prefab 的文件夹"); return; } Debug.Log($"[纯视觉] 共 {prefabPaths.Count} 个 prefab 待处理..."); int done = 0; foreach (var path in prefabPaths) { StripOne(path); done++; } Debug.Log($"[纯视觉] 处理完成 {done} 个 prefab(输出为 *_Visual.prefab)"); } [MenuItem("Tools/生成纯视觉Prefab(剥离逻辑脚本)", true)] static bool Validate() { return Selection.objects != null && Selection.objects.Length > 0; } static void StripOne(string path) { var root = PrefabUtility.LoadPrefabContents(path); int removed = 0; try { // 1. 删除非白名单 MonoBehaviour var mbs = root.GetComponentsInChildren(true); var toRemove = new List(); foreach (var mb in mbs) { if (mb == null) continue; if (!IsVisual(mb)) toRemove.Add(mb); } foreach (var c in toRemove) { Object.DestroyImmediate(c, true); removed++; } // 2. 清理 Missing 脚本组件(serialized 里的 m_Script 已失效的) foreach (var t in root.GetComponentsInChildren(true)) RemoveMissingScripts(t.gameObject); // 3. 另存 string dir = System.IO.Path.GetDirectoryName(path).Replace('\\', '/'); string name = System.IO.Path.GetFileNameWithoutExtension(path); string newPath = $"{dir}/{name}_Visual.prefab"; PrefabUtility.SaveAsPrefabAsset(root, newPath); Debug.Log($"[纯视觉] {name} → {newPath} (剥离 {removed} 个逻辑组件)"); } finally { PrefabUtility.UnloadPrefabContents(root); } } static bool IsVisual(Component c) { var t = c.GetType(); // 程序集白名单(精确优先) var asm = t.Assembly.GetName().Name; foreach (var k in KeepAssemblies) if (asm == k) return true; // 命名空间白名单 var ns = t.Namespace ?? ""; foreach (var k in KeepNamespaces) if (ns == k || ns.StartsWith(k + ".")) return true; return false; } static void RemoveMissingScripts(GameObject go) { var so = new SerializedObject(go); var comps = so.FindProperty("m_Component"); for (int i = comps.arraySize - 1; i >= 0; i--) { var comp = comps.GetArrayElementAtIndex(i).FindPropertyRelative("component"); if (comp != null && comp.objectReferenceValue == null) comps.DeleteArrayElementAtIndex(i); } so.ApplyModifiedPropertiesWithoutUndo(); } }