Unity/C# - Grids - Axis, Transformations and Drawing


Continuing the previous post on grids, let's move from defining grid to drawing them.

As mentioned before, Unity3D uses a continuous 3D world, so for that, we made a function to convert a coordinate from 3D-float space (world-space) to 2D-int space (grid-space). Now, in order to draw the grid, we need to make the conversion back. The conversion back is pretty straight forward since we are using the Round function to convert from float to int.

    public static Vector3 GridToWorld(Vector2Int gridPosition)
    {
        return new Vector3(gridPosition.x * DISCRETE_SIZE, gridPosition.y * DISCRETE_SIZE);
    }


This convert an X, Y grid coordinate to X, Y, Z. In this case, the Z is always 0 since our grid does not have a 3rd dimension. To easily show some visual in Unity3D we are going to make a class named GridSegmentView.

public class GridSegmentView : MonoBehaviour
{
    public RectInt gridSegment = new RectInt(0, 0, 10, 10);
    public GridSegmentGraph gridSegmentGraph;

    public void Start()
    {
        gridSegmentGraph = new GridSegmentGraph(gridSegment, Grid.VonNeumannNeighborhood);
    }

    public void OnDrawGizmos()
    {
        if(gridSegmentGraph != null)
        {
            foreach (var graphVertex in gridSegmentGraph)
            {
                Gizmos.DrawWireCube(Grid.GridToWorld(graphVertex), Vector3.one * Grid.DISCRETE_SIZE);
            }
        }
        else
        {
            foreach (var gridPoint in gridSegment.Enumerate())
            {
                Gizmos.DrawWireCube(Grid.GridToWorld(gridPoint), Vector3.one * Grid.DISCRETE_SIZE);
            }
        }
    }
}

When you hit the Play button in the editor, it is going to create an instance of the graph and draw some gizmos for it. In case there is no graph created yet, we just iterate through each point in the grid segment and draw it.


Grid gizmos.

Okay, so our grid being in 2 dimensions doesn't mean that the axis in grid space needs to be the same as world space. If they are the same we end up with something like a top-down view. If we make the world X, Y as a composition of grid X, Y we can end up with something like an isometric view.

Now, in this post, I'm going to talk a lot about the transformation from grid-space to world-space and vice versa. These kind of transformation are not something new, they are studied in Linear Algebra ever since a long, long time ago. At some point, someone found that in order to transform from one space to another, we can just multiply the points by a matrix, and the inverse of that matrix is the inverse transformation (assuming it is invertible).

public static Matrix4x4 GridToWorldXYMatrix => new Matrix4x4(
        new Vector4(1, 0, 0, 0)*DISCRETE_SIZE,
        new Vector4(0, 1, 0, 0)*DISCRETE_SIZE,
        new Vector4(0, 0, 0, 0),
        new Vector4(0, 0, 0, 0));

If you don't know much about transformation matrices, let me just say that each of these vectors is a column of the matrix. When multiplying the vector by a matrix, the X coordinate of the vector will be multiplied by the first column, the Y coordinate will be multiplied by the 2nd column, and so on. So the previous matrix transform grid X*DISCRETE_SIZE to world X and grid Y*DISCRETE_SIZE to world Y. If you want the grid Y to be the world Z you just need to change the matrix to:

public static Matrix4x4 GridToWorldXZMatrix => new Matrix4x4(
        new Vector4(1, 0, 0, 0)*DISCRETE_SIZE,
        new Vector4(0, 0, 1, 0)*DISCRETE_SIZE,
        new Vector4(0, 0, 0, 0),
        new Vector4(0, 0, 0, 0));

In some games, it is also common to have a kind-of-isometric (dimetric) grid where a grid cell width is twice its height. A transformation matrix to that space would be:

public static Matrix4x4 GridToWorldMatrix => new Matrix4x4(
    new Vector4(0.8944276f, 0.4472128f, 0, 0)* DISCRETE_SIZE,
    new Vector4(-0.8944276f, 0.4472128f, 0, 0) * DISCRETE_SIZE,
    new Vector4(0, 0, 0, 0),
    new Vector4(0, 0, 0, 0));

Now, since we are using a matrix, we need to change our view to consider it.

    public void OnDrawGizmos()
    {
        var oldMatrix = Gizmos.matrix;
        Gizmos.matrix = Grid.GridToWorldMatrix;
        if (gridSegmentGraph != null)
        {
            foreach (var graphVertex in gridSegmentGraph)
            {
                Gizmos.DrawWireCube((Vector2)graphVertex, Vector3.one);
            }
        }
        else
        {
            foreach (var gridPoint in gridSegment.Enumerate())
            {
                Gizmos.DrawWireCube((Vector2)gridPoint, Vector3.one);
            }
        }
        Gizmos.matrix = oldMatrix;
    }

This will make every point in the Gizmos function be multiplied by the matrix before being drawn. So since the conversion from grid space to world space will be done by the matrix we just need to pass the parameters in grid space to the Gizmos functions. This works with any of the previous matrices.

Isometric grid (2:1)

Unity3D has a built-in class for grids (named Grid) that is easily configurable. I find that the downside is that it is not segmented and the documentation is not very clear about how it works. So I hope this makes clear to someone how to make something similar.

Comments

Popular posts from this blog

Unity3D/C# - Asynchronous Programming - Coroutines vs await/async

C# - Simple Graph Interfaces and a MeshGraph

Unity/C# - Grids - Simple Grid Segment struct