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