Skip to content

Commit 546c923

Browse files
authored
Merge pull request godotengine#1368 from paulloz/csharp-custom-drawing-in-2d
Translate 'Custom drawing in 2D' to C#
2 parents 767473f + fb32da9 commit 546c923

File tree

1 file changed

+212
-46
lines changed

1 file changed

+212
-46
lines changed

tutorials/2d/custom_drawing_in_2d.rst

Lines changed: 212 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,22 @@ Add a script to any :ref:`CanvasItem <class_CanvasItem>`
3939
derived node, like :ref:`Control <class_Control>` or
4040
:ref:`Node2D <class_Node2D>`. Then override the _draw() function.
4141

42-
::
42+
.. tabs::
43+
.. code-tab:: gdscript GDScript
4344

4445
extends Node2D
4546

4647
func _draw():
47-
#your draw commands here
48+
# Your draw commands here
4849
pass
4950

51+
.. code-tab:: csharp
52+
53+
public override void _Draw()
54+
{
55+
// Your draw commands here
56+
}
57+
5058
Draw commands are described in the :ref:`CanvasItem <class_CanvasItem>`
5159
class reference. There are plenty of them.
5260

@@ -63,7 +71,8 @@ in that same node and a new _draw() call will happen.
6371
Here is a little more complex example. A texture variable that will be
6472
redrawn if modified:
6573

66-
::
74+
.. tabs::
75+
.. code-tab:: gdscript GDScript
6776

6877
extends Node2D
6978

@@ -78,20 +87,61 @@ redrawn if modified:
7887
func _draw():
7988
draw_texture(texture, Vector2())
8089

90+
.. code-tab:: csharp
91+
92+
public class CustomNode2D : Node2D
93+
{
94+
private Texture _texture;
95+
public Texture Texture
96+
{
97+
get
98+
{
99+
return _texture;
100+
}
101+
102+
set
103+
{
104+
_texture = value;
105+
Update();
106+
}
107+
}
108+
109+
public override void _Draw()
110+
{
111+
DrawTexture(_texture, new Vector2());
112+
}
113+
}
114+
81115
In some cases, it may be desired to draw every frame. For this, just
82116
call update() from the _process() callback, like this:
83117

84-
::
118+
.. tabs::
119+
.. code-tab:: gdscript GDScript
85120

86121
extends Node2D
87122

88123
func _draw():
89-
#your draw commands here
124+
# Your draw commands here
90125
pass
91126

92127
func _process(delta):
93128
update()
94129

130+
.. code-tab:: csharp
131+
132+
public class CustomNode2D : Node2D
133+
{
134+
public override _Draw()
135+
{
136+
// Your draw commands here
137+
}
138+
139+
public override _Process(delta)
140+
{
141+
Update();
142+
}
143+
}
144+
95145

96146
An example: drawing circular arcs
97147
----------------------------------
@@ -106,20 +156,38 @@ An arc is defined by its support circle parameters. That is: the center position
106156

107157
Basically, drawing a shape on screen requires it to be decomposed into a certain number of points, linked from one to the following one. As you can imagine, the more points your shape is made of, the smoother it will appear, but the heavier it will 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 reduce its number of points to save processing costs. This is called *Level of Detail (LoD)*. In our example, we will simply use a fixed number of points, no matter the radius.
108158

109-
::
159+
.. tabs::
160+
.. code-tab:: gdscript GDScript
110161

111162
func draw_circle_arc(center, radius, angle_from, angle_to, color):
112163
var nb_points = 32
113164
var points_arc = PoolVector2Array()
114165

115166
for i in range(nb_points+1):
116-
var angle_point = angle_from + i * (angle_to-angle_from) / nb_points - 90
117-
var point = center + Vector2(cos(deg2rad(angle_point)), sin(deg2rad(angle_point))) * radius
118-
points_arc.push_back(point)
167+
var angle_point = deg2rad(angle_from + i * (angle_to-angle_from) / nb_points - 90)
168+
points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
119169

120170
for index_point in range(nb_points):
121171
draw_line(points_arc[index_point], points_arc[index_point + 1], color)
122172

173+
.. code-tab:: csharp
174+
175+
public void DrawCircleArc(Vector2 center, float radius, float angleFrom, float angleTo, Color color)
176+
{
177+
int nbPoints = 32;
178+
var pointsArc = new Vector2[nbPoints];
179+
180+
for (int i = 0; i < nbPoints; ++i)
181+
{
182+
float anglePoint = Mathf.Deg2Rad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90f);
183+
pointsArc[i] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius;
184+
}
185+
186+
for (int i = 0; i < nbPoints - 1; ++i)
187+
DrawLine(pointsArc[i], pointsArc[i + 1], color);
188+
}
189+
190+
123191
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 PoolVector2Array, which is simply an array of Vector2.
124192

125193
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.
@@ -134,7 +202,9 @@ Draw the arc on screen
134202
^^^^^^^^^^^^^^^^^^^^^^
135203
We now have a function that draws stuff on the screen: it is time to call in the _draw() function.
136204

137-
::
205+
.. tabs::
206+
207+
.. code-tab:: gdscript GDScript
138208

139209
func _draw():
140210
var center = Vector2(200, 200)
@@ -144,6 +214,18 @@ We now have a function that draws stuff on the screen: it is time to call in the
144214
var color = Color(1.0, 0.0, 0.0)
145215
draw_circle_arc(center, radius, angle_from, angle_to, color)
146216

217+
.. code-tab:: csharp
218+
219+
public override void _Draw()
220+
{
221+
var center = new Vector2(200, 200);
222+
float radius = 80;
223+
float angleFrom = 75;
224+
float angleTo = 195;
225+
var color = new Color(1, 0, 0);
226+
DrawCircleArc(center, radius, angleFrom, angleTo, color);
227+
}
228+
147229
Result:
148230

149231
.. image:: img/result_drawarc.png
@@ -154,7 +236,8 @@ Arc polygon function
154236
^^^^^^^^^^^^^^^^^^^^
155237
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 previously, except that we draw a polygon instead of lines:
156238

157-
::
239+
.. tabs::
240+
.. code-tab:: gdscript GDScript
158241

159242
func draw_circle_arc_poly(center, radius, angle_from, angle_to, color):
160243
var nb_points = 32
@@ -163,10 +246,28 @@ We can take this a step further and not only write a function that draws the pla
163246
var colors = PoolColorArray([color])
164247

165248
for i in range(nb_points+1):
166-
var angle_point = angle_from + i * (angle_to - angle_from) / nb_points - 90
167-
points_arc.push_back(center + Vector2(cos(deg2rad(angle_point)), sin(deg2rad(angle_point))) * radius)
249+
var angle_point = deg2rad(angle_from + i * (angle_to - angle_from) / nb_points - 90
250+
points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
168251
draw_polygon(points_arc, colors)
169252

253+
.. code-tab:: csharp
254+
255+
public void DrawCircleArcPoly(Vector2 center, float radius, float angleFrom, float angleTo, Color color)
256+
{
257+
int nbPoints = 32;
258+
var pointsArc = new Vector2[nbPoints + 1];
259+
pointsArc[0] = center;
260+
var colors = new Color[] { color };
261+
262+
for (int i = 0; i < nbPoints; ++i)
263+
{
264+
float anglePoint = Mathf.Deg2Rad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90);
265+
pointsArc[i + 1] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius;
266+
}
267+
268+
DrawPolygon(pointsArc, colors);
269+
}
270+
170271

171272
.. image:: img/result_drawarc_poly.png
172273

@@ -176,48 +277,95 @@ Alright, we are now able to draw custom stuff on screen. However, it is very sta
176277

177278
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().
178279

179-
::
280+
.. tabs::
281+
.. code-tab:: gdscript GDScript
180282

181-
extends Node2D
283+
extends Node2D
182284

183-
var rotation_ang = 50
184-
var angle_from = 75
185-
var angle_to = 195
285+
var rotation_angle = 50
286+
var angle_from = 75
287+
var angle_to = 195
186288

289+
.. code-tab:: csharp
187290

291+
public class CustomNode2D : Node2D
292+
{
293+
private float _rotationAngle = 50;
294+
private float _angleFrom = 75;
295+
private float _angleTo = 195;
296+
}
188297

189298
We make these values change in the _process(delta) function.
190299

191300
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 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. Since Godot doesn't provide a wrap() function, we'll create it here, as it is relatively simple.
192301

193302
Finally, we must not forget to call the update() function, which automatically calls _draw(). This way, you can control when you want to refresh the frame.
194303

195-
::
304+
.. tabs::
305+
.. code-tab:: gdscript GDScript
196306

