Unity — Drawing Custom Debug Shapes — Part 8
Draw Debug Cubes
Like the previous tutorial (Unity — Draw Debug Spheres), this one continues expanding our understanding of drawing 3D debug shapes. This tutorial will cover how to draw debug cubes and rectangular cuboids using a few different approaches, while still keeping the code clean and easy to understand. Cubes are often seen in, and associated with, games and 3D graphics, so it only makes sense to have a way of debug-drawing them during the development of a game. Typically, cubes and rectangular cuboids (boxes) are used to describe certain volumes of influence or importance.
Drawing a Cube
Cube is a regular hexahedron (a polyhedron with six faces), whose faces are squares. By definition, like any cuboid, a cube is defined by three pairs of parallel faces. Those faces share certain edges, and therefore for drawing a debug cube, we can do a slight optimization, and instead of drawing 6 individual squares, we can draw two (ABCD and EFGH), and connect them with four lines/edges (AE, BF, CG, DH) as shown in the image below:
Since we have already covered how to draw a rectangle/square in one of the previous tutorials, we can start drawing a box by calling the DrawRectangle method twice, with an offset along the z-axis. This offset should be added to the origin of the rectangle. If we label the length of one of edges as L, we can say that the blue rectangle is -L/2 units away from the origin along the z-axis, and that the red rectangle is L/2 units away along the z-axis from the graph’s origin. In Unity, to offset something along the z axis, we can use the built-in Vector3.forward vector. Since DrawRectangle method requires a rotation parameter, for now we can simply use Quaternion.identity. In the later steps, like with the previous tutorials, we will introduce the orientation.
public static void DrawCube(Vector3 position, float size)
{
Vector3 offsetZ = Vector3.forward * size * 0.5f;
DrawRectangle(position - offsetZ, Quaternion.identity, Vector2.one * size, Color.red);
DrawRectangle(position + offsetZ, Quaternion.identity, Vector2.one * size, Color.blue);
}
By calling this function in our Example class Update method, we get the following (for clarity, a tiny debug sphere is added at the origin):
void Update()
{
Debug.DrawCube(transform.position, 1.0f);
Debug.DrawSphere(transform.position, transform.rotation, 0.05f, Color.green, 4);
}
Next, we need to connect the two rectangles, as shown in the first image. To do so, we need to calculate where the points A-H are located, and use Debug.DrawLine method to draw lines between them. To do so, first we need to add two more offset values, offset along the x-axis and offset along the y-axis. Using different combinations of those offsets we can get the points A-H. Since the points of one pair (for example, A and E) only differ in the z-axis offset, we can shorten the code by defining only the initial four points (A-D) with z-axis set to zero, and offsetting them while drawing (calling the Debug.DrawLine method):
public static void DrawCube(Vector3 position, float size)
{
Vector3 offsetX = Vector3.right * size * 0.5f;
Vector3 offsetY = Vector3.up * size * 0.5f;
Vector3 offsetZ = Vector3.forward * size * 0.5f;
Vector3 pointA = -offsetX + offsetY;
Vector3 pointB = offsetX + offsetY;
Vector3 pointC = offsetX - offsetY;
Vector3 pointD = -offsetX - offsetY;
DrawRectangle(position - offsetZ, Quaternion.identity, Vector2.one * size, Color.red);
DrawRectangle(position + offsetZ, Quaternion.identity, Vector2.one * size, Color.blue);
DrawLine(pointA - offsetZ, pointA + offsetZ, Color.green);
DrawLine(pointB - offsetZ, pointB + offsetZ, Color.green);
DrawLine(pointC - offsetZ, pointC + offsetZ, Color.green);
DrawLine(pointD - offsetZ, pointD + offsetZ, Color.green);
}
This extension gives us the following result:
Like with the previous tutorials, we can introduce the orientation. Specifics on how vector rotation using quaternions is handled is already explained in more detail in Draw Debug Circles — Part 2 tutorial, so I would suggest you to check it if you want to gain a bit deeper understanding. To avoid repetition, we can simply utilize it in our DrawCube method. Also, we can already introduce the color parameter to our method, so that the cube is drawn using a specific color:
public static void DrawCube(Vector3 position, Quaternion orientation, float size, Color color)
{
Vector3 offsetX = orientation * Vector3.right * size * 0.5f;
Vector3 offsetY = orientation * Vector3.up * size * 0.5f;
Vector3 offsetZ = orientation * Vector3.forward * size * 0.5f;
Vector3 pointA = -offsetX + offsetY;
Vector3 pointB = offsetX + offsetY;
Vector3 pointC = offsetX - offsetY;
Vector3 pointD = -offsetX - offsetY;
DrawRectangle(position - offsetZ, orientation, Vector2.one * size, color);
DrawRectangle(position + offsetZ, orientation, Vector2.one * size, color);
DrawLine(pointA - offsetZ, pointA + offsetZ, color);
DrawLine(pointB - offsetZ, pointB + offsetZ, color);
DrawLine(pointC - offsetZ, pointC + offsetZ, color);
DrawLine(pointD - offsetZ, pointD + offsetZ, color);
}
We can call this updated version of the DrawCube method in our Example class’ Update method:
void Update()
{
Debug.DrawCube(transform.position, transform.rotation, 1.0f, Color.cyan);
}
Drawing a Rectangular Cuboid
To draw a rectangular cuboid (for simplicity, I will continue by using the term “box” instead), we simply need to extend the DrawCube method by introducing the size as a 3D vector instead of a scalar. To do so, we can create a copy of the method, and change its signature as shown below. Each offset needs to be multiplied with corresponding dimension of the new size vector, and also, two rectangles need to be drawn with the x and y dimensions of the new size vector instead of using Vector2.one multiplied by the size:
public static void DrawBox(Vector3 position, Quaternion orientation, Vector3 size, Color color)
{
Vector3 offsetX = orientation * Vector3.right * size.x * 0.5f;
Vector3 offsetY = orientation * Vector3.up * size.y * 0.5f;
Vector3 offsetZ = orientation * Vector3.forward * size.z * 0.5f;
Vector3 pointA = -offsetX + offsetY;
Vector3 pointB = offsetX + offsetY;
Vector3 pointC = offsetX - offsetY;
Vector3 pointD = -offsetX - offsetY;
DrawRectangle(position - offsetZ, orientation, new Vector2(size.x, size.y), color);
DrawRectangle(position + offsetZ, orientation, new Vector2(size.x, size.y), color);
DrawLine(pointA - offsetZ, pointA + offsetZ, color);
DrawLine(pointB - offsetZ, pointB + offsetZ, color);
DrawLine(pointC - offsetZ, pointC + offsetZ, color);
DrawLine(pointD - offsetZ, pointD + offsetZ, color);
}
By calling this function in our Example class Update method, with size vector set to [3, 2, 1] we get the following result:
void Update()
{
Debug.DrawBox(transform.position, transform.rotation, new Vector3(3.0f, 2.0f, 1.0f), Color.cyan);
}
Wrap-up
To finalize this tutorial, we can do one simple cleanup of the code (to, again enforce the DRY principle). Namely, since the cube is technically a rectangular cuboid with equally sized sides, we can simply call the DrawBox method from DrawCube method and pass the size as a unit-sized vector (Vector3.one) multiplied by the size parameter:
public static void DrawCube(Vector3 position, Quaternion orientation, float size, Color color)
{
DrawBox(position, orientation, Vector3.one * size, color);
}
Calling both DrawBox (using the size [3, 2, 1]) and DrawCube (using the size 0.5) in our Example class, we get the following result:
void Update()
{
Debug.DrawBox(transform.position, transform.rotation, new Vector3(3.0f, 2.0f, 1.0f), Color.cyan);
Debug.DrawCube(transform.position, transform.rotation, 0.5f, Color.magenta);
}
With this implemented, we have concluded this tutorial.
Please leave a comment if you have any questions or suggestions, or if you have some topic that you would like to to cover in the future.
In the meanwhile, you can also reach me on LinkedIn.
The following tutorial cover how to draw debug cylinders and capsules, which are commonly used to visualize characters and their colliders in game development project, as well as AI agents used in 3D space, and each of them can be extremely useful and common in game development.