2
0
Эх сурвалжийг харах

Merge pull request #7532 from jynus/drawing

Update custom drawing in 2d tutorial to Godot4
Matthew 1 жил өмнө
parent
commit
ded3f08a0f

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

@@ -6,21 +6,19 @@ Custom drawing in 2D
 Introduction
 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:
 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
 -  Drawing shapes or logic that existing nodes can't do, such as an image
    with trails or a special animated polygon.
    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,
 -  Making a custom UI control. There are plenty of controls available,
    but when you have unusual needs, you will likely need a custom
    but when you have unusual needs, you will likely need a custom
    control.
    control.
@@ -30,7 +28,8 @@ Drawing
 
 
 Add a script to any :ref:`CanvasItem <class_CanvasItem>`
 Add a script to any :ref:`CanvasItem <class_CanvasItem>`
 derived node, like :ref:`Control <class_Control>` or
 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
@@ -38,45 +37,49 @@ derived node, like :ref:`Control <class_Control>` or
     extends Node2D
     extends Node2D
 
 
     func _draw():
     func _draw():
-        # Your draw commands here
-        pass
+        pass  # Your draw commands here.
 
 
  .. code-tab:: csharp
  .. 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>`
 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
 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.
 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
 
 
     extends Node2D
     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():
     func _draw():
         draw_texture(texture, Vector2())
         draw_texture(texture, Vector2())
@@ -87,8 +90,10 @@ redrawn if modified:
 
 
     public partial class MyNode2D : Node2D
     public partial class MyNode2D : Node2D
     {
     {
-        private Texture _texture;
-        public Texture Texture
+        private Texture2D _texture;
+
+        [Export]
+        public Texture2D Texture
         {
         {
             get
             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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. code-tab:: gdscript GDScript
@@ -117,21 +129,20 @@ call ``queue_redraw()`` from the ``_process()`` callback, like this:
     extends Node2D
     extends Node2D
 
 
     func _draw():
     func _draw():
-        # Your draw commands here
-        pass
+        pass  # Your draw commands here.
 
 
-    func _process(delta):
+    func _process(_delta):
         queue_redraw()
         queue_redraw()
 
 
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
     using Godot;
     using Godot;
 
 
-    public partial class CustomNode2D : Node2D
+    public partial class MyNode2D : Node2D
     {
     {
         public override void _Draw()
         public override void _Draw()
         {
         {
-            // Your draw commands here
+            // Your draw commands here.
         }
         }
 
 
         public override void _Process(double delta)
         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
 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<class_CanvasItem_method_draw_set_transform>` or
 :ref:`draw_set_transform_matrix<class_CanvasItem_method_draw_set_transform_matrix>`.
 :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
 .. 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);
         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
 .. 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);
         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
 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. 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
  .. 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::
 .. 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
  .. code-tab:: gdscript GDScript
 
 
     func _draw():
     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
  .. code-tab:: csharp
 
 
     public override void _Draw()
     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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. 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
  .. 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. 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
  .. 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. 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
  .. 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. 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
  .. 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::
 .. tabs::
  .. code-tab:: gdscript GDScript
  .. 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()
         queue_redraw()
 
 
+    func _draw():
+        ...
+        draw_polyline(mouth, white, _mouth_width)
+        ...
+
  .. code-tab:: csharp
  .. code-tab:: csharp
 
 
+    private float _mouthWidth = 4.4f;
+    private float _maxWidth = 7f;
+    private float _time = 0f;
+
     public override void _Process(double delta)
     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