Last active
May 3, 2024 02:04
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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