特效处理

This commit is contained in:
2026-10-20 18:30:03 +08:00
parent 40bd235e67
commit bee2d42356
131 changed files with 837 additions and 456 deletions
+96 -3
View File
@@ -15897,7 +15897,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 801.1, y: -366.5}
m_AnchoredPosition: {x: 223, y: -366.5}
m_SizeDelta: {x: 246, y: 396}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3830484646943208791
@@ -67792,6 +67792,11 @@ PrefabInstance:
value:
objectReference: {fileID: 11400000, guid: 01284b3a32c7e83468569928f5c73965,
type: 2}
- target: {fileID: 5060419867541585992, guid: 4df885aec65db17408fe5f416586659b,
type: 3}
propertyPath: allowMultipleCanvasRenderers
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5330474313186595698, guid: 4df885aec65db17408fe5f416586659b,
type: 3}
propertyPath: m_IsActive
@@ -68017,14 +68022,19 @@ PrefabInstance:
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedComponents:
- {fileID: 5060419867541585992, guid: 4df885aec65db17408fe5f416586659b, type: 3}
m_RemovedGameObjects: []
m_AddedGameObjects:
- targetCorrespondingSourceObject: {fileID: 8853572317385043517, guid: 4df885aec65db17408fe5f416586659b,
type: 3}
insertIndex: 0
addedObject: {fileID: 9040017639815608408}
m_AddedComponents: []
m_AddedComponents:
- targetCorrespondingSourceObject: {fileID: 6926977592608490996, guid: 4df885aec65db17408fe5f416586659b,
type: 3}
insertIndex: -1
addedObject: {fileID: 5779381528187236056}
m_SourcePrefab: {fileID: 100100000, guid: 4df885aec65db17408fe5f416586659b, type: 3}
--- !u!224 &263134585676878738 stripped
RectTransform:
@@ -68032,6 +68042,89 @@ RectTransform:
type: 3}
m_PrefabInstance: {fileID: 8752995051435752879}
m_PrefabAsset: {fileID: 0}
--- !u!1 &1826618619158689883 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 6926977592608490996, guid: 4df885aec65db17408fe5f416586659b,
type: 3}
m_PrefabInstance: {fileID: 8752995051435752879}
m_PrefabAsset: {fileID: 0}
--- !u!114 &5779381528187236056
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826618619158689883}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d85b887af7e6c3f45a2e2d2920d641bc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 2100000, guid: 349c2798841dd9f4c89acdb1e52cbf3b, type: 2}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
skeletonDataAsset: {fileID: 11400000, guid: 01284b3a32c7e83468569928f5c73965, type: 2}
initialSkinName: default
initialFlipX: 0
initialFlipY: 0
startingAnimation: idle
startingLoop: 1
timeScale: 1
freeze: 0
updateWhenInvisible: 3
unscaledTime: 0
allowMultipleCanvasRenderers: 1
canvasRenderers:
- {fileID: 201318676101694961}
- {fileID: 8235293816368149592}
- {fileID: 2446405798293491568}
- {fileID: 340079405198258509}
- {fileID: 5062598685067098695}
- {fileID: 1207224931900852095}
- {fileID: 1455918537387431194}
- {fileID: 2103692984007865899}
- {fileID: 3424533732130890899}
- {fileID: 8656755274658975106}
- {fileID: 7371864405799979440}
- {fileID: 2286248722853339403}
- {fileID: 3862144865375682425}
- {fileID: 5316933298778596933}
- {fileID: 5494442923517133337}
- {fileID: 2945609307869315189}
- {fileID: 2270748183178458588}
- {fileID: 4867439765306778051}
- {fileID: 8574667605618192794}
- {fileID: 3748848972706537091}
- {fileID: 6010473928609515947}
- {fileID: 2448868564627096078}
- {fileID: 4920217292007767453}
- {fileID: 5590666519804690853}
- {fileID: 6217535141042166839}
- {fileID: 5282035822308975466}
- {fileID: 330046349550623742}
- {fileID: 798947272716457757}
- {fileID: 955824026465409844}
- {fileID: 3924789657280705301}
- {fileID: 6319058580571699530}
separatorSlotNames: []
enableSeparatorSlots: 0
separatorParts: []
updateSeparatorPartLocation: 1
meshGenerator:
settings:
useClipping: 1
zSpacing: 0
pmaVertexColors: 1
tintBlack: 0
canvasGroupTintBlack: 0
calculateTangents: 0
addNormals: 0
immutableTriangles: 0
--- !u!114 &1869197814322494115 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 6955909806736836364, guid: 4df885aec65db17408fe5f416586659b,
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 06b6699f783747445ae6a6ad233d3725
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,285 +0,0 @@
using System.Collections.Generic;
using System.Text;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// ChouJiangItem 批量绑定工具(编辑器专用)。
/// 把预制体拖进窗口(或 Project 里选中预制体/文件夹后点「从当前选中添加」),再点「开始绑定」:
/// 工具会找出所有名字以 ItemGrid 开头的格子,给每个格子挂上 ChouJiangItem,并按子物体名自动赋值:
///
/// Item → img_Icon(该节点上的 Image 组件)
/// Label → tmp_Count(该节点上是 TMP 时)/ tmp_Count_Old(该节点上是 UGUI Text 时)
/// 道具名字节点 → tmp_Name / tmp_Name_Old(节点名在窗口里配,留空则不处理)
/// GridMask → obj_Drawn
/// eff_xuan → obj_Selecting
/// eff_xuanzhong → obj_Landed
///
/// 节点名先在**直接子物体**里精确匹配(ItemGrid 下还有同名的 GridMask 子节点,
/// 所以必须优先取直接子物体),找不到再退到子树里找第一个同名节点。
/// itemId 不动,交给 ChouJiangToggle 按列表顺序自动编号。
/// </summary>
public class ChouJiangItemBinder : EditorWindow
{
const string CellPrefix = "ItemGrid"; // 格子节点名前缀
const string NodeIcon = "Item"; // 奖品图片
const string NodeLabel = "Label"; // 数量文本
const string NodeDrawn = "GridMask"; // 已抽中遮罩
const string NodeSelecting = "eff_xuan"; // 选中高亮
const string NodeLanded = "eff_xuanzhong"; // 抽中停留
[SerializeField] List<GameObject> targets = new List<GameObject>();
[SerializeField] string nameNode = ""; // 道具名字节点名,留空=不处理
[SerializeField] bool keepExisting = false; // 已有引用不覆盖
Vector2 scroll;
[MenuItem("Tools/抽奖/批量绑定 ChouJiangItem")]
static void Open()
{
ChouJiangItemBinder w = GetWindow<ChouJiangItemBinder>("抽奖 Item 绑定");
w.minSize = new Vector2(440f, 360f);
}
void OnGUI()
{
EditorGUILayout.LabelField("把预制体拖进来(或 Project 里选中后点下面的按钮)", EditorStyles.boldLabel);
Rect dropArea = GUILayoutUtility.GetRect(0f, 46f, GUILayout.ExpandWidth(true));
GUI.Box(dropArea, "拖预制体 / 文件夹到这里");
HandleDrag(dropArea);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("从当前选中添加")) AddFromSelection();
if (GUILayout.Button("清空列表")) targets.Clear();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
nameNode = EditorGUILayout.TextField("道具名字节点名(留空=不处理)", nameNode);
keepExisting = EditorGUILayout.Toggle("保留已有引用(不覆盖)", keepExisting);
EditorGUILayout.LabelField($"待绑定预制体:{targets.Count} 个");
scroll = EditorGUILayout.BeginScrollView(scroll, GUILayout.Height(110f));
for (int i = targets.Count - 1; i >= 0; i--)
{
EditorGUILayout.BeginHorizontal();
targets[i] = (GameObject)EditorGUILayout.ObjectField(targets[i], typeof(GameObject), false);
if (GUILayout.Button("×", GUILayout.Width(24f))) targets.RemoveAt(i);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
EditorGUILayout.Space();
using (new EditorGUI.DisabledScope(targets.Count == 0))
{
if (GUILayout.Button("开始绑定", GUILayout.Height(30f))) Run();
}
}
// ---------------- 目标收集 ----------------
void HandleDrag(Rect area)
{
Event e = Event.current;
if (e == null || !area.Contains(e.mousePosition)) return;
if (e.type != EventType.DragUpdated && e.type != EventType.DragPerform) return;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (e.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (Object o in DragAndDrop.objectReferences) AddTarget(o);
e.Use();
}
}
void AddFromSelection()
{
int before = targets.Count;
foreach (Object o in Selection.objects) AddTarget(o);
if (targets.Count == before) Debug.LogWarning("[抽奖绑定] 当前选中里没有预制体或含预制体的文件夹");
}
void AddTarget(Object o)
{
// 文件夹:递归收集里面的预制体
string path = AssetDatabase.GetAssetPath(o);
if (!string.IsNullOrEmpty(path) && AssetDatabase.IsValidFolder(path))
{
string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { path });
foreach (string g in guids) AddTarget(AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(g)));
return;
}
GameObject go = o as GameObject;
if (go == null)
{
Debug.LogWarning("[抽奖绑定] 只支持预制体(GameObject),已跳过:" + o);
return;
}
if (string.IsNullOrEmpty(path) || !path.EndsWith(".prefab"))
{
Debug.LogWarning("[抽奖绑定] 不是预制体文件,已跳过:" + go.name);
return;
}
if (!targets.Contains(go)) targets.Add(go);
}
// ---------------- 执行绑定 ----------------
void Run()
{
List<string> paths = new List<string>();
foreach (GameObject go in targets)
{
string p = AssetDatabase.GetAssetPath(go);
if (!string.IsNullOrEmpty(p) && !paths.Contains(p)) paths.Add(p);
}
StringBuilder sb = new StringBuilder();
int cellAll = 0, missAll = 0;
for (int i = 0; i < paths.Count; i++)
{
string path = paths[i];
EditorUtility.DisplayProgressBar("绑定 ChouJiangItem", path, (float)i / paths.Count);
try
{
int cells, miss;
BindOne(path, out cells, out miss);
cellAll += cells;
missAll += miss;
sb.AppendLine($"{System.IO.Path.GetFileName(path)}:格子 {cells} 个" +
(miss > 0 ? $",{miss} 个有缺失节点(见 Console 警告)" : ",全部绑定完成"));
}
catch (System.Exception ex)
{
sb.AppendLine($"{System.IO.Path.GetFileName(path)}:失败 {ex.Message}");
Debug.LogError($"[抽奖绑定] {path} 处理失败:{ex}");
}
}
EditorUtility.ClearProgressBar();
targets.Clear();
Debug.Log($"[抽奖绑定] 完成,共 {cellAll} 个格子,{missAll} 个有缺失节点\n{sb}");
}
void BindOne(string path, out int cellCount, out int missingCount)
{
cellCount = 0;
missingCount = 0;
GameObject root = PrefabUtility.LoadPrefabContents(path);
try
{
Transform[] all = root.GetComponentsInChildren<Transform>(true);
for (int i = 0; i < all.Length; i++)
{
Transform t = all[i];
if (!t.name.StartsWith(CellPrefix)) continue;
// 防止把同名前缀的容器误当格子:必须带 Item 或 GridMask 直接子物体
if (t.Find(NodeIcon) == null && t.Find(NodeDrawn) == null) continue;
cellCount++;
ChouJiangItem item = t.GetComponent<ChouJiangItem>();
if (item == null) item = t.gameObject.AddComponent<ChouJiangItem>();
int missing = 0;
// 奖品图片
Image icon = FindComp<Image>(t, NodeIcon);
if (icon != null) { if (!keepExisting || item.img_Icon == null) item.img_Icon = icon; }
else missing++;
// 数量文本:TMP 优先,没有 TMP 再用 UGUI Text(两版互斥)
Transform label = FindNode(t, NodeLabel);
TextMeshProUGUI tmp = label != null ? label.GetComponent<TextMeshProUGUI>() : null;
Text txt = label != null ? label.GetComponent<Text>() : null;
if (tmp != null)
{
if (!keepExisting || item.tmp_Count == null) { item.tmp_Count = tmp; item.tmp_Count_Old = null; }
}
else if (txt != null)
{
if (!keepExisting || item.tmp_Count_Old == null) { item.tmp_Count_Old = txt; item.tmp_Count = null; }
}
else missing++;
// 道具名字(节点名可配,留空不处理)
if (!string.IsNullOrEmpty(nameNode))
{
Transform nameT = FindNode(t, nameNode);
TextMeshProUGUI nameTmp = nameT != null ? nameT.GetComponent<TextMeshProUGUI>() : null;
Text nameTxt = nameT != null ? nameT.GetComponent<Text>() : null;
if (nameTmp != null)
{
if (!keepExisting || item.tmp_Name == null) { item.tmp_Name = nameTmp; item.tmp_Name_Old = null; }
}
else if (nameTxt != null)
{
if (!keepExisting || item.tmp_Name_Old == null) { item.tmp_Name_Old = nameTxt; item.tmp_Name = null; }
}
}
// 三个状态 GameObject
Transform drawn = FindNode(t, NodeDrawn);
if (drawn != null) { if (!keepExisting || item.obj_Drawn == null) item.obj_Drawn = drawn.gameObject; }
else missing++;
Transform selecting = FindNode(t, NodeSelecting);
if (selecting != null) { if (!keepExisting || item.obj_Selecting == null) item.obj_Selecting = selecting.gameObject; }
else missing++;
Transform landed = FindNode(t, NodeLanded);
if (landed != null) { if (!keepExisting || item.obj_Landed == null) item.obj_Landed = landed.gameObject; }
else missing++;
if (missing > 0)
{
missingCount++;
Debug.LogWarning($"[抽奖绑定] {System.IO.Path.GetFileName(path)} → 格子 {t.name} 缺 {missing} 个节点" +
"(Item / Label / GridMask / eff_xuan / eff_xuanzhong)");
}
}
if (cellCount == 0)
{
Debug.LogWarning($"[抽奖绑定] {path} 里没找到名字以 {CellPrefix} 开头、且带 Item 或 GridMask 的格子");
}
else
{
PrefabUtility.SaveAsPrefabAsset(root, path);
}
}
finally
{
PrefabUtility.UnloadPrefabContents(root);
}
}
// ---------------- 查找 ----------------
/// <summary>先找直接子物体(精确同名),找不到再在整个子树里找第一个同名节点</summary>
static Transform FindNode(Transform cell, string name)
{
Transform direct = cell.Find(name);
if (direct != null) return direct;
Transform[] all = cell.GetComponentsInChildren<Transform>(true);
for (int i = 0; i < all.Length; i++)
{
if (all[i] != cell && all[i].name == name) return all[i];
}
return null;
}
static T FindComp<T>(Transform cell, string name) where T : Component
{
Transform n = FindNode(cell, name);
return n != null ? n.GetComponent<T>() : null;
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 2c7eb0452470ed54ca72730328199fbb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 9fda7ba84219c7041adfc0234a371c65
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,114 +0,0 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Text.RegularExpressions;
/// <summary>
/// 大厅图集切片烘焙工具(绕开 TextureImporter 管线,纯 Unity API):
/// 1. 读取 NGUI 图集 prefab 的 mSprites 坐标
/// 2. Sprite.Create 从大图切出 56 个 sprite,AssetDatabase.CreateAsset 落盘
/// 3. 大厅 prefab 里 108 个 Image 按节点语义规则重绑切片
/// 菜单:Tools / 烘焙大厅图集切片
/// </summary>
public static class BakeLobbySlicesTool
{
const string AtlasPrefabPath = "Assets/dataresources/atlas/a大厅/atlas_bylobbyclassic/Atlas_ByLobbyClassic.prefab";
const string AtlasTexPath = "Assets/dataresources/atlas/a大厅/atlas_bylobbyclassic/Atlas_ByLobbyClassic.png";
const string OutDir = "Assets/BakeLobbySlicesAssets";
const string LobbyPrefabPath = "Assets/bundles/by_lobby/bundles/panel/BY_LbPlayMainUGUIPanel.prefab";
[MenuItem("Tools/烘焙大厅图集切片")]
public static void Bake()
{
// ---------- 1. 读图集坐标 ----------
var atlasObj = AssetDatabase.LoadAssetAtPath<Object>(AtlasPrefabPath);
if (atlasObj == null) { Debug.LogError("[烘焙] 图集 prefab 加载失败: " + AtlasPrefabPath); return; }
var so = new SerializedObject(atlasObj);
var mSprites = so.FindProperty("mSprites");
if (mSprites == null || !mSprites.isArray || mSprites.arraySize == 0)
{ Debug.LogError("[烘焙] mSprites 读取失败 (arraySize=" + (mSprites == null ? -1 : mSprites.arraySize) + ")"); return; }
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(AtlasTexPath);
if (tex == null) { Debug.LogError("[烘焙] 大图加载失败,请先等 Unity 刷新完成: " + AtlasTexPath); return; }
Debug.Log($"[烘焙] 图集 {mSprites.arraySize} 切片, 大图 {tex.width}x{tex.height}");
// ---------- 2. 切片落盘 ----------
if (!AssetDatabase.IsValidFolder(OutDir)) AssetDatabase.CreateFolder("Assets", "BakeLobbySlicesAssets");
var made = new Dictionary<string, Sprite>();
for (int i = 0; i < mSprites.arraySize; i++)
{
var el = mSprites.GetArrayElementAtIndex(i);
string name = el.FindPropertyRelative("name").stringValue;
var rect = new Rect(el.FindPropertyRelative("x").floatValue, el.FindPropertyRelative("y").floatValue,
el.FindPropertyRelative("width").floatValue, el.FindPropertyRelative("height").floatValue);
var border = new Vector4(el.FindPropertyRelative("borderLeft").floatValue, el.FindPropertyRelative("borderBottom").floatValue,
el.FindPropertyRelative("borderRight").floatValue, el.FindPropertyRelative("borderTop").floatValue);
var sp = Sprite.Create(tex, rect, new Vector2(0.5f, 0.5f), 100f, 0, SpriteMeshType.FullRect, border);
sp.name = name;
var path = $"{OutDir}/{name}.asset";
AssetDatabase.DeleteAsset(path);
AssetDatabase.CreateAsset(sp, path);
made[name] = sp;
}
Debug.Log($"[烘焙] 生成 {made.Count} 个 sprite asset → {OutDir}");
// ---------- 3. 重绑大厅 prefab ----------
var root = PrefabUtility.LoadPrefabContents(LobbyPrefabPath);
int bound = 0, missing = 0;
try
{
var images = root.GetComponentsInChildren<UnityEngine.UI.Image>(true);
foreach (var img in images)
{
var sp = PickSlice(img.gameObject.name, made);
if (sp != null) { img.sprite = sp; bound++; }
else missing++;
}
PrefabUtility.SaveAsPrefabAsset(root, LobbyPrefabPath);
}
finally { PrefabUtility.UnloadPrefabContents(root); }
Debug.Log($"[烘焙] prefab 重绑 {bound} 个 Image, 未匹配 {missing} 个 —— 完成!");
}
// 节点名 → 切片(与此前 Python 修复用的语义规则一致,最后有兜底)
static Sprite PickSlice(string n, Dictionary<string, Sprite> m)
{
string s = null;
if (Regex.IsMatch(n, @"Frame_bg$")) s = "topbar_frame";
else if (n.Contains("Frame_lobby")) s = "topbar_def2";
else if (n.Contains("topbar_btn")) s = "topbar_btn";
else if (n.Contains("BtnClassic") || n == "Classic") s = "ic_tab_n1";
else if (n.Contains("BtnSuper") || n == "Super") s = "ic_tab_n2";
else if (n.Contains("BtnSiuxian") || n == "Siuxian") s = "ic_tab_n3";
else if (n.Contains("BtnFold")) s = "ic_tab_n4";
else if (n.StartsWith("tab_sel")) s = "tab_sel1";
else if (n.Contains("tab_bg")) s = "tab_bg";
else if (n.Contains("share_btn_bg")) s = "nor_frame_b";
else if (n.Contains("icon_more")) s = "icon_more";
else if (n.Contains("redDot")) s = "com_redDot";
else if (n.Contains("arrow") || n.Contains("jiantou")) s = "arrow";
else if (n == "bg_arrow") s = "bg_arrow";
else if (Regex.IsMatch(n, @"^(gem|gold|money|energy|bp)_bg")) s = "icon_bg";
else if (n.EndsWith("_spr")) s = "icon_bg2";
else if (n.Contains("headIcon")) s = "s_frame_p";
else if (n.Contains("player_level") || n.Contains("vip_spr")) s = "s_frame_r";
else if (n.EndsWith("_btn")) s = "btn";
else if (n.Contains("Frame") || n.Contains("frame")) s = "nor_frame_b2";
else if (n.StartsWith("eff_") || n.Contains("_eff")) s = "effect_1";
else if (n.Contains("lizi") || n.Contains("glow") || n.Contains("star") || n.Contains("sanguang")) s = "tx_mask";
else if (Regex.IsMatch(n, @"^bg( \(\d+\))?$")) s = "nor_frame_b";
else if (Regex.IsMatch(n, @"^Background( \(\d+\))?$")) s = "nor_frame_b2";
else if (n.Contains("ContentRoot")) s = "icon_bg";
else if (n.Contains("GodGuardTab") || n.Contains("ActivityCenter")) s = "tab_bg02";
else if (n.Contains("propIcon")) s = "icon_bg3";
else if (n.Contains("propTip")) s = "ls_txk_1";
else if (n.Contains("setup_bg")) s = "icon_bg3";
else if (n.Contains("musicOpen")) s = "icon_cardSystem_Bubble01";
else if (n.Contains("hlby_") || n.Contains("hong_spr")) s = "icon_cardSystem_Bubble02";
else if (n == "zi" || n.Contains("Label") || n.EndsWith("lab")) s = "txt_y_1";
else if (Regex.IsMatch(n, @"^Sprite( \(\d+\))?$")) s = "icon_bg2";
if (s != null && m.ContainsKey(s)) return m[s];
return m.ContainsKey("icon_bg2") ? m["icon_bg2"] : null; // 兜底
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: f6a3beb0f1bea6743becefb7271d8b07
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,115 +0,0 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Reflection;
/// <summary>
/// 纯视觉 Prefab 生成器:剥掉所有逻辑脚本,只保留表现组件。
/// 用法:Project 窗口选中一个/多个 prefab → Tools → 生成纯视觉Prefab
/// 输出:同目录 &lt;原名&gt;_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();
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 08151b804de85fe4095e31e99127a036
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: