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