Layered Grids

We have used grids in 3D space in a variety of ways before. Mostly, these come down to mapping a grid in 3D space – often on the surface of a 3D shape – and using 3D objects instead of sprites. This example is a true volumetric grid, representing layers of grids stacked on top of each other.

There are of course many ways to implement this type of grid. The simplest way would be to have an array of grids, and an array of maps the map grids to different heights. Accessing the grid would require the layer index, as well as the 2D grid point. Of course, you can wrap this functionality in proper interfaces, and take advantage of all the Grid algorithms, and this is exactly what we have done here.

There are three components: a LayeredPoint, LayeredGrid, and SimpleLayeredMap. LayeredPoint is simply a struct that holds some other grid point, and a layer index. LayeredGrid is an array of grids, as described above. It provides factor methods that allows you to construct a layered grid of any type of other grid (hex, cairo, rect!) SimpleLayeredMap does the usual conversion between grid points and world points.

The code for setting up a grid and handling cell clicks is similar to the usual logic.

public class LayerTest : GLMonoBehaviour
{
   public GameObject cellPrefab;

   private LayeredGrid grid;
   private SimpleLayeredMap map;

   public void Start()
   {
      map = new SimpleLayeredMap(
	     new PointyHexMap(new Vector2(69, 80)*5f), 200, 0);

      var shapes = new []
      {
         PointyHexGrid.BeginShape().Hexagon(6),
         PointyHexGrid.BeginShape().Hexagon(5),
         PointyHexGrid.BeginShape().Hexagon(4),
         PointyHexGrid.BeginShape().Hexagon(3),
         PointyHexGrid.BeginShape().Hexagon(2),
         PointyHexGrid.BeginShape().Hexagon(1)
      };

      grid = LayeredGrid.Make<
         PointyHexShapeInfo, 
         PointyHexGrid, 
         PointyHexPoint, PointyHexPoint, PointyHexOp>(
		    shapes);

      foreach (LayeredPoint point in grid)
      {
         var cell = Instantiate(cellPrefab);

         cell.transform.parent = transform;
         cell.transform.localPosition = map[point];
         
         var color = ExampleUtils.colors[point.point.GetColor2_4() + 4];
         cell.renderer.material.color = color;

         grid[point] = cell;
      }
   }

   public void Update()
   {
      if (Input.GetMouseButtonDown(0))
      {
         var mousePosition = Input.mousePosition;
         var ray = Camera.main.ScreenPointToRay(mousePosition);

         RaycastHit hitInfo;

         if (Physics.Raycast(ray, out hitInfo))
         {
            var worldPoint = hitInfo.point;
            var gridPoint = map[worldPoint];

            if (grid.Contains(gridPoint))
            {
               grid[gridPoint].renderer.material.color = 
			      ExampleUtils.colors[7];
            }
         }
      }
   }
}

The one thing that is a bit scary is all the type arguments you have to pass to the Make function (figuring out what they should be was quite a challenge!). Fortunately, the need for such complicated type specifications is a rare one, and I think we can make it simpler by introducing another interface (whether we should is a different story; an extra interface just moves the complexity around). Another flaw that is exposed by this example is how the fluent shape building interface can become less intuitive. It’s nice that you can use the shape building tech to build each layer, but the fact that you have BeginShape without corresponding EndShape calls is a bit weird. (The EndShape method is in fact called internally when the grid is actually constructed.) One solution would be to add methods for layered shapes, so that you can write something like the following:

LayeredGrid
   .BeginShape()
      .BeginLayer()
         .Hexagon(1)
      .EndLayer()
      //...
      //etc.
   .EndShape();

Implementing that would be quite complex, and I am not sure it would be worth the effort. But if you use this kind of grid, let us know!

Here is a package containing all the code:

LayeredGrid.unitypackage

Import it into a new project, and import Grids.To use test the example, make a new scene, and add the LayerTest script to onto an empty GameObject. Drag any 3D object into the “cellPrefab” slot. For it to work, the object must have a Renderer attached. The image above was made with spheres rendered with a specular material.

Scroll to Top