Last active
March 11, 2025 10:59
-
-
Save GeorgiyRyaposov/953824e30cfe3f5ee611e281510eae08 to your computer and use it in GitHub Desktop.
Unity hexagon grid helpers
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
| namespace Code.Scripts.Data.Hexes | |
| { | |
| public enum HexDirection | |
| { | |
| NE, | |
| E, | |
| SE, | |
| SW, | |
| W, | |
| NW | |
| } | |
| /// <summary> | |
| /// Extension methods for <see cref="HexDirection"/>. | |
| /// </summary> | |
| public static class HexDirectionExtensions | |
| { | |
| /// <summary> | |
| /// Get the opposite of a hex direction. | |
| /// </summary> | |
| /// <param name="direction">A given direction.</param> | |
| /// <returns>The opposite direction.</returns> | |
| public static HexDirection Opposite(this HexDirection direction) => | |
| (int)direction < 3 ? (direction + 3) : (direction - 3); | |
| /// <summary> | |
| /// Get the previous direction, rotating counter-clockwise. | |
| /// </summary> | |
| /// <param name="direction">A given direction.</param> | |
| /// <returns>A direction rotated one step counter-clockwise.</returns> | |
| public static HexDirection Previous(this HexDirection direction) => | |
| direction == HexDirection.NE ? HexDirection.NW : (direction - 1); | |
| /// <summary> | |
| /// Get the next direction, rotating clockwise. | |
| /// </summary> | |
| /// <param name="direction">A given direction.</param> | |
| /// <returns>A direction rotated one step clockwise.</returns> | |
| public static HexDirection Next(this HexDirection direction) => | |
| direction == HexDirection.NW ? HexDirection.NE : (direction + 1); | |
| /// <summary> | |
| /// Get the same result as invoking <see cref="Previous"/> twice. | |
| /// </summary> | |
| /// <param name="direction">A given direction.</param> | |
| /// <returns>A direction rotated two steps counter-clockwise.</returns> | |
| public static HexDirection Previous2(this HexDirection direction) | |
| { | |
| direction -= 2; | |
| return direction >= HexDirection.NE ? direction : (direction + 6); | |
| } | |
| /// <summary> | |
| /// Get the same result as invoking <see cref="Next"/> twice. | |
| /// </summary> | |
| /// <param name="direction">A given direction.</param> | |
| /// <returns>A direction rotated two steps clockwise.</returns> | |
| public static HexDirection Next2(this HexDirection direction) | |
| { | |
| direction += 2; | |
| return direction <= HexDirection.NW ? direction : (direction - 6); | |
| } | |
| public static float RotationAngle(this HexDirection direction, bool flatOrientation) | |
| { | |
| var angle = (int)direction * 60f; | |
| if (!flatOrientation) | |
| { | |
| angle += 30f; | |
| } | |
| return angle; | |
| } | |
| } | |
| } |
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.Collections.Generic; | |
| using UnityEngine; | |
| namespace Code.Scripts.Data.Hexes | |
| { | |
| public class HexGridMetrics | |
| { | |
| private readonly Orientation _orientation; | |
| private readonly bool _flatOrientation; | |
| public HexGridMetrics(float size, bool flatOrientation) | |
| { | |
| _flatOrientation = flatOrientation; | |
| _orientation = HexOrientationFabric.CreateOrientation(size, flatOrientation); | |
| } | |
| public float GetDistanceBetweenTwoCubes(Vector3Int a, Vector3Int b) | |
| { | |
| return Mathf | |
| .Max(Mathf.Abs(a.x - b.x), | |
| Mathf.Abs(a.y - b.y), | |
| Mathf.Abs(a.z - b.z)); | |
| } | |
| public float GetRotationAngle(HexDirection direction) | |
| { | |
| return direction.RotationAngle(_flatOrientation); | |
| } | |
| public Vector3Int GetNeighbor(Vector3Int from, HexDirection direction, int size) | |
| { | |
| return GetNeighbor(from, (int)direction, size); | |
| } | |
| /// <summary> | |
| /// Calculates adjacent cube coordinate for a given direction and distance | |
| /// </summary> | |
| /// <param name="cube">Vector3 origin</param> | |
| /// <param name="direction">direction of neighbor (0-5 is valid)</param> | |
| /// <param name="distance">distance from origin (>=1 is valid)</param> | |
| /// <returns>Vector3 cube coordinate</returns> | |
| public Vector3Int GetNeighbor(Vector3Int cube, int direction, int distance) | |
| { | |
| return cube + _orientation.Directions[direction] * distance; | |
| } | |
| /// <summary> | |
| /// Calculates diagonally adjacent cube coordinate for a given direction and distance | |
| /// </summary> | |
| /// <param name="origin">Vector3 origin</param> | |
| /// <param name="direction">direction of neighbor (0-5 is valid)</param> | |
| /// <param name="distance">distance from origin (>=1 is valid)</param> | |
| /// <returns>Vector3 cube coordinate</returns> | |
| public Vector3Int GetDiagonalNeighbor(Vector3Int origin, int direction, int distance) | |
| { | |
| return origin + _orientation.Diagonals[direction] * distance; | |
| } | |
| /// <summary> | |
| /// Calculates adjacent cube coordinates over a number of steps | |
| /// </summary> | |
| /// <param name="origin">Vector3 origin</param> | |
| /// <param name="steps">steps from origin (>=1 is valid)</param> | |
| /// <returns>List of Vector3 cube coordinates</returns> | |
| public List<Vector3Int> GetNeighbors(Vector3Int origin, int steps) | |
| { | |
| var results = new List<Vector3Int>(); | |
| for (var x = (origin.x - steps); x <= (origin.x + steps); x++) | |
| for (var y = (origin.y - steps); y <= (origin.y + steps); y++) | |
| for (var z = (origin.z - steps); z <= (origin.z + steps); z++) | |
| if ((x + y + z) == 0) results.Add(new Vector3Int(x, y, z)); | |
| return results; | |
| } | |
| public Vector3 ConvertCubeToWorldPosition(Vector3Int cube) | |
| { | |
| return _orientation.ConvertCubeToWorldPosition(cube); | |
| } | |
| public Vector3Int ConvertWorldPositionToCube(Vector3 position) | |
| { | |
| return ConvertAxialToCube(ConvertWorldPositionToAxial(position)); | |
| } | |
| private Vector3Int ConvertAxialToCube(Vector2Int axial) | |
| { | |
| return new Vector3Int(axial.x, (-axial.x - axial.y), axial.y); | |
| } | |
| private Vector3 ConvertAxialToCube(Vector2 axial) | |
| { | |
| return new Vector3(axial.x, (-axial.x - axial.y), axial.y); | |
| } | |
| private static Vector2Int ConvertCubeToAxial(Vector3Int cube) | |
| { | |
| return new Vector2Int(cube.x, cube.z); | |
| } | |
| private Vector2Int ConvertWorldPositionToAxial(Vector3 position) | |
| { | |
| var axial = _orientation.ConvertWorldPositionToAxial(position); | |
| return RoundAxial(axial); | |
| } | |
| /// <summary> | |
| /// Rounds a calculated (may not be valid) axial coordinate to the nearest valid Axial coordinate | |
| /// </summary> | |
| /// <param name="axial">Vector2 axial coordinate</param> | |
| /// <returns>Vector2 axial coordinate</returns> | |
| private Vector2Int RoundAxial(Vector2 axial) | |
| { | |
| var cube = RoundCube(ConvertAxialToCube(axial)); | |
| return ConvertCubeToAxial(cube); | |
| } | |
| /// <summary> | |
| /// Rounds a provided Vector3 to the nearest valid cube coordinate | |
| /// </summary> | |
| /// <param name="cube">Vector3 input cube coordinate (does not have to be valid)</param> | |
| /// <returns>Vector3 cube coordinate</returns> | |
| private static Vector3Int RoundCube(Vector3 cube) | |
| { | |
| var rx = Mathf.RoundToInt(cube.x); | |
| var ry = Mathf.RoundToInt(cube.y); | |
| var rz = Mathf.RoundToInt(cube.z); | |
| var xDiff = Mathf.Abs(rx - cube.x); | |
| var yDiff = Mathf.Abs(ry - cube.y); | |
| var zDiff = Mathf.Abs(rz - cube.z); | |
| if (xDiff > yDiff && xDiff > zDiff) | |
| rx = -ry - rz; | |
| else if (yDiff > zDiff) | |
| ry = -rx - rz; | |
| else | |
| rz = -rx - ry; | |
| return new Vector3Int(rx, ry, rz); | |
| } | |
| #region Debug | |
| public void DrawHexGrid(int mapWidth, int mapHeight) | |
| { | |
| foreach (var cube in GetNeighbors(Vector3Int.zero, mapWidth * mapHeight)) | |
| { | |
| var corners = HexCorners(cube); | |
| DrawHex(corners); | |
| } | |
| } | |
| public void DrawCell(Vector3 worldPosition, Color color) | |
| { | |
| var corners = HexCorners(worldPosition); | |
| DrawHex(corners, color); | |
| } | |
| public void DrawCell(Vector3Int cube, Color color) | |
| { | |
| var corners = HexCorners(cube); | |
| DrawHex(corners, color); | |
| } | |
| private Vector3[] HexCorners(Vector3 position) | |
| { | |
| var cube = ConvertWorldPositionToCube(position); | |
| return HexCorners(cube); | |
| } | |
| private Vector3[] HexCorners(Vector3Int cube) | |
| { | |
| return _orientation.HexCorners(cube); | |
| } | |
| private void DrawHex(Vector3[] corners) | |
| { | |
| DrawHex(corners, Color.white); | |
| } | |
| private void DrawHex(Vector3[] corners, Color color) | |
| { | |
| if (corners == null || corners.Length == 0) | |
| { | |
| return; | |
| } | |
| var startCorner = corners[0]; | |
| for (int i = 1; i < corners.Length; i++) | |
| { | |
| var corner = corners[i]; | |
| var lineStart = startCorner; | |
| var lineEnd = corner; | |
| Debug.DrawLine(lineStart, lineEnd, color); | |
| startCorner = corner; | |
| } | |
| Debug.DrawLine(startCorner, corners[0]); | |
| } | |
| #endregion //debug | |
| } | |
| } |
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; | |
| namespace Code.Scripts.Data.Hexes | |
| { | |
| public static class HexOrientationFabric | |
| { | |
| public static Orientation CreateOrientation(float size, bool flatOrientation = false) | |
| { | |
| return flatOrientation | |
| ? CreateFlatOrientation(size) | |
| : CreatePointyOrientation(size); | |
| } | |
| private static Orientation CreateFlatOrientation(float size) | |
| { | |
| return new FlatOrientation(size); | |
| } | |
| private static Orientation CreatePointyOrientation(float size) | |
| { | |
| return new PointyOrientation(size); | |
| } | |
| } | |
| public abstract class Orientation | |
| { | |
| protected float Size; | |
| public float SpacingX; | |
| public float SpacingZ; | |
| protected const float Sqrt = 1.73205080757f;// Mathf.Sqrt(3) comes from sin(60°). | |
| protected const float OuterRadiusNormalized = 1f; | |
| protected const float RelationOuterToInnerNormalized = Sqrt * 0.5f * OuterRadiusNormalized; | |
| protected const float InnerRadiusNormalized = OuterRadiusNormalized * RelationOuterToInnerNormalized; | |
| public abstract Vector3Int[] Directions { get; } | |
| public abstract Vector3Int[] Diagonals { get; } | |
| public abstract Vector3[] Corners { get; } | |
| public abstract Vector2 ConvertWorldPositionToAxial(Vector3 position); | |
| public abstract Vector3 ConvertCubeToWorldPosition(Vector3Int cube); | |
| public Vector3[] HexCorners(Vector3Int cube) | |
| { | |
| var center = ConvertCubeToWorldPosition(cube); | |
| var corners = new Vector3[Corners.Length]; | |
| for (int i = 0; i < Corners.Length; i++) | |
| { | |
| corners[i] = center + Corners[i] * Size; | |
| } | |
| return corners; | |
| } | |
| } | |
| public class FlatOrientation : Orientation | |
| { | |
| public override Vector3Int[] Directions { get; } = | |
| { | |
| new(1, -1, 0), | |
| new(1, 0, -1), | |
| new(0, 1, -1), | |
| new(-1, 1, 0), | |
| new(-1, 0, 1), | |
| new(0, -1, 1) | |
| }; | |
| public override Vector3Int[] Diagonals { get; } = | |
| { | |
| new(1, -2, 1), | |
| new(2, -1, -1), | |
| new(1, 1, -2), | |
| new(-1, 2, -1), | |
| new(-2, 1, 1), | |
| new(-1, -1, 2), | |
| }; | |
| public override Vector3[] Corners { get; } = { | |
| new(0.5f * OuterRadiusNormalized, 0f, InnerRadiusNormalized), | |
| new(OuterRadiusNormalized, 0f, 0), | |
| new(0.5f * OuterRadiusNormalized, 0f, -InnerRadiusNormalized), | |
| new(-0.5f * OuterRadiusNormalized, 0f, -InnerRadiusNormalized), | |
| new(-OuterRadiusNormalized, 0f, 0), | |
| new(-0.5f * OuterRadiusNormalized, 0f, InnerRadiusNormalized), | |
| new(0.5f * OuterRadiusNormalized, 0f, InnerRadiusNormalized), | |
| }; | |
| public FlatOrientation(float size) | |
| { | |
| Size = size; | |
| SpacingX = size * 1.5f; // 3/2 * size | |
| SpacingZ = ((Mathf.Sqrt(3) / 2.0f) * (size * 2) / 2); //Mathf.Sqrt(3) * size | |
| } | |
| public override Vector2 ConvertWorldPositionToAxial(Vector3 position) | |
| { | |
| var q = (position.x * (2.0f / 3.0f)) / Size; | |
| var r = ((-position.x / 3.0f) + ((Mathf.Sqrt(3) / 3.0f) * position.z)) / Size; | |
| return new Vector2(q, r); | |
| } | |
| public override Vector3 ConvertCubeToWorldPosition(Vector3Int cube) | |
| { | |
| return new Vector3(cube.x * SpacingX, | |
| 0f, | |
| cube.x * SpacingZ + cube.z * SpacingZ * 2f); | |
| } | |
| } | |
| public class PointyOrientation : Orientation | |
| { | |
| public override Vector3Int[] Directions { get; } = | |
| { | |
| new(0, -1, 1), | |
| new(1, -1, 0), | |
| new(1, 0, -1), | |
| new(0, 1, -1), | |
| new(-1, 1, 0), | |
| new(-1, 0, 1), | |
| }; | |
| public override Vector3Int[] Diagonals { get; } = | |
| { | |
| new(1, -2, 1), | |
| new(2, -1, -1), | |
| new(1, 1, -2), | |
| new(-1, 2, -1), | |
| new(-2, 1, 1), | |
| new(-1, -1, 2), | |
| }; | |
| public override Vector3[] Corners { get; } = { | |
| new(0f, 0f, OuterRadiusNormalized), | |
| new(InnerRadiusNormalized, 0f, 0.5f * OuterRadiusNormalized), | |
| new(InnerRadiusNormalized, 0f, -0.5f * OuterRadiusNormalized), | |
| new(0f, 0f, -OuterRadiusNormalized), | |
| new(-InnerRadiusNormalized, 0f, -0.5f * OuterRadiusNormalized), | |
| new(-InnerRadiusNormalized, 0f, 0.5f * OuterRadiusNormalized), | |
| new(0f, 0f, OuterRadiusNormalized) | |
| }; | |
| public PointyOrientation(float size) | |
| { | |
| Size = size; | |
| SpacingX = ((Mathf.Sqrt(3) / 2.0f) * (size * 2) / 2); | |
| SpacingZ = size * 1.5f; | |
| } | |
| public override Vector2 ConvertWorldPositionToAxial(Vector3 position) | |
| { | |
| var q = ((-position.z / 3.0f) + ((Mathf.Sqrt(3) / 3.0f) * position.x)) / Size; | |
| var r = (position.z * (2.0f / 3.0f)) / Size; | |
| return new Vector2(q, r); | |
| } | |
| public override Vector3 ConvertCubeToWorldPosition(Vector3Int cube) | |
| { | |
| return new Vector3(cube.z * SpacingX + cube.x * SpacingX * 2f, | |
| 0f, | |
| cube.z * SpacingZ); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment