custom_drawing_in_2d.rst 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059
  1. .. _doc_custom_drawing_in_2d:
  2. Custom drawing in 2D
  3. ====================
  4. Introduction
  5. ------------
  6. Godot has nodes to draw sprites, polygons, particles, text, and many other
  7. common game development needs. However, if you need something specific
  8. not covered with the standard nodes you can make any 2D node (for example,
  9. :ref:`Control <class_Control>` or :ref:`Node2D <class_Node2D>`-based)
  10. draw on screen using custom commands.
  11. Custom drawing in a 2D node is *really* useful. Here are some use cases:
  12. - Drawing shapes or logic that existing nodes can't do, such as an image
  13. with trails or a special animated polygon.
  14. - Drawing a large number of simple objects, such as a grid or a board
  15. for a 2d game. Custom drawing avoids the overhead of using a large number
  16. of nodes, possibly lowering memory usage and improving performance.
  17. - Making a custom UI control. There are plenty of controls available,
  18. but when you have unusual needs, you will likely need a custom
  19. control.
  20. Drawing
  21. -------
  22. Add a script to any :ref:`CanvasItem <class_CanvasItem>`
  23. derived node, like :ref:`Control <class_Control>` or
  24. :ref:`Node2D <class_Node2D>`. Then override the
  25. :ref:`_draw()<class_CanvasItem_private_method__draw>` function.
  26. .. tabs::
  27. .. code-tab:: gdscript GDScript
  28. extends Node2D
  29. func _draw():
  30. pass # Your draw commands here.
  31. .. code-tab:: csharp
  32. using Godot;
  33. public partial class MyNode2D : Node2D
  34. {
  35. public override void _Draw()
  36. {
  37. // Your draw commands here.
  38. }
  39. }
  40. Draw commands are described in the :ref:`CanvasItem <class_CanvasItem>`
  41. class reference. There are plenty of them and we will see some of them
  42. in the examples below.
  43. Updating
  44. --------
  45. The :ref:`_draw <class_CanvasItem_private_method__draw>` function is only called
  46. once, and then the draw commands are cached and remembered, so further calls
  47. are unnecessary.
  48. If re-drawing is required because a variable or something else changed,
  49. call :ref:`CanvasItem.queue_redraw <class_CanvasItem_method_queue_redraw>`
  50. in that same node and a new ``_draw()`` call will happen.
  51. Here is a little more complex example, where we have a texture variable
  52. that can be modified at any time, and using a
  53. :ref:`setter<doc_gdscript_basics_setters_getters>`, it forces a redraw
  54. of the texture when modified:
  55. .. tabs::
  56. .. code-tab:: gdscript GDScript
  57. extends Node2D
  58. @export var texture : Texture2D:
  59. set(value):
  60. texture = value
  61. queue_redraw()
  62. func _draw():
  63. draw_texture(texture, Vector2())
  64. .. code-tab:: csharp
  65. using Godot;
  66. public partial class MyNode2D : Node2D
  67. {
  68. private Texture2D _texture;
  69. [Export]
  70. public Texture2D Texture
  71. {
  72. get
  73. {
  74. return _texture;
  75. }
  76. set
  77. {
  78. _texture = value;
  79. QueueRedraw();
  80. }
  81. }
  82. public override void _Draw()
  83. {
  84. DrawTexture(_texture, new Vector2());
  85. }
  86. }
  87. To see it in action, you can set the texture to be the Godot icon on the
  88. editor by dragging and dropping the default ``icon.svg`` from the
  89. ``FileSystem`` tab to the Texture property on the ``Inspector`` tab.
  90. When changing the ``Texture`` property value while the previous script is
  91. running, the texture will also change automatically.
  92. In some cases, we may need to redraw every frame. For this,
  93. call :ref:`queue_redraw <class_CanvasItem_method_queue_redraw>`
  94. from the :ref:`_process <class_Node_private_method__process>` method, like this:
  95. .. tabs::
  96. .. code-tab:: gdscript GDScript
  97. extends Node2D
  98. func _draw():
  99. pass # Your draw commands here.
  100. func _process(_delta):
  101. queue_redraw()
  102. .. code-tab:: csharp
  103. using Godot;
  104. public partial class MyNode2D : Node2D
  105. {
  106. public override void _Draw()
  107. {
  108. // Your draw commands here.
  109. }
  110. public override void _Process(double delta)
  111. {
  112. QueueRedraw();
  113. }
  114. }
  115. Coordinates and line width alignment
  116. ------------------------------------
  117. The drawing API uses the CanvasItem's coordinate system, not necessarily pixel
  118. coordinates. This means ``_draw()`` uses the coordinate space created after
  119. applying the CanvasItem's transform. Additionally, you can apply a custom
  120. transform on top of it by using
  121. :ref:`draw_set_transform<class_CanvasItem_method_draw_set_transform>` or
  122. :ref:`draw_set_transform_matrix<class_CanvasItem_method_draw_set_transform_matrix>`.
  123. When using :ref:`draw_line <class_CanvasItem_method_draw_line>`, you should
  124. consider the width of the line. When using a width that is an odd size, the
  125. position of the start and end points should be shifted by ``0.5`` to keep the
  126. line centered, as shown below.
  127. .. image:: img/draw_line.png
  128. .. tabs::
  129. .. code-tab:: gdscript GDScript
  130. func _draw():
  131. draw_line(Vector2(1.5, 1.0), Vector2(1.5, 4.0), Color.GREEN, 1.0)
  132. draw_line(Vector2(4.0, 1.0), Vector2(4.0, 4.0), Color.GREEN, 2.0)
  133. draw_line(Vector2(7.5, 1.0), Vector2(7.5, 4.0), Color.GREEN, 3.0)
  134. .. code-tab:: csharp
  135. public override void _Draw()
  136. {
  137. DrawLine(new Vector2(1.5f, 1.0f), new Vector2(1.5f, 4.0f), Colors.Green, 1.0f);
  138. DrawLine(new Vector2(4.0f, 1.0f), new Vector2(4.0f, 4.0f), Colors.Green, 2.0f);
  139. DrawLine(new Vector2(7.5f, 1.0f), new Vector2(7.5f, 4.0f), Colors.Green, 3.0f);
  140. }
  141. The same applies to the :ref:`draw_rect <class_CanvasItem_method_draw_rect>`
  142. method with ``filled = false``.
  143. .. image:: img/draw_rect.png
  144. .. tabs::
  145. .. code-tab:: gdscript GDScript
  146. func _draw():
  147. draw_rect(Rect2(1.0, 1.0, 3.0, 3.0), Color.GREEN)
  148. draw_rect(Rect2(5.5, 1.5, 2.0, 2.0), Color.GREEN, false, 1.0)
  149. draw_rect(Rect2(9.0, 1.0, 5.0, 5.0), Color.GREEN)
  150. draw_rect(Rect2(16.0, 2.0, 3.0, 3.0), Color.GREEN, false, 2.0)
  151. .. code-tab:: csharp
  152. public override void _Draw()
  153. {
  154. DrawRect(new Rect2(1.0f, 1.0f, 3.0f, 3.0f), Colors.Green);
  155. DrawRect(new Rect2(5.5f, 1.5f, 2.0f, 2.0f), Colors.Green, false, 1.0f);
  156. DrawRect(new Rect2(9.0f, 1.0f, 5.0f, 5.0f), Colors.Green);
  157. DrawRect(new Rect2(16.0f, 2.0f, 3.0f, 3.0f), Colors.Green, false, 2.0f);
  158. }
  159. Antialiased drawing
  160. -------------------
  161. Godot offers method parameters in :ref:`draw_line<class_CanvasItem_method_draw_line>`
  162. to enable antialiasing, but not all custom drawing methods offer this ``antialiased``
  163. parameter.
  164. For custom drawing methods that don't provide an ``antialiased`` parameter,
  165. you can enable 2D MSAA instead, which affects rendering in the entire viewport.
  166. This provides high-quality antialiasing, but a higher performance cost and only
  167. on specific elements. See :ref:`doc_2d_antialiasing` for more information.
  168. Here is a comparison of a line of minimal width (``width=-1``) drawn with
  169. ``antialiased=false``, ``antialiased=true``, and ``antialiased=false`` with
  170. 2D MSAA 2x, 4x, and 8x enabled.
  171. .. image:: img/draw_antialiasing_options.webp
  172. Tools
  173. -----
  174. Drawing your own nodes might also be desired while running them in the
  175. editor. This can be used as a preview or visualization of some feature or
  176. behavior.
  177. To do this, you can use the :ref:`tool annotation<doc_gdscript_tool_mode>`
  178. on both GDScript and C#. See
  179. :ref:`the example below<doc_draw_show_drawing_while_editing_example>` and
  180. :ref:`doc_running_code_in_the_editor` for more information.
  181. .. _doc_draw_custom_example_1:
  182. Example 1: drawing a custom shape
  183. ---------------------------------
  184. We will now use the custom drawing functionality of the Godot Engine to draw
  185. something that Godot doesn't provide functions for. We will recreate the Godot
  186. logo but with code- only using drawing functions.
  187. You will have to code a function to perform this and draw it yourself.
  188. .. note::
  189. The following instructions use a fixed set of coordinates that could be too small
  190. for high resolution screens (larger than 1080p). If that is your case, and the
  191. drawing is too small consider increasing your window scale in the project setting
  192. :ref:`Display > Window > Stretch > Scale<class_ProjectSettings_property_display/window/stretch/scale>`
  193. to adjust the project to a higher resolution (a 2 or 4 scale tends to work well).
  194. Drawing a custom polygon shape
  195. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  196. While there is a dedicated node to draw custom polygons (
  197. :ref:`Polygon2D <class_Polygon2D>`), we will use in this case exclusively lower
  198. level drawing functions to combine them on the same node and be able to create
  199. more complex shapes later on.
  200. First, we will define a set of points -or X and Y coordinates- that will form
  201. the base of our shape:
  202. .. tabs::
  203. .. code-tab:: gdscript GDScript
  204. extends Node2D
  205. var coords_head : Array = [
  206. [ 22.952, 83.271 ], [ 28.385, 98.623 ],
  207. [ 53.168, 107.647 ], [ 72.998, 107.647 ],
  208. [ 99.546, 98.623 ], [ 105.048, 83.271 ],
  209. [ 105.029, 55.237 ], [ 110.740, 47.082 ],
  210. [ 102.364, 36.104 ], [ 94.050, 40.940 ],
  211. [ 85.189, 34.445 ], [ 85.963, 24.194 ],
  212. [ 73.507, 19.930 ], [ 68.883, 28.936 ],
  213. [ 59.118, 28.936 ], [ 54.494, 19.930 ],
  214. [ 42.039, 24.194 ], [ 42.814, 34.445 ],
  215. [ 33.951, 40.940 ], [ 25.637, 36.104 ],
  216. [ 17.262, 47.082 ], [ 22.973, 55.237 ]
  217. ]
  218. .. code-tab:: csharp
  219. using Godot;
  220. public partial class MyNode2D : Node2D
  221. {
  222. private float[,] _coordsHead =
  223. {
  224. { 22.952f, 83.271f }, { 28.385f, 98.623f },
  225. { 53.168f, 107.647f }, { 72.998f, 107.647f },
  226. { 99.546f, 98.623f }, { 105.048f, 83.271f },
  227. { 105.029f, 55.237f }, { 110.740f, 47.082f },
  228. { 102.364f, 36.104f }, { 94.050f, 40.940f },
  229. { 85.189f, 34.445f }, { 85.963f, 24.194f },
  230. { 73.507f, 19.930f }, { 68.883f, 28.936f },
  231. { 59.118f, 28.936f }, { 54.494f, 19.930f },
  232. { 42.039f, 24.194f }, { 42.814f, 34.445f },
  233. { 33.951f, 40.940f }, { 25.637f, 36.104f },
  234. { 17.262f, 47.082f }, { 22.973f, 55.237f }
  235. };
  236. }
  237. This format, while compact, is not the one that Godot understands to
  238. draw a polygon. In a different scenario we could have to load
  239. these coordinates from a file or calculate the positions while the
  240. application is running, so some transformation may be needed.
  241. To transform these coordinates into the right format, we will create a new
  242. method ``float_array_to_Vector2Array()``. Then we will override the ``_ready()``
  243. function, which Godot will call only once -at the start of the execution-
  244. to load those coordinates into a variable:
  245. .. tabs::
  246. .. code-tab:: gdscript GDScript
  247. var head : PackedVector2Array
  248. func float_array_to_Vector2Array(coords : Array) -> PackedVector2Array:
  249. # Convert the array of floats into a PackedVector2Array.
  250. var array : PackedVector2Array = []
  251. for coord in coords:
  252. array.append(Vector2(coord[0], coord[1]))
  253. return array
  254. func _ready():
  255. head = float_array_to_Vector2Array(coords_head);
  256. .. code-tab:: csharp
  257. private Vector2[] _head;
  258. private Vector2[] FloatArrayToVector2Array(float[,] coords)
  259. {
  260. // Convert the array of floats into an array of Vector2.
  261. int size = coords.GetUpperBound(0);
  262. Vector2[] array = new Vector2[size + 1];
  263. for (int i = 0; i <= size; i++)
  264. {
  265. array[i] = new Vector2(coords[i, 0], coords[i, 1]);
  266. }
  267. return array;
  268. }
  269. public override void _Ready()
  270. {
  271. _head = FloatArrayToVector2Array(_coordsHead);
  272. }
  273. To finally draw our first shape, we will use the method
  274. :ref:`draw_polygon <class_CanvasItem_method_draw_polygon>`
  275. and pass the points (as an array of Vector2 coordinates) and its color,
  276. like this:
  277. .. tabs::
  278. .. code-tab:: gdscript GDScript
  279. func _draw():
  280. # We are going to paint with this color.
  281. var godot_blue : Color = Color("478cbf")
  282. # We pass the PackedVector2Array to draw the shape.
  283. draw_polygon(head, [ godot_blue ])
  284. .. code-tab:: csharp
  285. public override void _Draw()
  286. {
  287. // We are going to paint with this color.
  288. Color godotBlue = new Color("478cbf");
  289. // We pass the array of Vector2 to draw the shape.
  290. DrawPolygon(_head, new Color[]{ godotBlue });
  291. }
  292. When running it you should see something like this:
  293. .. image:: img/draw_godot_logo_polygon.webp
  294. Note the lower part of the logo looks segmented- this is because a low
  295. amount of points were used to define that part. To simulate a smooth curve,
  296. we could add more points to our array, or maybe use a mathematical function to
  297. interpolate a curve and create a smooth shape from code (see
  298. :ref:`example 2<doc_draw_custom_example_2>`).
  299. Polygons will always **connect its last defined point to its first
  300. one** in order to have a closed shape.
  301. Drawing connected lines
  302. ^^^^^^^^^^^^^^^^^^^^^^^
  303. Drawing a sequence of connected lines that don't close down to form a polygon
  304. is very similar to the previous method. We will use a connected set of lines to
  305. draw Godot's logo mouth.
  306. First, we will define the list of coordinates that form the mouth shape, like this:
  307. .. tabs::
  308. .. code-tab:: gdscript GDScript
  309. var coords_mouth = [
  310. [ 22.817, 81.100 ], [ 38.522, 82.740 ],
  311. [ 39.001, 90.887 ], [ 54.465, 92.204 ],
  312. [ 55.641, 84.260 ], [ 72.418, 84.177 ],
  313. [ 73.629, 92.158 ], [ 88.895, 90.923 ],
  314. [ 89.556, 82.673 ], [ 105.005, 81.100 ]
  315. ]
  316. .. code-tab:: csharp
  317. private float[,] _coordsMouth =
  318. {
  319. { 22.817f, 81.100f }, { 38.522f, 82.740f },
  320. { 39.001f, 90.887f }, { 54.465f, 92.204f },
  321. { 55.641f, 84.260f }, { 72.418f, 84.177f },
  322. { 73.629f, 92.158f }, { 88.895f, 90.923f },
  323. { 89.556f, 82.673f }, { 105.005f, 81.100f }
  324. };
  325. We will load these coordinates into a variable and define an additional
  326. variable with the configurable line thickness:
  327. .. tabs::
  328. .. code-tab:: gdscript GDScript
  329. var mouth : PackedVector2Array
  330. var _mouth_width : float = 4.4
  331. func _ready():
  332. head = float_array_to_Vector2Array(coords_head);
  333. mouth = float_array_to_Vector2Array(coords_mouth);
  334. .. code-tab:: csharp
  335. private Vector2[] _mouth;
  336. private float _mouthWidth = 4.4f;
  337. public override void _Ready()
  338. {
  339. _head = FloatArrayToVector2Array(_coordsHead);
  340. _mouth = FloatArrayToVector2Array(_coordsMouth);
  341. }
  342. And finally we will use the method
  343. :ref:`draw_polyline <class_CanvasItem_method_draw_polyline>` to actually
  344. draw the line, like this:
  345. .. tabs::
  346. .. code-tab:: gdscript GDScript
  347. func _draw():
  348. # We will use white to draw the line.
  349. var white : Color = Color.WHITE
  350. var godot_blue : Color = Color("478cbf")
  351. draw_polygon(head, [ godot_blue ])
  352. # We draw the while line on top of the previous shape.
  353. draw_polyline(mouth, white, _mouth_width)
  354. .. code-tab:: csharp
  355. public override void _Draw()
  356. {
  357. // We will use white to draw the line.
  358. Color white = Colors.White;
  359. Color godotBlue = new Color("478cbf");
  360. DrawPolygon(_head, new Color[]{ godotBlue });
  361. // We draw the while line on top of the previous shape.
  362. DrawPolyline(_mouth, white, _mouthWidth);
  363. }
  364. You should get the following output:
  365. .. image:: img/draw_godot_logo_polyline.webp
  366. Unlike ``draw_polygon()``, polylines can only have a single unique color
  367. for all its points (the second argument). This method has 2 additional
  368. arguments: the width of the line (which is as small as possible by default)
  369. and enabling or disabling the antialiasing (it is disabled by default).
  370. The order of the ``_draw`` calls is important- like with the Node positions on
  371. the tree hierarchy, the different shapes will be drawn from top to bottom,
  372. resulting in the latest shapes hiding earlier ones if they overlap. In this
  373. case we want the mouth drawn over the head, so we put it afterwards.
  374. Notice how we can define colors in different ways, either with a hexadecimal
  375. code or a predefined color name. Check the class :ref:`Color <class_Color>` for other
  376. constants and ways to define Colors.
  377. Drawing circles
  378. ^^^^^^^^^^^^^^^
  379. To create the eyes, we are going to add 4 additional calls to draw the eye
  380. shapes, in different sizes, colors and positions.
  381. To draw a circle, you position it based on its center using the
  382. :ref:`draw_circle <class_CanvasItem_method_draw_circle>` method. The first
  383. parameter is a :ref:`Vector2<class_Vector2>` with the coordinates of its center, the second is
  384. its radius, and the third is its color:
  385. .. tabs::
  386. .. code-tab:: gdscript GDScript
  387. func _draw():
  388. var white : Color = Color.WHITE
  389. var godot_blue : Color = Color("478cbf")
  390. var grey : Color = Color("414042")
  391. draw_polygon(head, [ godot_blue ])
  392. draw_polyline(mouth, white, _mouth_width)
  393. # Four circles for the 2 eyes: 2 white, 2 grey.
  394. draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
  395. draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
  396. draw_circle(Vector2(43.423, 65.92), 6.246, grey)
  397. draw_circle(Vector2(84.626, 66.008), 6.246, grey)
  398. .. code-tab:: csharp
  399. public override void _Draw()
  400. {
  401. Color white = Colors.White;
  402. Color godotBlue = new Color("478cbf");
  403. Color grey = new Color("414042");
  404. DrawPolygon(_head, new Color[]{ godotBlue });
  405. DrawPolyline(_mouth, white, _mouthWidth);
  406. // Four circles for the 2 eyes: 2 white, 2 grey.
  407. DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
  408. DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
  409. DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
  410. DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
  411. }
  412. When executing it, you should have something like this:
  413. .. image:: img/draw_godot_logo_circle.webp
  414. For partial, unfilled arcs (portions of a circle shape between certain
  415. arbitrary angles), you can use the method
  416. :ref:`draw_arc <class_CanvasItem_method_draw_arc>`.
  417. Drawing lines
  418. ^^^^^^^^^^^^^
  419. To draw the final shape (the nose) we will use a line to approximate it.
  420. :ref:`draw_line <class_CanvasItem_method_draw_line>` can be used to draw
  421. a single segment by providing its start and end coordinates as arguments,
  422. like this:
  423. .. tabs::
  424. .. code-tab:: gdscript GDScript
  425. func _draw():
  426. var white : Color = Color.WHITE
  427. var godot_blue : Color = Color("478cbf")
  428. var grey : Color = Color("414042")
  429. draw_polygon(head, [ godot_blue ])
  430. draw_polyline(mouth, white, _mouth_width)
  431. draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
  432. draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
  433. draw_circle(Vector2(43.423, 65.92), 6.246, grey)
  434. draw_circle(Vector2(84.626, 66.008), 6.246, grey)
  435. # Draw a short but thick white vertical line for the nose.
  436. draw_line(Vector2(64.273, 60.564), Vector2(64.273, 74.349), white, 5.8)
  437. .. code-tab:: csharp
  438. public override void _Draw()
  439. {
  440. Color white = Colors.White;
  441. Color godotBlue = new Color("478cbf");
  442. Color grey = new Color("414042");
  443. DrawPolygon(_head, new Color[]{ godotBlue });
  444. DrawPolyline(_mouth, white, _mouthWidth);
  445. DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
  446. DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
  447. DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
  448. DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
  449. // Draw a short but thick white vertical line for the nose.
  450. DrawLine(new Vector2(64.273f, 60.564f), new Vector2(64.273f, 74.349f),
  451. white, 5.8f);
  452. }
  453. You should now be able to see the following shape on screen:
  454. .. image:: img/draw_godot_logo_line.webp
  455. Note that if multiple unconnected lines are going to be drawn at the same time,
  456. you may get additional performance by drawing all of them in a single call, using
  457. the :ref:`draw_multiline <class_CanvasItem_method_draw_multiline>` method.
  458. Drawing text
  459. ^^^^^^^^^^^^
  460. While using the :ref:`Label <class_Label>` Node is the most common way to add
  461. text to your application, the low-level `_draw` function includes functionality
  462. to add text to your custom Node drawing. We will use it to add the name "GODOT"
  463. under the robot head.
  464. We will use the :ref:`draw_string <class_CanvasItem_method_draw_string>` method
  465. to do it, like this:
  466. .. tabs::
  467. .. code-tab:: gdscript GDScript
  468. var default_font : Font = ThemeDB.fallback_font;
  469. func _draw():
  470. var white : Color = Color.WHITE
  471. var godot_blue : Color = Color("478cbf")
  472. var grey : Color = Color("414042")
  473. draw_polygon(head, [ godot_blue ])
  474. draw_polyline(mouth, white, _mouth_width)
  475. draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
  476. draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
  477. draw_circle(Vector2(43.423, 65.92), 6.246, grey)
  478. draw_circle(Vector2(84.626, 66.008), 6.246, grey)
  479. draw_line(Vector2(64.273, 60.564), Vector2(64.273, 74.349), white, 5.8)
  480. # Draw GODOT text below the logo with the default font, size 22.
  481. draw_string(default_font, Vector2(20, 130), "GODOT",
  482. HORIZONTAL_ALIGNMENT_CENTER, 90, 22)
  483. .. code-tab:: csharp
  484. private Font _defaultFont = ThemeDB.FallbackFont;
  485. public override void _Draw()
  486. {
  487. Color white = Colors.White;
  488. Color godotBlue = new Color("478cbf");
  489. Color grey = new Color("414042");
  490. DrawPolygon(_head, new Color[]{ godotBlue });
  491. DrawPolyline(_mouth, white, _mouthWidth);
  492. DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
  493. DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
  494. DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
  495. DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
  496. DrawLine(new Vector2(64.273f, 60.564f), new Vector2(64.273f, 74.349f),
  497. white, 5.8f);
  498. // Draw GODOT text below the logo with the default font, size 22.
  499. DrawString(_defaultFont, new Vector2(20f, 130f), "GODOT",
  500. HorizontalAlignment.Center, 90, 22);
  501. }
  502. Here we first load into the defaultFont variable the configured default theme
  503. font (a custom one can be set instead) and then we pass the following
  504. parameters: font, position, text, horizontal alignment, width, and font size.
  505. You should see the following on your screen:
  506. .. image:: img/draw_godot_logo_text.webp
  507. Additional parameters as well as other methods related to text and characters
  508. can be found on the :ref:`CanvasItem <class_CanvasItem>` class reference.
  509. .. _doc_draw_show_drawing_while_editing_example:
  510. Show the drawing while editing
  511. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  512. While the code so far is able to draw the logo on a running window, it will
  513. not show up on the ``2D view`` on the editor. In certain cases you would
  514. also like to show your custom Node2D or control on the editor, to position
  515. and scale it appropriately, like most other nodes do.
  516. To show the logo directly on the editor (without running it), you can use the
  517. :ref:`@tool<doc_gdscript_tool_mode>` annotation to request the custom drawing
  518. of the node to also appear while editing, like this:
  519. .. tabs::
  520. .. code-tab:: gdscript GDScript
  521. @tool
  522. extends Node2D
  523. .. code-tab:: csharp
  524. using Godot;
  525. [Tool]
  526. public partial class MyNode2D : Node2D
  527. You will need to save your scene, rebuild your project (for C# only) and reload
  528. the current scene manually at the menu option ``Scene > Reload Saved Scene``
  529. to refresh the current node in the ``2D`` view the first time you add or remove
  530. the ``@tool`` annotation.
  531. Animation
  532. ^^^^^^^^^
  533. If we wanted to make the custom shape change at runtime, we could modify the
  534. methods called or its arguments at execution time, or apply a transform.
  535. For example, if we want the custom shape we just designed to rotate, we could add
  536. the following variable and code to the ``_ready`` and ``_process`` methods:
  537. .. tabs::
  538. .. code-tab:: gdscript GDScript
  539. extends Node2D
  540. @export var rotation_speed : float = 1 # In radians per second.
  541. func _ready():
  542. rotation = 0
  543. ...
  544. func _process(delta: float):
  545. rotation -= rotation_speed * delta
  546. .. code-tab:: csharp
  547. [Export]
  548. public float RotationSpeed { get; set; } = 1.0f; // In radians per second.
  549. public override void _Ready()
  550. {
  551. Rotation = 0;
  552. ...
  553. }
  554. public override void _Process(double delta)
  555. {
  556. Rotation -= RotationSpeed * (float)delta;
  557. }
  558. The problem with the above code is that because we have created the points
  559. approximately on a rectangle starting from the upper left corner, the ``(0, 0)``
  560. coordinate and extending to the right and down, we see that the rotation is done
  561. using the top left corner as pivot. A position transform change on the node
  562. won't help us here, as the rotation transform is applied first.
  563. While we could rewrite all of the points' coordinates to be centered around
  564. ``(0, 0)``, including negative coordinates, that would be a lot of work.
  565. One possible way to work around this is to use the lower level
  566. :ref:`draw_set_transform<class_CanvasItem_method_draw_set_transform>`
  567. method to fix this issue, translating all points in the CanvasItem's own space,
  568. and then moving it back to its original place with a regular node transform,
  569. either in the editor or in code, like this:
  570. .. tabs::
  571. .. code-tab:: gdscript GDScript
  572. func _ready():
  573. rotation = 0
  574. position = Vector2(60, 60)
  575. ...
  576. func _draw():
  577. draw_set_transform(Vector2(-60, -60))
  578. ...
  579. .. code-tab:: csharp
  580. public override void _Ready()
  581. {
  582. Rotation = 0;
  583. Position = new Vector2(60, 60);
  584. ...
  585. }
  586. public override void _Draw()
  587. {
  588. DrawSetTransform(new Vector2(-60.0f, -60.0f));
  589. ...
  590. }
  591. This is the result, rotating around a pivot now on ``(60, 60)``:
  592. .. image:: img/draw_godot_rotation.webp
  593. If what we wanted to animate was a property inside the ``_draw()`` call, we must remember to
  594. call ``queue_redraw()`` to force a refresh, as otherwise it would not be updated on screen.
  595. For example, this is how we can make the robot appear to open and close its mouth, by
  596. changing the width of its mouth line follow a sinusoidal (:ref:`sin<class_@globalscope_method_sin>`) curve:
  597. .. tabs::
  598. .. code-tab:: gdscript GDScript
  599. var _mouth_width : float = 4.4
  600. var _max_width : float = 7
  601. var _time : float = 0
  602. func _process(delta : float):
  603. _time += delta
  604. _mouth_width = abs(sin(_time) * _max_width)
  605. queue_redraw()
  606. func _draw():
  607. ...
  608. draw_polyline(mouth, white, _mouth_width)
  609. ...
  610. .. code-tab:: csharp
  611. private float _mouthWidth = 4.4f;
  612. private float _maxWidth = 7f;
  613. private float _time = 0f;
  614. public override void _Process(double delta)
  615. {
  616. _time += (float)delta;
  617. _mouthWidth = Mathf.Abs(Mathf.Sin(_time) * _maxWidth);
  618. QueueRedraw();
  619. }
  620. public override void _Draw()
  621. {
  622. ...
  623. DrawPolyline(_mouth, white, _mouthWidth);
  624. ...
  625. }
  626. It will look somewhat like this when run:
  627. .. image:: img/draw_godot_mouth_animation.webp
  628. Please note that ``_mouth_width`` is a user defined property like any other
  629. and it or any other used as a drawing argument can be animated using more
  630. standard and high-level methods such as a :ref:`Tween<class_Tween>` or an
  631. :ref:`AnimationPlayer<class_AnimationPlayer>` Node. The only difference is
  632. that a ``queue_redraw()`` call is needed to apply those changes so they get
  633. shown on screen.
  634. .. _doc_draw_custom_example_2:
  635. Example 2: drawing a dynamic line
  636. ---------------------------------
  637. The previous example was useful to learn how to draw and modify nodes with
  638. custom shapes and animations. This could have some advantages, such as using
  639. exact coordinates and vectors for drawing, rather than bitmaps -which means
  640. they will scale well when transformed on screen. In some cases, similar results
  641. could be achieved composing higher level functionality with nodes such as
  642. :ref:`sprites<class_Sprite2D>` or
  643. :ref:`AnimatedSprites<class_AnimatedSprite2D>` loading SVG resources (which are
  644. also images defined with vectors) and the
  645. :ref:`AnimationPlayer<class_AnimationPlayer>` node.
  646. In other cases that will not be possible because we will not know what the
  647. resulting graphical representation will be before running the code. Here we
  648. will see how to draw a dynamic line whose coordinates are not known beforehand,
  649. and are affected by the user's input.
  650. Drawing a straight line between 2 points
  651. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  652. Let's assume we want to draw a straight line between 2 points, the first one
  653. will be fixed on the upper left corner ``(0, 0)`` and the second will be defined
  654. by the cursor position on screen.
  655. We could draw a dynamic line between those 2 points like this:
  656. .. tabs::
  657. .. code-tab:: gdscript GDScript
  658. extends Node2D
  659. var point1 : Vector2 = Vector2(0, 0)
  660. var width : int = 10
  661. var color : Color = Color.GREEN
  662. var _point2 : Vector2
  663. func _process(_delta):
  664. var mouse_position = get_viewport().get_mouse_position()
  665. if mouse_position != _point2:
  666. _point2 = mouse_position
  667. queue_redraw()
  668. func _draw():
  669. draw_line(point1, _point2, color, width)
  670. .. code-tab:: csharp
  671. using Godot;
  672. using System;
  673. public partial class MyNode2DLine : Node2D
  674. {
  675. public Vector2 Point1 { get; set; } = new Vector2(0f, 0f);
  676. public int Width { get; set; } = 10;
  677. public Color Color { get; set; } = Colors.Green;
  678. private Vector2 _point2;
  679. public override void _Process(double delta)
  680. {
  681. Vector2 mousePosition = GetViewport().GetMousePosition();
  682. if (mousePosition != _point2)
  683. {
  684. _point2 = mousePosition;
  685. QueueRedraw();
  686. }
  687. }
  688. public override void _Draw()
  689. {
  690. DrawLine(Point1, _point2, Color, Width);
  691. }
  692. }
  693. In this example we obtain the position of the mouse in the default viewport
  694. every frame with the method
  695. :ref:`get_mouse_position <class_Viewport_method_get_mouse_position>`. If the
  696. position has changed since the last draw request (a small optimization to
  697. avoid redrawing on every frame)- we will schedule a redraw. Our ``_draw()``
  698. method only has one line: requesting the drawing of a green line of
  699. width 10 pixels between the top left corner and that obtained position.
  700. The width, color, and position of the starting point can be configured with
  701. with the corresponding properties.
  702. It should look like this when run:
  703. .. image:: img/draw_line_between_2_points.webp
  704. Drawing an arc between 2 points
  705. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  706. The above example works, but we may want to join those 2 points with a
  707. different shape or function, other than a straight line.
  708. Let's try now creating an arc (a portion of a circumference) between
  709. both points.
  710. Exporting the line starting point, segments, width, color, and antialiasing will
  711. allow us to modify those properties very easily directly from the editor
  712. inspector panel:
  713. .. tabs::
  714. .. code-tab:: gdscript GDScript
  715. extends Node2D
  716. @export var point1 : Vector2 = Vector2(0, 0)
  717. @export_range(1, 1000) var segments : int = 100
  718. @export var width : int = 10
  719. @export var color : Color = Color.GREEN
  720. @export var antialiasing : bool = false
  721. var _point2 : Vector2
  722. .. code-tab:: csharp
  723. using Godot;
  724. using System;
  725. public partial class MyNode2DLine : Node2D
  726. {
  727. [Export]
  728. public Vector2 Point1 { get; set; } = new Vector2(0f, 0f);
  729. [Export]
  730. public float Length { get; set; } = 350f;
  731. [Export(PropertyHint.Range, "1,1000,")]
  732. public int Segments { get; set; } = 100;
  733. [Export]
  734. public int Width { get; set; } = 10;
  735. [Export]
  736. public Color Color { get; set; } = Colors.Green;
  737. [Export]
  738. public bool AntiAliasing { get; set; } = false;
  739. private Vector2 _point2;
  740. }
  741. .. image:: img/draw_dynamic_exported_properties.webp
  742. To draw the arc, we can use the method
  743. :ref:`draw_arc<class_CanvasItem_method_draw_arc>`. There are many
  744. arcs that pass through 2 points, so we will chose for this example
  745. the semicircle that has its center in the middle point between the 2 initial
  746. points.
  747. Calculating this arc will be more complex than in the case of the line:
  748. .. tabs::
  749. .. code-tab:: gdscript GDScript
  750. func _draw():
  751. # Calculate the arc parameters.
  752. var center : Vector2 = Vector2((_point2.x - point1.x) / 2,
  753. (_point2.y - point1.y) / 2)
  754. var radius : float = point1.distance_to(_point2) / 2
  755. var start_angle : float = (_point2 - point1).angle()
  756. var end_angle : float = (point1 - _point2).angle()
  757. if end_angle < 0: # end_angle is likely negative, normalize it.
  758. end_angle += TAU
  759. # Finally, draw the arc.
  760. draw_arc(center, radius, start_angle, end_angle, segments, color,
  761. width, antialiasing)
  762. .. code-tab:: csharp
  763. public override void _Draw()
  764. {
  765. // Calculate the arc parameters.
  766. Vector2 center = new Vector2((_point2.X - Point1.X) / 2.0f,
  767. (_point2.Y - Point1.Y) / 2.0f);
  768. float radius = Point1.DistanceTo(_point2) / 2.0f;
  769. float startAngle = (_point2 - Point1).Angle();
  770. float endAngle = (Point1 - _point2).Angle();
  771. if (endAngle < 0.0f) // endAngle is likely negative, normalize it.
  772. {
  773. endAngle += Mathf.Tau;
  774. }
  775. // Finally, draw the arc.
  776. DrawArc(center, radius, startAngle, endAngle, Segments, Color,
  777. Width, AntiAliasing);
  778. }
  779. The center of the semicircle will be the middle point between both points.
  780. The radius will be half the distance between both points.
  781. The start and end angles will be the angles of the vector from point1
  782. to point2 and vice-versa.
  783. Note we had to normalize the ``end_angle`` in positive values because if
  784. ``end_angle`` is less than ``start_angle``, the arc will be drawn
  785. counter-clockwise, which we don't want in this case (the arc would be
  786. upside-down).
  787. The result should be something like this, with the arc going down and
  788. between the points:
  789. .. image:: img/draw_arc_between_2_points.webp
  790. Feel free to play with the parameters in the inspector to obtain different
  791. results: change the color, the width, the antialiasing, and increase the
  792. number of segments to increase the curve smoothness, at the cost of extra
  793. performance.