hexagon_core
Hexagonal grid math in pure Dart: coordinates, pixel layouts, map shapes, pathfinding, movement range and field of view.
No Flutter dependency, so the same code runs in a Flutter app, a Flame game,
a command-line tool or a game server. For widgets, use
hexagon, which re-exports this package.
The math follows Red Blob Games' Hexagonal Grids guide.
Coordinates
Hex stores axial coordinates (q, r); the third cube coordinate s is
derived.
const a = Hex(2, -1);
const b = Hex(-1, 3);
a.distanceTo(b); // 4
a.neighbors; // the 6 adjacent hexes
a + Hex.directions[0]; // one step in direction 0
a.rotateRight(); // 60° clockwise around the origin
a.rotateAround(b, 2); // 120° clockwise around b
a.lineTo(b); // hexes on a straight line, both ends included
a.ring(2); // hexes exactly 2 steps away
a.spiral(2); // a, then its rings 1 and 2
Convert to and from rectangular "column, row" systems:
final hex = Hex.fromOffset(3, 4, HexagonType.flat); // odd-q
final (:column, :row) = hex.toOffset(HexagonType.flat);
Hex.fromOffset(3, 4, HexagonType.pointy, parity: OffsetParity.even);
Hex.fromDoubled(4, 2, HexagonType.pointy);
Pixels
HexLayout maps hexes to pixel positions and back, for drawing and hit
testing:
const layout = HexLayout.pointy(radius: 24, spacing: 2);
layout.hexToPixel(const Hex(1, 2)); // center of the hex
layout.pixelToHex(const PixelPoint(130, 75)); // hex under a point
layout.corners(const Hex(1, 2)); // its 6 corners
layout.boundsOf(cells); // bounding box of a map
layout.hexesInRect(viewport); // hexes visible in a rect
Maps
HexShape builds common map shapes, and HexMap stores a value per cell:
final board = HexShape.hexagon(4); // 61 hexes
final field = HexShape.rectangle(20, 12); // offset rectangle
final terrain = HexMap<Terrain>.fromCells(field, generateTerrain);
terrain.neighborsOf(const Hex(3, 3)); // neighbors in the map
Triangles point up (resting on their base), down, left or right, for both hexagon types. Where the base runs along the hexagons' flat sides the triangle is exact; otherwise its sides are stepped.
// Pointy hexagons, pointing up, sitting on a flat bottom row.
HexShape.triangle(5, type: HexagonType.pointy);
// Flat hexagons pointing right, centered on a hex, only the edges.
HexShape.triangle(
5,
type: HexagonType.flat,
pointing: HexTrianglePointing.right,
origin: const Hex(10, 4),
centered: true,
hollow: true,
);
// Works with any shape.
HexShape.outline(HexShape.rectangle(8, 6)); // just the border cells
HexShape.centerOn(HexShape.parallelogram(4, 3), const Hex(5, 5));
Pathfinding and visibility
bool passable(Hex hex) => terrain[hex]?.walkable ?? false;
double? cost(Hex from, Hex to) => terrain[to]?.movementCost;
// Every hex reachable with 5 movement points, with its cost.
final range = unit.reachable(movement: 5, passable: passable, cost: cost);
// Cheapest path (A*), or null if there is none.
final path = unit.pathTo(target, passable: passable, cost: cost);
// Line of sight and field of view.
unit.canSee(target, blocksSight: (hex) => terrain[hex]!.isWall);
unit.fieldOfView(8, blocksSight: (hex) => terrain[hex]!.isWall);
Searches on an unbounded plane are limited by movement and maxVisited;
pass passable to keep them inside your map.
Performance
benchmark/hexagon_core_benchmark.dart times the operations games call
every frame or turn on a 200 × 200 map. Run it with:
dart run benchmark/hexagon_core_benchmark.dart
Libraries
- hexagon_core
- Hexagonal grid math in pure Dart.