286 lines
12 KiB
C#
286 lines
12 KiB
C#
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;
|
||
}
|
||
}
|