#if UNITY_EDITOR using UnityEditor; using UnityEngine; using System.Collections.Generic; using UnityEditor.SceneManagement; using UnityEngine.SceneManagement; using UnityEngine.UIElements; using System.Linq; using UnityEngine.UI; namespace Aliciza.UXTool { public static class SceneViewContextMenu { // 右键状态和拖动状态 private static bool rightMouseDown = false; private static bool hasDrag = false; // 右键菜单的委托,允许外部插件扩展菜单 public delegate void AddContextMenuFunc(); public static AddContextMenuFunc addContextMenuFunc; // 用于复制/粘贴RectTransform数据的缓存 private static RectTransformCache clipboard = null; [InitializeOnLoadMethod] public static void Init() { // 在SceneView中注册事件回调 if (UXDesinUtil.InDesign) { SceneView.duringSceneGui += OnSceneGUI; } else { SceneView.duringSceneGui -= OnSceneGUI; } } static void OnSceneGUI(SceneView sceneView) { if (PrefabStageUtils.InEmptyStage) return; // 如果不是右键点击,直接返回 if (Event.current == null || Event.current.button != 1) { return; } // 右键按下,初始化状态 if (Event.current.type == EventType.MouseDown) { hasDrag = false; rightMouseDown = true; return; } // 拖动过程中标记状态 if (Event.current.type == EventType.MouseDrag && rightMouseDown) { hasDrag = true; return; } // 右键松开时弹出菜单 if (Event.current.type == EventType.MouseUp && rightMouseDown && !hasDrag) { HandleRightClick(); Event.current.Use(); // 防止事件进一步传播 } } // 右键菜单生成和显示的核心方法 private static void HandleRightClick() { // 生成右键菜单项 GenerateRectTransformMenu(); GenerateSelectObjectMenu(); // 调用外部扩展菜单 addContextMenuFunc?.Invoke(); // 显示菜单 ContextMenuUtils.ShowContextMenu(); // 调用修改后的 ShowContextMenu // 重置状态 rightMouseDown = false; } #region RectTransform 操作菜单 private static void GenerateRectTransformMenu() { if (UXSelectionUtil.gameObjects == null || UXSelectionUtil.gameObjects.Length == 0) return; if (!(UXSelectionUtil.gameObjects[0].transform is RectTransform)) { return; } GameObject[] selected = UXSelectionUtil.gameObjects; // 添加分隔符 ContextMenuUtils.BuildSeparator(); // 添加通用菜单项 AddCommonItem(); // 布局与对齐 ContextMenuUtils.BuildSeparator(); ContextMenuUtils.BuildContextMenu("对齐/Left", false, () => AlignSelected(AlignType.Left)); ContextMenuUtils.BuildContextMenu("对齐/Center (水平)", false, () => AlignSelected(AlignType.CenterHorizontal)); ContextMenuUtils.BuildContextMenu("对齐/Right", false, () => AlignSelected(AlignType.Right)); ContextMenuUtils.BuildContextMenu("对齐/Top", false, () => AlignSelected(AlignType.Top)); ContextMenuUtils.BuildContextMenu("对齐/Middle (垂直)", false, () => AlignSelected(AlignType.CenterVertical)); ContextMenuUtils.BuildContextMenu("对齐/Bottom", false, () => AlignSelected(AlignType.Bottom)); ContextMenuUtils.BuildSeparator(); ContextMenuUtils.BuildContextMenu("分发/水平分发", false, () => DistributeSelected(DistributeAxis.Horizontal)); ContextMenuUtils.BuildContextMenu("分发/垂直分发", false, () => DistributeSelected(DistributeAxis.Vertical)); ContextMenuUtils.BuildSeparator(); ContextMenuUtils.BuildContextMenu("大小/匹配第一个尺寸", false, () => MatchSizeToFirst()); ContextMenuUtils.BuildContextMenu("像素对齐/位置取整", false, () => PixelSnapSelected()); ContextMenuUtils.BuildContextMenu("重置/位置与旋转与缩放", false, () => ResetTransformSelected()); ContextMenuUtils.BuildSeparator(); ContextMenuUtils.BuildContextMenu("锚点/设为四角(保持位置)", false, () => SetAnchorsToCornersKeepPos()); ContextMenuUtils.BuildContextMenu("锚点/居中锚点(保持位置)", false, () => SetAnchorsToCenterKeepPos()); ContextMenuUtils.BuildSeparator(); ContextMenuUtils.BuildContextMenu("临时操作/复制 RectTransform", false, () => CopyRectTransform()); ContextMenuUtils.BuildContextMenu("临时操作/粘贴 RectTransform", clipboard == null, () => PasteRectTransform()); ContextMenuUtils.BuildSeparator(); ContextMenuUtils.BuildContextMenu("对象/向前移动(置为最后Sibling)", false, () => BringToFront()); ContextMenuUtils.BuildContextMenu("对象/发送到最底层(置为第0)", false, () => SendToBack()); ContextMenuUtils.BuildContextMenu("对象/创建容器并包裹", false, () => CreateContainerAroundSelection()); ContextMenuUtils.BuildContextMenu("对象/复制(场景内)", false, () => DuplicateSelection()); ContextMenuUtils.BuildContextMenu("对象/重命名选中", false, () => RenameSelection()); ContextMenuUtils.BuildContextMenu("对象/选择父对象", false, () => SelectParent()); ContextMenuUtils.BuildContextMenu("对象/聚焦(场景视图)", false, () => FrameSelection()); ContextMenuUtils.BuildSeparator(); ContextMenuUtils.BuildContextMenu("Graphic/切换 RaycastTarget", false, () => ToggleRaycastTarget()); ContextMenuUtils.BuildContextMenu("排序/按名称排序同级子项", false, () => SortSiblingsByName()); // 如果可以组合RectTransform,则添加组合项 ContextMenuUtils.BuildSeparator(); if (CombineWidgetLogic.CanCombine(UXSelectionUtil.gameObjects)) { ContextMenuUtils.BuildContextMenu("组合", false, () => CombineWidgetLogic.GenCombineRootRect(UXSelectionUtil.gameObjects)); } else { ContextMenuUtils.BuildContextMenu("组合", true, null); // Disabled item } } private static void SetAnchors(GameObject[] selectedObjects) { // 这里是设置锚点的逻辑,示例函数 Debug.Log("设置锚点的功能..."); } #endregion private static void AddCommonItem() { } #region UI 选择菜单 private static void GenerateSelectObjectMenu() { var prefabStage = PrefabStageUtils.GetCurrentPrefabStage(); List inSceneObjs = GetAllUIObjects(prefabStage); // 获取鼠标位置并转换为屏幕坐标(GUI坐标调整) Camera camera = SceneView.currentDrawingSceneView.camera; Vector2 mousePosGui = Event.current.mousePosition; Vector2 mousePos = new Vector2(mousePosGui.x, camera.pixelHeight - mousePosGui.y); // 排序UI对象 SortedList sortedUIObjects = SortUIObjects(inSceneObjs); // 将UI对象添加到菜单中 foreach (string key in sortedUIObjects.Keys) { RectTransform obj = sortedUIObjects[key]; if (RectTransformUtility.RectangleContainsScreenPoint(obj, mousePos, camera)) { ContextMenuUtils.BuildContextMenu(obj.name, false, () => UXSelectionUtil.activeGameObject = obj.gameObject); } } } // 获取场景中的所有UI对象 private static List GetAllUIObjects(PrefabStage prefabStage) { List uiObjects = new List(); if (prefabStage != null) { RectTransform[] allObjects = prefabStage.prefabContentsRoot.GetComponentsInChildren(true); foreach (RectTransform obj in allObjects) { if (FindContainerLogic.ObjectFit(obj.gameObject)) { uiObjects.Add(obj); } } } else { Scene scene = SceneManager.GetActiveScene(); GameObject[] rootObjects = scene.GetRootGameObjects(); foreach (GameObject rootObj in rootObjects) { RectTransform[] childRects = rootObj.GetComponentsInChildren(true); foreach (RectTransform rect in childRects) { if (FindContainerLogic.ObjectFit(rect.gameObject)) { uiObjects.Add(rect); } } } } return uiObjects; } // 排序UI对象 private static SortedList SortUIObjects(List uiObjects) { List uiNames = new List(); foreach (var rect in uiObjects) { uiNames.Add(GetTransformHierarchyString("", rect)); } // 按照名称排序 SortedList sortedList = new SortedList(new DuplicateKeyComparer()); for (int i = 0; i < uiObjects.Count; i++) { // 确保 key 唯一:包含索引 string key = uiNames[i] + "_" + i; sortedList.Add(key, uiObjects[i]); } return sortedList; } // 获取UI对象的层级字符串 private static string GetTransformHierarchyString(string prefix, Transform trans) { string str = string.IsNullOrEmpty(prefix) ? trans.GetSiblingIndex().ToString() : $"{trans.GetSiblingIndex()}.{prefix}"; if (trans.parent != null) { return GetTransformHierarchyString(str, trans.parent); } return str; } #endregion #region 辅助类与功能实现 // 自定义比较器,用于排序UI元素 private class DuplicateKeyComparer : IComparer { public int Compare(string x, string y) { return -string.Compare(x, y); // 逆序排序 } } private enum AlignType { Left, CenterHorizontal, Right, Top, CenterVertical, Bottom } private enum DistributeAxis { Horizontal, Vertical } private class RectTransformCache { public Vector2 anchoredPosition; public Vector2 sizeDelta; public Vector2 anchorMin; public Vector2 anchorMax; public Vector2 pivot; public Quaternion rotation; public Vector3 localScale; } private static void CopyRectTransform() { if (UXSelectionUtil.gameObjects == null || UXSelectionUtil.gameObjects.Length == 0) return; var rt = UXSelectionUtil.gameObjects[0].GetComponent(); if (rt == null) return; clipboard = new RectTransformCache() { anchoredPosition = rt.anchoredPosition, sizeDelta = rt.sizeDelta, anchorMin = rt.anchorMin, anchorMax = rt.anchorMax, pivot = rt.pivot, rotation = rt.localRotation, localScale = rt.localScale }; Debug.Log("已复制 RectTransform 数据到剪贴板"); } private static void PasteRectTransform() { if (clipboard == null || UXSelectionUtil.gameObjects == null) return; foreach (var go in UXSelectionUtil.gameObjects) { var rt = go.GetComponent(); if (rt == null) continue; Undo.RecordObject(rt, "Paste RectTransform"); rt.anchorMin = clipboard.anchorMin; rt.anchorMax = clipboard.anchorMax; rt.pivot = clipboard.pivot; rt.sizeDelta = clipboard.sizeDelta; rt.localRotation = clipboard.rotation; rt.localScale = clipboard.localScale; // 保持世界位置:计算当前 world pos,然后应用 anchoredPosition 使其看起来相同 Vector3 worldPos = rt.position; rt.anchoredPosition = clipboard.anchoredPosition; rt.position = worldPos; EditorUtility.SetDirty(rt); } } private static void AlignSelected(AlignType align) { var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray(); if (gos == null || gos.Length <= 1) return; // 统一以第一个为目标 RectTransform[] rts = gos.Select(g => g.GetComponent()).Where(r => r != null).ToArray(); if (rts.Length <= 1) return; // 计算所有对象在世界空间的边界 switch (align) { case AlignType.Left: { float target = rts.Min(rt => GetWorldLeft(rt)); foreach (var rt in rts) MoveWorldLeftTo(rt, target); } break; case AlignType.Right: { float target = rts.Max(rt => GetWorldRight(rt)); foreach (var rt in rts) MoveWorldRightTo(rt, target); } break; case AlignType.CenterHorizontal: { float minX = rts.Min(rt => GetWorldLeft(rt)); float maxX = rts.Max(rt => GetWorldRight(rt)); float centerX = (minX + maxX) / 2f; foreach (var rt in rts) MoveWorldCenterXTo(rt, centerX); } break; case AlignType.Top: { float target = rts.Max(rt => GetWorldTop(rt)); foreach (var rt in rts) MoveWorldTopTo(rt, target); } break; case AlignType.Bottom: { float target = rts.Min(rt => GetWorldBottom(rt)); foreach (var rt in rts) MoveWorldBottomTo(rt, target); } break; case AlignType.CenterVertical: { float minY = rts.Min(rt => GetWorldBottom(rt)); float maxY = rts.Max(rt => GetWorldTop(rt)); float centerY = (minY + maxY) / 2f; foreach (var rt in rts) MoveWorldCenterYTo(rt, centerY); } break; } } private static float GetWorldLeft(RectTransform rt) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); return corners[0].x; // bottom-left.x } private static float GetWorldRight(RectTransform rt) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); return corners[2].x; // top-right.x } private static float GetWorldTop(RectTransform rt) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); return corners[1].y; // top-left.y } private static float GetWorldBottom(RectTransform rt) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); return corners[0].y; // bottom-left.y } // 将世界空间的位移转换为父空间的局部位移,并应用到 rectTransform.localPosition private static void ApplyWorldDeltaToRectTransform(RectTransform rt, Vector3 worldDelta, string undoName) { if (rt == null || rt.parent == null) return; Undo.RecordObject(rt, undoName); // 将世界向量转换为父对象的局部向量(旋转会被考虑,但位置不重要因为这是增量) Vector3 localDelta = rt.parent.InverseTransformVector(worldDelta); // 应用到 localPosition(而不是直接修改 world position),这样对于 UI 更稳定 rt.localPosition += localDelta; EditorUtility.SetDirty(rt); } private static void MoveWorldLeftTo(RectTransform rt, float worldX) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); float curLeft = corners[0].x; float delta = worldX - curLeft; if (Mathf.Approximately(delta, 0f)) return; ApplyWorldDeltaToRectTransform(rt, new Vector3(delta, 0, 0), "Align Left"); } private static void MoveWorldRightTo(RectTransform rt, float worldX) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); float curRight = corners[2].x; float delta = worldX - curRight; if (Mathf.Approximately(delta, 0f)) return; ApplyWorldDeltaToRectTransform(rt, new Vector3(delta, 0, 0), "Align Right"); } private static void MoveWorldTopTo(RectTransform rt, float worldY) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); float curTop = corners[1].y; float delta = worldY - curTop; if (Mathf.Approximately(delta, 0f)) return; ApplyWorldDeltaToRectTransform(rt, new Vector3(0, delta, 0), "Align Top"); } private static void MoveWorldBottomTo(RectTransform rt, float worldY) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); float curBottom = corners[0].y; float delta = worldY - curBottom; if (Mathf.Approximately(delta, 0f)) return; ApplyWorldDeltaToRectTransform(rt, new Vector3(0, delta, 0), "Align Bottom"); } private static void MoveWorldCenterXTo(RectTransform rt, float worldX) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); float curCenterX = (corners[0].x + corners[2].x) / 2f; float delta = worldX - curCenterX; if (Mathf.Approximately(delta, 0f)) return; ApplyWorldDeltaToRectTransform(rt, new Vector3(delta, 0, 0), "Align Center X"); } private static void MoveWorldCenterYTo(RectTransform rt, float worldY) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); float curCenterY = (corners[0].y + corners[2].y) / 2f; float delta = worldY - curCenterY; if (Mathf.Approximately(delta, 0f)) return; ApplyWorldDeltaToRectTransform(rt, new Vector3(0, delta, 0), "Align Center Y"); } private static void DistributeSelected(DistributeAxis axis) { var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray(); if (gos == null || gos.Length <= 2) return; // 两个对象没必要 RectTransform[] rts = gos.Select(g => g.GetComponent()).Where(r => r != null).ToArray(); if (rts.Length <= 2) return; // 按中心排序 if (axis == DistributeAxis.Horizontal) { var ordered = rts.OrderBy(rt => GetWorldCenter(rt).x).ToArray(); float left = GetWorldLeft(ordered.First()); float right = GetWorldRight(ordered.Last()); float totalWidth = ordered.Sum(rt => GetWidthWorld(rt)); float space = (right - left - totalWidth) / (ordered.Length - 1); float curX = left; for (int i = 0; i < ordered.Length; i++) { float w = GetWidthWorld(ordered[i]); float targetCenter = curX + w / 2f; MoveWorldCenterXTo(ordered[i], targetCenter); curX += w + space; } } else { var ordered = rts.OrderBy(rt => GetWorldCenter(rt).y).ToArray(); float bottom = GetWorldBottom(ordered.First()); float top = GetWorldTop(ordered.Last()); float totalHeight = ordered.Sum(rt => GetHeightWorld(rt)); float space = (top - bottom - totalHeight) / (ordered.Length - 1); float curY = bottom; for (int i = 0; i < ordered.Length; i++) { float h = GetHeightWorld(ordered[i]); float targetCenter = curY + h / 2f; MoveWorldCenterYTo(ordered[i], targetCenter); curY += h + space; } } } private static Vector2 GetWorldCenter(RectTransform rt) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); return (corners[0] + corners[2]) / 2f; } private static float GetWidthWorld(RectTransform rt) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); return Mathf.Abs(corners[2].x - corners[0].x); } private static float GetHeightWorld(RectTransform rt) { var corners = new Vector3[4]; rt.GetWorldCorners(corners); return Mathf.Abs(corners[1].y - corners[0].y); } private static void MatchSizeToFirst() { var gos = UXSelectionUtil.gameObjects; if (gos == null || gos.Length <= 1) return; var first = gos[0].GetComponent(); if (first == null) return; for (int i = 1; i < gos.Length; i++) { var rt = gos[i].GetComponent(); if (rt == null) continue; Undo.RecordObject(rt, "Match Size"); rt.sizeDelta = first.sizeDelta; EditorUtility.SetDirty(rt); } } private static void PixelSnapSelected() { foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0]) { var rt = go.GetComponent(); if (rt == null) continue; Undo.RecordObject(rt, "Pixel Snap"); Vector2 ap = rt.anchoredPosition; ap.x = Mathf.Round(ap.x); ap.y = Mathf.Round(ap.y); rt.anchoredPosition = ap; EditorUtility.SetDirty(rt); } } private static void ResetTransformSelected() { foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0]) { var rt = go.GetComponent(); if (rt == null) continue; Undo.RecordObject(rt, "Reset Transform"); rt.anchoredPosition = Vector2.zero; rt.localRotation = Quaternion.identity; rt.localScale = Vector3.one; EditorUtility.SetDirty(rt); } } private static void SetAnchorsToCornersKeepPos() { foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0]) { var rt = go.GetComponent(); if (rt == null) continue; Undo.RecordObject(rt, "Set Anchors to Corners Keep Position"); Transform parent = rt.parent; if (parent == null) continue; RectTransform parentRT = parent as RectTransform; if (parentRT == null) continue; Vector3[] corners = new Vector3[4]; rt.GetWorldCorners(corners); Vector3[] parentCorners = new Vector3[4]; parentRT.GetWorldCorners(parentCorners); Vector2 newAnchorMin = new Vector2( Mathf.InverseLerp(parentCorners[0].x, parentCorners[2].x, corners[0].x), Mathf.InverseLerp(parentCorners[0].y, parentCorners[2].y, corners[0].y)); Vector2 newAnchorMax = new Vector2( Mathf.InverseLerp(parentCorners[0].x, parentCorners[2].x, corners[2].x), Mathf.InverseLerp(parentCorners[0].y, parentCorners[2].y, corners[2].y)); Vector2 oldAnchored = rt.anchoredPosition; rt.anchorMin = newAnchorMin; rt.anchorMax = newAnchorMax; rt.anchoredPosition = oldAnchored; // 尝试保留视觉位置 EditorUtility.SetDirty(rt); } } private static void SetAnchorsToCenterKeepPos() { foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0]) { var rt = go.GetComponent(); if (rt == null) continue; Undo.RecordObject(rt, "Set Anchors to Center Keep Position"); Transform parent = rt.parent; if (parent == null) continue; RectTransform parentRT = parent as RectTransform; if (parentRT == null) continue; Vector3[] corners = new Vector3[4]; rt.GetWorldCorners(corners); Vector3[] parentCorners = new Vector3[4]; parentRT.GetWorldCorners(parentCorners); float centerX = (corners[0].x + corners[2].x) / 2f; float centerY = (corners[0].y + corners[2].y) / 2f; Vector2 anchor = new Vector2( Mathf.InverseLerp(parentCorners[0].x, parentCorners[2].x, centerX), Mathf.InverseLerp(parentCorners[0].y, parentCorners[2].y, centerY)); Vector2 oldAnchored = rt.anchoredPosition; rt.anchorMin = anchor; rt.anchorMax = anchor; rt.anchoredPosition = oldAnchored; EditorUtility.SetDirty(rt); } } private static void BringToFront() { foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0]) { Transform parent = go.transform.parent; Undo.RecordObject(go.transform, "Bring To Front"); if (parent != null) { go.transform.SetSiblingIndex(parent.childCount - 1); } else { go.transform.SetSiblingIndex(go.transform.GetSiblingIndex()); } EditorUtility.SetDirty(go); } } private static void SendToBack() { foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0]) { Transform parent = go.transform.parent; Undo.RecordObject(go.transform, "Send To Back"); go.transform.SetSiblingIndex(0); EditorUtility.SetDirty(go); } } private static void CreateContainerAroundSelection() { var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray(); if (gos == null || gos.Length == 0) return; // 只允许相同父级 Transform parent = gos[0].transform.parent; if (gos.Any(g => g.transform.parent != parent)) { Debug.LogWarning("创建容器要求所有对象在同一父级下"); return; } // 计算包围盒(世界坐标) var rts = gos.Select(g => g.GetComponent()).Where(r => r != null).ToArray(); if (rts.Length == 0) return; Vector3 worldMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); Vector3 worldMax = new Vector3(float.MinValue, float.MinValue, float.MinValue); foreach (var rt in rts) { Vector3[] corners = new Vector3[4]; rt.GetWorldCorners(corners); worldMin = Vector3.Min(worldMin, corners[0]); worldMax = Vector3.Max(worldMax, corners[2]); } // 创建容器 GameObject root = UIBuilderUtil.CreateUIObj("Container"); Undo.RegisterCreatedObjectUndo(root, "Create Container"); var rootRT = root.GetComponent(); root.transform.SetParent(parent); root.transform.SetSiblingIndex(rts.Min(r => r.GetSiblingIndex())); rootRT.localScale = Vector3.one; // 将世界包围盒转换为父本地坐标 RectTransform parentRT = parent as RectTransform; if (parentRT == null) { Debug.LogWarning("父对象不是 RectTransform,无法创建容器"); return; } Vector3 localMin = parentRT.InverseTransformPoint(worldMin); Vector3 localMax = parentRT.InverseTransformPoint(worldMax); Vector2 size = new Vector2(localMax.x - localMin.x, localMax.y - localMin.y); Vector2 center = (localMin + localMax) / 2f; rootRT.sizeDelta = size; rootRT.anchoredPosition = center; // 将选中对象设置为容器子对象并保持位置 foreach (var g in gos) { Undo.SetTransformParent(g.transform, root.transform, "Wrap In Container"); } Selection.activeGameObject = root; EditorUtility.SetDirty(rootRT); } private static void DuplicateSelection() { var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray(); if (gos == null || gos.Length == 0) return; List newOnes = new List(); foreach (var g in gos) { GameObject dup = Object.Instantiate(g, g.transform.parent); dup.name = g.name + " Copy"; Undo.RegisterCreatedObjectUndo(dup, "Duplicate UI Element"); dup.transform.SetSiblingIndex(g.transform.GetSiblingIndex() + 1); newOnes.Add(dup); } Selection.objects = newOnes.ToArray(); } private static void RenameSelection() { if (UXSelectionUtil.gameObjects == null || UXSelectionUtil.gameObjects.Length == 0) return; Selection.activeGameObject = UXSelectionUtil.gameObjects[0]; // 调用编辑器的重命名命令 EditorApplication.ExecuteMenuItem("Edit/Rename"); } private static void SelectParent() { if (UXSelectionUtil.gameObjects == null || UXSelectionUtil.gameObjects.Length == 0) return; var go = UXSelectionUtil.gameObjects[0]; if (go.transform.parent != null) { Selection.activeGameObject = go.transform.parent.gameObject; } } private static void FrameSelection() { SceneView.lastActiveSceneView?.FrameSelected(); } private static void ToggleRaycastTarget() { foreach (var go in UXSelectionUtil.gameObjects ?? new GameObject[0]) { var g = go.GetComponent(); if (g == null) continue; Undo.RecordObject(g, "Toggle RaycastTarget"); g.raycastTarget = !g.raycastTarget; EditorUtility.SetDirty(g); } } private static void SortSiblingsByName() { var gos = UXSelectionUtil.gameObjects?.Where(g => g != null).ToArray(); if (gos == null || gos.Length == 0) return; Transform parent = gos[0].transform.parent; if (parent == null) return; var children = parent.Cast().Select(t => t.gameObject).OrderBy(o => o.name).ToArray(); for (int i = 0; i < children.Length; i++) { Undo.RecordObject(children[i].transform, "Sort By Name"); children[i].transform.SetSiblingIndex(i); } } #endregion } // CombineWidgetLogic 放在同一文件以便完整交付 public static class CombineWidgetLogic { /// /// 组合节点 /// /// 需要组合的对象 需要所有对象在同一层级 /// 组合之后的父节点 public static GameObject GenCombineRootRect(List rects) { if (rects == null || rects.Count == 0) return null; // 确保所有在同一父级 Transform parent = rects[0].transform.parent; if (rects.Any(r => r.transform.parent != parent)) { Debug.LogWarning("所有对象必须在同一父级下才能组合"); return null; } // 计算世界包围盒 Vector3 worldMin = Vector3.positiveInfinity; Vector3 worldMax = Vector3.negativeInfinity; foreach (var rt in rects) { Vector3[] corners = new Vector3[4]; rt.GetWorldCorners(corners); worldMin = Vector3.Min(worldMin, corners[0]); worldMax = Vector3.Max(worldMax, corners[2]); } // 创建容器 GameObject root = UIBuilderUtil.CreateUIObj("CombineRoot"); RectTransform rootRT = root.GetComponent(); root.transform.SetParent(parent); rootRT.localScale = Vector3.one; // 将世界包围盒转换为父本地坐标 RectTransform parentRT = parent as RectTransform; if (parentRT == null) { Debug.LogWarning("父对象不是 RectTransform,无法创建组合容器"); return null; } Vector3 localMin = parentRT.InverseTransformPoint(worldMin); Vector3 localMax = parentRT.InverseTransformPoint(worldMax); Vector2 size = new Vector2(localMax.x - localMin.x, localMax.y - localMin.y); Vector2 center = (localMin + localMax) / 2f; rootRT.sizeDelta = size; rootRT.anchoredPosition = center; // 按原顺序设置为子对象 rects.Sort((a, b) => a.GetSiblingIndex() - b.GetSiblingIndex()); foreach (var rt in rects) { Undo.SetTransformParent(rt.transform, root.transform, "Combine Widgets"); } Selection.activeGameObject = root; return root; } /// /// 组合节点 /// /// /// public static GameObject GenCombineRootRect(GameObject[] objs) { List rects = objs.ToList().Select(a => a.GetComponent()).Where(r => r != null).ToList(); return GenCombineRootRect(rects); } /// /// /// /// /// private static bool AllHaveSameParent(GameObject[] objs) { if (objs.Length == 1) { return true; } Transform parent = objs[0].transform.parent; for (int i = 1; i < objs.Length; i++) { if (objs[i].transform.parent != parent) { return false; } } return true; } private static bool AllHaveRectTransform(GameObject[] objs) { for (int i = 0; i < objs.Length; i++) { if (objs[i].GetComponent() == null) { return false; } } return true; } public static bool CanCombine(GameObject[] objs) { if (objs == null || objs.Length <= 1) { return false; } bool SameLevelUI = false; if (AllHaveSameParent(objs)) { SameLevelUI = true; } bool UIInPrefab = false; foreach (GameObject go in objs) { string assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go); if (!string.IsNullOrEmpty(assetPath)) { UIInPrefab = true; } } if (SameLevelUI && !UIInPrefab) { return true; } return false; } } } #endif