What is circle arc in unity – draw custom debug shapes?

Unity: Dibuja Formas Debug Personalizadas

13/12/2014

Valoración: 4.47 (11859 votos)

Cuando estamos desarrollando un juego en Unity, la depuración visual es una de nuestras herramientas más poderosas. Nos permite entender qué está pasando en nuestra escena sin tener que leer líneas y líneas de logs en la consola. La clase Debug de Unity nos ofrece métodos básicos como Debug.DrawLine y Debug.DrawRay, que son útiles, pero a menudo se quedan cortos cuando necesitamos visualizar lógicas más complejas. ¿Cómo representas el campo de visión de un enemigo, el área de efecto de una habilidad o un sistema de radar? La respuesta está en crear nuestras propias formas de depuración personalizadas.

What is dbgdraw in Unity?
It's similar to Unity's Gizmos and Handles API's. Unity provides a rather limited set of debug draw functions in the Debug class, such as Debug.DrawLine for example. Unlike Unity's Gizmo, which requires rendering code to be located in a OnDrawGizmos method, you can issue DbgDraw calls from anywhere in your code.

En este artículo, vamos a sumergirnos en el fascinante mundo de la geometría para el desarrollo de videojuegos. Aprenderemos a dibujar arcos de círculo, segmentos y sectores. Estas formas no solo son increíblemente útiles para depurar elementos de gameplay, sino que también nos abrirán la puerta a crear visualizaciones mucho más claras y, por qué no, divertidas. ¡Al final, serás capaz de dibujar tu propia pizza de depuración!

Índice de Contenido

Más Allá de la Línea Recta: La Necesidad de Formas Complejas

Imagina que estás programando la inteligencia artificial de un guardia. Este guardia tiene un cono de visión. Si el jugador entra en ese cono, el guardia se pone en alerta. Usando solo Debug.DrawLine, representar ese cono sería una tarea tediosa y el resultado no sería muy claro. Necesitaríamos dibujar muchas líneas desde el guardia hacia el borde de su campo de visión. Sin embargo, si tuviéramos una función Debug.DrawArc o Debug.DrawSector, podríamos visualizar ese cono con una sola llamada, de forma limpia y precisa. Esto es aplicable a innumerables situaciones:

  • Campos de visión (FOV) de NPCs y cámaras.
  • Áreas de efecto (AoE) de hechizos o explosiones.
  • Radares y sensores que escanean un área específica.
  • Visualización de ángulos de ataque de un personaje.
  • Depuración de físicas que involucran overlaps circulares.

Crear nuestras propias herramientas de depuración nos da un control sin precedentes sobre nuestro proceso de desarrollo, permitiéndonos identificar y solucionar bugs de una forma mucho más intuitiva y rápida.

El Arco de Círculo: Nuestro Punto de Partida

Un arco de círculo es, simplemente, una porción de la circunferencia de un círculo. Aunque matemáticamente se puede definir con dos puntos, en el desarrollo de videojuegos es mucho más práctico definirlo usando un ángulo de inicio y un ángulo final. Esto nos da dos posibles arcos:

  • Arco Menor: El camino más corto entre los dos ángulos.
  • Arco Mayor: El camino más largo entre los dos ángulos.

Nuestro objetivo es crear un método estático que, al igual que Debug.DrawLine, podamos llamar desde cualquier parte de nuestro código. Empezaremos con una versión básica que dibuja un arco en el origen (0,0,0) y luego la iremos mejorando.

La lógica consiste en aproximar el arco dibujando una serie de pequeños segmentos de línea recta. Cuantos más segmentos usemos, más suave y preciso se verá el arco. Aquí está el código C# inicial para una clase de ayuda que podríamos llamar CustomDebug:

public static void DrawArc(float startAngle, float endAngle, float radius, Color color, int arcSegments = 32) { float arcSpan = Mathf.DeltaAngle(startAngle, endAngle); if (arcSpan <= 0) { arcSpan += 360.0f; } float angleStep = (arcSpan / arcSegments) * Mathf.Deg2Rad; float stepOffset = startAngle * Mathf.Deg2Rad; Vector3 lineStart = Vector3.zero; Vector3 lineEnd = Vector3.zero; for (int i = 0; i < arcSegments; i++) { float stepStart = angleStep * i + stepOffset; float 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; Debug.DrawLine(lineStart, lineEnd, color); } }

Este código calcula el ángulo total del arco (arcSpan), lo divide en pequeños pasos (angleStep) y luego, en un bucle, calcula las posiciones de inicio y fin de cada pequeño segmento usando trigonometría (Coseno para la coordenada X y Seno para la Y) para después unirlos con Debug.DrawLine.

Añadiendo Segmentos y Sectores

Ahora que tenemos un arco, podemos extender su funcionalidad fácilmente para crear dos formas relacionadas y muy útiles: el segmento y el sector.

Segmento Circular

Un segmento circular es el área delimitada por un arco y la línea recta que une sus puntos de inicio y fin. A esta línea se le llama cuerda. Para dibujarlo, solo necesitamos añadir una línea extra a nuestra función, conectando el primer punto del arco con el último. Podemos controlar esto con un simple booleano drawChord.

Sector Circular

Un sector circular es la región con forma de porción de tarta, delimitada por un arco y dos líneas (radios) que conectan los extremos del arco con el centro del círculo. Esta es la forma perfecta para un cono de visión. Para dibujarlo, conectamos los puntos de inicio y fin del arco con el origen del círculo. Lo controlaremos con otro booleano, drawSector.

La función evolucionada se vería así:

public static void DrawArc(float startAngle, float endAngle, float radius, Color color, bool drawChord = false, bool drawSector = false, int arcSegments = 32) { // ... (cálculo inicial de arcSpan y angleStep igual que antes) Vector3 lineStart = Vector3.zero; Vector3 lineEnd = Vector3.zero; Vector3 arcStart = Vector3.zero; Vector3 arcEnd = Vector3.zero; Vector3 arcOrigin = Vector3.zero; // El centro del círculo for (int i = 0; i < arcSegments; i++) { // ... (cálculo de lineStart y lineEnd igual que antes) if(i == 0) { arcStart = lineStart; } if(i == arcSegments - 1) { arcEnd = lineEnd; } Debug.DrawLine(lineStart, lineEnd, color); } if (drawChord) { Debug.DrawLine(arcStart, arcEnd, color); } if (drawSector) { Debug.DrawLine(arcStart, arcOrigin, color); Debug.DrawLine(arcEnd, arcOrigin, color); } }

Posición y Rotación en el Mundo 3D

Nuestra función es genial, pero tiene una gran limitación: siempre dibuja en el origen (0,0,0) y sin rotación. Para que sea verdaderamente útil, necesitamos poder especificar una posición y una orientación en el espacio 3D. Para ello, añadimos dos parámetros más: un Vector3 position y un Quaternion orientation.

La lógica es sencilla: una vez que calculamos cada punto del arco en el espacio local (alrededor del origen), primero lo rotamos multiplicándolo por el cuaternión de orientación y luego lo trasladamos sumándole el vector de posición. Es crucial el orden: primero rotar, luego trasladar.

La versión final y más versátil de nuestra función es la siguiente:

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) { // ... (cálculo inicial de arcSpan y angleStep) Vector3 lineStart = Vector3.zero; Vector3 lineEnd = Vector3.zero; Vector3 arcStart = Vector3.zero; Vector3 arcEnd = Vector3.zero; for (int i = 0; i < arcSegments; i++) { // ... (cálculo de lineStart y lineEnd en espacio local) // Rotar los puntos lineStart = orientation * lineStart; lineEnd = orientation * lineEnd; // Trasladar los puntos lineStart += position; lineEnd += position; if (i == 0) { arcStart = lineStart; } if(i == arcSegments - 1) { arcEnd = lineEnd; } Debug.DrawLine(lineStart, lineEnd, color); } if (drawChord) { Debug.DrawLine(arcStart, arcEnd, color); } if (drawSector) { Debug.DrawLine(arcStart, position, color); Debug.DrawLine(arcEnd, position, color); } }

El Ejemplo Definitivo: La "Debug Pizza"

Para demostrar el poder y la flexibilidad de nuestra nueva herramienta, vamos a hacer algo divertido: dibujar una pizza con una porción separada. Este ejemplo utiliza todas las formas que hemos aprendido a crear.

void DrawDebugPizza() { Vector3 sliceOffset = (Vector3.up + Vector3.right) * 0.16f; // Dibuja la porción separada (un sector menor) Debug.DrawArc(20, 75, transform.position + sliceOffset, transform.rotation, 1.0f, Color.yellow, false, true); // Dibuja el resto de la pizza (un sector mayor) Debug.DrawArc(75, 20, transform.position, transform.rotation, 1.0f, Color.yellow, false, true); // Dibuja toppings usando círculos y segmentos // ... (código para dibujar círculos y arcos como toppings) }

Llamando a esta función desde el Update() de un GameObject, veremos en la escena una pizza perfectamente visualizada, demostrando cómo combinar sectores mayores y menores, y aplicarles offsets de posición, nos permite construir visualizaciones complejas con muy poco esfuerzo.

Una Alternativa Potente: DbgDraw

Si bien crear tus propias funciones de depuración es una excelente práctica de aprendizaje, a veces necesitas una solución más robusta y lista para usar. Aquí es donde entra en juego DbgDraw, una API de terceros para Unity.

DbgDraw es similar a las APIs Gizmos y Handles de Unity, pero con una ventaja clave: puedes realizar llamadas de dibujo desde cualquier parte de tu código (Start, Update, etc.), no solo desde OnDrawGizmos. La API recolecta todas las llamadas y las renderiza más tarde, lo que ofrece una flexibilidad enorme. Proporciona una gran variedad de formas, tanto en modo wireframe como sólido: cubos, esferas, discos, pirámides, y por supuesto, arcos.

Comparativa de Herramientas de Debug

CaracterísticaDebug.Draw*OnDrawGizmosDbgDraw
Lugar de llamadaCualquieraSolo OnDrawGizmosCualquiera
VisibilidadSolo en Play ModeSiempre en el EditorPlay Mode (en Dev Builds)
Formas disponiblesLíneas, RayosCubos, Esferas, Mallas...Gran variedad
Relleno SólidoNo

Preguntas Frecuentes (FAQ)

¿Por qué mi código de DbgDraw no funciona en una build?

DbgDraw, al ser una herramienta de depuración, solo funciona en builds que están marcadas como "Development Build" en la ventana de Build Settings (File > Build Settings). Asegúrate de que esa casilla está activada.

¿Se incluyen las llamadas a DbgDraw en la versión final de mi juego?

No. Si desactivas la casilla "Development Build", el compilador elimina automáticamente todas las llamadas a la API de DbgDraw. Esto asegura que tu juego final no tenga ninguna sobrecarga de rendimiento debido a código de depuración.

¿Dónde debo colocar los métodos de dibujo personalizados que hemos creado?

La mejor práctica es crear una clase estática, por ejemplo public static class DebugExtensions o public static class CustomDebug. De esta forma, podrás llamar a tus métodos desde cualquier script de tu proyecto de una manera muy limpia y organizada, como por ejemplo: CustomDebug.DrawArc(...).

¿Es muy costoso en rendimiento dibujar muchas formas de debug?

Sí, puede serlo. Las herramientas de depuración no están optimizadas para el rendimiento, ya que su propósito es ayudar durante el desarrollo. Dibuja solo lo que necesites para entender la lógica y evita tener cientos de formas complejas actualizándose en cada frame si notas que el rendimiento del editor se ve afectado.

Si quieres conocer otros artículos parecidos a Unity: Dibuja Formas Debug Personalizadas puedes visitar la categoría Juegos.

Subir