Browse Source

Update custom drawing in 2d to Godot4

This tutorial had 2 main issues: the code wasn't adapted to Godot
4 syntax and API and the suggested tutorial contained information
to workaround the (at the time) non existence of the `draw_arc()`
function.

A general update has been made, keeping the general structure but
changing the example for something more useful and fun
(replicating the godot logo with draw primitives) and an
additional example with a dynamicaly built drawing (dynamic line).

Too many informalities have been removed on the initial paragraph.
Fixed references to Texture instead of Texture2D.
Fixed old style of exporting properties to the editor.
Fixed inconsistencies calling Node2D, myNode2D and customNode2D.
Moved examples to the end of the page, leaving general information
at the top, and completing that info with images and references.
Removed references to an old demo project (Tetris).

Fixes #4184
Fixes #7521

Co-authored-by: Raul Santos <[email protected]>
Co-authored-by: A Thousand Ships <[email protected]>
Jcrespo 2 years ago
parent
commit
2a164ee259

+ 789 - 259
tutorials/2d/custom_drawing_in_2d.rst

@@ -6,21 +6,19 @@ Custom drawing in 2D
 Introduction
 ------------
 
-Godot has nodes to draw sprites, polygons, particles, and all sorts of
-stuff. For most cases, this is enough. If there's no node to draw something specific
-you need, you can make any 2D node (for example, :ref:`Control <class_Control>` or
-:ref:`Node2D <class_Node2D>` based) draw custom commands.
+Godot has nodes to draw sprites, polygons, particles, text, and many other
+common game development needs. However, if you need something specific
+not covered with the standard nodes you can make any 2D node (for example,
+:ref:`Control <class_Control>` or :ref:`Node2D <class_Node2D>`-based)
+draw on screen using custom commands.
 
 Custom drawing in a 2D node is *really* useful. Here are some use cases:
 
 -  Drawing shapes or logic that existing nodes can't do, such as an image
    with trails or a special animated polygon.
--  Visualizations that are not that compatible with nodes, such as a
-   tetris board. (The tetris example uses a custom draw function to draw
-   the blocks.)
--  Drawing a large number of simple objects. Custom drawing avoids the
-   overhead of using a large number of nodes, possibly lowering memory
-   usage and improving performance.
+-  Drawing a large number of simple objects, such as a grid or a board
+   for a 2d game. Custom drawing avoids the overhead of using a large number
+   of nodes, possibly lowering memory usage and improving performance.
 -  Making a custom UI control. There are plenty of controls available,
    but when you have unusual needs, you will likely need a custom
    control.
@@ -30,7 +28,8 @@ Drawing
 
 Add a script to any :ref:`CanvasItem <class_CanvasItem>`
 derived node, like :ref:`Control <class_Control>` or
-:ref:`Node2D <class_Node2D>`. Then override the ``_draw()`` function.
+:ref:`Node2D <class_Node2D>`. Then override the
+:ref:`_draw()<class_CanvasItem_private_method__draw>` function.
 
 .. tabs::
  .. code-tab:: gdscript GDScript
@@ -38,45 +37,49 @@ derived node, like :ref:`Control <class_Control>` or
     extends Node2D
 
     func _draw():
-        # Your draw commands here
-        pass
+        pass  # Your draw commands here.
 
  .. code-tab:: csharp
 
-    public override void _Draw()
+    using Godot;
+
+    public partial class MyNode2D : Node2D
     {
-        // Your draw commands here
+        public override void _Draw()
+        {
+            // Your draw commands here.
+        }
     }
 
 Draw commands are described in the :ref:`CanvasItem <class_CanvasItem>`
-class reference. There are plenty of them.
+class reference. There are plenty of them and we will see some of them
+in the examples below.
 
 Updating
 --------
 
-The ``_draw()`` function is only called once, and then the draw commands
-are cached and remembered, so further calls are unnecessary.
+The :ref:`_draw <class_CanvasItem_private_method__draw>` function is only called
+once, and then the draw commands are cached and remembered, so further calls
+are unnecessary.
 
-If re-drawing is required because a state or something else changed,
-call :ref:`CanvasItem.queue_redraw() <class_CanvasItem_method_queue_redraw>`
+If re-drawing is required because a variable or something else changed,
+call :ref:`CanvasItem.queue_redraw <class_CanvasItem_method_queue_redraw>`
 in that same node and a new ``_draw()`` call will happen.
 
-Here is a little more complex example, a texture variable that will be
-redrawn if modified:
+Here is a little more complex example, where we have a texture variable
+that can be modified at any time, and using a
+:ref:`setter<doc_gdscript_basics_setters_getters>`, it forces a redraw
+of the texture when modified:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
     extends Node2D
 
-    @export var texture: Texture: 
-	    set = _set_texture
-
-    func _set_texture(value):
-        # If the texture variable is modified externally,
-        # this callback is called.
-        texture = value  # Texture was changed.
-        queue_redraw()  # Trigger a redraw of the node.
+    @export var texture : Texture2D:
+        set(value):
+            texture = value
+            queue_redraw()
 
     func _draw():
         draw_texture(texture, Vector2())
@@ -87,8 +90,10 @@ redrawn if modified:
 
     public partial class MyNode2D : Node2D
     {
-        private Texture _texture;
-        public Texture Texture
+        private Texture2D _texture;
+
+        [Export]
+        public Texture2D Texture
         {
             get
             {
@@ -108,8 +113,15 @@ redrawn if modified:
         }
     }
 
-In some cases, it may be desired to draw every frame. For this,
-call ``queue_redraw()`` from the ``_process()`` callback, like this:
+To see it in action, you can set the texture to be the Godot icon on the
+editor by dragging and dropping the default ``icon.svg`` from the
+``FileSystem`` tab to the Texture property on the ``Inspector`` tab.
+When changing the ``Texture`` property value while the previous script is
+running, the texture will also change automatically.
+
+In some cases, we may need to redraw every frame. For this,
+call :ref:`queue_redraw <class_CanvasItem_method_queue_redraw>`
+from the :ref:`_process <class_Node_private_method__process>` method, like this:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
@@ -117,21 +129,20 @@ call ``queue_redraw()`` from the ``_process()`` callback, like this:
     extends Node2D
 
     func _draw():
-        # Your draw commands here
-        pass
+        pass  # Your draw commands here.
 
-    func _process(delta):
+    func _process(_delta):
         queue_redraw()
 
  .. code-tab:: csharp
 
     using Godot;
 
-    public partial class CustomNode2D : Node2D
+    public partial class MyNode2D : Node2D
     {
         public override void _Draw()
         {
-            // Your draw commands here
+            // Your draw commands here.
         }
 
         public override void _Process(double delta)
@@ -140,19 +151,20 @@ call ``queue_redraw()`` from the ``_process()`` callback, like this:
         }
     }
 
-Coordinates
------------
+Coordinates and line width alignment
+------------------------------------
 
 The drawing API uses the CanvasItem's coordinate system, not necessarily pixel
-coordinates. Which means it uses the coordinate space created after applying
-the CanvasItem's transform. Additionally, you can apply a custom transform on
-top of it by using
+coordinates. This means ``_draw()`` uses the coordinate space created after
+applying the CanvasItem's transform. Additionally, you can apply a custom
+transform on top of it by using
 :ref:`draw_set_transform<class_CanvasItem_method_draw_set_transform>` or
 :ref:`draw_set_transform_matrix<class_CanvasItem_method_draw_set_transform_matrix>`.
 
-When using ``draw_line``, you should consider the width of the line.
-When using a width that is an odd size, the position should be shifted
-by ``0.5`` to keep the line centered as shown below.
+When using :ref:`draw_line <class_CanvasItem_method_draw_line>`, you should
+consider the width of the line. When using a width that is an odd size, the
+position of the start and end points should be shifted by ``0.5`` to keep the
+line centered, as shown below.
 
 .. image:: img/draw_line.png
 
@@ -173,7 +185,8 @@ by ``0.5`` to keep the line centered as shown below.
         DrawLine(new Vector2(7.5f, 1.0f), new Vector2(7.5f, 4.0f), Colors.Green, 3.0f);
     }
 
-The same applies to the ``draw_rect`` method with ``filled = false``.
+The same applies to the :ref:`draw_rect <class_CanvasItem_method_draw_rect>`
+method with ``filled = false``.
 
 .. image:: img/draw_rect.png
 
@@ -196,333 +209,850 @@ The same applies to the ``draw_rect`` method with ``filled = false``.
         DrawRect(new Rect2(16.0f, 2.0f, 3.0f, 3.0f), Colors.Green, false, 2.0f);
     }
 
-An example: drawing circular arcs
+Antialiased drawing
+-------------------
+
+Godot offers method parameters in :ref:`draw_line<class_CanvasItem_method_draw_line>`
+to enable antialiasing, but not all custom drawing methods offer this ``antialiased``
+parameter.
+
+For custom drawing methods that don't provide an ``antialiased`` parameter,
+you can enable 2D MSAA instead, which affects rendering in the entire viewport.
+This provides high-quality antialiasing, but a higher performance cost and only
+on specific elements. See :ref:`doc_2d_antialiasing` for more information.
+
+Here is a comparison of a line of minimal width (``width=-1``) drawn with
+``antialiased=false``, ``antialiased=true``, and ``antialiased=false`` with
+2D MSAA 2x, 4x, and 8x enabled.
+
+.. image:: img/draw_antialiasing_options.webp
+
+Tools
+-----
+
+Drawing your own nodes might also be desired while running them in the
+editor. This can be used as a preview or visualization of some feature or
+behavior.
+
+To do this, you can use the :ref:`tool annotation<doc_gdscript_tool_mode>`
+on both GDScript and C#. See
+:ref:`the example below<doc_draw_show_drawing_while_editing_example>` and
+:ref:`doc_running_code_in_the_editor` for more information.
+
+.. _doc_draw_custom_example_1:
+
+Example 1: drawing a custom shape
 ---------------------------------
 
 We will now use the custom drawing functionality of the Godot Engine to draw
-something that Godot doesn't provide functions for. As an example, Godot provides
-a ``draw_circle()`` function that draws a whole circle. However, what about drawing a
-portion of a circle? You will have to code a function to perform this and draw it yourself.
+something that Godot doesn't provide functions for. We will recreate the Godot
+logo but with code- only using drawing functions.
 
-Arc function
-^^^^^^^^^^^^
+You will have to code a function to perform this and draw it yourself.
+
+.. note::
 
-An arc is defined by its support circle parameters, that is, the center position
-and the radius. The arc itself is then defined by the angle it starts from
-and the angle at which it stops. These are the 4 arguments that we have to provide to our drawing function.
-We'll also provide the color value, so we can draw the arc in different colors if we wish.
+    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
+    ``Menu > Project > Project settings > display/window/stretch/scale`` to adjust
+    the project to a higher resolution (a 2 or 4 scale tends to work well).
 
-Basically, drawing a shape on the screen requires it to be decomposed into a certain number of points
-linked from one to the next. As you can imagine, the more points your shape is made of,
-the smoother it will appear, but the heavier it will also be in terms of processing cost. In general,
-if your shape is huge (or in 3D, close to the camera), it will require more points to be drawn without
-it being angular-looking. On the contrary, if your shape is small (or in 3D, far from the camera),
-you may decrease its number of points to save processing costs; this is known as *Level of Detail (LOD)*.
-In our example, we will simply use a fixed number of points, no matter the radius.
+Drawing a custom polygon shape
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+While there is a dedicated node to draw custom polygons (
+:ref:`Polygon2D <class_Polygon2D>`), we will use in this case exclusively lower
+level drawing functions to combine them on the same node and be able to create
+more complex shapes later on.
+
+First, we will define a set of points -or X and Y coordinates- that will form
+the base of our shape:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-    func draw_circle_arc(center, radius, angle_from, angle_to, color):
-        var nb_points = 32
-        var points_arc = PackedVector2Array()
-
-        for i in range(nb_points + 1):
-            var angle_point = deg_to_rad(angle_from + i * (angle_to-angle_from) / nb_points - 90)
-            points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
+    extends Node2D
 
-        for index_point in range(nb_points):
-            draw_line(points_arc[index_point], points_arc[index_point + 1], color)
+    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 ]
+    ]
 
  .. code-tab:: csharp
 
-    public void DrawCircleArc(Vector2 center, float radius, float angleFrom, float angleTo, Color color)
-    {
-        int nbPoints = 32;
-        var pointsArc = new Vector2[nbPoints + 1];
+    using Godot;
 
-        for (int i = 0; i <= nbPoints; i++)
+    public partial class MyNode2D : Node2D
+    {
+        private float[,] _coordsHead =
         {
-            float anglePoint = Mathf.DegToRad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90f);
-            pointsArc[i] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius;
-        }
+            { 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 }
+        };
+    }
+
+This format, while compact, is not the one that Godot understands to
+draw a polygon. In a different scenario we could have to load
+these coordinates from a file or calculate the positions while the
+application is running, so some transformation may be needed.
+
+To transform these coordinates into the right format, we will create a new
+method ``float_array_to_Vector2Array()``. Then we will override the ``_ready()``
+function, which Godot will call only once -at the start of the execution-
+to load those coordinates into a variable:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    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
 
-        for (int i = 0; i < nbPoints - 1; i++)
+    func _ready():
+        head = float_array_to_Vector2Array(coords_head);
+
+ .. code-tab:: csharp
+
+    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++)
         {
-            DrawLine(pointsArc[i], pointsArc[i + 1], color);
+            array[i] = new Vector2(coords[i, 0], coords[i, 1]);
         }
+        return array;
     }
 
+    public override void _Ready()
+    {
+        _head = FloatArrayToVector2Array(_coordsHead);
+    }
 
