Skip to content

Instantly share code, notes, and snippets.

@pyjamads
Last active December 29, 2022 10:07
Show Gist options
  • Save pyjamads/ae3d05e6ec9bce6e96ea2a62d2859294 to your computer and use it in GitHub Desktop.
Save pyjamads/ae3d05e6ec9bce6e96ea2a62d2859294 to your computer and use it in GitHub Desktop.
A Unity UI Auto expanding grid layout group, that automatically changes column and row count - adapted from collin_patrick's AutoExpandGridLayoutGroup https://forum.unity.com/threads/solved-how-to-make-grid-layout-group-cell-size-x-auto-expand.448534/
namespace UnityEngine.UI
{
[AddComponentMenu("Layout/Auto Expand Grid Layout Group", 152)]
public class AutoExpandGridLayoutGroup : LayoutGroup
{
public enum Corner { UpperLeft = 0, UpperRight = 1, LowerLeft = 2, LowerRight = 3 }
public enum Axis { Horizontal = 0, Vertical = 1 }
public enum Constraint { Flexible = 0, FixedColumnCount = 1, FixedRowCount = 2 }
[SerializeField]
protected Corner m_StartCorner = Corner.UpperLeft;
public Corner startCorner { get { return m_StartCorner; } set { SetProperty(ref m_StartCorner, value); } }
[SerializeField]
protected Axis m_StartAxis = Axis.Horizontal;
public Axis startAxis { get { return m_StartAxis; } set { SetProperty(ref m_StartAxis, value); } }
[SerializeField]
protected Vector2 m_CellSize = new Vector2(100, 100);
public Vector2 cellSize { get { return m_CellSize; } set { SetProperty(ref m_CellSize, value); } }
[SerializeField]
protected Vector2 m_Spacing = Vector2.zero;
public Vector2 spacing { get { return m_Spacing; } set { SetProperty(ref m_Spacing, value); } }
[SerializeField]
protected Constraint m_Constraint = Constraint.Flexible;
public Constraint constraint { get { return m_Constraint; } set { SetProperty(ref m_Constraint, value); } }
[SerializeField]
protected bool FlexibleSize = true;
[SerializeField]
protected int m_ConstraintCount = 2;
public int constraintCount { get { return m_ConstraintCount; } set { SetProperty(ref m_ConstraintCount, Mathf.Max(1, value)); } }
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
constraintCount = constraintCount;
}
void Update()
{
if (constraint == Constraint.Flexible && FlexibleSize)
{
constraintCount = Mathf.RoundToInt(Mathf.Max(1, Mathf.Sqrt(rectChildren.Count)));
m_CellSize = new Vector2(rectTransform.rect.width / m_ConstraintCount - 0.001f, rectTransform.rect.height / (float)m_ConstraintCount - 0.001f);
}
}
#endif
public override void CalculateLayoutInputHorizontal()
{
base.CalculateLayoutInputHorizontal();
int minColumns = 0;
int preferredColumns = 0;
if (m_Constraint == Constraint.FixedColumnCount)
{
minColumns = preferredColumns = m_ConstraintCount;
}
else if (m_Constraint == Constraint.FixedRowCount)
{
minColumns = preferredColumns = Mathf.CeilToInt(rectChildren.Count / (float)m_ConstraintCount - 0.001f);
}
else
{
if (FlexibleSize)
{
minColumns = preferredColumns = Mathf.CeilToInt(rectChildren.Count / (float)m_ConstraintCount - 0.001f);
}
else
{
minColumns = 1;
preferredColumns = Mathf.CeilToInt(Mathf.Sqrt(rectChildren.Count));
}
}
SetLayoutInputForAxis(
padding.horizontal + (cellSize.x + spacing.x) * minColumns - spacing.x,
padding.horizontal + (cellSize.x + spacing.x) * preferredColumns - spacing.x,
-1, 0);
}
public override void CalculateLayoutInputVertical()
{
int minRows = 0;
if (m_Constraint == Constraint.FixedColumnCount)
{
minRows = Mathf.CeilToInt(rectChildren.Count / (float)m_ConstraintCount - 0.001f);
}
else if (m_Constraint == Constraint.FixedRowCount)
{
minRows = m_ConstraintCount;
}
else
{
if (FlexibleSize)
{
minRows = m_ConstraintCount;
}
else
{
float width = rectTransform.rect.size.x;
int cellCountX = Mathf.Max(1, Mathf.FloorToInt((width - padding.horizontal + spacing.x + 0.001f) / (cellSize.x + spacing.x)));
minRows = Mathf.CeilToInt(rectChildren.Count / (float)cellCountX);
}
}
float minSpace = padding.vertical + (cellSize.y + spacing.y) * minRows - spacing.y;
SetLayoutInputForAxis(minSpace, minSpace, -1, 1);
}
public override void SetLayoutHorizontal()
{
SetCellsAlongAxis(0);
}
public override void SetLayoutVertical()
{
SetCellsAlongAxis(1);
}
private void SetCellsAlongAxis(int axis)
{
// Normally a Layout Controller should only set horizontal values when invoked for the horizontal axis
// and only vertical values when invoked for the vertical axis.
// However, in this case we set both the horizontal and vertical position when invoked for the vertical axis.
// Since we only set the horizontal position and not the size, it shouldn't affect children's layout,
// and thus shouldn't break the rule that all horizontal layout must be calculated before all vertical layout.
if (axis == 0)
{
// Only set the sizes when invoked for horizontal axis, not the positions.
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform rect = rectChildren[i];
m_Tracker.Add(this, rect,
DrivenTransformProperties.Anchors |
DrivenTransformProperties.AnchoredPosition |
DrivenTransformProperties.SizeDelta);
rect.anchorMin = Vector2.up;
rect.anchorMax = Vector2.up;
rect.sizeDelta = cellSize;
}
return;
}
float width = rectTransform.rect.size.x;
float height = rectTransform.rect.size.y;
int cellCountX = 1;
int cellCountY = 1;
if (m_Constraint == Constraint.FixedColumnCount)
{
cellCountX = m_ConstraintCount;
cellCountY = Mathf.CeilToInt(rectChildren.Count / (float)cellCountX - 0.001f);
}
else if (m_Constraint == Constraint.FixedRowCount)
{
cellCountY = m_ConstraintCount;
cellCountX = Mathf.CeilToInt(rectChildren.Count / (float)cellCountY - 0.001f);
}
else
{
if (FlexibleSize)
{
cellCountY = m_ConstraintCount;
cellCountX = Mathf.CeilToInt(rectChildren.Count / (float)cellCountY - 0.001f);
}
else
{
if (cellSize.x + spacing.x <= 0)
cellCountX = int.MaxValue;
else
cellCountX = Mathf.Max(1, Mathf.FloorToInt((width - padding.horizontal + spacing.x + 0.001f) / (cellSize.x + spacing.x)));
if (cellSize.y + spacing.y <= 0)
cellCountY = int.MaxValue;
else
cellCountY = Mathf.Max(1, Mathf.FloorToInt((height - padding.vertical + spacing.y + 0.001f) / (cellSize.y + spacing.y)));
}
}
int cornerX = (int)startCorner % 2;
int cornerY = (int)startCorner / 2;
int cellsPerMainAxis, actualCellCountX, actualCellCountY;
if (startAxis == Axis.Horizontal)
{
cellsPerMainAxis = cellCountX;
actualCellCountX = Mathf.Clamp(cellCountX, 1, rectChildren.Count);
actualCellCountY = Mathf.Clamp(cellCountY, 1, Mathf.CeilToInt(rectChildren.Count / (float)cellsPerMainAxis));
}
else
{
cellsPerMainAxis = cellCountY;
actualCellCountY = Mathf.Clamp(cellCountY, 1, rectChildren.Count);
actualCellCountX = Mathf.Clamp(cellCountX, 1, Mathf.CeilToInt(rectChildren.Count / (float)cellsPerMainAxis));
}
Vector2 requiredSpace = new Vector2(
actualCellCountX * cellSize.x + (actualCellCountX - 1) * spacing.x,
actualCellCountY * cellSize.y + (actualCellCountY - 1) * spacing.y
);
Vector2 startOffset = new Vector2(
GetStartOffset(0, requiredSpace.x),
GetStartOffset(1, requiredSpace.y)
);
for (int i = 0; i < rectChildren.Count; i++)
{
int positionX;
int positionY;
if (startAxis == Axis.Horizontal)
{
positionX = i % cellsPerMainAxis;
positionY = i / cellsPerMainAxis;
}
else
{
positionX = i / cellsPerMainAxis;
positionY = i % cellsPerMainAxis;
}
if (cornerX == 1)
positionX = actualCellCountX - 1 - positionX;
if (cornerY == 1)
positionY = actualCellCountY - 1 - positionY;
float realsize = ((width - (spacing[0] * (actualCellCountX - 1))) / actualCellCountX);
SetChildAlongAxis(rectChildren[i], 0, startOffset.x + (realsize + spacing[0]) * positionX, realsize);
if (FlexibleSize)
{
realsize = ((height - (spacing[1] * (actualCellCountY - 1))) / actualCellCountY);
SetChildAlongAxis(rectChildren[i], 1, startOffset.y + (realsize + spacing[1]) * positionY, realsize);
}
else
{
SetChildAlongAxis(rectChildren[i], 1, startOffset.y + (cellSize[1] + spacing[1]) * positionY, cellSize[1]);
}
}
}
}
}
@pyjamads
Copy link
Author

Hi, this little component is meant to be used instead of a normal GridLayout component, in case you don't know the size of your grid. But if I understand your purpose correctly, you know the size of your chess board, so why not just use the UnityEngine.UI.GridLayout and specify the rows and columns to be 8x8 ?

@Atlinx
Copy link

Atlinx commented Jul 25, 2022

Hey @pyjamads, I'm trying to use this component, but I can't seem to get it to work. When the constraint is set to flexible, I get weird behavior. For example, if I change cell size, it snaps to some random value. If I change the constraint count, it's stuck at 4. Also, the grid begins to grow infinitely in width when the constraint is set to flexible.

I'm trying to use this as a drop-in replacement layout group for the content of a scroll view.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment