using UnityEngine; using System.Collections.Generic; [ExecuteAlways] [RequireComponent(typeof(RectTransform))] public class HomeGridLayout : MonoBehaviour { public RectOffset padding = new RectOffset(); // 边距 public Vector2 cellSize = new Vector2(100f, 100f); // 单元格大小 public Vector2 spacing = new Vector2(10f, 10f); // 元素间距 public int rows = 7; // 固定行数 private bool _isDirty = true; //优化新增:脏标记与计时器 初始设为 true,确保第一帧会排版 private float _checkTimer = 0f; private const float CHECK_INTERVAL = 0.1f; // 100ms 检查间隔 /// /// 供外部或子元素调用,标记当前网格需要重新排版 /// public void MarkAsDirty() { _isDirty = true; } // 尺寸或子节点数量发生变化时,同样只打上脏标记 private void OnRectTransformDimensionsChange() => MarkAsDirty(); private void OnTransformChildrenChanged() => MarkAsDirty(); private void Update() { #if UNITY_EDITOR // 编辑器非运行状态下(Edit Mode)Update 帧率不稳定,为了方便拖拽预览,依然有脏标记就立即更新 if (!Application.isPlaying) { if (_isDirty) { UpdateLayout(); _isDirty = false; } return; } #endif // 运行时(Play Mode)每 100ms 轮询一次 _checkTimer += Time.deltaTime; if (_checkTimer >= CHECK_INTERVAL) { if (_isDirty) { UpdateLayout(); _isDirty = false; // 排版完成后,清除标志位 } _checkTimer = 0f; // 重置计时器 } } public void UpdateLayout() { if (rows <= 0 || transform.childCount == 0) return; List flowChildren = new List(); List fixedChildren = new List(); // 1. 遍历收集并分类所有激活的子物体 for (int i = 0; i < transform.childCount; i++) { RectTransform child = transform.GetChild(i) as RectTransform; if (child == null || !child.gameObject.activeSelf) continue; HomeGridLayoutCell cell = child.GetComponent(); if (cell != null && cell.isFixedPosition) fixedChildren.Add(child); else flowChildren.Add(child); } // 2. 排序流动物体 (按 sortIndex) flowChildren.Sort((a, b) => { var cellA = a.GetComponent(); var cellB = b.GetComponent(); int indexA = cellA != null ? cellA.sortIndex : int.MaxValue; int indexB = cellB != null ? cellB.sortIndex : int.MaxValue; return indexA.CompareTo(indexB); }); // 3. 排序固定物体 (优先 sortIndex,其次 subSortIndex) fixedChildren.Sort((a, b) => { var cellA = a.GetComponent(); var cellB = b.GetComponent(); int sortA = cellA != null ? cellA.sortIndex : int.MaxValue; int sortB = cellB != null ? cellB.sortIndex : int.MaxValue; if (sortA != sortB) return sortA.CompareTo(sortB); int subA = cellA != null ? cellA.subSortIndex : int.MaxValue; int subB = cellB != null ? cellB.subSortIndex : int.MaxValue; return subA.CompareTo(subB); }); // 4. 开始分配网格 (核心优化部分) int currentGridIndex = 0; // 当前推演到的真实网格坑位 int flowIndex = 0; int fixedIndex = 0; int totalValidChildren = flowChildren.Count + fixedChildren.Count; for (int i = 0; i < totalValidChildren; i++) { RectTransform targetChild = null; HomeGridLayoutCell nextFixedCell = null; if (fixedIndex < fixedChildren.Count) { nextFixedCell = fixedChildren[fixedIndex].GetComponent(); } // 分支 A:固定物体已经到了它期望的网格位置(或已经被前面的元素挤到了当前位置) if (nextFixedCell != null && nextFixedCell.sortIndex <= currentGridIndex) { targetChild = fixedChildren[fixedIndex]; fixedIndex++; } // 分支 B:当前位置没有固定物体抢占,且还有流动物体排队,让流动物体填补空缺 else if (flowIndex < flowChildren.Count) { targetChild = flowChildren[flowIndex]; flowIndex++; } // 分支 C:没有流动物体填补空缺了,直接让网格索引“快进”到下一个固定物体的位置 else if (nextFixedCell != null) { currentGridIndex = nextFixedCell.sortIndex; // 快进跳过中间的所有空白格子 targetChild = fixedChildren[fixedIndex]; fixedIndex++; } // 应用计算好的网格坐标 if (targetChild != null) { SetChildTransform(targetChild, currentGridIndex); currentGridIndex++; // 占位成功,坑位后移 } } } /// /// 将子物体放置到指定的网格索引位置 /// private void SetChildTransform(RectTransform child, int gridIndex) { int col = gridIndex / rows; int row = gridIndex % rows; // 1. 先计算出如果轴心在右上角时的理论边缘坐标 float edgeXPos = -padding.right - col * (cellSize.x + spacing.x); float edgeYPos = -padding.top - row * (cellSize.y + spacing.y); // 2. 为了消除团队潜规则,我们将格子的轴心改回正中心 (0.5, 0.5) // 因此实际坐标需要向左、向下再偏移半个单元格的大小 float centerXPos = edgeXPos - (cellSize.x * 0.5f); float centerYPos = edgeYPos - (cellSize.y * 0.5f); // 统一锚点为右上角 (1, 1) child.anchorMin = new Vector2(1, 1); child.anchorMax = new Vector2(1, 1); // 轴心(Pivot)改为正中心! child.pivot = new Vector2(0.5f, 0.5f); child.sizeDelta = cellSize; // 赋予中心坐标 child.anchoredPosition = new Vector2(centerXPos, centerYPos); } }