using System.Collections.Generic; using UnityEngine; namespace XericUI.VisualForm { /// /// 高级锚点坞 — 在 AnchorDock 基础上扩展屏幕空间碰撞分离功能。 /// 通过四叉树加速碰撞检测,迭代推挤分离重叠的UI窗口条目。 /// /// 核心流程: /// 1. 判断是否有脏标记(坐标变化),无脏则跳过碰撞 /// 2. 重置所有条目碰撞状态(从原始 ScreenOffset 重新计算,不依赖上一帧) /// 3. 刷新各 WindowState 中条目的 Canvas 本地空间碰撞矩形缓存 /// 4. 构建四叉树 → 迭代推挤分离 → 写入 CollisionOffset /// 5. 下一帧 UpdateStatePosition 通过 EffectiveOffset 自动应用碰撞偏移 /// [AddComponentMenu("Xeric UI Vessel/AnchorWindow/AdvancedAnchorDock")] public class AdvancedAnchorDock : AnchorDock { [Header("碰撞分离")] [SerializeField, Tooltip("启用屏幕空间碰撞分离计算")] private bool m_EnableCollision = false; [SerializeField, Tooltip("碰撞计算执行间隔(秒),值越小越频繁")] private float m_CollisionInterval = 0.04f; [SerializeField, Tooltip("碰撞分离最大迭代次数(10为推荐值)")] private int m_CollisionIterations = 10; [SerializeField, Tooltip("碰撞分离推力系数(0-1,0.5为推荐值)")] private float m_CollisionPushForce = 0.5f; [SerializeField, Tooltip("四叉树每节点最大容纳数")] private int m_QuadTreeMaxPerNode = 5; [SerializeField, Tooltip("四叉树最大深度")] private int m_QuadTreeMaxDepth = 4; // --- 运行时 --- private float _collisionTimer; private QuadTree _quadTree; private readonly List _collisionEntries = new List(); private readonly HashSet _overlapResults = new HashSet(); #region 生命周期 protected override void OnEnable() { base.OnEnable(); _collisionTimer = 0f; _quadTree = null; } /// /// 在基类位置更新之前注入碰撞分离计算。 /// 仅在启用碰撞且存在脏标记时才执行。 /// 摄像机变化、坐标变化或首次激活时立即触发,其余情况按间隔节流。 /// protected override void OnBeforePositionUpdates() { if (!m_EnableCollision) return; _collisionTimer += Time.unscaledDeltaTime; // 摄像机变化时立即触发碰撞(如场景启动) bool forceCheck = IsCameraDirty; bool timerElapsed = _collisionTimer >= m_CollisionInterval; if (!forceCheck && !timerElapsed) return; if (timerElapsed) _collisionTimer %= m_CollisionInterval; // 仅在有脏标记时才计算(摄像机变化或坐标变化触发,碰撞偏移本身不作为触发条件) if (!NeedsCollisionRecalculation()) return; ComputeCollisionSeparation(); } #endregion #region 碰撞流程 — 封装方法 /// /// 是否需要重新计算碰撞。仅在摄像机变化或窗口坐标变化时返回 true。 /// 使用独立的 CollisionDirty 标记(不被 UpdateStatePosition 清除),而非 CoordinateDirty。 /// private bool NeedsCollisionRecalculation() { if (IsCameraDirty) return true; foreach (var kvp in ActiveStates) { var state = kvp.Value; if (state == null || !state.IsEnabled) continue; if (state.CollisionDirty) return true; } return false; } /// /// 主碰撞分离计算入口。 /// 流程:重置 → 刷新矩形 → 构建四叉树 → 迭代推挤 → 应用结果。 /// private void ComputeCollisionSeparation() { // 1. 重置所有条目碰撞状态(从原始 ScreenOffset 重新计算) foreach (var kvp in ActiveStates) { if (kvp.Value == null) continue; kvp.Value.ResetCollisionState(); } // 2. 刷新所有条目的 Canvas 本地空间碰撞矩形缓存 RefreshAllCollisionRects(); // 3. 收集参与碰撞的条目 CollectCollisionEntries(); if (_collisionEntries.Count < 2) return; // 4. 构建四叉树 BuildQuadTree(); // 5. 迭代推挤分离 IterateCollisionPush(); // 6. 标记碰撞结果,让 EffectiveOffset 在下次 UpdateStatePosition 中生效 foreach (var kvp in ActiveStates) { if (kvp.Value == null) continue; kvp.Value.ApplyCollisionResult(); } } /// /// 刷新所有活跃 WindowState 中各条目的碰撞矩形缓存。 /// 每个矩形位于 Canvas 本地空间,中心 = 锚点世界→Canvas坐标 + ScreenOffset, /// 尺寸 = 条目实例 RectTransform.rect.size。 /// private void RefreshAllCollisionRects() { var camWorld = WorldCamera; var camUI = UICamera; var canvas = Canvas; var container = RectTransform; if (camWorld == null || canvas == null || container == null) return; foreach (var kvp in ActiveStates) { var state = kvp.Value; if (state == null || !state.IsEnabled || !state.HasAnyEnabled) continue; if (!state.HasAnyCollisionEntry) continue; var anchor = state.Anchor; if (anchor == null) continue; state.ClearCollisionRects(); Vector3 worldPos = anchor.CachedTransform.position; if (!TryWorldToScreenPoint(worldPos, camWorld, camUI, canvas, container, out Vector2 canvasPos)) continue; for (int i = 0; i < state.Entries.Count; i++) { var entry = state.Entries[i]; if (!entry.EnableCollision || !entry.Enabled || entry.Instance == null) continue; RectTransform rt = entry.Instance.transform as RectTransform; Vector2 size = (rt != null) ? rt.rect.size : Vector2.one * 10f; Vector2 center = canvasPos + entry.ScreenOffset; Rect rect = new Rect(center - size * 0.5f, size); state.SetCollisionRect(entry, rect); } } } /// /// 收集所有参与碰撞的活跃条目到一个扁平列表中。 /// private void CollectCollisionEntries() { _collisionEntries.Clear(); foreach (var kvp in ActiveStates) { var state = kvp.Value; if (state == null || !state.IsEnabled || !state.HasAnyEnabled) continue; if (!state.HasAnyCollisionEntry) continue; for (int i = 0; i < state.Entries.Count; i++) { var entry = state.Entries[i]; if (!entry.EnableCollision || !entry.Enabled || entry.Instance == null) continue; Rect rect = state.GetCollisionRect(entry); if (rect.width <= 0f || rect.height <= 0f) continue; _collisionEntries.Add(entry); } } } /// /// 构建四叉树:计算所有条目包围盒 → 清空重建 → 插入所有条目。 /// 树中存储的是 WindowEntry,getRectFunc 返回的是含 CollisionOffset 和 Margin 的实时矩形。 /// private void BuildQuadTree() { // 计算包围盒 Rect bounds = GetEntryPushedRect(_collisionEntries[0]); for (int i = 1; i < _collisionEntries.Count; i++) { var r = GetEntryPushedRect(_collisionEntries[i]); bounds = Encapsulate(bounds, r); } // 略微扩展边界 bounds = new Rect(bounds.x - 1f, bounds.y - 1f, bounds.width + 2f, bounds.height + 2f); if (_quadTree == null) _quadTree = new QuadTree(bounds, GetEntryPushedRect, m_QuadTreeMaxPerNode, m_QuadTreeMaxDepth); else _quadTree.Rebuild(_collisionEntries); } /// /// 迭代推挤分离算法。 /// 对每个条目从四叉树查询重叠对象,沿最小重叠轴推开,重复直到无重叠或达到最大迭代次数。 /// private void IterateCollisionPush() { float pushForce = Mathf.Clamp01(m_CollisionPushForce); int maxIter = Mathf.Clamp(m_CollisionIterations, 1, 50); for (int iter = 0; iter < maxIter; iter++) { bool anyOverlap = false; for (int i = 0; i < _collisionEntries.Count; i++) { var entryA = _collisionEntries[i]; var stateA = entryA.State; if (stateA == null) continue; Rect rectA = stateA.GetEntryPushedRect(entryA); _quadTree.Retrieve(rectA, _overlapResults); foreach (var entryB in _overlapResults) { if (entryB == entryA) continue; var stateB = entryB.State; if (stateB == null) continue; Rect rectB = stateB.GetEntryPushedRect(entryB); if (!CollisionUtils.Overlaps(rectA, rectB)) continue; anyOverlap = true; Vector2 push = CollisionUtils.CalculateRepulsion(rectA, rectB, pushForce); if (push == Vector2.zero) continue; stateA.SetCollisionOffset(entryA, entryA.CollisionOffset + push); stateB.SetCollisionOffset(entryB, entryB.CollisionOffset - push); // 更新 rectA 以反映新的碰撞偏移(同一次迭代内继续使用) rectA = stateA.GetEntryPushedRect(entryA); } } if (!anyOverlap) break; } } #endregion #region 辅助方法 /// /// 获取条目当前的推挤位置矩形(含 CollisionOffset + CollisionMargin)。 /// 作为四叉树的 getRectFunc 委托,实时反映条目最新碰撞偏移。 /// private static Rect GetEntryPushedRect(WindowEntry entry) { if (entry?.State == null) return Rect.zero; return entry.State.GetEntryPushedRect(entry); } /// /// 计算两个矩形的包围盒。 /// private static Rect Encapsulate(Rect a, Rect b) { float xMin = Mathf.Min(a.xMin, b.xMin); float yMin = Mathf.Min(a.yMin, b.yMin); float xMax = Mathf.Max(a.xMax, b.xMax); float yMax = Mathf.Max(a.yMax, b.yMax); return new Rect(xMin, yMin, xMax - xMin, yMax - yMin); } #endregion } }