Загрузка данных
using System;
using System.Collections.Generic;
using UnityEngine;
namespace CodeBase.HexGrid
{
public static class HexGridGenerator
{
private const float SquareRootOfThree = 1.7320508f;
public static HexGridLayout GenerateInRectangle(
int hexCount,
Vector3 rectangleCenter,
float rectangleWidth,
float rectangleDepth,
float hexGap = 0f,
float rectanglePadding = 0f)
{
if (hexCount < 0)
throw new ArgumentOutOfRangeException(nameof(hexCount));
if (rectangleWidth <= 0f)
throw new ArgumentOutOfRangeException(nameof(rectangleWidth));
if (rectangleDepth <= 0f)
throw new ArgumentOutOfRangeException(nameof(rectangleDepth));
if (hexCount == 0)
{
return new HexGridLayout(
Array.Empty<HexCellPlacement>(),
0,
0,
0f);
}
float availableWidth = rectangleWidth - rectanglePadding * 2f;
float availableDepth = rectangleDepth - rectanglePadding * 2f;
if (availableWidth <= 0f || availableDepth <= 0f)
throw new ArgumentException("Padding is too large for the rectangle size.");
hexGap = Mathf.Max(0f, hexGap);
HexGridDimensions gridDimensions = CalculateGridDimensions(
hexCount,
availableWidth,
availableDepth);
float hexRadius = CalculateHexRadius(
gridDimensions.ColumnCount,
gridDimensions.RowCount,
availableWidth,
availableDepth,
hexGap);
if (hexRadius <= 0f)
throw new ArgumentException("Rectangle is too small for the requested hex gap.");
float horizontalStep = SquareRootOfThree * hexRadius + hexGap;
float depthStep = 1.5f * hexRadius + hexGap;
HexCellPlacement[] placements = new HexCellPlacement[hexCount];
float minimumX = float.PositiveInfinity;
float maximumX = float.NegativeInfinity;
float minimumZ = float.PositiveInfinity;
float maximumZ = float.NegativeInfinity;
for (int hexIndex = 0; hexIndex < hexCount; hexIndex++)
{
int rowIndex = hexIndex / gridDimensions.ColumnCount;
int columnIndex = hexIndex % gridDimensions.ColumnCount;
int remainingHexCount = hexCount - rowIndex * gridDimensions.ColumnCount;
int hexCountInCurrentRow = Mathf.Min(gridDimensions.ColumnCount, remainingHexCount);
bool isShiftedRow = rowIndex % 2 == 1;
float shiftedRowOffset = isShiftedRow ? horizontalStep * 0.5f : 0f;
float incompleteRowCenteringOffset =
(gridDimensions.ColumnCount - hexCountInCurrentRow) * horizontalStep * 0.5f;
float localX = columnIndex * horizontalStep
+ shiftedRowOffset
+ incompleteRowCenteringOffset;
float localZ = rowIndex * depthStep;
minimumX = Mathf.Min(minimumX, localX);
maximumX = Mathf.Max(maximumX, localX);
minimumZ = Mathf.Min(minimumZ, localZ);
maximumZ = Mathf.Max(maximumZ, localZ);
placements[hexIndex] = new HexCellPlacement(
hexIndex,
rowIndex,
columnIndex,
new Vector3(localX, rectangleCenter.y, localZ));
}
float generatedCenterX = (minimumX + maximumX) * 0.5f;
float generatedCenterZ = (minimumZ + maximumZ) * 0.5f;
for (int placementIndex = 0; placementIndex < placements.Length; placementIndex++)
{
HexCellPlacement placement = placements[placementIndex];
Vector3 centeredPosition = new Vector3(
rectangleCenter.x + placement.Position.x - generatedCenterX,
rectangleCenter.y,
rectangleCenter.z + placement.Position.z - generatedCenterZ);
placements[placementIndex] = new HexCellPlacement(
placement.Index,
placement.RowIndex,
placement.ColumnIndex,
centeredPosition);
}
return new HexGridLayout(
placements,
gridDimensions.ColumnCount,
gridDimensions.RowCount,
hexRadius);
}
private static HexGridDimensions CalculateGridDimensions(
int hexCount,
float availableWidth,
float availableDepth)
{
float targetAspect = Mathf.Max(1f, availableWidth / availableDepth);
int bestColumnCount = hexCount;
int bestRowCount = 1;
float bestScore = float.PositiveInfinity;
int maximumRowCount = Mathf.CeilToInt(Mathf.Sqrt(hexCount));
for (int rowCount = 1; rowCount <= maximumRowCount; rowCount++)
{
int columnCount = Mathf.CeilToInt((float)hexCount / rowCount);
if (columnCount < rowCount)
continue;
float widthCoefficient = SquareRootOfThree *
(columnCount + (rowCount > 1 ? 0.5f : 0f));
float depthCoefficient = 1.5f * rowCount + 0.5f;
float candidateAspect = widthCoefficient / depthCoefficient;
int emptyCellCount = columnCount * rowCount - hexCount;
float aspectScore = Mathf.Abs(Mathf.Log(candidateAspect / targetAspect));
float emptyCellScore = emptyCellCount * 0.025f;
float score = aspectScore + emptyCellScore;
if (score < bestScore)
{
bestScore = score;
bestColumnCount = columnCount;
bestRowCount = rowCount;
}
}
return new HexGridDimensions(bestColumnCount, bestRowCount);
}
private static float CalculateHexRadius(
int columnCount,
int rowCount,
float availableWidth,
float availableDepth,
float hexGap)
{
float shiftedRowCoefficient = rowCount > 1 ? 0.5f : 0f;
float widthRadiusCoefficient = SquareRootOfThree *
(columnCount + shiftedRowCoefficient);
float widthGapCoefficient = columnCount - 1 + shiftedRowCoefficient;
float depthRadiusCoefficient = 1.5f * rowCount + 0.5f;
float depthGapCoefficient = rowCount - 1;
float radiusByWidth =
(availableWidth - hexGap * widthGapCoefficient) / widthRadiusCoefficient;
float radiusByDepth =
(availableDepth - hexGap * depthGapCoefficient) / depthRadiusCoefficient;
return Mathf.Min(radiusByWidth, radiusByDepth);
}
}
public readonly struct HexGridLayout
{
public HexGridLayout(
IReadOnlyList<HexCellPlacement> placements,
int columnCount,
int rowCount,
float hexRadius)
{
Placements = placements;
ColumnCount = columnCount;
RowCount = rowCount;
HexRadius = hexRadius;
}
public IReadOnlyList<HexCellPlacement> Placements { get; }
public int ColumnCount { get; }
public int RowCount { get; }
public float HexRadius { get; }
}
public readonly struct HexCellPlacement
{
public HexCellPlacement(
int index,
int rowIndex,
int columnIndex,
Vector3 position)
{
Index = index;
RowIndex = rowIndex;
ColumnIndex = columnIndex;
Position = position;
}
public int Index { get; }
public int RowIndex { get; }
public int ColumnIndex { get; }
public Vector3 Position { get; }
}
internal readonly struct HexGridDimensions
{
public HexGridDimensions(int columnCount, int rowCount)
{
ColumnCount = columnCount;
RowCount = rowCount;
}
public int ColumnCount { get; }
public int RowCount { get; }
}
}
using CodeBase.HexGrid;
using UnityEngine;
public sealed class HexGridExample : MonoBehaviour
{
[SerializeField] private GameObject hexPrefab;
[SerializeField] private int hexCount = 17;
[SerializeField] private float rectangleWidth = 12f;
[SerializeField] private float rectangleDepth = 7f;
[SerializeField] private float hexGap = 0.15f;
private void Start()
{
HexGridLayout layout = HexGridGenerator.GenerateInRectangle(
hexCount,
transform.position,
rectangleWidth,
rectangleDepth,
hexGap);
foreach (HexCellPlacement placement in layout.Placements)
{
GameObject hexInstance = Instantiate(
hexPrefab,
placement.Position,
Quaternion.identity,
transform);
hexInstance.name = $"Hex_{placement.Index}_Row_{placement.RowIndex}_Column_{placement.ColumnIndex}";
}
}
}