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();
}
}