-Remember the number of points our shape has to be decomposed into? We fixed this
-number in the ``nb_points`` variable to a value of ``32``. Then, we initialize an empty
-``PackedVector2Array``, which is simply an array of ``Vector2``\ s.
-
-The next step consists of computing the actual positions of these 32 points that
-compose an arc. This is done in the first for-loop: we iterate over the number of
-points for which we want to compute the positions, plus one to include the last point.
-We first determine the angle of each point, between the starting and ending angles.
-
-The reason why each angle is decreased by 90° is that we will compute 2D positions
-out of each angle using trigonometry (you know, cosine and sine stuff...). However,
-``cos()`` and ``sin()`` use radians, not degrees. The angle of 0° (0 radian)
-starts at 3 o'clock, although we want to start counting at 12 o'clock. So we decrease
-each angle by 90° in order to start counting from 12 o'clock.
-
-The actual position of a point located on a circle at angle ``angle`` (in radians)
-is given by ``Vector2(cos(angle), sin(angle))``. Since ``cos()`` and ``sin()`` return values
-between -1 and 1, the position is located on a circle of radius 1. To have this
-position on our support circle, which has a radius of ``radius``, we simply need to
-multiply the position by ``radius``. Finally, we need to position our support circle
-at the ``center`` position, which is performed by adding it to our ``Vector2`` value.
-Finally, we insert the point in the ``PackedVector2Array`` which was previously defined.
-
-Now, we need to actually draw our points. As you can imagine, we will not simply
-draw our 32 points: we need to draw everything that is between each of them.
-We could have computed every point ourselves using the previous method, and drew
-it one by one. But this is too complicated and inefficient (except if explicitly needed),
-so we simply draw lines between each pair of points. Unless the radius of our
-support circle is big, the length of each line between a pair of points will
-never be long enough to see them. If that were to happen, we would simply need to
-increase the number of points.
-
-Draw the arc on the screen
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-We now have a function that draws stuff on the screen;
-it is time to call it inside the ``_draw()`` function:
+To finally draw our first shape, we will use the method
+:ref:`draw_polygon <class_CanvasItem_method_draw_polygon>`
+and pass the points (as an array of Vector2 coordinates) and its color,
+like this:
 
 .. tabs::
+ .. code-tab:: gdscript GDScript
+
+    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 ])
+
+ .. code-tab:: csharp
+
+    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, new Color[]{ godotBlue });
+    }
+
+When running it you should see something like this:
 
+.. image:: img/draw_godot_logo_polygon.webp
+
+Note the lower part of the logo looks segmented- this is because a low
+amount of points were used to define that part. To simulate a smooth curve,
+we could add more points to our array, or maybe use a mathematical function to
+interpolate a curve and create a smooth shape from code (see
+:ref:`example 2<doc_draw_custom_example_2>`).
+
+Polygons will always **connect its last defined point to its first
+one** in order to have a closed shape.
+
+Drawing connected lines
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Drawing a sequence of connected lines that don't close down to form a polygon
+is very similar to the previous method. We will use a connected set of lines to
+draw Godot's logo mouth.
+
+First, we will define the list of coordinates that form the mouth shape, like this:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    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 ]
+    ]
+
+ .. code-tab:: csharp
+
+    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 }
+    };
+
+We will load these coordinates into a variable and define an additional
+variable with the configurable line thickness:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    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);
+
+ .. code-tab:: csharp
+
+    private Vector2[] _mouth;
+    private float _mouthWidth = 4.4f;
+
+    public override void _Ready()
+    {
+        _head = FloatArrayToVector2Array(_coordsHead);
+        _mouth = FloatArrayToVector2Array(_coordsMouth);
+    }
+
+And finally we will use the method
+:ref:`draw_polyline <class_CanvasItem_method_draw_polyline>` to actually
+draw the line, like this:
+
+.. tabs::
  .. code-tab:: gdscript GDScript
 
     func _draw():
-        var center = Vector2(200, 200)
-        var radius = 80
-        var angle_from = 75
-        var angle_to = 195
-        var color = Color(1.0, 0.0, 0.0)
-        draw_circle_arc(center, radius, angle_from, angle_to, color)
+        # 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)
+
 
  .. code-tab:: csharp
 
     public override void _Draw()
     {
-        var center = new Vector2(200, 200);
-        float radius = 80;
-        float angleFrom = 75;
-        float angleTo = 195;
-        var color = new Color(1, 0, 0);
-        DrawCircleArc(center, radius, angleFrom, angleTo, color);
+        // We will use white to draw the line.
+        Color white = Colors.White;
+        Color godotBlue = new Color("478cbf");
+
+        DrawPolygon(_head, new Color[]{ godotBlue });
+
+        // We draw the while line on top of the previous shape.
+        DrawPolyline(_mouth, white, _mouthWidth);
     }
 
-Result:
+You should get the following output:
+
+.. image:: img/draw_godot_logo_polyline.webp
 
-.. image:: img/result_drawarc.png
+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 anti-aliasing (it is disabled by default).
 
-Arc polygon function
-^^^^^^^^^^^^^^^^^^^^
+The order of the ``_draw`` calls is important- like with the Node positions on
+the tree hierarchy, the different shapes will be drawn from top to bottom,
+resulting in the latest shapes hiding earlier ones if they overlap. In this
+case we want the mouth drawn over the head, so we put it afterwards.
 
-We can take this a step further and not only write a function that draws the plain
-portion of the disc defined by the arc, but also its shape. The method is exactly
-the same as before, except that we draw a polygon instead of lines:
+Notice how we can define colors in different ways, either with a hexadecimal
+code or a predefined color name. Check the class :ref:`Color <class_Color>` for other
+constants and ways to define Colors.
+
+Drawing circles
+^^^^^^^^^^^^^^^
+
+To create the eyes, we are going to add 4 additional calls to draw the eye
+shapes, in different sizes, colors and positions.
+
+To draw a circle, you position it based on its center using the
+:ref:`draw_circle <class_CanvasItem_method_draw_circle>` method. The first
+parameter is a :ref:`Vector2<class_Vector2>` with the coordinates of its center, the second is
+its radius, and the third is its color:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-    func draw_circle_arc_poly(center, radius, angle_from, angle_to, color):
-        var nb_points = 32
-        var points_arc = PackedVector2Array()
-        points_arc.push_back(center)
-        var colors = PackedColorArray([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)
 
-        for i in range(nb_points + 1):
-            var angle_point = deg_to_rad(angle_from + i * (angle_to - angle_from) / nb_points - 90)
-            points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
-        draw_polygon(points_arc, colors)
+        # 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)
 
  .. code-tab:: csharp
 
-    public void DrawCircleArcPoly(Vector2 center, float radius, float angleFrom, float angleTo, Color color)
+
+    public override void _Draw()
     {
-        int nbPoints = 32;
-        var pointsArc = new Vector2[nbPoints + 2];
-        pointsArc[0] = center;
-        var colors = new Color[] { color };
+        Color white = Colors.White;
+        Color godotBlue = new Color("478cbf");
+        Color grey = new Color("414042");
+
+        DrawPolygon(_head, new Color[]{ 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);
+    }
 
-        for (int i = 0; i <= nbPoints; i++)
-        {
-            float anglePoint = Mathf.DegToRad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90);
-            pointsArc[i + 1] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius;
-        }
+When executing it, you should have something like this:
+
+.. image:: img/draw_godot_logo_circle.webp
+
+
+For partial, unfilled arcs (portions of a circle shape between certain
+arbitrary angles), you can use the method
+:ref:`draw_arc <class_CanvasItem_method_draw_arc>`.
 
-        DrawPolygon(pointsArc, colors);
+Drawing lines
+^^^^^^^^^^^^^
+
+To draw the final shape (the nose) we will use a line to approximate it.
+
+:ref:`draw_line <class_CanvasItem_method_draw_line>` can be used to draw
+a single segment by providing its start and end coordinates as arguments,
+like this:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    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)
+
+ .. code-tab:: csharp
+
+    public override void _Draw()
+    {
+        Color white = Colors.White;
+        Color godotBlue = new Color("478cbf");
+        Color grey = new Color("414042");
+
+        DrawPolygon(_head, new Color[]{ 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);
     }
 
+You should now be able to see the following shape on screen:
 
-.. image:: img/result_drawarc_poly.png
+.. image:: img/draw_godot_logo_line.webp
 
-Dynamic custom drawing
-^^^^^^^^^^^^^^^^^^^^^^
+Note that if multiple unconnected lines are going to be drawn at the same time,
+you may get additional performance by drawing all of them in a single call, using
+the :ref:`draw_multiline <class_CanvasItem_method_draw_multiline>` method.
+
+Drawing text
+^^^^^^^^^^^^
 
-All right, we are now able to draw custom stuff on the screen. However, it is static;
-let's make this shape turn around the center. The solution to do this is simply
-to change the angle_from and angle_to values over time. For our example,
-we will simply increment them by 50. This increment value has to remain
-constant or else the rotation speed will change accordingly.
+While using the :ref:`Label <class_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.
 
-First, we have to make both angle_from and angle_to variables global at the top
-of our script. Also note that you can store them in other nodes and access them
-using ``get_node()``.
+We will use the :ref:`draw_string <class_CanvasItem_method_draw_string>` method
+to do it, like this:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-    extends Node2D
+    var default_font : Font = ThemeDB.fallback_font;
 
-    var rotation_angle = 50
-    var angle_from = 75
-    var angle_to = 195
+    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)
 
  .. code-tab:: csharp
 
-    using Godot;
+    private Font _defaultFont = ThemeDB.FallbackFont;
 
-    public partial class MyNode2D : Node2D
+    public override void _Draw()
     {
-        private float _rotationAngle = 50;
-        private float _angleFrom = 75;
-        private float _angleTo = 195;
+        Color white = Colors.White;
+        Color godotBlue = new Color("478cbf");
+        Color grey = new Color("414042");
+
+        DrawPolygon(_head, new Color[]{ 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);
     }
 
-We make these values change in the _process(delta) function.
+Here we first load into the defaultFont variable the configured default theme
+font (a custom one can be set instead) and then we pass the following
+parameters: font, position, text, horizontal alignment, width, and font size.
+
+You should see the following on your screen:
+
+.. image:: img/draw_godot_logo_text.webp
+
+Additional parameters as well as other methods related to text and characters
+can be found on the :ref:`CanvasItem <class_CanvasItem>` class reference.
+
+.. _doc_draw_show_drawing_while_editing_example:
 
-We also increment our angle_from and angle_to values here. However, we must not
-forget to ``wrap()`` the resulting values between 0 and 360°! That is, if the angle
-is 361°, then it is actually 1°. If you don't wrap these values, the script will
-work correctly, but the angle values will grow bigger and bigger over time until
-they reach the maximum integer value Godot can manage (``2^31 - 1``).
-When this happens, Godot may crash or produce unexpected behavior.
+Show the drawing while editing
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Finally, we must not forget to call the ``queue_redraw()`` function, which automatically
-calls ``_draw()``. This way, you can control when you want to refresh the frame.
+While the code so far is able to draw the logo on a running window, it will
+not show up on the ``2D view`` on the editor. In certain cases you would
+also like to show your custom Node2D or control on the editor, to position
+and scale it appropriately, like most other nodes do.
+
+To show the logo directly on the editor (without running it), you can use the
+:ref:`@tool<doc_gdscript_tool_mode>` annotation to request the custom drawing
+of the node to also appear while editing, like this:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-    func _process(delta):
-        angle_from += rotation_angle
-        angle_to += rotation_angle
+    @tool
+    extends Node2D
 
-        # We only wrap angles when both of them are bigger than 360.
-        if angle_from > 360 and angle_to > 360:
-            angle_from = wrapf(angle_from, 0, 360)
-            angle_to = wrapf(angle_to, 0, 360)
-        queue_redraw()
+ .. code-tab:: csharp
+
+    using Godot;
+
+    [Tool]
+    public partial class MyNode2D : Node2D
+
+You will need to save your scene, rebuild your project (for C# only) and reload
+the current scene manually at the menu option ``Scene > Reload Saved Scene``
+to refresh the current node in the ``2D`` view the first time you add or remove
+the ``@tool`` annotation.
+
+Animation
+^^^^^^^^^
+
+If we wanted to make the custom shape change at runtime, we could modify the
+methods called or its arguments at execution time, or apply a transform.
+
+For example, if we want the custom shape we just designed to rotate, we could add
+the following variable and code to the ``_ready`` and ``_process`` methods:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    extends Node2D
+
+    @export var rotation_speed : float = 1  # In radians per second.
+
+    func _ready():
+        rotation = 0
+        ...
+
+    func _process(delta: float):
+        rotation -= rotation_speed * delta
 
  .. code-tab:: csharp
 
-    public override void _Process(double delta)
+    [Export]
+    public float RotationSpeed { get; set; } = 1.0f;  // In radians per second.
+
+    public override void _Ready()
     {
-        _angleFrom += _rotationAngle;
-        _angleTo += _rotationAngle;
+        Rotation = 0;
+        ...
+    }
 
-        // We only wrap angles when both of them are bigger than 360.
-        if (_angleFrom > 360 && _angleTo > 360)
-        {
-            _angleFrom = Mathf.Wrap(_angleFrom, 0, 360);
-            _angleTo = Mathf.Wrap(_angleTo, 0, 360);
-        }
-        QueueRedraw();
+    public override void _Process(double delta)
+    {
+        Rotation -= RotationSpeed * (float)delta;
     }
 
+The problem with the above code is that because we have created the points
+approximately on a rectangle starting from the upper left corner, the ``(0, 0)``
+coordinate and extending to the right and down, we see that the rotation is done
+using the top left corner as pivot. A position transform change on the node
+won't help us here, as the rotation transform is applied first.
+
+While we could rewrite all of the points' coordinates to be centered around
+``(0, 0)``, including negative coordinates, that would be a lot of work.
 
-Also, don't forget to modify the ``_draw()`` function to make use of these variables:
+One possible way to work around this is to use the lower level
+:ref:`draw_set_transform<class_CanvasItem_method_draw_set_transform>`
+method to fix this issue, translating all points in the CanvasItem's own space,
+and then moving it back to its original place with a regular node transform,
+either in the editor or in code, like this:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-     func _draw():
-        var center = Vector2(200, 200)
-        var radius = 80
-        var color = Color(1.0, 0.0, 0.0)
 
-        draw_circle_arc( center, radius, angle_from, angle_to, color )
+    func _ready():
+        rotation = 0
+        position = Vector2(60, 60)
+        ...
+
+    func _draw():
+        draw_set_transform(Vector2(-60, -60))
+        ...
 
  .. code-tab:: csharp
 
-    public override void _Draw()
+    public override void _Ready()
     {
-        var center = new Vector2(200, 200);
-        float radius = 80;
-        var color = new Color(1, 0, 0);
+        Rotation = 0;
+        Position = new Vector2(60, 60);
+        ...
+    }
 
-        DrawCircleArc(center, radius, _angleFrom, _angleTo, color);
+    public override void _Draw()
+    {
+        DrawSetTransform(new Vector2(-60.0f, -60.0f));
+        ...
     }
 
+This is the result, rotating around a pivot now on ``(60, 60)``:
 
-Let's run!
-It works, but the arc is rotating insanely fast! What's wrong?
+.. image:: img/draw_godot_rotation.webp
 
-The reason is that your GPU is actually displaying the frames as fast as it can.
-We need to "normalize" the drawing by this speed; to achieve that, we have to make
-use of the ``delta`` parameter of the ``_process()`` function. ``delta`` contains the
-time elapsed between the two last rendered frames. It is generally small
-(about 0.0003 seconds, but this depends on your hardware), so using ``delta`` to
-control your drawing ensures that your program runs at the same speed on
-everybody's hardware.
+If what we wanted to animate was a property inside the ``_draw()`` call, we must remember to
+call ``queue_redraw()`` to force a refresh, as otherwise it would not be updated on screen.
 
-In our case, we simply need to multiply our ``rotation_angle`` variable by ``delta``
-in the ``_process()`` function. This way, our 2 angles will be increased by a much
-smaller value, which directly depends on the rendering speed.
+For example, this is how we can make the robot appear to open and close its mouth, by
+changing the width of its mouth line follow a sinusoidal (:ref:`sin<class_@globalscope_method_sin>`) curve:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-    func _process(delta):
-        angle_from += rotation_angle * delta
-        angle_to += rotation_angle * delta
+    var _mouth_width : float = 4.4
+    var _max_width : float = 7
+    var _time : float = 0
 
-        # We only wrap angles when both of them are bigger than 360.
-        if angle_from > 360 and angle_to > 360:
-            angle_from = wrapf(angle_from, 0, 360)
-            angle_to = wrapf(angle_to, 0, 360)
+    func _process(delta : float):
+        _time += delta
+        _mouth_width = abs(sin(_time) * _max_width)
         queue_redraw()
 
+    func _draw():
+        ...
+        draw_polyline(mouth, white, _mouth_width)
+        ...
+
  .. code-tab:: csharp
 
+    private float _mouthWidth = 4.4f;
+    private float _maxWidth = 7f;
+    private float _time = 0f;
+
     public override void _Process(double delta)
     {
-        _angleFrom += _rotationAngle * (float)delta;
-        _angleTo += _rotationAngle * (float)delta;
+        _time += (float)delta;
+        _mouthWidth = Mathf.Abs(Mathf.Sin(_time) * _maxWidth);
+        QueueRedraw();
+    }
+
+    public override void _Draw()
+    {
+        ...
+        DrawPolyline(_mouth, white, _mouthWidth);
+        ...
+    }
+
+It will look somewhat like this when run:
+
+.. image:: img/draw_godot_mouth_animation.webp
+
+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 :ref:`Tween<class_Tween>` or an
+:ref:`AnimationPlayer<class_AnimationPlayer>` Node. The only difference is
+that a ``queue_redraw()`` call is needed to apply those changes so they get
+shown on screen.
+
+.. _doc_draw_custom_example_2:
+
+Example 2: drawing a dynamic line
+---------------------------------
+
+The previous example was useful to learn how to draw and modify nodes with
+custom shapes and animations. This could have some advantages, such as using
+exact coordinates and vectors for drawing, rather than bitmaps -which means
+they will scale well when transformed on screen. In some cases, similar results
+could be achieved composing higher level functionality with nodes such as
+:ref:`sprites<class_Sprite2D>` or
+:ref:`AnimatedSprites<class_AnimatedSprite2D>` loading SVG resources (which are
+also images defined with vectors) and the
+:ref:`AnimationPlayer<class_AnimationPlayer>` node.
+
+In other cases that will not be possible because we will not know what the
+resulting graphical representation will be before running the code. Here we
+will see how to draw a dynamic line whose coordinates are not known beforehand,
+and are affected by the user's input.
+
+Drawing a straight line between 2 points
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Let's assume we want to draw a straight line between 2 points, the first one
+will be fixed on the upper left corner ``(0, 0)`` and the second will be defined
+by the cursor position on screen.
+
+We could draw a dynamic line between those 2 points like this:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    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)
+
+ .. code-tab:: csharp
+
+    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;
 
-        // We only wrap angles when both of them are bigger than 360.
-        if (_angleFrom > 360 && _angleTo > 360)
+        private Vector2 _point2;
+
+        public override void _Process(double delta)
         {
-            _angleFrom = Wrap(_angleFrom, 0, 360);
-            _angleTo = Wrap(_angleTo, 0, 360);
+            Vector2 mousePosition = GetViewport().GetMousePosition();
+            if (mousePosition != _point2)
+            {
+                _point2 = mousePosition;
+                QueueRedraw();
+            }
+        }
+
+        public override void _Draw()
+        {
+            DrawLine(Point1, _point2, Color, Width);
         }
-        QueueRedraw();
     }
 
+In this example we obtain the position of the mouse in the default viewport
+every frame with the method
+:ref:`get_mouse_position <class_Viewport_method_get_mouse_position>`. If the
+position has changed since the last draw request (a small optimization to
+avoid redrawing on every frame)- we will schedule a redraw. Our ``_draw()``
+method only has one line: requesting the drawing of a green line of
+width 10 pixels between the top left corner and that obtained position.
 
-Let's run again! This time, the rotation displays fine!
+The width, color, and position of the starting point can be configured with
+with the corresponding properties.
 
-Antialiased drawing
-^^^^^^^^^^^^^^^^^^^
+It should look like this when run:
 
-Godot offers method parameters in :ref:`draw_line<class_CanvasItem_method_draw_line>`
-to enable antialiasing, but not all custom drawing methods offer this ``antialiased``
-parameter.
+.. image:: img/draw_line_between_2_points.webp
 
-For custom drawing methods that don't provide an ``antialiased`` parameter,
-you can enable 2D MSAA instead, which affects rendering in the entire viewport.
-This provides high-quality antialiasing, but a higher performance cost and only
-on specific elements. See :ref:`doc_2d_antialiasing` for more information.
+Drawing an arc between 2 points
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Tools
------
+The above example works, but we may want to join those 2 points with a
+different shape or function, other than a straight line.
 
-Drawing your own nodes might also be desired while running them in the
-editor. This can be used as a preview or visualization of some feature or
-behavior. See :ref:`doc_running_code_in_the_editor` for more information.
+Let's try now creating an arc (a portion of a circumference) between
+both points.
+
+Exporting the line starting point, segments, width, color, and antialiasing will
+allow us to modify those properties very easily directly from the editor
+inspector panel:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    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
+
+ .. code-tab:: csharp
+
+    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;
+    }
+
+.. image:: img/draw_dynamic_exported_properties.webp
+
+To draw the arc, we can use the method
+:ref:`draw_arc<class_CanvasItem_method_draw_arc>`. There are many
+arcs that pass through 2 points, so we will chose for this example
+the semicircle that has its center in the middle point between the 2 initial
+points.
+
+Calculating this arc will be more complex than in the case of the line:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    func _draw():
+        # Calculate the arc parameters.
+        var center : Vector2 = Vector2((_point2.x - point1.x) / 2,
+                                       (_point2.y - point1.y) / 2)
+        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)
+
+ .. code-tab:: csharp
+
+    public override void _Draw()
+    {
+        // Calculate the arc parameters.
+        Vector2 center = new Vector2((_point2.X - Point1.X) / 2.0f,
+                                        (_point2.Y - Point1.Y) / 2.0f);
+        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);
+    }
+
+The center of the semicircle will be the middle point between both points.
+The radius will be half the distance between both points.
+The start and end angles will be the angles of the vector from point1
+to point2 and vice-versa.
+Note we had to normalize the ``end_angle`` in positive values because if
+``end_angle`` is less than ``start_angle``, the arc will be drawn
+counter-clockwise, which we don't want in this case (the arc would be
+upside-down).
+
+The result should be something like this, with the arc going down and
+between the points:
+
+.. image:: img/draw_arc_between_2_points.webp
+
+Feel free to play with the parameters in the inspector to obtain different
+results: change the color, the width, the antialiasing, and increase the
+number of segments to increase the curve smoothness, at the cost of extra
+performance.

BIN
tutorials/2d/img/draw_antialiasing_options.webp


BIN
tutorials/2d/img/draw_arc_between_2_points.webp


BIN
tutorials/2d/img/draw_catenary_between_2_points.webp


BIN
tutorials/2d/img/draw_dynamic_exported_properties.webp


BIN
tutorials/2d/img/draw_godot_logo_circle.webp


BIN
tutorials/2d/img/draw_godot_logo_line.webp


BIN
tutorials/2d/img/draw_godot_logo_polygon.webp


BIN
tutorials/2d/img/draw_godot_logo_polyline.webp


BIN
tutorials/2d/img/draw_godot_logo_text.webp


BIN
tutorials/2d/img/draw_godot_mouth_animation.webp


BIN
tutorials/2d/img/draw_godot_rotation.webp


BIN
tutorials/2d/img/draw_line_between_2_points.webp


BIN
tutorials/2d/img/result_drawarc.png


BIN
tutorials/2d/img/result_drawarc_poly.png