116 lines
4.9 KiB
C#
116 lines
4.9 KiB
C#
using UnityEngine;
|
||
using UnityEditor;
|
||
using System.Collections.Generic;
|
||
using System.Reflection;
|
||
|
||
/// <summary>
|
||
/// 纯视觉 Prefab 生成器:剥掉所有逻辑脚本,只保留表现组件。
|
||
/// 用法:Project 窗口选中一个/多个 prefab → Tools → 生成纯视觉Prefab
|
||
/// 输出:同目录 <原名>_Visual.prefab(不覆盖原文件)
|
||
///
|
||
/// 保留白名单:Unity 内置表现组件、UGUI、TMP、Spine、Animancer、DOTween、URP Volume
|
||
/// 删除:其余所有 MonoBehaviour(BY_* 游戏逻辑、LuaBehaviour、FMOD、NGUI 等)
|
||
/// Missing 脚本(原逻辑脚本移除后的残留)同时清理。
|
||
/// </summary>
|
||
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<string>();
|
||
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<MonoBehaviour>(true);
|
||
var toRemove = new List<Component>();
|
||
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<Transform>(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();
|
||
}
|
||
}
|