custom_drawing_in_2d.rst 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. .. _doc_custom_drawing_in_2d:
  2. Custom drawing in 2D
  3. ====================
  4. Introduction
  5. ------------
  6. Godot has nodes to draw sprites, polygons, particles, and all sorts of
  7. stuff. For most cases, this is enough. If there's no node to draw something specific
  8. you need, you can make any 2D node (for example, :ref:`Control <class_Control>` or
  9. :ref:`Node2D <class_Node2D>` based) draw custom commands.
  10. Custom drawing in a 2D node is *really* useful. Here are some use cases:
  11. - Drawing shapes or logic that existing nodes can't do, such as an image
  12. with trails or a special animated polygon.
  13. - Visualizations that are not that compatible with nodes, such as a
  14. tetris board. (The tetris example uses a custom draw function to draw
  15. the blocks.)
  16. - Drawing a large number of simple objects. Custom drawing avoids the
  17. overhead of using a large number of nodes, possibly lowering memory
  18. usage and improving performance.
  19. - Making a custom UI control. There are plenty of controls available,
  20. but when you have unusual needs, you will likely need a custom
  21. control.
  22. Drawing
  23. -------
  24. Add a script to any :ref:`CanvasItem <class_CanvasItem>`
  25. derived node, like :ref:`Control <class_Control>` or
  26. :ref:`Node2D <class_Node2D>`. Then override the ``_draw()`` function.
  27. .. tabs::
  28. .. code-tab:: gdscript GDScript
  29. extends Node2D
  30. func _draw():
  31. # Your draw commands here
  32. pass
  33. .. code-tab:: csharp
  34. public override void _Draw()
  35. {
  36. // Your draw commands here
  37. }
  38. Draw commands are described in the :ref:`CanvasItem <class_CanvasItem>`
  39. class reference. There are plenty of them.
  40. Updating
  41. --------
  42. The ``_draw()`` function is only called once, and then the draw commands
  43. are cached and remembered, so further calls are unnecessary.
  44. If re-drawing is required because a state or something else changed,
  45. call :ref:`CanvasItem.queue_redraw() <class_CanvasItem_method_queue_redraw>`
  46. in that same node and a new ``_draw()`` call will happen.
  47. Here is a little more complex example, a texture variable that will be
  48. redrawn if modified:
  49. .. tabs::
  50. .. code-tab:: gdscript GDScript
  51. extends Node2D
  52. @export var texture: Texture:
  53. set = _set_texture
  54. func _set_texture(value):
  55. # If the texture variable is modified externally,
  56. # this callback is called.
  57. texture = value # Texture was changed.
  58. queue_redraw() # Trigger a redraw of the node.
  59. func _draw():
  60. draw_texture(texture, Vector2())
  61. .. code-tab:: csharp
  62. using Godot;
  63. public partial class MyNode2D : Node2D
  64. {
  65. private Texture _texture;
  66. public Texture Texture
  67. {
  68. get
  69. {
  70. return _texture;
  71. }
  72. set
  73. {
  74. _texture = value;
  75. QueueRedraw();
  76. }
  77. }
  78. public override void _Draw()
  79. {
  80. DrawTexture(_texture, new Vector2());
  81. }
  82. }
  83. In some cases, it may be desired to draw every frame. For this,
  84. call ``queue_redraw()`` from the ``_process()`` callback, like this:
  85. .. tabs::
  86. .. code-tab:: gdscript GDScript
  87. extends Node2D
  88. func _draw():
  89. # Your draw commands here
  90. pass
  91. func _process(delta):
  92. queue_redraw()
  93. .. code-tab:: csharp
  94. using Godot;
  95. public partial class CustomNode2D : Node2D
  96. {
  97. public override void _Draw()
  98. {
  99. // Your draw commands here
  100. }
  101. public override void _Process(double delta)
  102. {
  103. QueueRedraw();
  104. }
  105. }
  106. Coordinates
  107. -----------
  108. The drawing API uses the CanvasItem's coordinate system, not necessarily pixel
  109. coordinates. Which means it uses the coordinate space created after applying
  110. the CanvasItem's transform. Additionally, you can apply a custom transform on
  111. top of it by using
  112. :ref:`draw_set_transform<class_CanvasItem_method_draw_set_transform>` or
  113. :ref:`draw_set_transform_matrix<class_CanvasItem_method_draw_set_transform_matrix>`.
  114. When using ``draw_line``, you should consider the width of the line.
  115. When using a width that is an odd size, the position should be shifted
  116. by ``0.5`` to keep the line centered as shown below.
  117. .. image:: img/draw_line.png
  118. .. tabs::
  119. .. code-tab:: gdscript GDScript
  120. func _draw():
  121. draw_line(Vector2(1.5, 1.0), Vector2(1.5, 4.0), Color.GREEN, 1.0)
  122. draw_line(Vector2(4.0, 1.0), Vector2(4.0, 4.0), Color.GREEN, 2.0)
  123. draw_line(Vector2(7.5, 1.0), Vector2(7.5, 4.0), Color.GREEN, 3.0)
  124. .. code-tab:: csharp
  125. public override void _Draw()
  126. {
  127. DrawLine(new Vector2(1.5f, 1.0f), new Vector2(1.5f, 4.0f), Colors.Green, 1.0f);
  128. DrawLine(new Vector2(4.0f, 1.0f), new Vector2(4.0f, 4.0f), Colors.Green, 2.0f);
  129. DrawLine(new Vector2(7.5f, 1.0f), new Vector2(7.5f, 4.0f), Colors.Green, 3.0f);
  130. }
  131. The same applies to the ``draw_rect`` method with ``filled = false``.
  132. .. image:: img/draw_rect.png
  133. .. tabs::
  134. .. code-tab:: gdscript GDScript
  135. func _draw():
  136. draw_rect(Rect2(1.0, 1.0, 3.0, 3.0), Color.GREEN)
  137. draw_rect(Rect2(5.5, 1.5, 2.0, 2.0), Color.GREEN, false, 1.0)
  138. draw_rect(Rect2(9.0, 1.0, 5.0, 5.0), Color.GREEN)
  139. draw_rect(Rect2(16.0, 2.0, 3.0, 3.0), Color.GREEN, false, 2.0)
  140. .. code-tab:: csharp
  141. public override void _Draw()
  142. {
  143. DrawRect(new Rect2(1.0f, 1.0f, 3.0f, 3.0f), Colors.Green);
  144. DrawRect(new Rect2(5.5f, 1.5f, 2.0f, 2.0f), Colors.Green, false, 1.0f);
  145. DrawRect(new Rect2(9.0f, 1.0f, 5.0f, 5.0f), Colors.Green);
  146. DrawRect(new Rect2(16.0f, 2.0f, 3.0f, 3.0f), Colors.Green, false, 2.0f);
  147. }
  148. An example: drawing circular arcs
  149. ---------------------------------
  150. We will now use the custom drawing functionality of the Godot Engine to draw
  151. something that Godot doesn't provide functions for. As an example, Godot provides
  152. a ``draw_circle()`` function that draws a whole circle. However, what about drawing a
  153. portion of a circle? You will have to code a function to perform this and draw it yourself.
  154. Arc function
  155. ^^^^^^^^^^^^
  156. An arc is defined by its support circle parameters, that is, the center position
  157. and the radius. The arc itself is then defined by the angle it starts from
  158. and the angle at which it stops. These are the 4 arguments that we have to provide to our drawing function.
  159. We'll also provide the color value, so we can draw the arc in different colors if we wish.
  160. Basically, drawing a shape on the screen requires it to be decomposed into a certain number of points
  161. linked from one to the next. As you can imagine, the more points your shape is made of,
  162. the smoother it will appear, but the heavier it will also be in terms of processing cost. In general,
  163. if your shape is huge (or in 3D, close to the camera), it will require more points to be drawn without
  164. it being angular-looking. On the contrary, if your shape is small (or in 3D, far from the camera),
  165. you may decrease its number of points to save processing costs; this is known as *Level of Detail (LOD)*.
  166. In our example, we will simply use a fixed number of points, no matter the radius.
  167. .. tabs::
  168. .. code-tab:: gdscript GDScript
  169. func draw_circle_arc(center, radius, angle_from, angle_to, color):
  170. var nb_points = 32
  171. var points_arc = PackedVector2Array()
  172. for i in range(nb_points + 1):
  173. var angle_point = deg_to_rad(angle_from + i * (angle_to-angle_from) / nb_points - 90)
  174. points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
  175. for index_point in range(nb_points):
  176. draw_line(points_arc[index_point], points_arc[index_point + 1], color)
  177. .. code-tab:: csharp
  178. public void DrawCircleArc(Vector2 center, float radius, float angleFrom, float angleTo, Color color)
  179. {
  180. int nbPoints = 32;
  181. var pointsArc = new Vector2[nbPoints + 1];
  182. for (int i = 0; i <= nbPoints; i++)
  183. {
  184. float anglePoint = Mathf.DegToRad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90f);
  185. pointsArc[i] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius;
  186. }
  187. for (int i = 0; i < nbPoints - 1; i++)
  188. {
  189. DrawLine(pointsArc[i], pointsArc[i + 1], color);
  190. }
  191. }
  192. Remember the number of points our shape has to be decomposed into? We fixed this
  193. number in the ``nb_points`` variable to a value of ``32``. Then, we initialize an empty
  194. ``PackedVector2Array``, which is simply an array of ``Vector2``\ s.
  195. The next step consists of computing the actual positions of these 32 points that
  196. compose an arc. This is done in the first for-loop: we iterate over the number of
  197. points for which we want to compute the positions, plus one to include the last point.
  198. We first determine the angle of each point, between the starting and ending angles.
  199. The reason why each angle is decreased by 90° is that we will compute 2D positions
  200. out of each angle using trigonometry (you know, cosine and sine stuff...). However,
  201. ``cos()`` and ``sin()`` use radians, not degrees. The angle of 0° (0 radian)
  202. starts at 3 o'clock, although we want to start counting at 12 o'clock. So we decrease
  203. each angle by 90° in order to start counting from 12 o'clock.
  204. The actual position of a point located on a circle at angle ``angle`` (in radians)
  205. is given by ``Vector2(cos(angle), sin(angle))``. Since ``cos()`` and ``sin()`` return values
  206. between -1 and 1, the position is located on a circle of radius 1. To have this
  207. position on our support circle, which has a radius of ``radius``, we simply need to
  208. multiply the position by ``radius``. Finally, we need to position our support circle
  209. at the ``center`` position, which is performed by adding it to our ``Vector2`` value.
  210. Finally, we insert the point in the ``PackedVector2Array`` which was previously defined.
  211. Now, we need to actually draw our points. As you can imagine, we will not simply
  212. draw our 32 points: we need to draw everything that is between each of them.
  213. We could have computed every point ourselves using the previous method, and drew
  214. it one by one. But this is too complicated and inefficient (except if explicitly needed),
  215. so we simply draw lines between each pair of points. Unless the radius of our
  216. support circle is big, the length of each line between a pair of points will
  217. never be long enough to see them. If that were to happen, we would simply need to
  218. increase the number of points.
  219. Draw the arc on the screen
  220. ^^^^^^^^^^^^^^^^^^^^^^^^^^
  221. We now have a function that draws stuff on the screen;
  222. it is time to call it inside the ``_draw()`` function:
  223. .. tabs::
  224. .. code-tab:: gdscript GDScript
  225. func _draw():
  226. var center = Vector2(200, 200)
  227. var radius = 80
  228. var angle_from = 75
  229. var angle_to = 195
  230. var color = Color(1.0, 0.0, 0.0)
  231. draw_circle_arc(center, radius, angle_from, angle_to, color)
  232. .. code-tab:: csharp
  233. public override void _Draw()
  234. {
  235. var center = new Vector2(200, 200);
  236. float radius = 80;
  237. float angleFrom = 75;
  238. float angleTo = 195;
  239. var color = new Color(1, 0, 0);
  240. DrawCircleArc(center, radius, angleFrom, angleTo, color);
  241. }
  242. Result:
  243. .. image:: img/result_drawarc.png
  244. Arc polygon function
  245. ^^^^^^^^^^^^^^^^^^^^
  246. We can take this a step further and not only write a function that draws the plain
  247. portion of the disc defined by the arc, but also its shape. The method is exactly
  248. the same as before, except that we draw a polygon instead of lines:
  249. .. tabs::
  250. .. code-tab:: gdscript GDScript
  251. func draw_circle_arc_poly(center, radius, angle_from, angle_to, color):
  252. var nb_points = 32
  253. var points_arc = PackedVector2Array()
  254. points_arc.push_back(center)
  255. var colors = PackedColorArray([color])
  256. for i in range(nb_points + 1):
  257. var angle_point = deg_to_rad(angle_from + i * (angle_to - angle_from) / nb_points - 90)
  258. points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
  259. draw_polygon(points_arc, colors)
  260. .. code-tab:: csharp
  261. public void DrawCircleArcPoly(Vector2 center, float radius, float angleFrom, float angleTo, Color color)
  262. {
  263. int nbPoints = 32;
  264. var pointsArc = new Vector2[nbPoints + 2];
  265. pointsArc[0] = center;
  266. var colors = new Color[] { color };
  267. for (int i = 0; i <= nbPoints; i++)
  268. {
  269. float anglePoint = Mathf.DegToRad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90);
  270. pointsArc[i + 1] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius;
  271. }
  272. DrawPolygon(pointsArc, colors);
  273. }
  274. .. image:: img/result_drawarc_poly.png
  275. Dynamic custom drawing
  276. ^^^^^^^^^^^^^^^^^^^^^^
  277. All right, we are now able to draw custom stuff on the screen. However, it is static;
  278. let's make this shape turn around the center. The solution to do this is simply
  279. to change the angle_from and angle_to values over time. For our example,
  280. we will simply increment them by 50. This increment value has to remain
  281. constant or else the rotation speed will change accordingly.
  282. First, we have to make both angle_from and angle_to variables global at the top
  283. of our script. Also note that you can store them in other nodes and access them
  284. using ``get_node()``.
  285. .. tabs::
  286. .. code-tab:: gdscript GDScript
  287. extends Node2D
  288. var rotation_angle = 50
  289. var angle_from = 75
  290. var angle_to = 195
  291. .. code-tab:: csharp
  292. using Godot;
  293. public partial class MyNode2D : Node2D
  294. {
  295. private float _rotationAngle = 50;
  296. private float _angleFrom = 75;
  297. private float _angleTo = 195;
  298. }
  299. We make these values change in the _process(delta) function.
  300. We also increment our angle_from and angle_to values here. However, we must not
  301. forget to ``wrap()`` the resulting values between 0 and 360°! That is, if the angle
  302. is 361°, then it is actually 1°. If you don't wrap these values, the script will
  303. work correctly, but the angle values will grow bigger and bigger over time until
  304. they reach the maximum integer value Godot can manage (``2^31 - 1``).
  305. When this happens, Godot may crash or produce unexpected behavior.
  306. Finally, we must not forget to call the ``queue_redraw()`` function, which automatically
  307. calls ``_draw()``. This way, you can control when you want to refresh the frame.
  308. .. tabs::
  309. .. code-tab:: gdscript GDScript
  310. func _process(delta):
  311. angle_from += rotation_angle
  312. angle_to += rotation_angle
  313. # We only wrap angles when both of them are bigger than 360.
  314. if angle_from > 360 and angle_to > 360:
  315. angle_from = wrapf(angle_from, 0, 360)
  316. angle_to = wrapf(angle_to, 0, 360)
  317. queue_redraw()
  318. .. code-tab:: csharp
  319. public override void _Process(double delta)
  320. {
  321. _angleFrom += _rotationAngle;
  322. _angleTo += _rotationAngle;
  323. // We only wrap angles when both of them are bigger than 360.
  324. if (_angleFrom > 360 && _angleTo > 360)
  325. {
  326. _angleFrom = Mathf.Wrap(_angleFrom, 0, 360);
  327. _angleTo = Mathf.Wrap(_angleTo, 0, 360);
  328. }
  329. QueueRedraw();
  330. }
  331. Also, don't forget to modify the ``_draw()`` function to make use of these variables:
  332. .. tabs::
  333. .. code-tab:: gdscript GDScript
  334. func _draw():
  335. var center = Vector2(200, 200)
  336. var radius = 80
  337. var color = Color(1.0, 0.0, 0.0)
  338. draw_circle_arc( center, radius, angle_from, angle_to, color )
  339. .. code-tab:: csharp
  340. public override void _Draw()
  341. {
  342. var center = new Vector2(200, 200);
  343. float radius = 80;
  344. var color = new Color(1, 0, 0);
  345. DrawCircleArc(center, radius, _angleFrom, _angleTo, color);
  346. }
  347. Let's run!
  348. It works, but the arc is rotating insanely fast! What's wrong?
  349. The reason is that your GPU is actually displaying the frames as fast as it can.
  350. We need to "normalize" the drawing by this speed; to achieve that, we have to make
  351. use of the ``delta`` parameter of the ``_process()`` function. ``delta`` contains the
  352. time elapsed between the two last rendered frames. It is generally small
  353. (about 0.0003 seconds, but this depends on your hardware), so using ``delta`` to
  354. control your drawing ensures that your program runs at the same speed on
  355. everybody's hardware.
  356. In our case, we simply need to multiply our ``rotation_angle`` variable by ``delta``
  357. in the ``_process()`` function. This way, our 2 angles will be increased by a much
  358. smaller value, which directly depends on the rendering speed.
  359. .. tabs::
  360. .. code-tab:: gdscript GDScript
  361. func _process(delta):
  362. angle_from += rotation_angle * delta
  363. angle_to += rotation_angle * delta
  364. # We only wrap angles when both of them are bigger than 360.
  365. if angle_from > 360 and angle_to > 360:
  366. angle_from = wrapf(angle_from, 0, 360)
  367. angle_to = wrapf(angle_to, 0, 360)
  368. queue_redraw()
  369. .. code-tab:: csharp
  370. public override void _Process(double delta)
  371. {
  372. _angleFrom += _rotationAngle * (float)delta;
  373. _angleTo += _rotationAngle * (float)delta;
  374. // We only wrap angles when both of them are bigger than 360.
  375. if (_angleFrom > 360 && _angleTo > 360)
  376. {
  377. _angleFrom = Wrap(_angleFrom, 0, 360);
  378. _angleTo = Wrap(_angleTo, 0, 360);
  379. }
  380. QueueRedraw();
  381. }
  382. Let's run again! This time, the rotation displays fine!
  383. Antialiased drawing
  384. ^^^^^^^^^^^^^^^^^^^
  385. Godot offers method parameters in :ref:`draw_line<class_CanvasItem_method_draw_line>`
  386. to enable antialiasing, but not all custom drawing methods offer this ``antialiased``
  387. parameter.
  388. For custom drawing methods that don't provide an ``antialiased`` parameter,
  389. you can enable 2D MSAA instead, which affects rendering in the entire viewport.
  390. This provides high-quality antialiasing, but a higher performance cost and only
  391. on specific elements. See :ref:`doc_2d_antialiasing` for more information.
  392. Tools
  393. -----
  394. Drawing your own nodes might also be desired while running them in the
  395. editor. This can be used as a preview or visualization of some feature or
  396. behavior. See :ref:`doc_running_code_in_the_editor` for more information.