Unity — Drawing Custom Debug Shapes — Part 1
Overview and Draw Debug Circles — Basics
During the development of multiple projects in which I was involved, built-in drawing methods of the Unity’s Debug class proved to be a bit limiting.
While there is a plethora of Gizmos that can be used, sometimes being able to draw shapes at any point of execution was more useful. In this tutorial I will show you how to create those shapes using mainly Debug.DrawLine and Debug.DrawRay methods, and some math dark magic. This tutorial covers the following:
By combination of multiple of the shapes listed above, it is also possible to draw the following 3D shapes:
While developing a game, drawing a debug circle can be immensely helpful. Inspired by DrawDebugHelpers class of Unreal Engine, I have decided to extend (override) Unity’s Debug class to make that possible.
How To Draw A Circle
Drawing a circle can be done using some basic trigonometry operations. Instead of a “perfect” circle we will draw a polygon with an adjustable number of edges/segments which gives us “good enough” approximation of the said circle. This approach is also useful since the same method can be used to draw any regular polygon (triangle, quad, pentagon, hexagon, etc.).
While this method might not be the most performant way of doing it, it will still provide you with a toolset that can potentially make your development and debugging process much more convenient.
Main Ingredients: Sine and Cosine
Although the following part can be considered as least exciting by some, I would urge you to stick around since sine and cosine functions are cornerstones of this tutorial. As a basic example, let’s start with a circle defined in a XY coordinate space, with a radius 1.0 and with four points (A, B, C, D) marked on it. Additionally, we can check an angle of vectors connecting circle’s origin with each of the points, as well as a right-angle triangle formed by circle’s origin (O), A and A’ (projection of the point A on the X-axis):
Given the triangle (O, A, A’) it is possible to determine that the segment (A, A’) can be considered as “opposite” to the angle of the vector (O, A) and segment (O, A’) can be considered as “adjacent”.
Also, length of the hypothenuse of this triangle (O,A) is equal to the radius of the circle, which is in this case 1.0.
To calculate the length of “opposite” segment, we can use the equation:
sin(α)=opposite/hypothenuse
Since hypothenuse of the triangle is equal to 1.0, we can simply rearrange the equation as:
opposite=sin(α)
Same applies for “adjacent”, where the following equation is true:
cos(α)=adjacent/hypothenuse
and by using the same analogy, we get:
adjacent=cos(α)
Length of the “adjacent” side can be viewed as X coordinate of the point A, and length of the “opposite” side can be viewed as Y coordinate of the point A.
Using this relationship, each of the given points can be defined in (X, Y) format as follows:
A=(cos(45°),sin(45°))
B=(cos(135°),sin(135°))
C=(cos(225°),sin(225°))
D=(cos(315°),sin(315°))
This property gives us a possibility to find coordinates of any point on the circle, as long as we know the angle of the vector between the origin of the circle and the said point. Based on that, it is possible to define, for example, eight points on the circle, and by connecting them receive an inscribed octagon which is an approximation of the said circle:
As you can assume, these points could also be defined inside of the Unity project, and connected using the Debug.DrawLine function.
Extending Unity’s Debug Class
To be able to draw a circle conveniently, we will create one additional Debug Draw method. To make future usage of that method as streamlined as possible, it is necessary to override/extend Unity’s Debug class. To do so, first create a new C# class and name it Debug.cs (personally I prefer to store them in Assets/Scripts folder, but for this example it is not crucial).
Inside of that file add the following code:
using UnityEngine;
class Debug : UnityEngine.Debug
{
// UnityEngine class extensions
}
This new Debug class will be used as a base for all following extensions and tutorials.
As discussed in previous chapter, circle approximation can be described using the following properties:
- Center/origin
- Radius
- Number of segments
These properties need to be passed to the drawing function. Additionally, to make things prettier (and more useful), we can also pass desired drawing color. Therefore, a signature of the most basic version of the circle drawing method can look like this:
public static class DrawCircle(Vector3 position, float radius, float segments, Color color)
For the most basic implementation of this function, circle can be drawn in the XY plane (following tutorials will cover how to draw circles on different planes, as well as how to handle rotation).
To draw a circle, the method needs perform following steps:
- Check if radius is greater than zero,
- Check if number of segments is greater than zero,
- Calculate how many degrees each segment covers,
- For each segment, calculate start and end points,
- Offset those points by the location of origin/center,
- Connect those points using built-in Debug.DrawLine method, and using the passed color.
public static void DrawCircle(Vector3 position, float radius, int segments, Color color)
{
// If either radius or number of segments are less or equal to 0, skip drawing
if (radius <= 0.0f || segments <= 0)
{
return;
}
// Single segment of the circle covers (360 / number of segments) degrees
float angleStep = (360.0f / segments);
// Result is multiplied by Mathf.Deg2Rad constant which transforms degrees to radians
// which are required by Unity's Mathf class trigonometry methods
angleStep *= Mathf.Deg2Rad;
// lineStart and lineEnd variables are declared outside of the following for loop
Vector3 lineStart = Vector3.zero;
Vector3 lineEnd = Vector3.zero;
for (int i = 0; i < segments; i++)
{
// Line start is defined as starting angle of the current segment (i)
lineStart.x = Mathf.Cos(angleStep * i) ;
lineStart.y = Mathf.Sin(angleStep * i);
// Line end is defined by the angle of the next segment (i+1)
lineEnd.x = Mathf.Cos(angleStep * (i + 1));
lineEnd.y = Mathf.Sin(angleStep * (i + 1));
// Results are multiplied so they match the desired radius
lineStart *= radius;
lineEnd *= radius;
// Results are offset by the desired position/origin
lineStart += position;
lineEnd += position;
// Points are connected using DrawLine method and using the passed color
DrawLine(lineStart, lineEnd, color);
}
}
To test the functionality of this method, we can create a simple example MonoBehavior class. To do so, create a new C# file named Example.cs, empty and an empty GameObject named Example, and place the newly created C# script on it. Also, reset its transform to make sure it is located at (0,0,0).
Inside of the Example.cs script change the Update method to following:
void Update()
{
// Draw circles at the position of the GameObject, with different radii, number of segments and colors
Debug.DrawCircle(transform.position, 2.0f, 32, Color.red);
Debug.DrawCircle(transform.position, 3.0f, 16, Color.yellow);
Debug.DrawCircle(transform.position, 4.0f, 8, Color.green);
// Draw circles with a certain offset from the position of the GameObject
Debug.DrawCircle(transform.position + new Vector3(-5.0f, -5.0f, 0), 0.5f, 16, Color.white);
Debug.DrawCircle(transform.position + new Vector3(5.0f, 5.0f, 0), 0.5f, 16, Color.white);
Debug.DrawCircle(transform.position + new Vector3(-5.0f, 5.0f, 0), 0.5f, 16, Color.white);
Debug.DrawCircle(transform.position + new Vector3(5.0f, -5.0f, 0), 0.5f, 16, Color.white);
}
As you may notice, transform.position of this GameObject is used as the origin of the circles. If everything was done correctly, after starting the game, you should receive similar results as shown below (please not that the location of the MainCamera is (0, 0, -10)):
With this, we have covered basics of drawing Debug Circles in Unity. Thank you for your time and attention!
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 will cover how to implement custom rotation of the circles, so they can be drawn in any plane.