197-
func wrap(value, min_val, max_val):
198-
var f1 = value - min_val
199-
var f2 = max_val - min_val
200-
return fmod(f1, f2) + min_val
307+
func wrap(value, min_val, max_val):
308+
var f1 = value - min_val
309+
var f2 = max_val - min_val
310+
return fmod(f1, f2) + min_val
201311

202-
func _process(delta):
203-
angle_from += rotation_ang
204-
angle_to += rotation_ang
312+
func _process(delta):
313+
angle_from += rotation_ang
314+
angle_to += rotation_ang
205315

206-
# we only wrap angles if both of them are bigger than 360
207-
if angle_from > 360 and angle_to > 360:
208-
angle_from = wrap(angle_from, 0, 360)
209-
angle_to = wrap(angle_to, 0, 360)
210-
update()
316+
# We only wrap angles if both of them are bigger than 360
317+
if angle_from > 360 and angle_to > 360:
318+
angle_from = wrap(angle_from, 0, 360)
319+
angle_to = wrap(angle_to, 0, 360)
320+
update()
321+
322+
.. code-tab:: csharp
323+
324+
private float Wrap(float value, float minVal, float maxVal)
325+
{
326+
float f1 = value - minVal;
327+
float f2 = maxVal - minVal;
328+
return (f1 % f2) + minVal;
329+
}
330+
331+
public override void _Process(float delta)
332+
{
333+
_angleFrom += _rotationAngle;
334+
_angleTo += _rotationAngle;
335+
336+
// We only wrap angles if both of them are bigger than 360
337+
if (_angleFrom > 360 && _angleTo > 360)
338+
{
339+
_angleFrom = Wrap(_angleFrom, 0, 360);
340+
_angleTo = Wrap(_angleTo, 0, 360);
341+
}
342+
Update();
343+
}
344+
211345

212346
Also, don't forget to modify the _draw() function to make use of these variables:
213-
::
214347

215-
func _draw():
216-
var center = Vector2(200, 200)
217-
var radius = 80
218-
var color = Color(1.0, 0.0, 0.0)
348+
.. tabs::
349+
.. code-tab:: gdscript GDScript
350+
351+
func _draw():
352+
var center = Vector2(200, 200)
353+
var radius = 80
354+
var color = Color(1.0, 0.0, 0.0)
355+
356+
draw_circle_arc( center, radius, angle_from, angle_to, color )
357+
358+
.. code-tab:: csharp
359+
360+
public override void _Draw()
361+
{
362+
var center = new Vector2(200, 200);
363+
float radius = 80;
364+
var color = new Color(1, 0, 0);
365+
366+
DrawCircleArc(center, radius, _angleFrom, _angleTo, color);
367+
}
219368

220-
draw_circle_arc( center, radius, angle_from, angle_to, color )
221369

222370
Let's run!
223371
It works, but the arc is rotating insanely fast! What's wrong?
@@ -226,17 +374,35 @@ The reason is that your GPU is actually displaying the frames as fast as it can.
226374

227375
In our case, we simply need to multiply our 'rotation_ang' 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.
228376

229-
::
377+
.. tabs::
378+
.. code-tab:: gdscript GDScript
230379

231-
func _process(delta):
232-
angle_from += rotation_ang * delta
233-
angle_to += rotation_ang * delta
380+
func _process(delta):
381+
angle_from += rotation_ang * delta
382+
angle_to += rotation_ang * delta
234383

235-
# we only wrap angles if both of them are bigger than 360
236-
if angle_from > 360 and angle_to > 360:
237-
angle_from = wrap(angle_from, 0, 360)
238-
angle_to = wrap(angle_to, 0, 360)
239-
update()
384+
# we only wrap angles if both of them are bigger than 360
385+
if angle_from > 360 and angle_to > 360:
386+
angle_from = wrap(angle_from, 0, 360)
387+
angle_to = wrap(angle_to, 0, 360)
388+
update()
389+
390+
.. code-tab:: csharp
391+
392+
public override void _Process(float delta)
393+
{
394+
_angleFrom += _rotationAngle * delta;
395+
_angleTo += _rotationAngle * delta;
396+
397+
// We only wrap angles if both of them are bigger than 360
398+
if (_angleFrom > 360 && _angleTo > 360)
399+
{
400+
_angleFrom = Wrap(_angleFrom, 0, 360);
401+
_angleTo = Wrap(_angleTo, 0, 360);
402+
}
403+
Update();
404+
}
405+
240406

241407
Let's run again! This time, the rotation displays fine!
242408

0 commit comments

Comments
 (0)