Development Archives - dev-tut.com https://dev-tut.com/tag/development/ Bite-sized software development tutorials Sat, 16 Apr 2022 14:55:27 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.3 https://i0.wp.com/dev-tut.com/wp-content/uploads/2022/01/cropped-fav.png?fit=32%2C32&ssl=1 Development Archives - dev-tut.com https://dev-tut.com/tag/development/ 32 32 201541480 Unity – Draw A Debug Cylinder and Capsule https://dev-tut.com/2022/unity-draw-a-debug-cylinder-and-capsule/ Sat, 16 Apr 2022 14:53:23 +0000 https://dev-tut.com/?p=849 This tutorial is the last part of the Unity – Draw Custom Debug Shapes series. In this part we will cover two more shapes that are commonly used for debugging in game development projects. Since some of the concepts are already explained in the previous parts, I suggest you to go over those as well. […]

The post Unity – Draw A Debug Cylinder and Capsule appeared first on dev-tut.com.

]]>
This tutorial is the last part of the Unity – Draw Custom Debug Shapes series. In this part we will cover two more shapes that are commonly used for debugging in game development projects. Since some of the concepts are already explained in the previous parts, I suggest you to go over those as well.

Cylinder

Cylinder can be defined as a prism with circle as it’s base. In their most basic form cylinders can be defined by the radius of their base and their height. In the following image you can see a cylinder defined by two circles (base circle originating in point A, and top circle originating in point B), with radius r and height AB.

In the context of drawing a debug cylinder shape we can replace the tube connecting two circles with multiple lines. Traditionally this is done using four lines connecting points at 0°, 90°, 180° and 270° along each circle:

In the context of game development, position of the circles can be defined in two way:

a) Base circle is positioned at the desired location, and a top circle is positioned height units away, parallel to the base circle, or

b) Both base and top circles are 1/2 height units away from the desired location (center) but in the opposite directions.

Traditionally, in a context of game development, cylinders are used to visualize location and orientation of AI agents, but, of course they can be used in any number of ways.

Drawing a cylinder

To draw a debug cylinder for its base we need to do the following steps:

  • Draw a base circle,
  • Draw a top circle parallel to the base circle and height units away along the Y-axis,
  • Define connecting points along the top and base circle and draw lines between the parallel pairs (A to A’ etc.), perpendicular to both circles.

Drawing a circle is covered in detail through a four-part tutorial series, so if you haven’t seen it so far I strongly suggest visiting those first.

As a first step we need to define which parameters define a cylinder, as those will need to be passed by the user of the drawing method. Those are:

  • position,
  • orientation,
  • height,
  • radius and
  • drawing color

Additionally, we need one more parameter, namely drawFromBase boolean, which we can set to default to true (of course this can be changed to fit the needs of the project in which the method would be used).

Therefore our DrawCylinder method’s signature will look like this:

public static void DrawCylinder(Vector3 position, Quaternion orientation, float height, float radius, Color color, bool drawFromBase = true)

As a first step, we can draw the two parallel circles with the given radius, offset and color. Since the circles need to be draw in the XY plane, we need to pass that information to our DrawCircle method as orientation. For now we can define that orientation using Euler angles (90°, 0, 0).

public static void DrawCylinder(Vector3 position, Quaternion orientation, float height, float radius, Color color, bool drawFromBase = true)
{
    Vector3 basePosition = position;
    Vector3 topPosition = basePosition + Vector3.up * height;
        
    Quaternion circleOrientation = Quaternion.Euler(90, 0, 0);

    DrawCircle(basePosition, circleOrientation, radius, color, 32);
    DrawCircle(topPosition, circleOrientation, radius, color, 32);
}

While copying position parameter in an additional variable might seem wasteful at the moment, it is just a groundwork for the following steps, when we get to drawing a cylinder from a central position. In any case, calling this method from our Example.cs Update method (covered in Part 1 of the Draw a Debug Circle tutorial), we get the following:

void Update()
{
    Debug.DrawCylinder(transform.position, transform.rotation, 2.0f, 0.5f, Color.green, true);
}

Next, we need to define connecting points and lines (in this case rays) originating at those points. Based on the image below, we can define these points as following (r = circle radius):

A = [r, 0, 0]
B =  [0, 0, r]
C = [-r, 0, 0]
D = [0, 0, -r]

Unity already provides us with predefined vectors Vector3.forward and Vector3.right, so we can use those as well (it will also come handy when we introduce orientation parameter). In that sense, these points can be defined as following:

Vector3 pointA = basePosition + Vector3.right * radius;
Vector3 pointB = basePosition + Vector3.forward * radius;
Vector3 pointC = basePosition - Vector3.right * radius;
Vector3 pointD = basePosition - Vector3.forward * radius;

From those points, we can draw rays towards the top circle with the length equal to the cylinder’s height:

public static void DrawCylinder(Vector3 position, Quaternion orientation, float height, float radius, Color color, bool drawFromBase = true)
{
    Vector3 basePosition = position;
    Vector3 topPosition = basePosition + Vector3.up * height;
        
    Quaternion circleOrientation = Quaternion.Euler(90, 0, 0);

    Vector3 pointA = basePosition + Vector3.right * radius;
    Vector3 pointB = basePosition + Vector3.forward * radius;
    Vector3 pointC = basePosition - Vector3.right * radius;
    Vector3 pointD = basePosition - Vector3.forward * radius;

    DrawRay(pointA, Vector3.up * height, color);
    DrawRay(pointB, Vector3.up * height, color);
    DrawRay(pointC, Vector3.up * height, color);
    DrawRay(pointD, Vector3.up * height, color);

    DrawCircle(basePosition, circleOrientation, radius, color, 32);
    DrawCircle(topPosition, circleOrientation, radius, color, 32);
}

By calling this updated version of DrawCylinder method in our Example.cs Update, we get the following result (camera has been offset for better visibility):

The next factor we need to introduce is orientation. This topic is covered in detail in Draw A Circle – Part 3 tutorial, so we will just leverage those concepts to modify our directional vectors and use those modified versions for drawing the cylinder. What we need to do is multiply the orientation quaternion with each direction vector (right, forward and up) to get the local-space versions and simply pass them to the drawing methods. Also, base orientation needs to be multiplied with the provided orientation parameter.

public static void DrawCylinder(Vector3 position, Quaternion orientation, float height, float radius, Color color, bool drawFromBase = true)
{
    Vector3 localUp = orientation * Vector3.up;
    Vector3 localRight = orientation * Vector3.right;
    Vector3 localForward = orientation * Vector3.forward;

    Vector3 basePosition = position;
    Vector3 topPosition = basePosition + localUp * height;
        
    Quaternion circleOrientation = orientation * Quaternion.Euler(90, 0, 0);

    Vector3 pointA = basePosition + localRight * radius;
    Vector3 pointB = basePosition + localForward * radius;
    Vector3 pointC = basePosition - localRight * radius;
    Vector3 pointD = basePosition - localForward * radius;

    DrawRay(pointA, localUp * height, color);
    DrawRay(pointB, localUp * height, color);
    DrawRay(pointC, localUp * height, color);
    DrawRay(pointD, localUp * height, color);

    DrawCircle(basePosition, circleOrientation, radius, color, 32);
    DrawCircle(topPosition, circleOrientation, radius, color, 32);
}

In the following image you can see a few cylinders drawing using arbitrary orientations and positions (cylinders have the same size, but they appear different due to the camera settings):

The last step we need to do is to introduce the bDrawFromBase flag. To make it clearer, the following image shows two cylinders, one drawn from its base (green), and the second one drawn from its center (red). This position is also visualized using a tiny sphere:

To implement this, the only thing we need to modify is basePosition (hence the copy of the passed position). To do this in a clean manner, we can use ternary or conditional operator (? 🙂. Ternary operator evaluates a condition and returns the result of one of the two expressions that follow:

x = condition ? expressionA : expressionB

In this case, if the condition is met, result of expressionA will be assigned to x. Otherwise, if the condition is not met, result of the expressionB will be assigned.

In our example, we can use ternary operator to modify base position using the bDrawFromBase parameter as a condition. If the bDrawFrom base is true, offset will be zero, and if it is false (meaning that we want to draw from a center), the half-height units offset along the local up-axis will be added:

Vector3 basePositionOffset = drawFromBase ? Vector3.zero : (localUp * height * 0.5f);
Vector3 basePosition = position - basePositionOffset;

Below you can see the final version of our DrawCylinderMethod:


public static void DrawCylinder(Vector3 position, Quaternion orientation, float height, float radius, Color color, bool drawFromBase = true)
{
    Vector3 localUp = orientation * Vector3.up;
    Vector3 localRight = orientation * Vector3.right;
    Vector3 localForward = orientation * Vector3.forward;

    Vector3 basePositionOffset = drawFromBase ? Vector3.zero : (localUp * height * 0.5f);
    Vector3 basePosition = position - basePositionOffset;
    Vector3 topPosition = basePosition + localUp * height;
        
    Quaternion circleOrientation = orientation * Quaternion.Euler(90, 0, 0);

    Vector3 pointA = basePosition + localRight * radius;
    Vector3 pointB = basePosition + localForward * radius;
    Vector3 pointC = basePosition - localRight * radius;
    Vector3 pointD = basePosition - localForward * radius;

    DrawRay(pointA, localUp * height, color);
    DrawRay(pointB, localUp * height, color);
    DrawRay(pointC, localUp * height, color);
    DrawRay(pointD, localUp * height, color);

    DrawCircle(basePosition, circleOrientation, radius, color, 32);
    DrawCircle(topPosition, circleOrientation, radius, color, 32);
}

Drawing A Capsule

To draw a capsule we need to make just a few additions to the DrawCylinder method. We can imagine that the capsule is a cylinder with two hemispheres at the base and the top. Since we already have base and top circles, we need to add two arcs to each to visualize the hemispheres. We have already covered how to draw arcs in one of the previous tutorials, so we will simply utilize that knowledge.

One important thing to figure out is how the height of a capsule and its radius influences the height of the cylinder that is used as its base. The following image displays that relationship (for clarity it is done using a 2D projection of the said shapes):

It is possible to see that the height of the capsules “core” cylinder (red) is two radii smaller than the height of the comparable cylinder (blue). It is important to note that the total height should be at least twice as large as the radius (in which case we will get a sphere). With all that in mind we can define the following steps required to draw a capsule from its base (note that the arcs are using the same orientation like the base circles of the cylinder):

  • Draw a base hemisphere using two arcs with radius-units offset along the up-axis from the base,
  • Draw a cylinder using the same base, but with the height which is two radii lesser than the total capsule height,
  • Draw a top hemisphere cylinder-height offset along the up-axis from the cylinder/base arc base:
public static void DrawCapsule(Vector3 position, Quaternion orientation, float height, float radius, Color color, bool drawFromBase = true)
{
    // Clamp the radius to a half of the capsule's height
    radius = Mathf.Clamp(radius, 0, height * 0.5f);
    Vector3 localUp = orientation * Vector3.up;
    Quaternion arcOrientation = orientation * Quaternion.Euler(0, 90, 0);

    Vector3 baseArcPosition = position + localUp * radius;
    DrawArc(180, 360, baseArcPosition, orientation, radius, color);
    DrawArc(180, 360, baseArcPosition, arcOrientation, radius, color);

    float cylinderHeight = height - radius * 2.0f;
    DrawCylinder(baseArcPosition, orientation, cylinderHeight, radius, color, true);

    Vector3 topArcPosition = baseArcPosition + localUp * cylinderHeight;
        
    DrawArc(0, 180, topArcPosition, orientation, radius, color);
    DrawArc(0, 180, topArcPosition, arcOrientation, radius, color);
}

Please notice that the “core” cylinder is drawn with bDrawFromBase parameter hardcoded to true. This will be important in the later steps.

Calling this new method in our Example class’s Update, we get the following. In addition to the capsule, a green cylinder is drawn for comparison (note that the green cylinder has a slightly larger radius for clarity).:

The last factor we need to include is the bDrawFromBase flag. Like with the cylinder, it is sufficient just to offset the base position by that offset, and everything else will fall in place (this is where the hardcoded value mentioned earlier comes in play, since otherwise the offset would be added twice when drawing the core cylinder, fine the bDrawFromBase is set to false):

public static void DrawCapsule(Vector3 position, Quaternion orientation, float height, float radius, Color color, bool drawFromBase = true)
{
    // Clamp the radius to a half of the capsule's height
    radius = Mathf.Clamp(radius, 0, height * 0.5f);
    Vector3 localUp = orientation * Vector3.up;
    Quaternion arcOrientation = orientation * Quaternion.Euler(0, 90, 0);

    Vector3 basePositionOffset = drawFromBase ? Vector3.zero : (localUp * height * 0.5f);
    Vector3 baseArcPosition = position + localUp * radius - basePositionOffset;
    DrawArc(180, 360, baseArcPosition, orientation, radius, color);
    DrawArc(180, 360, baseArcPosition, arcOrientation, radius, color);

    float cylinderHeight = height - radius * 2.0f;
    DrawCylinder(baseArcPosition, orientation, cylinderHeight, radius, color, true);

    Vector3 topArcPosition = baseArcPosition + localUp * cylinderHeight;
        
    DrawArc(0, 180, topArcPosition, orientation, radius, color);
    DrawArc(0, 180, topArcPosition, arcOrientation, radius, color);
}

With this modification we can set the bDrawFromBase parameter to false and get the following result:

With this step we have concluded this tutorial and with it the whole Unity – Draw custom Debug Shapes series. Next tutorial series will focus more on some basic gameplay programming concepts that can be applied in a wide range of game development project. Stay tuned!

The post Unity – Draw A Debug Cylinder and Capsule appeared first on dev-tut.com.

]]>
849
Unity – Draw A Debug Circle – Wrap-up https://dev-tut.com/2022/unity-draw-a-debug-circle-part-5/ Sun, 20 Feb 2022 01:38:47 +0000 https://dev-tut.com/?p=730 Through the previous four parts of this tutorial we have seen how to draw a debug circle in Unity, how to rotate it in 3D space and how to draw arcs, segments and sectors of a circle. In this part we will cover some basic C# concepts to make the extension functions a bit more […]

The post Unity – Draw A Debug Circle – Wrap-up appeared first on dev-tut.com.

]]>
Through the previous four parts of this tutorial we have seen how to draw a debug circle in Unity, how to rotate it in 3D space and how to draw arcs, segments and sectors of a circle.

In this part we will cover some basic C# concepts to make the extension functions a bit more useful and convenient. Examples show in this tutorial are just a first step from which you should be able to modify the code according to your needs.

Drawing on a Plane

In many cases (at least in my experience) drawing a shape is handled within an existing plane of the 3D space. For convenience, these planes can be named based on the axes that define them, namely: XY, XZ and YZ.

This can be accomplished in two ways, either having three dedicated methods, or by passing the desired plane identifier as a parameter. For the sake of explaining the both approaches we can also do a hybrid of those two.

Let’s start with drawing a circle in XY plane (as it is also the most common in first person, and 2D games made in Unity, since Unity uses Y as vertical axis). First thing to do is to modify the signature of the DrawCircle method, so that the number of segments has a default value of 16. This is done purely for convenience, but also to match the signature of the DrawArc method:

public static void DrawCircle(Vector3 position, Quaternion orientation, float radius, Color color, int segments = 16)

With this modification out of the way, we can continue with the next step and create the DrawCircleXY method. The only role of this method is to call the existing DrawCircle method and pass the Quaternion.idenitity as the orientation parameter. Since this orientation will be constant, the signature of DrawCircleXY does not need to contain it:

public static void DrawCircleXY(Vector3 position, float radius, Color color, int segments = 16)
{
    DrawCircle(position, Quaternion.identity, radius, color, segments);
}

Same approach can be used for the remaining two planes XZ and YZ, where the only difference is that we need to pass a different orientation. In the case of XZ, we need to use (0, -90°, 0°) and in the case of YZ we need to use (90°, 0°, 0°).

public static void DrawCircleXZ(Vector3 position, float radius, Color color, int segments = 16)
{
    DrawCircle(position, Quaternion.Euler(0, -90, 0), radius, color, segments);
}

public static void DrawCircleYZ(Vector3 position, float radius, Color color, int segments = 16)
{
    DrawCircle(position, Quaternion.Euler(90, 0, 0), radius, color, segments);
}

The second approach, as previously mentioned, is to create a method which takes the plane identifier as an parameter. The most common way of defining this parameter would be to use enum. An enum is a value type that defines a number of named constants. In this case we need to create an enum called DrawingPlane and its constants can be named simply to match the planes: XY, XZ, YZ. By default, these constants will have an underlaying type int.

public enum DrawingPlane
{
    XY,
    XZ, 
    YZ
}

Next thing that we need to do is to overload the DrawCircle method. Simply described, method overloading is a way to implement multiple methods with the same name, but with a different signature. This can be useful if we need methods with the same name, but with a different number, type or an order of parameters. Overloaded version of DrawCircle method needs to include the DrawingPlane enum, and based on its value call the appropriate method for drawing the circle in a specific plane. The decision which method to call is handled through a switch statement (default case of this statement calls the same method as the DrawingPlane.XY case):

public static void DrawCircle(Vector3 position, DrawingPlane drawingPlane, float radius, Color color, int segments = 16)
{
    switch (drawingPlane)
    {
        case DrawingPlane.XY:
            DrawCircleXY(position, radius, color, segments);
            break;

        case DrawingPlane.XZ:
            DrawCircleXZ(position, radius, color, segments);
            break;

        case DrawingPlane.YZ:
            DrawCircleYZ(position, radius, color, segments);
            break;

        default:
            DrawCircleXY(position, radius, color, segments);
            break;
    }
}

To test this functionality, we can use the following code in the Example class:

void Update()
{
    Debug.DrawCircleXY(Vector3.zero, 1, Color.blue, 32);
    Debug.DrawCircleXZ(Vector3.zero, 1, Color.red, 32);
    Debug.DrawCircleYZ(Vector3.zero, 1, Color.green, 32);

    Debug.DrawCircle(Vector3.zero, DrawingPlane.XY, 0.5f, Color.blue, 32);
    Debug.DrawCircle(Vector3.zero, DrawingPlane.XZ, 0.5f, Color.red, 32);
    Debug.DrawCircle(Vector3.zero, DrawingPlane.YZ, 0.5f, Color.green, 32);
}

The result of this code can be seen in the following image (for better preview, camera location is set to (5, 4, -5), rotation to (30, -45, 0) and FOV to 30 degrees):

Same logic can be applied to the DrawArc method. The following code implements exactly the same concepts as the previous code, and it can be seen below:

public static void DrawArcXY(float startAngle, float endAngle, 
    Vector3 position, float radius, Color color, bool drawChord = false, 
    bool drawSector = false, int arcSegments = 16)
{
    DrawArc(startAngle, endAngle, position, Quaternion.identity, radius, 
        color, drawChord, drawSector, arcSegments);
}

public static void DrawArcXZ(float startAngle, float endAngle, 
    Vector3 position, float radius, Color color, bool drawChord = false, 
    bool drawSector = false, int arcSegments = 16)
{
    DrawArc(startAngle, endAngle, position, Quaternion.Euler(0, -90, 0), radius, 
        color, drawChord, drawSector, arcSegments);
}

public static void DrawArcYZ(float startAngle, float endAngle, 
    Vector3 position, float radius, Color color, bool drawChord = false, 
    bool drawSector = false, int arcSegments = 16)
{
    DrawArc(startAngle, endAngle, position, Quaternion.Euler(90, 0, 0), radius, 
        color, drawChord, drawSector, arcSegments);
}

public static void DrawArc(float startAngle, float endAngle, 
    Vector3 position, DrawingPlane drawingPlane, float radius, 
    Color color, bool drawChord = false, bool drawSector = false, 
    int arcSegments = 16)
{
    switch (drawingPlane)
    {
        case DrawingPlane.XY:
            DrawArcXY(startAngle, endAngle, position, radius, color, 
                drawChord, drawSector, arcSegments);
            break;

        case DrawingPlane.XZ:
            DrawArcXZ(startAngle, endAngle, position, radius, color, 
                drawChord, drawSector, arcSegments);
            break;

        case DrawingPlane.YZ:
            DrawArcYZ(startAngle, endAngle, position, radius, color, 
                drawChord, drawSector, arcSegments);
            break;

        default:
            DrawArcXY(startAngle, endAngle, position, radius, color, 
                drawChord, drawSector, arcSegments);
            break;
    }
}

We can test this new code using our Example class:

void Update()
{
    Debug.DrawArcXY(0.0f, 90.0f, Vector3.zero, 1.0f, Color.blue, false, true, 16);
    Debug.DrawArcXZ(90.0f, 180.0f, Vector3.zero, 1.0f, Color.red, false, true, 16);
    Debug.DrawArcYZ(-90.0f, 0.0f, Vector3.zero, 1.0f, Color.green, false, true, 16);

    Debug.DrawArc(0.0f, 90.0f, Vector3.zero, DrawingPlane.XY, 0.5f, Color.blue, false, true, 16);
    Debug.DrawArc(90.0f, 180.0f, Vector3.zero, DrawingPlane.XZ, 0.5f, Color.red, false, true, 16);
    Debug.DrawArc(-90.0f, 0.0f, Vector3.zero, DrawingPlane.YZ, 0.5f, Color.green, false, true, 16);
}

The result of this code can be seen in the following image:

Coming Up!

The following tutorials will address how to expand the Debug class with multiple methods for drawing triangles and rectangles. Stay tuned!

The post Unity – Draw A Debug Circle – Wrap-up appeared first on dev-tut.com.

]]>
730
Unity – Draw A Debug Circle – Part 4 https://dev-tut.com/2022/unity-draw-a-debug-circle-part-4/ Sun, 13 Feb 2022 14:17:58 +0000 https://dev-tut.com/?p=702 In previous part we have seen how to draw a debug circle with rotation in 3D space in Unity project. This tutorial will introduce concepts of circle arc, segment and sector which can be particularly interesting for debugging multiple gameplay elements, for example, NPC field of view, radar cone, circle overlaps, but MOST importantly, they […]

The post Unity – Draw A Debug Circle – Part 4 appeared first on dev-tut.com.

]]>
In previous part we have seen how to draw a debug circle with rotation in 3D space in Unity project. This tutorial will introduce concepts of circle arc, segment and sector which can be particularly interesting for debugging multiple gameplay elements, for example, NPC field of view, radar cone, circle overlaps, but MOST importantly, they will allow us to draw a Debug Pizza.

This tutorial is a part of tutorial series Unity – Draw Custom Debug Shapes

Circle Arc

Circle arc is part of the circumference of a circle. Although arc is usually defined with two points, in the context of game development it is more useful to define it using two angles. Given a circle and two angles along its circumference, it is possible to define two arcs:

  • Minor arc – shorter arc between two points
  • Major arc – longer arc between two points

Minor arc (blue) and major arc (red) are shown on the following image:

By default, arcs between two angles are drawn in counter-clockwise direction. This might not be optimal in most cases, so we will need to introduce a way to to select direction. Additionally, this will introduce a small issue when drawing an arc which starts at an angle greater than 180° and ends at the angle lesser than 180° (as show on the previous image).

To prevent this issue, while still maintaining some degree of convenience for end user (potentially game designer, or another developer), our method will internally calculate the arc between 0° and the span between the defined start and end angle, and then offset it by the start angle.

As with the previous tutorials, we will start with the most basic implementation of the arc drawing method, which only takes start angle, end angle, radius and drawing color as parameters. For direction, it is dictated by the difference between starting and ending angle. If (mapped to 0°-360° range) start angle is lesser than end angle, arc will be drawn counterclockwise, otherwise, it will be drawn clockwise.

public static void DrawArc(float startAngle, float endAngle, float radius, Color color, int arcSegments = 32)
{
    float arcSpan = Mathf.DeltaAngle(startAngle, endAngle);

    // Since Mathf.DeltaAngle returns a signed angle of the shortest path between two angles, it 
    // is necessary to offset it by 360.0 degrees to get a positive value
    if (arcSpan <= 0)
    {
        arcSpan += 360.0f;
    }

    // angle step is calculated by dividing the arc span by number of approximation segments
    float angleStep = (arcSpan / arcSegments) * Mathf.Deg2Rad;
    float stepOffset = startAngle * Mathf.Deg2Rad;

    // stepStart, stepEnd, lineStart and lineEnd variables are declared outside of the following for loop
    float stepStart = 0.0f;
    float stepEnd = 0.0f;
    Vector3 lineStart = Vector3.zero;
    Vector3 lineEnd = Vector3.zero;

    for (int i = 0; i < arcSegments; i++)
    {
        // Calculate approximation segment start and end, and offset them by start angle
        stepStart = angleStep * i + stepOffset;
        stepEnd = angleStep * (i + 1) + stepOffset;

        lineStart.x = Mathf.Cos(stepStart);
        lineStart.y = Mathf.Sin(stepStart);
        lineStart *= radius;

        lineEnd.x = Mathf.Cos(stepEnd);
        lineEnd.y = Mathf.Sin(stepEnd);
        lineEnd *= radius;

        DrawLine(lineStart, lineEnd, color);
    }
} 

To make a few examples, we can use this method in the Update of Example class:

void Update()
{
    Debug.DrawArc(-45, 45, 1.0f, Color.green);
    Debug.DrawArc(45, -45, 0.9f, Color.magenta);
    Debug.DrawArc(45, 135, 0.8f, Color.red);
    Debug.DrawArc(135, 45, 0.7f, Color.yellow);
    Debug.DrawArc(180, 0, 0.6f, Color.cyan);
    Debug.DrawArc(0, 180, 0.5f, Color.white);
}

This example results in the following image:

Circle Segment

Circle segment is a region bounded by an arc of the circle and a line segment connecting the arc’s starting and ending point. This line segment is also named chord.

Depending on which arc is used to define a segment, segments are also split in two types: minor and major. These segments are shown on the following image:

We can extend the DrawArc method to include a flag if the chord should be drawn, and therefore the segment. Besides extending the method signature with the drawChord argument is is necessary to store arc start point (arcStart) and arc end point (arcEnd), and simply connect them using the Debug.DrawLine method.

public static void DrawArc(float startAngle, float endAngle, float radius, Color color, bool drawChord = false, int arcSegments = 32)
{
    float arcSpan = Mathf.DeltaAngle(startAngle, endAngle);

    // Since Mathf.DeltaAngle returns a signed angle of the shortest path between two angles, it 
    // is necessary to offset it by 360.0 degrees to get a positive value
    if (arcSpan <= 0)
    {
        arcSpan += 360.0f;
    }

    // angle step is calculated by dividing the arc span by number of approximation segments
    float angleStep = (arcSpan / arcSegments) * Mathf.Deg2Rad;
    float stepOffset = startAngle * Mathf.Deg2Rad;

    // stepStart, stepEnd, lineStart and lineEnd variables are declared outside of the following for loop
    float stepStart = 0.0f;
    float stepEnd = 0.0f;
    Vector3 lineStart = Vector3.zero;
    Vector3 lineEnd = Vector3.zero;

    // arcStart and arcEnd need to be stored to be able to draw segment chord
    Vector3 arcStart = Vector3.zero;
    Vector3 arcEnd = Vector3.zero;

    for (int i = 0; i < arcSegments; i++)
    {
        // Calculate approximation segment start and end, and offset them by start angle
        stepStart = angleStep * i + stepOffset;
        stepEnd = angleStep * (i + 1) + stepOffset;

        lineStart.x = Mathf.Cos(stepStart);
        lineStart.y = Mathf.Sin(stepStart);
        lineStart *= radius;

        lineEnd.x = Mathf.Cos(stepEnd);
        lineEnd.y = Mathf.Sin(stepEnd);
        lineEnd *= radius;

        // If this is the first iteration, set the chordStart
        if(i == 0)
        {
            arcStart = lineStart;
        }

        // If this is the last iteration, set the chordEnd
        if(i == arcSegments - 1)
        {
            arcEnd = lineEnd;
        }

        DrawLine(lineStart, lineEnd, color);
    }

    if (drawChord)
    {
        DrawLine(arcStart, arcEnd, color);
    }
}

Using the Example class we can demonstrate this new functionality:

void Update()
{
    Debug.DrawArc(-35, 45, 1.0f, Color.cyan, true);
    Debug.DrawArc(45, -35, 1.0f, Color.red, false);
}

Following image displays the result:

Circle sector

Similar to the circle segment, circle sector represents a region which is bounded with a circle arc, and (instead of a single) two line segments named rays. Circle sector requires two ray:

  • Connecting arc start with the circle origin,
  • Connecting arc end with the circle origin.

As with the circle arcs and segments, sectors too can be minor and major, depending on which arc defines them.

Similarly to the circle segment implementation, we need to extend the signature of DrawArc method with a drawSector argument. Only change that is needed in the method implementation, if the drawSector is true, to connect arcStart and arcEnd with the circle origin (at this moment it is still (0,0,0)).

public static void DrawArc(float startAngle, float endAngle, float radius, Color color, bool drawChord = false, bool drawSector = false, int arcSegments = 32)
{
    float arcSpan = Mathf.DeltaAngle(startAngle, endAngle);

    // Since Mathf.DeltaAngle returns a signed angle of the shortest path between two angles, it 
    // is necessary to offset it by 360.0 degrees to get a positive value
    if (arcSpan <= 0)
    {
        arcSpan += 360.0f;
    }

    // angle step is calculated by dividing the arc span by number of approximation segments
    float angleStep = (arcSpan / arcSegments) * Mathf.Deg2Rad;
    float stepOffset = startAngle * Mathf.Deg2Rad;

    // stepStart, stepEnd, lineStart and lineEnd variables are declared outside of the following for loop
    float stepStart = 0.0f;
    float stepEnd = 0.0f;
    Vector3 lineStart = Vector3.zero;
    Vector3 lineEnd = Vector3.zero;

    // arcStart and arcEnd need to be stored to be able to draw segment chord
    Vector3 arcStart = Vector3.zero;
    Vector3 arcEnd = Vector3.zero;

    // arcOrigin represents an origin of a circle which defines the arc
    Vector3 arcOrigin = Vector3.zero;

    for (int i = 0; i < arcSegments; i++)
    {
        // Calculate approximation segment start and end, and offset them by start angle
        stepStart = angleStep * i + stepOffset;
        stepEnd = angleStep * (i + 1) + stepOffset;

        lineStart.x = Mathf.Cos(stepStart);
        lineStart.y = Mathf.Sin(stepStart);
        lineStart *= radius;

        lineEnd.x = Mathf.Cos(stepEnd);
        lineEnd.y = Mathf.Sin(stepEnd);
        lineEnd *= radius;

        // If this is the first iteration, set the chordStart
        if(i == 0)
        {
            arcStart = lineStart;
        }

        // If this is the last iteration, set the chordEnd
        if(i == arcSegments - 1)
        {
            arcEnd = lineEnd;
        }

        DrawLine(lineStart, lineEnd, color);
    }

    if (drawChord)
    {
        DrawLine(arcStart, arcEnd, color);
    }
    if (drawSector)
    {
        DrawLine(arcStart, arcOrigin, color);
        DrawLine(arcEnd, arcOrigin, color);
    }
}

Using the Example class we can demonstrate this new functionality:

void Update()
{
    Debug.DrawArc(-35, 45, 1.0f, Color.cyan, false, true);
    Debug.DrawArc(45, -35, 1.0f, Color.red);
}

Following image displays the result:

Position and Rotation

To make the newly created DrawArc method, we can extend it with position and orientation arguments. Details of this implementation are discussed in detail the previous tutorial, and the same implementation is still valid for this example:

public static void DrawArc(float startAngle, float endAngle, 
    Vector3 position, Quaternion orientation, float radius, 
    Color color, bool drawChord = false, bool drawSector = false, 
    int arcSegments = 32)
{
    float arcSpan = Mathf.DeltaAngle(startAngle, endAngle);

    // Since Mathf.DeltaAngle returns a signed angle of the shortest path between two angles, it 
    // is necessary to offset it by 360.0 degrees to get a positive value
    if (arcSpan <= 0)
    {
        arcSpan += 360.0f;
    }

    // angle step is calculated by dividing the arc span by number of approximation segments
    float angleStep = (arcSpan / arcSegments) * Mathf.Deg2Rad;
    float stepOffset = startAngle * Mathf.Deg2Rad;

    // stepStart, stepEnd, lineStart and lineEnd variables are declared outside of the following for loop
    float stepStart = 0.0f;
    float stepEnd = 0.0f;
    Vector3 lineStart = Vector3.zero;
    Vector3 lineEnd = Vector3.zero;

    // arcStart and arcEnd need to be stored to be able to draw segment chord
    Vector3 arcStart = Vector3.zero;
    Vector3 arcEnd = Vector3.zero;

    // arcOrigin represents an origin of a circle which defines the arc
    Vector3 arcOrigin = position;

    for (int i = 0; i < arcSegments; i++)
    {
        // Calculate approximation segment start and end, and offset them by start angle
        stepStart = angleStep * i + stepOffset;
        stepEnd = angleStep * (i + 1) + stepOffset;

        lineStart.x = Mathf.Cos(stepStart);
        lineStart.y = Mathf.Sin(stepStart);
        lineStart.z = 0.0f;

        lineEnd.x = Mathf.Cos(stepEnd);
        lineEnd.y = Mathf.Sin(stepEnd);
        lineEnd.z = 0.0f;

        // Results are multiplied so they match the desired radius
        lineStart *= radius;
        lineEnd *= radius;

        // Results are multiplied by the orientation quaternion to rotate them 
        // since this operation is not commutative, result needs to be
        // reassigned, instead of using multiplication assignment operator (*=)
        lineStart = orientation * lineStart;
        lineEnd = orientation * lineEnd;

        // Results are offset by the desired position/origin 
        lineStart += position;
        lineEnd += position;

        // If this is the first iteration, set the chordStart
        if (i == 0)
        {
            arcStart = lineStart;
        }

        // If this is the last iteration, set the chordEnd
        if(i == arcSegments - 1)
        {
            arcEnd = lineEnd;
        }

        DrawLine(lineStart, lineEnd, color);
    }

    if (drawChord)
    {
        DrawLine(arcStart, arcEnd, color);
    }
    if (drawSector)
    {
        DrawLine(arcStart, arcOrigin, color);
        DrawLine(arcEnd, arcOrigin, color);
    }
}

Debug Draw Pizza

With all technicalities out of the way, we can make the pièce de résistance of this tutorial: Drawing a debug pizza!

We need the following ingredients:

  • Pizza slice (minor sector)
  • Rest of the pie (major sector)
  • Toppings (minor and major segments, depending on the location)
void DrawDebugPizza()
{
    // Draw a slice
    Vector3 sliceOffset = (Vector3.up + Vector3.right) * 0.16f;
    Debug.DrawArc(20, 75, transform.position + sliceOffset, transform.rotation, 1.0f, Color.yellow, false, true);
    Debug.DrawArc(20, 75, transform.position + sliceOffset, transform.rotation, 0.85f, Color.yellow, false, true);

    // Draw the rest of the pie
    Debug.DrawArc(75, 20, transform.position, transform.rotation, 1.0f, Color.yellow, false, true);
    Debug.DrawArc(75, 20, transform.position, transform.rotation, 0.85f, Color.yellow, false, true);

    // Draw full toppings
    Debug.DrawCircle(transform.position + new Vector3(-0.4f, 0.2f,0), transform.rotation, 0.22f, 16, Color.red);
    Debug.DrawCircle(transform.position + new Vector3(0.5f, -0.35f,0), transform.rotation, 0.17f, 16, Color.red);
    Debug.DrawCircle(transform.position + new Vector3(-0.1f, -0.5f,0), transform.rotation, 0.22f, 16, Color.red);

    // Draw sliced toppings
    Debug.DrawArc(242, 88, transform.position + sliceOffset + new Vector3(0.2f, 0.55f, 0), transform.rotation, 0.20f, Color.red, true);
    Debug.DrawArc(88, 242, transform.position + new Vector3(0.2f, 0.55f, 0), transform.rotation, 0.20f, Color.red, true);

    Debug.DrawArc(29, 192, transform.position + sliceOffset + new Vector3(0.5f, 0.15f, 0), transform.rotation, 0.20f, Color.red, true);
    Debug.DrawArc(192, 29, transform.position + new Vector3(0.5f, 0.15f, 0), transform.rotation, 0.20f, Color.red, true);

}

And finally, we can by calling this method from Update of the Example class, we get our own Debug Pizza:

The following part covers some basic C# concepts to make the extension functions a bit more useful and convenient.

The post Unity – Draw A Debug Circle – Part 4 appeared first on dev-tut.com.

]]>
702
Unity – Draw A Debug Circle – Part 3 https://dev-tut.com/2022/unity-draw-a-circle-part3/ Sat, 29 Jan 2022 16:48:34 +0000 https://dev-tut.com/?p=600 In previous part we have seen how to draw a simple debug circle in Unity project. This tutorial will extend that functionality and show how to draw circle with any rotation or axle alignment. Additionally this tutorial introduces some basic concepts about the rotation/orientation in a context of game development. This tutorial is a part […]

The post Unity – Draw A Debug Circle – Part 3 appeared first on dev-tut.com.

]]>
In previous part we have seen how to draw a simple debug circle in Unity project. This tutorial will extend that functionality and show how to draw circle with any rotation or axle alignment. Additionally this tutorial introduces some basic concepts about the rotation/orientation in a context of game development.

This tutorial is a part of tutorial series Unity – Draw Custom Debug Shapes

Understanding Rotation

One of many tasks most (if not all) modern game engines, including Unity, are able to handle is rotation of a body/object in three dimensions. This rotation is most often parametrized in two main ways (one common way of storing rotation is by using 4×4 matrices, but that will be covered in one of the future tutorials):

  • Euler angles,
  • Unit quaternions (versors)

Euler angles

Swiss mathematician and physicist Leonhard Euler has introduced Euler angles as a way to describe orientation of a body in a fixed coordinate space using three angles (nomenclature may differ in different environments, but usually those angles are represented by X, Y, Z or roll, pitch and yaw). Although Euler angles are more intuitive (for humans) and allow us to deconstruct orientation in the said components, they introduce (at least) two issues:

  • Ambiguity – different component values (triplets) might represent the same rotation.
  • Gimbal lock – during rotation sequences, it is possible that two of the angles might cause rotation around the same axle (aligned gimbals).

One of the advantages of Euler angles is that they can parametrize scale of the rotation. For example, [-630°, 0°, 0°] and [450°, 0°, 0°] produce the identical Quaternions, but they might be interpreted differently, since the information about the scale might be used as, for example, angular velocity of a body.

Unit Quaternion

Alternative way of parametrizing body orientation is by using unit quaternions. Quaternions in a context of game development are mainly used to perform operations on and with rotations, particularly rotations sequences (addition, subtraction) and interpolation. Quaternions are represented with four scalars x, y, z and w.

Main disadvantage of quaternions is a lack of intuitiveness from human perspective, and potentially higher memory requirements (using four scalars instead of three), as well as lack of information about the rotation magnitude.

Conversion from Euler angles [roll, pitch, yaw] to quaternion [x, y, z, w] can be performed through the following steps:

x=sin(roll/2) \times cos(pitch/2) \times cos(yaw/2) - cos(roll/2) \times sin(pitch/2) \times sin(yaw/2) \newline

y = cos(roll/2) \times sin(pitch/2) \times cos(yaw/2) + sin(roll/2) \times cos(pitch/2) \times sin(yaw/2) \newline

z = cos(roll/2) \times cos(pitch/2) \times sin(yaw/2) - sin(roll/2) \times sin(pitch/2) \times cos(yaw/2) \newline

w = cos(roll/2) \times cos(pitch/2) \times cos(yaw/2) + sin(roll/2) \times sin(pitch/2) \times sin(yaw/2) \newline

Rotating a vector

After this (bit long) intro, we can return to the main problem of this tutorial. How to rotate a location of a point (in a context of predefined coordinate system). Luckily Unity (as well as many other game engines) are overloading quaternion operators in a way to make this really convenient. More specifically, to rotate a vector it is enough to multiply rotation quaternion by it, for example, given the starting vector (v) and quaternion (q), we can calculate the rotated vector (vr) as :

vr=q \times v

To visualize this, we can use the following code where the original vector (Vector3.right) is rotated by 15 degrees and drawn as a ray (rayColor hue value is equal to the current angle):

Color rayColor;
Quaternion rotationQuaternion;

for(float i = 0; i < 360.0f; i += 15)
{
    rotationQuaternion = Quaternion.Euler(0, 0, i);
    rayColor = Color.HSVToRGB(i/360.0f, 1.0f, 1.0f);

    Debug.DrawRay(Vector3.zero, rotationQuaternion * Vector3.right, rayColor);
}

Result of the code can be seen in the following image:

With this concept in our toolset, we can continue to the main topic of this tutorial.

Rotating the Debug Circle

To extend our Debug.DrawCircle function, its signature is extended with Quaternion rotation parameter. Additionally, on each iteration of the for loop, z value of the lineStart and lineEnd needs to be reset to 0.0f, since the rotation of the vector might modify that value (lines 26 and 31). Rotation of lineStart and lineEnd points (which can be considered as vectors from the circles origin) are performed by multiplying the rotation quaternion with their respective values, and reassigning them (lines 40 and 41).

public static void DrawCircle(Vector3 position, Quaternion rotation, 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);
        lineStart.z = 0.0f;

        // 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));
        lineEnd.z = 0.0f;

        // Results are multiplied so they match the desired radius
        lineStart *= radius;
        lineEnd *= radius;

        // Results are multiplied by the rotation quaternion to rotate them 
        // since this operation is not commutative, result needs to be
        // reassigned, instead of using multiplication assignment operator (*=)
        lineStart = rotation * lineStart;
        lineEnd = rotation * lineEnd;

        // 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 demonstrate this change, following code is used in the Example.cs script (old Update method is replaced since the Debug.DrawCircle method signature was changed, although, in one of the following tutorials we will see how this issue can be mitigated by the use of method overloading, which is one of the C#’s most useful features):

void Update()
{
    Color rayColor;
    Quaternion rotationQuaternion;

    for(float i = 0; i < 360.0f; i += 30)
    {
        rotationQuaternion = Quaternion.Euler(0, i * 0.5f, 0);
        rayColor = Color.HSVToRGB(i/360.0f, 1.0f, 1.0f);

        Debug.DrawCircle(transform.position, rotationQuaternion, 1.0f, 32, rayColor);
    }
}

The result of the example code can be seen in the following image (note that the camera was rotated by 45 degrees over the x-axis, to increase clarity on what is happening). As in the previous example, color hue is matching the rotation angle:

Even better example, how this extended Debug.DrawCircle function can be used is if we pass Example GameObject rotation as the rotation parameter. What way circle rotation will describe the rotation of (in this case invisible) GameObject, which can greatly help us with debugging (as this rotation is currently directionally ambiguous, in one of the future tutorials, we will see how to include a normal ray which will inform us about the “forward” direction of the object).

void Update()
{
    Debug.DrawCircle(transform.position, transform.rotation, 1.0f, 32, Color.magenta);
}

Now, position and orientation of the circle matches the position and orientation of the Example GameObject, as can be seen in the following images:

With this, main topic of the tutorial is finished. The following part covers how to modify this functionality to draw arcs, segments, sectors, and… pizzas!

The post Unity – Draw A Debug Circle – Part 3 appeared first on dev-tut.com.

]]>
600
Unity – Draw A Debug Circle – Part 2 https://dev-tut.com/2022/unity-draw-a-circle-part2/ Sat, 22 Jan 2022 15:58:44 +0000 https://dev-tut.com/?p=556 In previous part we have seen how a circle can be defined using the basic trigonometry equations. In this part we will see how to transform that knowledge into code and use it in an actual Unity project. This tutorial is a part of tutorial series Unity – Draw Custom Debug Shapes Extending Debug Class […]

The post Unity – Draw A Debug Circle – Part 2 appeared first on dev-tut.com.

]]>
In previous part we have seen how a circle can be defined using the basic trigonometry equations. In this part we will see how to transform that knowledge into code and use it in an actual Unity project.

This tutorial is a part of tutorial series Unity – Draw Custom Debug Shapes

Extending Debug Class

In this tutorial 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 insert 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 tutorial, circle approximation can be described with 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)):

The following tutorial will cover how to implement custom rotation of the circles, so they can be drawn in any plane.

The post Unity – Draw A Debug Circle – Part 2 appeared first on dev-tut.com.

]]>
556
Unity – Draw A Debug Circle – Part 1 https://dev-tut.com/2022/unity-draw-a-circle-part1/ Sat, 22 Jan 2022 13:41:48 +0000 https://dev-tut.com/?p=537 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. This tutorial is a part of tutorial series Unity – Draw Custom Debug Shapes How to Draw a Circle Drawing a sphere can […]

The post Unity – Draw A Debug Circle – Part 1 appeared first on dev-tut.com.

]]>
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.

This tutorial is a part of tutorial series Unity – Draw Custom Debug Shapes

How to Draw a Circle

Drawing a sphere 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.).

Main Ingredients: SIne and Cosine

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(\alpha)=opposite/hypothenuse

Since hypothenuse of the triangle is equal to 1.0, we can simply rearrange the equation as:

opposite = sin(\alpha)

Same applies for “adjacent”, where the following equation is true:

cos(\alpha)=adjacent/hypothenuse

and by using the same analogy, we get:

adjacent=cos(\alpha)

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\degree), sin(45\degree))

B = (cos(135\degree), sin(135\degree))

C = (cos(225\degree), sin(225\degree))

D = (cos(315\degree), sin(315\degree))

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. Next part of this tutorial explains how to do that.

The post Unity – Draw A Debug Circle – Part 1 appeared first on dev-tut.com.

]]>
537
Unity – Draw custom Debug shapes https://dev-tut.com/2022/unity-debug/ Sun, 09 Jan 2022 15:59:33 +0000 https://dev-tut.com/?p=524 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 […]

The post Unity – Draw custom Debug shapes appeared first on dev-tut.com.

]]>
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:

The post Unity – Draw custom Debug shapes appeared first on dev-tut.com.

]]>
524