How do we make it easier to create simple grids?
Over the last few days we have been investigating making simple grids simpler to create, especially from code. These grids – rect grids in rectangular shapes, and hex grids in hexagonal and rectangular shapes – are the ones most often used by far. And unfortunately, to support all the many shapes and ways of rendering grids, creating a grid in code is a chore, so we want to see if we can do something to make at least the most common cases simpler.
We ask ourselves two questions:
- Can we add a mechanism to create simple grids in one line of code?
- And if so, should we?
Before delving into these questions, let’s go over the way we can create grids in code now. There are two types of grids – grids that are used as data structures, and or not in the scene – and grids that are used as scene objects; logical and physical grids. In the typical scenario, you set up your grid in the scene using a grid builder, and add logic to that through grid behaviours. If you need a separate logical grid, you clone the physical grid in the new type, and fill the contents with what you need. You can also use the grid behaviour to configure the physical grid from data or a logical grid (that can be constructed from data in a file, for example).
This works fine for games where the grids are fixed-shaped. However, in many games, you need to construct grids of different shapes or sizes. In this case, you have two options.
The first is to assemble your grids from scratch. This involves building grid, and a grid map, which in turns is composed of a round map and a space map. And each of these involves a few concepts that you need to understand for everything to work, so it is somewhat complicated. It is also a far from a one-liner – typically about 10 lines are necessary. Since this will typically be only in one place in the game, I don’t consider the amount of code a problem. But the mental strain is.
The second method is to construct a “bounding” grid in the inspector. This grid should be big enough to contain all the shapes you intend to use. You then add a grid behaviour that can activate the cells in the desired shape you want. All you have to understand using this method is how shapes work – maps, mouse interaction, cell creation are all taken care of by the grid builder. This method works well when your grids occupy the same place in space so that the holding shape is not much bigger than the typical grid.
So there are three scenarios – using grid builders for fixed grids, using code to build dynamic grids from scratch, and using a grid behaviour to filter active cells of a fixed grid built from a grid builder to get dynamic grids.
Of these, only the second presents a problem (in terms of complexity); so this is the situation we wanted to address.
Now lets look at the first question – is it possible?
If we were to consider only logical grids, it is possible, and very simple. For physical grids – grids that involve objects in the scene – the situation is more complicated. There are a few reasons for this:
- Cells can be of various types – sprites, 3D objects, or part of a mesh.
- Physical grids require grid maps to handle mouse input. So we cannot simply return a grid object from a method to create a grid – we somehow need to bind the grid and map. Ideally, the grid and map needs to be wrapped in a single object.
- Grids in code are generic, while Unity components are not. As it stands, grid builders are not generic – the “conversion” happens in grid behaviours. So your grid behaviour can access typed grids because it extends from a generic grid behaviour. This makes wrapping a grid and map more complicated – should the object be generic, or should we provide a different type for each possibility? The first makes it impossible to make this type a component, so we need to handle interaction with the grid in a somewhat clumsy way.
It is complicated, but possible. The best solution requires a new type, PhysicalGrid<tpoint, tcell=””>, that holds the grid, grid map, and game object that is the root of the grid; and that allows you to add a grid event trigger. It requires 16 methods (4 shapes, object vs mesh, and XY vs XZ). The implementation of this requires many small changes to the library to hook everything up.
Then…should we add this to the library?
I am not convinced.
First, the PhysicalGrid class is awkward. It really “needs” to be a component – but it cannot. It’s not a class that is idiomatic to either Unity or the Grids library, and working with it makes code look strange.
Second, it is adding one more grid class. It’s already difficult to keep track of the concepts; how does this class fit with the other classes?
Third, it is adding 16 more methods; 16 methods that need to be tested, documented, and maintained.
Fourth, it goes against the way we intended grids to be used and the rest of the library design. Once we add it, there will be demand for more features – features that already exist through the existing mechanism, but would need to be converted to work with PhysicalGrids.
Fifth, it adds an extra choice when building a game – should you use this way, or another? There are already a lot of choices: in the scene, or in code? Using tiles, or a mesh?
We are still mulling this over in the studio. There are also some alternative design possibilities.
We could, for instance, refactor our classes so that PhysicalGrid become the core component of grids in scenes, regardless of whether they have been built with a builder or from code. Like grid builders, they will not be generic, and you will need to add behaviour in GridBehaviours and attach these programmatically. This would be a very big and complicated change – and a breaking one at that.
Another would be to provide methods for creating logical grids, and some utility methods to build physical grids with objects or meshes.
So far, there are two takeaways from this investigation.
- Most of the complexity of the library lies in grid creation. This has also been the case with Grids 1, although there the complexity in the rest of the library and its usage was increased by the separation of different grid types (such as rect and hex grids).
- It seems impossible to reduce the complexity without reducing the features. We would not need the concept of “maps”, “shapes”, and “grid points” if we only wanted to represent simple cases. If you make one thing simpler, another becomes more complicated or impossible. There are no silver bullets.
Our investigation continues; let us know if you have any ideas!