AlicizaX/Client/Packages/com.alicizax.uxtool/Editor/UXGUI/SceneView/ContextMenu/SceneViewContextMenu.cs

988 lines
37 KiB
C#
Raw Normal View History

2025-12-01 16:46:28 +08:00
#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<RectTransform> 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<string, RectTransform> 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<RectTransform> GetAllUIObjects(PrefabStage prefabStage)
{
List<RectTransform> uiObjects = new List<RectTransform>();
if (prefabStage != null)
{
RectTransform[] allObjects = prefabStage.prefabContentsRoot.GetComponentsInChildren<RectTransform>(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<RectTransform>(true);
foreach (RectTransform rect in childRects)
{
if (FindContainerLogic.ObjectFit(rect.gameObject))
{
uiObjects.Add(rect);
}
}
}
}
return uiObjects;
}
// 排序UI对象
private static SortedList<string, RectTransform> SortUIObjects(List<RectTransform> uiObjects)
{
List<string> uiNames = new List<string>();
foreach (var rect in uiObjects)
{
uiNames.Add(GetTransformHierarchyString("", rect));
}
// 按照名称排序
SortedList<string, RectTransform> sortedList = new SortedList<string, RectTransform>(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<string>
{
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<RectTransform>();
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<RectTransform>();
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<RectTransform>()).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<RectTransform>()).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<RectTransform>();
if (first == null) return;
for (int i = 1; i < gos.Length; i++)
{
var rt = gos[i].GetComponent<RectTransform>();
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<RectTransform>();
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<RectTransform>();
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<RectTransform>();
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<RectTransform>();
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<RectTransform>()).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<RectTransform>();
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<GameObject> newOnes = new List<GameObject>();
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<Graphic>();
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<Transform>().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
{
/// <summary>
/// 组合节点
/// </summary>
/// <param name="rects">需要组合的对象 需要所有对象在同一层级</param>
/// <returns>组合之后的父节点</returns>
public static GameObject GenCombineRootRect(List<RectTransform> 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<RectTransform>();
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;
}
/// <summary>
/// 组合节点
/// </summary>
/// <param name="rects"></param>
/// <returns></returns>
public static GameObject GenCombineRootRect(GameObject[] objs)
{
List<RectTransform> rects = objs.ToList().Select(a => a.GetComponent<RectTransform>()).Where(r => r != null).ToList();
return GenCombineRootRect(rects);
}
/// <summary>
///
/// </summary>
/// <param name="rects"></param>
/// <returns></returns>
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<RectTransform>() == 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