Dibujos personalizados en 2D
Introducción
Godot posee nodos para dibujar sprites, polígonos, partículas, etc., para la mayoría de los casos eso es suficiente, pero no siempre. Antes de desesperar al ver que no existe un nodo para dibujar algo específico, es bueno saber que es posible hacer que cualquier nodo 2D (ya sea uno basado en Control o Node2D ) puede aceptar comandos de dibujo personalizados. Esto es muy fácil de hacer.
Dibujar manualmente en un nodo 2D es realmente útil, aquí hay algunos casos de uso:
Dibujar formas o lógica que los nodos existentes no pueden hacer, como una imagen con estelas o un polígono animado especial.
Dibujar un gran número de objetos simples, como una cuadricula o un tablero para un juego 2D. El dibujo personalizado evita la sobrecarga del uso de nodos lo que lo hace menos intensivo en el uso de memoria y potencialmente más rápido.
Hacer un control de UI personalizado. Hay muchos nodos Control disponibles, Sin embargo, cuando tienes necesidades poco comunes, es probable que necesites un control personalizado.
Dibujando
Agrega un script a cualquier nodo derivado de CanvasItem, como Control o Node2D. Luego sobreescribe la función _draw()<class_CanvasItem_private_method__draw>.
extends Node2D
func _draw():
pass # Your draw commands here.
using Godot;
public partial class MyNode2D : Node2D
{
public override void _Draw()
{
// Your draw commands here.
}
}
Los comandos están descriptos en la referencia de la clase CanvasItem. Hay varios de ellos y los veremos algunos en los siguientes ejemplos.
Actualizando
La función _draw solo es llamada una vez, y luego, los comandos de dibujo se almacenan en caché y se recuerdan, de modo que las llamadas adicionales son innecesarias.
Si es necesario volver a dibujar porque cambió una variable o algo más, llame a CanvasItem.queue_redraw en ese mismo nodo y se realizará una nueva llamada _draw().
Aquí hay un ejemplo un poco más complejo, donde tenemos una variable de textura que se puede modificar en cualquier momento, y usando un setter, fuerza un redibujado de la textura cuando se modifica:
extends Node2D
@export var texture : Texture2D:
set(value):
texture = value
queue_redraw()
func _draw():
draw_texture(texture, Vector2())
using Godot;
public partial class MyNode2D : Node2D
{
private Texture2D _texture;
[Export]
public Texture2D Texture
{
get
{
return _texture;
}
set
{
_texture = value;
QueueRedraw();
}
}
public override void _Draw()
{
DrawTexture(_texture, new Vector2());
}
}
Para verlo en acción, puede configurar la textura para que sea el ícono de Godot en el editor arrastrando y soltando el icon.svg predeterminado desde la pestaña FileSystem a la propiedad Textura en la pestalla Inspector. Al cambiar el valor de la propiedad Textura mientras se ejecuta el script anterior, la textura también cambiará automáticamente.
En algunos casos, podríamos necesitar redibujar en cada frame. Para esto, llama queue_redraw desde el método _process tal que así:
extends Node2D
func _draw():
pass # Your draw commands here.
func _process(_delta):
queue_redraw()
using Godot;
public partial class MyNode2D : Node2D
{
public override void _Draw()
{
// Your draw commands here.
}
public override void _Process(double delta)
{
QueueRedraw();
}
}
Coordenadas y ancho de línea
La API de dibujo utiliza el sistema de coordenadas de CanvasItem, no necesariamente coordenadas de píxeles. Esto significa que _draw() usa el espacio de coordenadas creado después de aplicar la transformación de CanvasItem. Además, se puede aplicar una transformación personalizada encima usando draw_set_transform o draw_set_transform_matrix.
Al usar draw_line, debes considerar el ancho de la línea. Cuando se utiliza un ancho de tamaño impar, la posición de los puntos inicial y final se debe cambiar en 0.5 para mantener la línea centrada, como se muestra a continuación.
func _draw():
draw_line(Vector2(1.5, 1.0), Vector2(1.5, 4.0), Color.GREEN, 1.0)
draw_line(Vector2(4.0, 1.0), Vector2(4.0, 4.0), Color.GREEN, 2.0)
draw_line(Vector2(7.5, 1.0), Vector2(7.5, 4.0), Color.GREEN, 3.0)
public override void _Draw()
{
DrawLine(new Vector2(1.5f, 1.0f), new Vector2(1.5f, 4.0f), Colors.Green, 1.0f);
DrawLine(new Vector2(4.0f, 1.0f), new Vector2(4.0f, 4.0f), Colors.Green, 2.0f);
DrawLine(new Vector2(7.5f, 1.0f), new Vector2(7.5f, 4.0f), Colors.Green, 3.0f);
}
Lo mismo se aplica al método draw_rect con filled = false.
func _draw():
draw_rect(Rect2(1.0, 1.0, 3.0, 3.0), Color.GREEN)
draw_rect(Rect2(5.5, 1.5, 2.0, 2.0), Color.GREEN, false, 1.0)
draw_rect(Rect2(9.0, 1.0, 5.0, 5.0), Color.GREEN)
draw_rect(Rect2(16.0, 2.0, 3.0, 3.0), Color.GREEN, false, 2.0)
public override void _Draw()
{
DrawRect(new Rect2(1.0f, 1.0f, 3.0f, 3.0f), Colors.Green);
DrawRect(new Rect2(5.5f, 1.5f, 2.0f, 2.0f), Colors.Green, false, 1.0f);
DrawRect(new Rect2(9.0f, 1.0f, 5.0f, 5.0f), Colors.Green);
DrawRect(new Rect2(16.0f, 2.0f, 3.0f, 3.0f), Colors.Green, false, 2.0f);
}
Dibujo antialiasing
Godot ofrece parámetros de método en draw_line para habilitar el antialiasing, pero no todos los métodos personalizados de dibujo ofrecen este parámetro suavizado.
Para los métodos de dibujo personalizados que no proporcionan un parámetro antialiased, puede habilitar MSAA 2D, lo que afecta el renderizado en todo el viewport. Esto proporciona un antialiasing de alta calidad, pero con un mayor costo de rendimiento y solo en elementos específicos. Consulta Antialiasing 2D: para obtener más información.
Aquí hay una comparación de una línea de ancho mínimo (width=-1) dibujada con antialiased=false, antialiased=true y antialiased=false con MSAA 2D 2x , 4x y 8x habilitados.
Herramientas
Dibujar tus propios nodos también puede ser conveniente mientras los ejecutas en el editor. Esto puede usarse como una preview o visualización de alguna funcionalidad o comportamiento del mismo.
Para hacer esto, puede usar la anotación de herramienta tanto en GDScript como en C#. Consulta el siguiente ejemplo y Ejecutando código en el editor para obtener más información.
Ejemplo 1: dibujando una figura personalizada
Ahora usaremos la funcionalidad de dibujo personalizado de Godot Engine para dibujar algo para lo que Godot no ofrece ninguna función. Recrearemos el logo de Godot pero con código, utilizando únicamente las funciones de dibujo.
Tendrá que programar una función para realizar esto y dibujarla Ud. mismo.
Nota
The following instructions use a fixed set of coordinates that could be too small for high resolution screens (larger than 1080p). If that is your case, and the drawing is too small consider increasing your window scale in the project setting Display > Window > Stretch > Scale to adjust the project to a higher resolution (a 2 or 4 scale tends to work well).
Dibujando una forma de un polígono personalizada
Si bien hay un nodo dedicado para dibujar polígonos personalizados ( Polygon2D), en este caso usaremos exclusivamente funciones de dibujo de nivel inferior para combinarlas en el mismo nodo y poder crear formas más complejas más adelante. .
Primero, definiremos un conjunto de puntos (o coordenadas X e Y) que formarán la base de nuestra forma:
extends Node2D
var coords_head : Array = [
[ 22.952, 83.271 ], [ 28.385, 98.623 ],
[ 53.168, 107.647 ], [ 72.998, 107.647 ],
[ 99.546, 98.623 ], [ 105.048, 83.271 ],
[ 105.029, 55.237 ], [ 110.740, 47.082 ],
[ 102.364, 36.104 ], [ 94.050, 40.940 ],
[ 85.189, 34.445 ], [ 85.963, 24.194 ],
[ 73.507, 19.930 ], [ 68.883, 28.936 ],
[ 59.118, 28.936 ], [ 54.494, 19.930 ],
[ 42.039, 24.194 ], [ 42.814, 34.445 ],
[ 33.951, 40.940 ], [ 25.637, 36.104 ],
[ 17.262, 47.082 ], [ 22.973, 55.237 ]
]
using Godot;
public partial class MyNode2D : Node2D
{
private float[,] _coordsHead =
{
{ 22.952f, 83.271f }, { 28.385f, 98.623f },
{ 53.168f, 107.647f }, { 72.998f, 107.647f },
{ 99.546f, 98.623f }, { 105.048f, 83.271f },
{ 105.029f, 55.237f }, { 110.740f, 47.082f },
{ 102.364f, 36.104f }, { 94.050f, 40.940f },
{ 85.189f, 34.445f }, { 85.963f, 24.194f },
{ 73.507f, 19.930f }, { 68.883f, 28.936f },
{ 59.118f, 28.936f }, { 54.494f, 19.930f },
{ 42.039f, 24.194f }, { 42.814f, 34.445f },
{ 33.951f, 40.940f }, { 25.637f, 36.104f },
{ 17.262f, 47.082f }, { 22.973f, 55.237f }
};
}
Este formato, aunque compacto, no es el que entiende Godot para dibujar un polígono. En un escenario diferente podríamos tener que cargar estas coordenadas desde un archivo o calcular las posiciones mientras la aplicación se está ejecutando, por lo que puede ser necesaria alguna transformación.
Para transformar estas coordenadas al formato correcto, crearemos un nuevo método float_array_to_Vector2Array(). Luego anularemos la función _ready(), que Godot llamará solo una vez -al inicio de la ejecución- para cargar esas coordenadas en una variable:
var head : PackedVector2Array
func float_array_to_Vector2Array(coords : Array) -> PackedVector2Array:
# Convert the array of floats into a PackedVector2Array.
var array : PackedVector2Array = []
for coord in coords:
array.append(Vector2(coord[0], coord[1]))
return array
func _ready():
head = float_array_to_Vector2Array(coords_head);
private Vector2[] _head;
private Vector2[] FloatArrayToVector2Array(float[,] coords)
{
// Convert the array of floats into an array of Vector2.
int size = coords.GetUpperBound(0);
Vector2[] array = new Vector2[size + 1];
for (int i = 0; i <= size; i++)
{
array[i] = new Vector2(coords[i, 0], coords[i, 1]);
}
return array;
}
public override void _Ready()
{
_head = FloatArrayToVector2Array(_coordsHead);
}
Para finalmente dibujar nuestra primera forma, usaremos el método draw_polygon y pasaremos los puntos (como una matriz de coordenadas Vector2) y su color, de esta manera:
func _draw():
# We are going to paint with this color.
var godot_blue : Color = Color("478cbf")
# We pass the PackedVector2Array to draw the shape.
draw_polygon(head, [ godot_blue ])
public override void _Draw()
{
// We are going to paint with this color.
Color godotBlue = new Color("478cbf");
// We pass the array of Vector2 to draw the shape.
DrawPolygon(_head, [godotBlue]);
}
Al ejecutarse deberías ver algo como esto:
Ten en cuenta que la parte inferior del logotipo parece segmentada; esto se debe a que se utilizó una cantidad baja de puntos para definir esa parte. Para simular una curva suave, podríamos agregar más puntos a nuestra matriz, o tal vez usar una función matemática para interpolar una curva y crear una forma suave a partir del código (ver ejemplo 2).
Los polígonos siempre conectarán su último punto definido con el primero para tener una forma cerrada.
Dibujar líneas conectadas
Dibujar una secuencia de líneas conectadas que no se cierran para formar un polígono es muy similar al método anterior. Usaremos un conjunto de líneas conectadas para dibujar la boca del logo de Godot.
Primero, definiremos la lista de coordenadas que forman la forma de la boca, de esta manera:
var coords_mouth = [
[ 22.817, 81.100 ], [ 38.522, 82.740 ],
[ 39.001, 90.887 ], [ 54.465, 92.204 ],
[ 55.641, 84.260 ], [ 72.418, 84.177 ],
[ 73.629, 92.158 ], [ 88.895, 90.923 ],
[ 89.556, 82.673 ], [ 105.005, 81.100 ]
]
private float[,] _coordsMouth =
{
{ 22.817f, 81.100f }, { 38.522f, 82.740f },
{ 39.001f, 90.887f }, { 54.465f, 92.204f },
{ 55.641f, 84.260f }, { 72.418f, 84.177f },
{ 73.629f, 92.158f }, { 88.895f, 90.923f },
{ 89.556f, 82.673f }, { 105.005f, 81.100f }
};
Cargaremos estas coordenadas en una variable y definiremos una variable adicional con el grosor de línea configurable:
var mouth : PackedVector2Array
var _mouth_width : float = 4.4
func _ready():
head = float_array_to_Vector2Array(coords_head);
mouth = float_array_to_Vector2Array(coords_mouth);
private Vector2[] _mouth;
private float _mouthWidth = 4.4f;
public override void _Ready()
{
_head = FloatArrayToVector2Array(_coordsHead);
_mouth = FloatArrayToVector2Array(_coordsMouth);
}
Y finalmente usaremos el método draw_polyline para dibujar la línea, de la siguiente manera:
func _draw():
# We will use white to draw the line.
var white : Color = Color.WHITE
var godot_blue : Color = Color("478cbf")
draw_polygon(head, [ godot_blue ])
# We draw the while line on top of the previous shape.
draw_polyline(mouth, white, _mouth_width)
public override void _Draw()
{
// We will use white to draw the line.
Color white = Colors.White;
Color godotBlue = new Color("478cbf");
DrawPolygon(_head, [godotBlue]);
// We draw the while line on top of the previous shape.
DrawPolyline(_mouth, white, _mouthWidth);
}
Deberías obtener la siguiente salida:
Unlike draw_polygon(), polylines can only have a single unique color
for all its points (the second argument). This method has 2 additional
arguments: the width of the line (which is as small as possible by default)
and enabling or disabling the antialiasing (it is disabled by default).
El orden de las llamadas _draw es importante; al igual que con las posiciones de los Nodos en la jerarquía del árbol, las diferentes formas se dibujarán de arriba a abajo, lo que dará como resultado que las últimas formas oculten las anteriores si se superponen. En este caso queremos que la boca esté dibujada sobre la cabeza, así que la ponemos después.
Observe cómo podemos definir los colores de diferentes formas, ya sea con un código hexadecimal o un nombre de color predefinido. Consulta la clase Color para conocer otras constantes y formas de definir colores.
Dibujando círculos
Para crear los ojos, vamos a añadir 4 llamadas adicionales para dibujar las formas de los ojos, en diferentes tamaños, colores y posiciones.
Para dibujar un círculo, colóquelo según su centro usando el método draw_circle. El primer parámetro es un Vector2 con las coordenadas de su centro, el segundo es su radio y el tercero es su color:
func _draw():
var white : Color = Color.WHITE
var godot_blue : Color = Color("478cbf")
var grey : Color = Color("414042")
draw_polygon(head, [ godot_blue ])
draw_polyline(mouth, white, _mouth_width)
# Four circles for the 2 eyes: 2 white, 2 grey.
draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
draw_circle(Vector2(43.423, 65.92), 6.246, grey)
draw_circle(Vector2(84.626, 66.008), 6.246, grey)
public override void _Draw()
{
Color white = Colors.White;
Color godotBlue = new Color("478cbf");
Color grey = new Color("414042");
DrawPolygon(_head, [godotBlue]);
DrawPolyline(_mouth, white, _mouthWidth);
// Four circles for the 2 eyes: 2 white, 2 grey.
DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
}
Al ejecutarse deberías ver algo como esto:
Para arcos parciales y sin relleno (porciones de una forma de círculo entre ciertos ángulos arbitrarios), puede usar el método draw_arc.
Dibujar líneas
Para dibujar la última forma (la nariz) usaremos una línea para aproximarla.
draw_line se puede usar para dibujar un solo segmento proporcionando sus coordenadas inicial y final como argumentos, como éste:
func _draw():
var white : Color = Color.WHITE
var godot_blue : Color = Color("478cbf")
var grey : Color = Color("414042")
draw_polygon(head, [ godot_blue ])
draw_polyline(mouth, white, _mouth_width)
draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
draw_circle(Vector2(43.423, 65.92), 6.246, grey)
draw_circle(Vector2(84.626, 66.008), 6.246, grey)
# Draw a short but thick white vertical line for the nose.
draw_line(Vector2(64.273, 60.564), Vector2(64.273, 74.349), white, 5.8)
public override void _Draw()
{
Color white = Colors.White;
Color godotBlue = new Color("478cbf");
Color grey = new Color("414042");
DrawPolygon(_head, [godotBlue]);
DrawPolyline(_mouth, white, _mouthWidth);
DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
// Draw a short but thick white vertical line for the nose.
DrawLine(new Vector2(64.273f, 60.564f), new Vector2(64.273f, 74.349f),
white, 5.8f);
}
Ahora deberías ser capaz de ver la siguiente figura en pantalla:
Ten en cuenta que si se van a dibujar varias líneas no conectadas al mismo tiempo, puede obtener un mejor rendimiento dibujándolas todas en una sola llamada, utilizando el método draw_multiline.
Dibujando texto
While using the Label Node is the most common way to add text to your application, the low-level _draw function includes functionality to add text to your custom Node drawing. We will use it to add the name "GODOT" under the robot head.
Usaremos el método draw_string para hacerlo, de este modo:
var default_font : Font = ThemeDB.fallback_font;
func _draw():
var white : Color = Color.WHITE
var godot_blue : Color = Color("478cbf")
var grey : Color = Color("414042")
draw_polygon(head, [ godot_blue ])
draw_polyline(mouth, white, _mouth_width)
draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
draw_circle(Vector2(43.423, 65.92), 6.246, grey)
draw_circle(Vector2(84.626, 66.008), 6.246, grey)
draw_line(Vector2(64.273, 60.564), Vector2(64.273, 74.349), white, 5.8)
# Draw GODOT text below the logo with the default font, size 22.
draw_string(default_font, Vector2(20, 130), "GODOT",
HORIZONTAL_ALIGNMENT_CENTER, 90, 22)
private Font _defaultFont = ThemeDB.FallbackFont;
public override void _Draw()
{
Color white = Colors.White;
Color godotBlue = new Color("478cbf");
Color grey = new Color("414042");
DrawPolygon(_head, [godotBlue]);
DrawPolyline(_mouth, white, _mouthWidth);
DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
DrawLine(new Vector2(64.273f, 60.564f), new Vector2(64.273f, 74.349f),
white, 5.8f);
// Draw GODOT text below the logo with the default font, size 22.
DrawString(_defaultFont, new Vector2(20f, 130f), "GODOT",
HorizontalAlignment.Center, 90, 22);
}
Aquí primero cargamos en la variable defaultFont la fuente del tema configurada de manera predeterminada (en su lugar se puede configurar una personalizada) y luego pasamos los siguientes parámetros: fuente, posición, texto, alineación horizontal, ancho y tamaño de fuente.
Deberías ver lo siguiente en tu pantalla:
Se pueden encontrar parámetros adicionales, así como otros métodos relacionados con texto y caracteres, en la referencia de la clase CanvasItem.
Mostrar el dibujo durante la edición
Si bien hasta ahora el código puede dibujar el logotipo en una ventana en ejecución, no aparecerá en la vista 2D del editor. En ciertos casos, también le gustaría mostrar su Node2D personalizado o control en el editor, para posicionarlo y escalarlo adecuadamente, como lo hacen la mayoría de los otros nodos.
Para mostrar el logotipo directamente en el editor (sin ejecutarlo), puede usar la anotación @tool para solicitar que el dibujo personalizado del nodo también aparezca durante la edición, así:
@tool
extends Node2D
using Godot;
[Tool]
public partial class MyNode2D : Node2D
Necesitará guardar su escena, reconstruir tu proyecto (solo para C#) y recargar la escena actual manualmente. Eso se hace con la opción de menú Escena > Recargar Escena Guardada para actualizar el nodo actual en la vista 2D la primera vez que agrega o elimina la anotación @tool.
Animación
Si quisiéramos hacer que la forma personalizada cambie en tiempo de ejecución, podríamos modificar los métodos llamados o sus argumentos en tiempo de ejecución, o aplicar una transformación.
Por ejemplo, si queremos que la forma personalizada que acabamos de diseñar gire, podríamos agregar la siguiente variable y código a los métodos _ready y _process:
extends Node2D
@export var rotation_speed : float = 1 # In radians per second.
func _ready():
rotation = 0
...
func _process(delta: float):
rotation -= rotation_speed * delta
[Export]
public float RotationSpeed { get; set; } = 1.0f; // In radians per second.
public override void _Ready()
{
Rotation = 0;
...
}
public override void _Process(double delta)
{
Rotation -= RotationSpeed * (float)delta;
}
El problema con el código anterior es que debido a que hemos creado los puntos aproximadamente en un rectángulo comenzando desde la esquina superior izquierda, la coordenada (0, 0), y extendiéndose hacia la derecha y hacia abajo, vemos que la rotación se hace usando la esquina superior izquierda como pivote. Un cambio de transformación de posición en el nodo no nos ayudará aquí, ya que la transformación de rotación se aplica primero.
Si bien podríamos reescribir todas las coordenadas de los puntos para que se centren alrededor de (0, 0), incluidas las coordenadas negativas, eso requeriría mucho trabajo.
Una forma posible de solucionar esto es utilizar el método de bajo nivel draw_set_transform para solucionar este problema, traduciendo todos los puntos en el propio espacio del CanvasItem. Y luego moviéndolo de nuevo a su lugar original con un nodo de transformación normal, ya sea en el editor o en código. Sería así:
func _ready():
rotation = 0
position = Vector2(60, 60)
...
func _draw():
draw_set_transform(Vector2(-60, -60))
...
public override void _Ready()
{
Rotation = 0;
Position = new Vector2(60, 60);
...
}
public override void _Draw()
{
DrawSetTransform(new Vector2(-60.0f, -60.0f));
...
}
Este es el resultado de girar alrededor de un pivote ahora en (60, 60):
Si lo que queríamos animar era una propiedad dentro de la llamada _draw(), debemos recordar llamar a queue_redraw() para forzar una actualización, ya que de lo contrario no se actualizaría en pantalla.
Por ejemplo, así es como podemos hacer que parezca que el robot abre y cierra la boca, cambiando el ancho de la línea de su boca y siga una curva sinusoidal (sin):
var _mouth_width : float = 4.4
var _max_width : float = 7
var _time : float = 0
func _process(delta : float):
_time += delta
_mouth_width = abs(sin(_time) * _max_width)
queue_redraw()
func _draw():
...
draw_polyline(mouth, white, _mouth_width)
...
private float _mouthWidth = 4.4f;
private float _maxWidth = 7f;
private float _time = 0f;
public override void _Process(double delta)
{
_time += (float)delta;
_mouthWidth = Mathf.Abs(Mathf.Sin(_time) * _maxWidth);
QueueRedraw();
}
public override void _Draw()
{
...
DrawPolyline(_mouth, white, _mouthWidth);
...
}
Debería verse similar a esto al ejecutarse:
Please note that _mouth_width is a user defined property like any other
and it or any other used as a drawing argument can be animated using more
standard and high-level methods such as a Tween or an
AnimationPlayer Node. The only difference is
that a queue_redraw() call is needed to apply those changes so they get
shown on screen.
Ejemplo 2: dibujando una linea dinámica
El ejemplo anterior fue útil para aprender a dibujar y modificar nodos con formas y animaciones personalizadas. Esto podría tener algunas ventajas, como utilizar coordenadas y vectores exactos para dibujar, en lugar de mapas de bits, lo que significa que se escalarán bien cuando se transformen en la pantalla. En algunos casos, se podrían lograr resultados similares componiendo funcionalidades de nivel superior con nodos como sprites o AnimatedSprites cargando recursos SVG (que también son imágenes definidas con vectores) y el nodo AnimationPlayer.
En otros casos eso no será posible porque no sabremos cuál será la representación gráfica resultante antes de ejecutar el código. Aquí veremos cómo dibujar una línea dinámica cuyas coordenadas no se conocen de antemano y se ven afectadas por la entrada del usuario.
Dibujar una línea recta entre 2 puntos
Supongamos que queremos dibujar una línea recta entre 2 puntos, el primero estará fijo en la esquina superior izquierda (0, 0) y el segundo estará definido por la posición del cursor en la pantalla.
Podríamos trazar una línea dinámica entre esos 2 puntos así:
extends Node2D
var point1 : Vector2 = Vector2(0, 0)
var width : int = 10
var color : Color = Color.GREEN
var _point2 : Vector2
func _process(_delta):
var mouse_position = get_viewport().get_mouse_position()
if mouse_position != _point2:
_point2 = mouse_position
queue_redraw()
func _draw():
draw_line(point1, _point2, color, width)
using Godot;
using System;
public partial class MyNode2DLine : Node2D
{
public Vector2 Point1 { get; set; } = new Vector2(0f, 0f);
public int Width { get; set; } = 10;
public Color Color { get; set; } = Colors.Green;
private Vector2 _point2;
public override void _Process(double delta)
{
Vector2 mousePosition = GetViewport().GetMousePosition();
if (mousePosition != _point2)
{
_point2 = mousePosition;
QueueRedraw();
}
}
public override void _Draw()
{
DrawLine(Point1, _point2, Color, Width);
}
}
En este ejemplo obtenemos la posición del puntero del ratón en la ventana gráfica predeterminada en cada frame con el método get_mouse_position. Si la posición ha cambiado desde la última solicitud de dibujo (una pequeña optimización para evitar volver a dibujar en cada frame), programaremos un nuevo redibujo. Nuestro método _draw() solo tiene una línea: solicitando el dibujo de una línea verde de 10 píxeles de ancho entre la esquina superior izquierda y la posición obtenida.
El ancho, color y posición del punto inicial pueden ser configurados con las propiedades correspondientes.
Debería verse como esto al ejecutarse:
Dibujar un arco entre 2 puntos
El ejemplo anterior funciona, pero es posible que queramos unir esos 2 puntos con una forma o función diferente, que no sea una línea recta.
Intentemos ahora crear un arco (una porción de circunferencia) entre ambos puntos.
Exportar el punto de inicio de la línea, los segmentos, el ancho, el color y el suavizado nos permitirá modificar esas propiedades muy fácilmente directamente desde el panel del inspector del editor:
extends Node2D
@export var point1 : Vector2 = Vector2(0, 0)
@export_range(1, 1000) var segments : int = 100
@export var width : int = 10
@export var color : Color = Color.GREEN
@export var antialiasing : bool = false
var _point2 : Vector2
using Godot;
using System;
public partial class MyNode2DLine : Node2D
{
[Export]
public Vector2 Point1 { get; set; } = new Vector2(0f, 0f);
[Export]
public float Length { get; set; } = 350f;
[Export(PropertyHint.Range, "1,1000,")]
public int Segments { get; set; } = 100;
[Export]
public int Width { get; set; } = 10;
[Export]
public Color Color { get; set; } = Colors.Green;
[Export]
public bool AntiAliasing { get; set; } = false;
private Vector2 _point2;
}
Para dibujar el arco, podemos usar el método draw_arc. Hay muchos arcos que pasan por 2 puntos, por eso elegiremos para este ejemplo el semicírculo que tiene su centro en el punto medio entre los 2 puntos iniciales.
Calcular este arco será más complejo que en el caso de la recta:
func _draw():
# Average points to get center.
var center : Vector2 = Vector2((_point2.x + point1.x) / 2,
(_point2.y + point1.y) / 2)
# Calculate the rest of the arc parameters.
var radius : float = point1.distance_to(_point2) / 2
var start_angle : float = (_point2 - point1).angle()
var end_angle : float = (point1 - _point2).angle()
if end_angle < 0: # end_angle is likely negative, normalize it.
end_angle += TAU
# Finally, draw the arc.
draw_arc(center, radius, start_angle, end_angle, segments, color,
width, antialiasing)
public override void _Draw()
{
// Average points to get center.
Vector2 center = new Vector2((_point2.X + Point1.X) / 2.0f,
(_point2.Y + Point1.Y) / 2.0f);
// Calculate the rest of the arc parameters.
float radius = Point1.DistanceTo(_point2) / 2.0f;
float startAngle = (_point2 - Point1).Angle();
float endAngle = (Point1 - _point2).Angle();
if (endAngle < 0.0f) // endAngle is likely negative, normalize it.
{
endAngle += Mathf.Tau;
}
// Finally, draw the arc.
DrawArc(center, radius, startAngle, endAngle, Segments, Color,
Width, AntiAliasing);
}
El centro del semicírculo será el punto medio entre ambos puntos. El radio será la mitad de la distancia entre ambos puntos. Los ángulos inicial y final serán los ángulos del vector del punto1 al punto2 y viceversa. Tenga en cuenta que tuvimos que normalizar end_angle en valores positivos porque si end_angle es menor que start_angle, el arco se dibujará en el sentido contrario a las agujas del reloj, lo cual no queremos en este caso (el arco estaría al revés).
El resultado debería ser algo como esto, con el arco bajando y entre los puntos:
Siéntete libre de jugar con los parámetros del inspector para obtener diferentes resultados: cambia el color, el ancho, el antialiasing y aumenta el número de segmentos para aumentar la suavidad de la curva, a costa de un rendimiento adicional.