Skip to content

Instantly share code, notes, and snippets.

@PopupAsylumUK
Last active May 3, 2024 02:04
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save PopupAsylumUK/56d2f889a5b195552db20da6a9a50c11 to your computer and use it in GitHub Desktop.
Save PopupAsylumUK/56d2f889a5b195552db20da6a9a50c11 to your computer and use it in GitHub Desktop.
A Layout group for arranging children in a hexagon grid, and a Hexagon graphic
using System;
using UnityEngine;
using UnityEngine.UI;
public class Hexagon : MaskableGraphic, ILayoutElement, ICanvasRaycastFilter {
public enum Direction {
Horizontal,
Vertical
}
[SerializeField]
public Direction direction;
public float minWidth { get { return 0; } }
public float preferredWidth { get { return 0; } }
public float flexibleWidth { get { return -1; } }
public float minHeight { get { return 0; } }
public float preferredHeight { get { return 0; } }
public float flexibleHeight { get { return -1; } }
public int layoutPriority { get { return 0; } }
public void CalculateLayoutInputHorizontal() { }
public void CalculateLayoutInputVertical() { }
public Color baseColor = Color.white;
public float gradientDirection;
[SerializeField] private Sprite m_Sprite;
public Sprite sprite { get { return m_Sprite; } set { if (SetPropertyUtility.SetClass(ref m_Sprite, value)) SetAllDirty(); } }
[NonSerialized]
private Sprite m_OverrideSprite;
public Sprite overrideSprite { get { return activeSprite; } set { if (SetPropertyUtility.SetClass(ref m_OverrideSprite, value)) SetAllDirty(); } }
private Sprite activeSprite { get { return m_OverrideSprite != null ? m_OverrideSprite : sprite; } }
public override Texture mainTexture {
get {
if (activeSprite == null) {
if (material != null && material.mainTexture != null) {
return material.mainTexture;
}
return s_WhiteTexture;
}
return activeSprite.texture;
}
}
public bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) {
Vector2 local;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local))
return false;
Rect rect = GetPixelAdjustedRect();
// Convert to have lower left corner as reference point.
local.x += rectTransform.pivot.x * rect.width * 0.5f;
local.y += rectTransform.pivot.y * rect.height * 0.5f;
Vector2 normalized = new Vector2(local.x / rect.width, local.y / rect.height);
return normalized.magnitude < 0.5f;
}
protected override void OnPopulateMesh(VertexHelper vh) {
var color32 = color;
vh.Clear();
Rect rect = GetPixelAdjustedRect();
float rw = rect.width * 0.5f;
float rh = rect.height * 0.5f;
Vector2 rwh = new Vector2(rw, rh);
Vector2 origin = new Vector2(rect.x, rect.y);
Vector2 half = Vector2.one * 0.5f;
Vector2 v0, v1, v2, v3, v4, v5;
if (direction == Direction.Horizontal) {
v0 = new Vector2(-Mathf.Cos(Mathf.PI / 3), 1);
v1 = new Vector2(Mathf.Cos(Mathf.PI / 3), 1);
v2 = new Vector2(-1, 0);
v3 = new Vector2(1, 0);
v4 = new Vector2(-Mathf.Cos(Mathf.PI / 3), -1);
v5 = new Vector2(Mathf.Cos(Mathf.PI / 3), -1);
} else {
v0 = new Vector2(-1, Mathf.Cos(Mathf.PI / 3));
v1 = new Vector2(-1, -Mathf.Cos(Mathf.PI / 3));
v2 = new Vector2(0, 1);
v3 = new Vector2(0, -1);
v4 = new Vector2(1, Mathf.Cos(Mathf.PI / 3));
v5 = new Vector2(1, -Mathf.Cos(Mathf.PI / 3));
}
vh.AddVert(Vector2.Scale(v0, rwh) + origin +rwh, GetColor(v0), v0 * 0.5f + half);
vh.AddVert(Vector2.Scale(v1, rwh) + origin +rwh, GetColor(v1), v1 * 0.5f + half);
vh.AddVert(Vector2.Scale(v2, rwh) + origin +rwh, GetColor(v2), v2 * 0.5f + half);
vh.AddVert(Vector2.Scale(v3, rwh) + origin +rwh, GetColor(v3), v3 * 0.5f + half);
vh.AddVert(Vector2.Scale(v4, rwh) + origin +rwh, GetColor(v4), v4 * 0.5f + half);
vh.AddVert(Vector2.Scale(v5, rwh) + origin +rwh, GetColor(v5), v5 * 0.5f + half);
vh.AddTriangle(0, 1, 2);
vh.AddTriangle(1, 3, 2);
vh.AddTriangle(2,3, 4);
vh.AddTriangle(3, 5, 4);
}
Color32 GetColor(Vector2 v) {
Vector2 dir = new Vector2(Mathf.Sin(gradientDirection * Mathf.Deg2Rad), Mathf.Cos(gradientDirection * Mathf.Deg2Rad));
float lerp = Vector2.Dot(v, dir) * 0.5f + 0.5f;
return Color32.Lerp(baseColor, color, lerp);
}
[ContextMenu("Set Hex Aspect")]
void SetHexAspect() {
rectTransform.sizeDelta = new Vector2(rectTransform.sizeDelta.x, (direction == Direction.Horizontal ? Mathf.Sin(Mathf.PI / 3) * rectTransform.sizeDelta.x : rectTransform.sizeDelta.x / Mathf.Sin(Mathf.PI / 3)));
}
}
using UnityEngine;
using UnityEngine.UI;
public class HexGridLayout : LayoutGroup {
const float SQUARE_ROOT_OF_3 = 1.73205f;
public enum Axis { Horizontal = 0, Vertical = 1 }
public enum Constraint { Flexible = 0, FixedColumnCount = 1, FixedRowCount = 2 }
[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 int m_ConstraintCount = 2;
public int constraintCount { get { return m_ConstraintCount; } set { SetProperty(ref m_ConstraintCount, Mathf.Max(1, value)); } }
public Axis direction;
protected HexGridLayout() { }
#if UNITY_EDITOR
protected override void OnValidate() {
base.OnValidate();
constraintCount = constraintCount;
foreach (var hex in GetComponentsInChildren<Hexagon>()) {
hex.direction = direction == Axis.Horizontal ? Hexagon.Direction.Horizontal : Hexagon.Direction.Vertical;
hex.SetVerticesDirty();
}
}
#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 {
minColumns = 1;
preferredColumns = Mathf.CeilToInt(Mathf.Sqrt(rectChildren.Count));
}
if (direction == Axis.Horizontal) {
float space = padding.horizontal + (preferredColumns * 0.75f + 0.25f) * (cellSize.x + spacing.x) - spacing.x;
SetLayoutInputForAxis(space, space, -1, 0);
} else {
bool extra = rectChildren.Count / (preferredColumns * 2) > 1;
float space = padding.horizontal + (preferredColumns + (extra ? 0.5f : 0)) * (cellSize.x + spacing.x) - spacing.x;
SetLayoutInputForAxis(space, space, -1, 0);
}
}
public override void CalculateLayoutInputVertical() {
if (direction == Axis.Horizontal) {
float width = rectTransform.rect.size.x - (cellSize.x + spacing.x) * 0.25f - padding.horizontal+ spacing.x;
float cellWidth = (cellSize.x + spacing.x) * 0.75f;
int columns = Mathf.Max(1, Mathf.FloorToInt((width / cellWidth) + 0.001f));
int rows = Mathf.CeilToInt(rectChildren.Count / (float)columns);
int cellsOnLastRow = rectChildren.Count - columns * (rows - 1);
float space = padding.vertical + (cellSize.y + spacing.y) * rows + (cellsOnLastRow % 2 != 1 ? cellSize.y * 0.5f : 0) - spacing.y;
SetLayoutInputForAxis(space, space, -1, 1);
} else {
float cellWidth = (cellSize.x + spacing.x);
float firstLineWidth = rectTransform.rect.size.x - padding.horizontal + spacing.x;
float secondLineWdith = firstLineWidth - (cellSize.x+spacing.x) * 0.5f;
int columnsInFirstLine = (int)(firstLineWidth / cellWidth);
int columnsInSecondLine = (int)(secondLineWdith / cellWidth);
int columnsCombined = columnsInFirstLine + columnsInSecondLine;
int completeRowsIfItWasOneLine = Mathf.FloorToInt(rectChildren.Count / (float)columnsCombined);
int elementsOnLastRowIfItWasOneLine = rectChildren.Count % columnsCombined;
int rows = completeRowsIfItWasOneLine * 2;
rows += elementsOnLastRowIfItWasOneLine > columnsInFirstLine ? 2 : elementsOnLastRowIfItWasOneLine > 0 ? 1 : 0;
float space = padding.vertical + (rows * 0.75f + 0.25f) * (cellSize.y + spacing.y) - spacing.y;
SetLayoutInputForAxis(space, space, -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 - padding.horizontal;
float height = rectTransform.rect.size.y - padding.vertical;
Vector2 cellSizeWithPadding = new Vector2(cellSize.x + spacing.x, cellSize.y + spacing.y);
Vector2 rectSizeWithPadding = new Vector2(width + spacing.x, height + spacing.y);
float columnWidth = direction == Axis.Horizontal ? cellSizeWithPadding.x * 0.75f: cellSizeWithPadding.x;
float rowHeight = direction == Axis.Horizontal ? cellSizeWithPadding.y : cellSizeWithPadding.y * 0.75f;
//when we start a new line we may havce to account for the < as we add up
float defaultFill = 0;
if (startAxis == Axis.Horizontal) {
if (direction == Axis.Horizontal) {
defaultFill = cellSizeWithPadding.x * 0.25f;
}
} else {
if (direction == Axis.Vertical) {
defaultFill = cellSizeWithPadding.y * 0.25f;
}
}
float offsetLineFill = 0;
if (startAxis == Axis.Horizontal) {
if (direction == Axis.Vertical) {
offsetLineFill = cellSizeWithPadding.x * 0.5f;
}
} else {
if (direction == Axis.Horizontal) {
offsetLineFill = cellSizeWithPadding.y * 0.5f;
}
}
float fillPerCell = 0;
if (startAxis == Axis.Horizontal) {
fillPerCell = columnWidth;
} else {
fillPerCell = rowHeight;
}
float fillLimit = 0;
if (startAxis == Axis.Horizontal) {
fillLimit = rectSizeWithPadding.x;
} else {
fillLimit = rectSizeWithPadding.y;
}
float constraintLimit = fillLimit;
if (m_Constraint == Constraint.FixedColumnCount){
if (startAxis == Axis.Horizontal) {
if (direction == Axis.Horizontal) {
constraintLimit = cellSizeWithPadding.x * constraintCount - cellSizeWithPadding.x * 0.25f;
} else {
constraintLimit = cellSizeWithPadding.x * constraintCount;
}
}
}else if (m_Constraint == Constraint.FixedRowCount) {
if (startAxis == Axis.Vertical) {
if (direction == Axis.Vertical) {
constraintLimit = cellSizeWithPadding.y * constraintCount - cellSizeWithPadding.y * 0.25f;
} else {
constraintLimit = cellSizeWithPadding.y * constraintCount;
}
}
}
fillLimit = Mathf.Min(fillLimit, constraintLimit);
int row = 0;
int column = 0;
float filled = defaultFill;
for (int i = 0; i < rectChildren.Count; i++) {
float nextFilled = filled + fillPerCell;
if (nextFilled > fillLimit) {
row++;
column = 0;
filled = defaultFill + fillPerCell;
if (direction != startAxis && row % 2 == 1) {
filled += offsetLineFill;
}
} else {
filled = nextFilled;
}
Vector2 position;
int x = startAxis == Axis.Horizontal ? column : row;
int y = startAxis == Axis.Horizontal ? row : column;
if (direction == Axis.Vertical) {
position = new Vector2(x * cellSizeWithPadding.x + (y % 2 != 0 ? cellSizeWithPadding.x * 0.5f : 0), y * cellSizeWithPadding.y * 0.75f);
} else {
position = new Vector2(x * cellSizeWithPadding.x * 0.75f, y * cellSizeWithPadding.y + (x % 2 != 0 ? cellSizeWithPadding.y * 0.5f : 0));
}
column++;
SetChildAlongAxis(rectChildren[i], 0, position.x +padding.left, cellSize.x);
SetChildAlongAxis(rectChildren[i], 1, position.y + padding.top, cellSize.y);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment