Browse Source

Add a custom drawing in 2D demo (#1185)

Hugo Locurcio 3 months ago
parent
commit
f78efc69ee

+ 23 - 0
2d/custom_drawing/README.md

@@ -0,0 +1,23 @@
+# Custom drawing in 2D
+
+A demo showing how to draw 2D elements in Godot without using nodes. This can be done
+to create procedural graphics, perform debug drawing to help troubleshoot issues in
+game logic, or to improve performance by not creating a node for every visible element.
+
+Antialiasing can be performed using two approaches: either by enabling the `antialiasing`
+parameter provided by some of the CanvasItem `draw_*` methods, or by enabling 2D MSAA
+in the Project Settings. 2D MSAA is generally slower, but it works with any kind of line-based
+or polygon-based 2D drawing, even for `draw_*` methods that don't support an `antialiasing`
+parameter. Note that 2D MSAA is only available in the Forward+ and Mobile
+renderers, not Compatibility.
+
+See [Custom drawing in 2D](https://docs.godotengine.org/en/latest/tutorials/2d/custom_drawing_in_2d.html)
+in the documentation for more information.
+
+Language: GDScript
+
+Renderer: Mobile
+
+## Screenshots
+
+![Screenshot](screenshots/custom_drawing.webp)

+ 43 - 0
2d/custom_drawing/animation.gd

@@ -0,0 +1,43 @@
+# This is a `@tool` script so that the custom 2D drawing can be seen in the editor.
+@tool
+extends Panel
+
+var use_antialiasing := false
+
+var time := 0.0
+
+func _process(delta: float) -> void:
+	# Increment a counter variable that we use in `_draw()`.
+	time += delta
+	# Force redrawing on every processed frame, so that the animation can visibly progress.
+	# Only do this when the node is visible in tree, so that we don't force continuous redrawing
+	# when not needed (low-processor usage mode is enabled in this demo).
+	if is_visible_in_tree():
+		queue_redraw()
+
+
+func _draw() -> void:
+	var margin := Vector2(240, 70)
+	var offset := Vector2()
+
+	# Line width of `-1.0` is only usable with draw antialiasing disabled,
+	# as it uses hardware line drawing as opposed to polygon-based line drawing.
+	# Automatically use polygon-based line drawing when needed to avoid runtime warnings.
+	# We also use a line width of `0.5` instead of `1.0` to better match the appearance
+	# of non-antialiased line drawing, as draw antialiasing tends to make lines look thicker.
+	var line_width_thin := 0.5 if use_antialiasing else -1.0
+
+	# Draw an animated arc to simulate a circular progress bar.
+	# The start angle is set so the arc starts from the top.
+	const POINT_COUNT = 48
+	var progress := wrapf(time, 0.0, 1.0)
+	draw_arc(
+			margin + offset,
+			50.0,
+			0.75 * TAU,
+			(0.75 + progress) * TAU,
+			POINT_COUNT,
+			Color.MEDIUM_AQUAMARINE,
+			line_width_thin,
+			use_antialiasing
+	)

+ 1 - 0
2d/custom_drawing/animation.gd.uid

@@ -0,0 +1 @@
+uid://b8d4d0s3gujbp

+ 36 - 0
2d/custom_drawing/animation_slice.gd

@@ -0,0 +1,36 @@
+extends Control
+
+var use_antialiasing := false
+
+func _draw() -> void:
+	var margin := Vector2(240, 70)
+	var offset := Vector2(0, 150)
+	# This is an example of using draw commands to create animations.
+	# For "continuous" animations, you can use a timer within `_draw()` and call `queue_redraw()`
+	# in `_process()` to redraw every frame.
+	# Animation length in seconds. The animation will loop after the specified duration.
+	const ANIMATION_LENGTH = 2.0
+	# 5 frames per second.
+	const ANIMATION_FRAMES = 10
+
+	# Declare an animation frame with randomized rotation and color for each frame.
+	# `draw_animation_slice()` makes it so the following draw commands are only visible
+	# on screen when the current time is within the animation slice.
+	# NOTE: Pause does not affect animations drawn by `draw_animation_slice()`
+	# (they will keep playing).
+	for frame in ANIMATION_FRAMES:
+		# `remap()` is useful to determine the time slice in which a frame is visible.
+		# For example, on the 2nd frame, `slice_begin` is `0.2` and `slice_end` is `0.4`.
+		var slice_begin := remap(frame, 0, ANIMATION_FRAMES, 0, ANIMATION_LENGTH)
+		var slice_end := remap(frame + 1, 0, ANIMATION_FRAMES, 0, ANIMATION_LENGTH)
+		draw_animation_slice(ANIMATION_LENGTH, slice_begin, slice_end)
+		draw_set_transform(margin + offset, deg_to_rad(randf_range(-5.0, 5.0)))
+		draw_rect(
+			Rect2(Vector2(), Vector2(100, 50)),
+			Color.from_hsv(randf(), 0.4, 1.0),
+			true,
+			-1.0,
+			use_antialiasing
+	)
+
+	draw_end_animation()

+ 1 - 0
2d/custom_drawing/animation_slice.gd.uid

@@ -0,0 +1 @@
+uid://wksdrvv65620

+ 14 - 0
2d/custom_drawing/custom_drawing.gd

@@ -0,0 +1,14 @@
+extends Control
+
+
+func _on_msaa_2d_item_selected(index: int) -> void:
+	get_viewport().msaa_2d = index as Viewport.MSAA
+
+
+func _on_draw_antialiasing_toggled(toggled_on: bool) -> void:
+	var nodes: Array[Node] = %TabContainer.get_children()
+	nodes.push_back(%AnimationSlice)
+	for tab: Control in nodes:
+		tab.use_antialiasing = toggled_on
+		# Force all tabs to redraw so that the antialiasing updates.
+		tab.queue_redraw()

+ 1 - 0
2d/custom_drawing/custom_drawing.gd.uid

@@ -0,0 +1 @@
+uid://cinaeqsrawkbw

+ 272 - 0
2d/custom_drawing/custom_drawing.tscn

@@ -0,0 +1,272 @@
+[gd_scene load_steps=10 format=3 uid="uid://btxwm0qudsn3t"]
+
+[ext_resource type="Script" uid="uid://cinaeqsrawkbw" path="res://custom_drawing.gd" id="1_rtndo"]
+[ext_resource type="Script" uid="uid://3gt2v4l0gy1" path="res://lines.gd" id="2_exx0l"]
+[ext_resource type="Script" uid="uid://cquneapbjf3e0" path="res://rectangles.gd" id="3_yrx86"]
+[ext_resource type="Script" uid="uid://clsf8dubgyrig" path="res://polygons.gd" id="4_obj11"]
+[ext_resource type="Script" uid="uid://dtxyrnurokare" path="res://textures.gd" id="5_84cac"]
+[ext_resource type="Script" uid="uid://dy8ofskb8bg4a" path="res://meshes.gd" id="5_exx0l"]
+[ext_resource type="Script" uid="uid://0kv1wvfyg058" path="res://text.gd" id="6_4w081"]
+[ext_resource type="Script" uid="uid://b8d4d0s3gujbp" path="res://animation.gd" id="8_yrx86"]
+[ext_resource type="Script" uid="uid://wksdrvv65620" path="res://animation_slice.gd" id="9_obj11"]
+
+[node name="CustomDrawing" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_rtndo")
+
+[node name="TabContainer" type="TabContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+current_tab = 0
+
+[node name="Lines" type="Panel" parent="TabContainer"]
+layout_mode = 2
+script = ExtResource("2_exx0l")
+metadata/_tab_index = 0
+
+[node name="DrawLine" type="Label" parent="TabContainer/Lines"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 48.0
+offset_right = 172.0
+offset_bottom = 97.0
+theme_override_colors/font_color = Color(0.501961, 1, 0.501961, 1)
+text = "draw_line()
+draw_dashed_line()"
+
+[node name="DrawCircle" type="Label" parent="TabContainer/Lines"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 154.0
+offset_right = 122.0
+offset_bottom = 177.0
+theme_override_colors/font_color = Color(0.501961, 1, 0.501961, 1)
+text = "draw_circle()"
+
+[node name="DrawArc" type="Label" parent="TabContainer/Lines"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 264.0
+offset_right = 109.0
+offset_bottom = 287.0
+theme_override_colors/font_color = Color(0.501961, 1, 0.501961, 1)
+text = "draw_arc()"
+
+[node name="Rectangles" type="Panel" parent="TabContainer"]
+visible = false
+layout_mode = 2
+script = ExtResource("3_yrx86")
+metadata/_tab_index = 1
+
+[node name="DrawRect" type="Label" parent="TabContainer/Rectangles"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 48.0
+offset_right = 109.0
+offset_bottom = 71.0
+theme_override_colors/font_color = Color(0.501961, 1, 0.501961, 1)
+text = "draw_rect()"
+
+[node name="DrawStyleBox" type="Label" parent="TabContainer/Rectangles"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 296.0
+offset_right = 153.0
+offset_bottom = 319.0
+text = "draw_style_box()"
+
+[node name="Polygons" type="Panel" parent="TabContainer"]
+visible = false
+layout_mode = 2
+script = ExtResource("4_obj11")
+metadata/_tab_index = 2
+
+[node name="DrawPrimitive" type="Label" parent="TabContainer/Polygons"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 48.0
+offset_right = 207.0
+offset_bottom = 97.0
+text = "draw_primitive()"
+
+[node name="DrawPolygon" type="Label" parent="TabContainer/Polygons"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 168.0
+offset_right = 207.0
+offset_bottom = 217.0
+text = "draw_polygon()
+draw_colored_polygon()"
+
+[node name="DrawPolyline" type="Label" parent="TabContainer/Polygons"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 264.0
+offset_right = 195.0
+offset_bottom = 313.0
+theme_override_colors/font_color = Color(0.501961, 1, 0.501961, 1)
+text = "draw_polyline()
+draw_polyline_colors()"
+
+[node name="DrawMultiline" type="Label" parent="TabContainer/Polygons"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 392.0
+offset_right = 203.0
+offset_bottom = 441.0
+theme_override_colors/font_color = Color(0.501961, 1, 0.501961, 1)
+text = "draw_multiline()
+draw_multiline_colors()"
+
+[node name="Meshes" type="Panel" parent="TabContainer"]
+visible = false
+layout_mode = 2
+script = ExtResource("5_exx0l")
+metadata/_tab_index = 3
+
+[node name="DrawMesh" type="Label" parent="TabContainer/Meshes"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 48.0
+offset_right = 207.0
+offset_bottom = 97.0
+text = "draw_mesh()"
+
+[node name="DrawMultiMesh" type="Label" parent="TabContainer/Meshes"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 208.0
+offset_right = 207.0
+offset_bottom = 257.0
+text = "draw_multimesh()"
+
+[node name="Textures" type="Panel" parent="TabContainer"]
+visible = false
+texture_repeat = 2
+layout_mode = 2
+script = ExtResource("5_84cac")
+metadata/_tab_index = 4
+
+[node name="DrawTexture" type="Label" parent="TabContainer/Textures"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 48.0
+offset_right = 175.0
+offset_bottom = 97.0
+text = "draw_texture()
+draw_texture_rect()"
+
+[node name="DrawTextureRectRegion" type="Label" parent="TabContainer/Textures"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 392.0
+offset_right = 231.0
+offset_bottom = 415.0
+text = "draw_texture_rect_region()"
+
+[node name="Text" type="Panel" parent="TabContainer"]
+visible = false
+layout_mode = 2
+script = ExtResource("6_4w081")
+metadata/_tab_index = 5
+
+[node name="DrawChar" type="Label" parent="TabContainer/Text"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 48.0
+offset_right = 125.0
+offset_bottom = 97.0
+text = "draw_char()
+draw_string()"
+
+[node name="Animation" type="Panel" parent="TabContainer"]
+visible = false
+layout_mode = 2
+script = ExtResource("8_yrx86")
+metadata/_tab_index = 6
+
+[node name="DrawArcTime" type="Label" parent="TabContainer/Animation"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 48.0
+offset_right = 125.0
+offset_bottom = 97.0
+theme_override_colors/font_color = Color(0.501961, 1, 0.501961, 1)
+text = "draw_arc()
++ time variable"
+
+[node name="DrawAnimationSlice" type="Label" parent="TabContainer/Animation"]
+layout_mode = 0
+offset_left = 24.0
+offset_top = 216.0
+offset_right = 201.0
+offset_bottom = 265.0
+text = "draw_animation_slice()"
+
+[node name="AnimationSlice" type="Control" parent="TabContainer/Animation"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("9_obj11")
+
+[node name="Options" type="HBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_left = 24.0
+offset_top = -64.0
+offset_right = 441.0
+offset_bottom = -24.0
+grow_vertical = 0
+theme_override_constants/separation = 20
+
+[node name="MSAA2DLabel" type="Label" parent="Options"]
+layout_mode = 2
+text = "MSAA 2D"
+
+[node name="MSAA2DOptionButton" type="OptionButton" parent="Options"]
+layout_mode = 2
+selected = 0
+item_count = 4
+popup/item_0/text = "Disabled"
+popup/item_0/id = 0
+popup/item_1/text = "2×"
+popup/item_1/id = 1
+popup/item_2/text = "4×"
+popup/item_2/id = 2
+popup/item_3/text = "8×"
+popup/item_3/id = 3
+
+[node name="VSeparator" type="VSeparator" parent="Options"]
+layout_mode = 2
+
+[node name="DrawAntialiasing" type="CheckButton" parent="Options"]
+layout_mode = 2
+tooltip_text = "Performs antialiasing by adding a feathered outline
+to lines that are drawn in 2D. This is generally faster to render
+than 2D MSAA, but not all draw commands support it.
+
+Commands that support draw antialiasing are
+highlighted in green."
+theme_override_colors/font_color = Color(0.501961, 1, 0.501961, 1)
+theme_override_colors/font_focus_color = Color(0.501961, 1, 0.501961, 1)
+theme_override_colors/font_pressed_color = Color(0.501961, 1, 0.501961, 1)
+text = "Draw Antialiasing"
+
+[connection signal="item_selected" from="Options/MSAA2DOptionButton" to="." method="_on_msaa_2d_item_selected"]
+[connection signal="toggled" from="Options/DrawAntialiasing" to="." method="_on_draw_antialiasing_toggled"]

+ 1 - 0
2d/custom_drawing/icon.svg

@@ -0,0 +1 @@
+<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><path d="m15.999738 8a7.9997377 7.9997377 0 0 0 -7.999738 7.999738v67.99777c11.999607 3.999869 7.999738-7.999738 15.999475-7.999738v-51.998295h79.997375v17.599423a19.319367 19.319367 0 0 1 15.99948 3.999869v-29.599029a7.9997377 7.9997377 0 0 0 -7.99974-7.999738zm10.879643 81.43733c-2.263926 1.351955-4.127865 3.727878-5.159831 6.919773-3.327891 10.215667-19.3353659-7.519754-7.567752 16.071477 3.719878 7.44775 15.295499 9.61568 22.679257 5.78381a15.375496 15.375496 0 0 0 6.639782-20.439334c-4.623848-9.263696-11.59962-11.287629-16.591456-8.327726zm17.775417-5.599817 10.175666 19.959347 56.550146-28.815056a11.319629 11.319629 0 0 0 -10.07167-20.271336zm27.343104-43.838562v7.999737h-7.999738v7.999738h-15.999476v7.999738h-7.999737v7.999738h-7.999738v7.999737h1.999935l53.998229-27.783089v-4.215862h-7.999738v-7.999737z" fill="#808080" stroke-width=".999999"/></svg>

+ 37 - 0
2d/custom_drawing/icon.svg.import

@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dfc361vvrguif"
+path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.svg"
+dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false

+ 89 - 0
2d/custom_drawing/lines.gd

@@ -0,0 +1,89 @@
+# This is a `@tool` script so that the custom 2D drawing can be seen in the editor.
+@tool
+extends Panel
+
+var use_antialiasing := false
+
+
+func _draw() -> void:
+	var margin := Vector2(200, 50)
+
+	# Line width of `-1.0` is only usable with draw antialiasing disabled,
+	# as it uses hardware line drawing as opposed to polygon-based line drawing.
+	# Automatically use polygon-based line drawing when needed to avoid runtime warnings.
+	# We also use a line width of `0.5` instead of `1.0` to better match the appearance
+	# of non-antialiased line drawing, as draw antialiasing tends to make lines look thicker.
+	var line_width_thin := 0.5 if use_antialiasing else -1.0
+
+	# Make thick lines 1 pixel thinner when draw antialiasing is enabled,
+	# as draw antialiasing tends to make lines look thicker.
+	var antialiasing_width_offset := 1.0 if use_antialiasing else 0.0
+
+	var offset := Vector2()
+	var line_length := Vector2(140, 35)
+	draw_line(margin + offset, margin + offset + line_length, Color.GREEN, line_width_thin, use_antialiasing)
+	offset += Vector2(line_length.x + 15, 0)
+	draw_line(margin + offset, margin + offset + line_length, Color.GREEN, 2.0 - antialiasing_width_offset, use_antialiasing)
+	offset += Vector2(line_length.x + 15, 0)
+	draw_line(margin + offset, margin + offset + line_length, Color.GREEN, 6.0 - antialiasing_width_offset, use_antialiasing)
+	offset += Vector2(line_length.x + 15, 0)
+	draw_dashed_line(margin + offset, margin + offset + line_length, Color.CYAN, line_width_thin, 5.0, true, use_antialiasing)
+	offset += Vector2(line_length.x + 15, 0)
+	draw_dashed_line(margin + offset, margin + offset + line_length, Color.CYAN, 2.0 - antialiasing_width_offset, 10.0, true, use_antialiasing)
+	offset += Vector2(line_length.x + 15, 0)
+	draw_dashed_line(margin + offset, margin + offset + line_length, Color.CYAN, 6.0 - antialiasing_width_offset, 15.0, true, use_antialiasing)
+
+
+	offset = Vector2(40, 120)
+	draw_circle(margin + offset, 40, Color.ORANGE, false, line_width_thin, use_antialiasing)
+
+	offset += Vector2(100, 0)
+	draw_circle(margin + offset, 40, Color.ORANGE, false, 2.0 - antialiasing_width_offset, use_antialiasing)
+
+	offset += Vector2(100, 0)
+	draw_circle(margin + offset, 40, Color.ORANGE, false, 6.0 - antialiasing_width_offset, use_antialiasing)
+
+	# Draw a filled circle. The width parameter is ignored for filled circles (it's set to `-1.0` to avoid warnings).
+	# We also reduce the radius by half the antialiasing width offset.
+	# Otherwise, the circle becomes very slightly larger when draw antialiasing is enabled.
+	offset += Vector2(100, 0)
+	draw_circle(margin + offset, 40 - antialiasing_width_offset * 0.5, Color.ORANGE, true, -1.0, use_antialiasing)
+
+	# `draw_set_transform()` is a stateful command: it affects *all* `draw_` methods within this
+	# `_draw()` function after it. This can be used to translate, rotate or scale `draw_` methods
+	# that don't offer dedicated parameters for this (such as `draw_primitive()` not having a position parameter).
+	# To reset back to the initial transform, call `draw_set_transform(Vector2())`.
+	#
+	# Draw an horizontally stretched circle.
+	offset += Vector2(200, 0)
+	draw_set_transform(margin + offset, 0.0, Vector2(3.0, 1.0))
+	draw_circle(Vector2(), 40, Color.ORANGE, false, line_width_thin, use_antialiasing)
+	draw_set_transform(Vector2())
+
+	# Draw a quarter circle (`TAU` represents a full turn in radians).
+	const POINT_COUNT_HIGH = 24
+	offset = Vector2(0, 200)
+	draw_arc(margin + offset, 60, 0, 0.25 * TAU, POINT_COUNT_HIGH, Color.YELLOW, line_width_thin, use_antialiasing)
+
+	offset += Vector2(100, 0)
+	draw_arc(margin + offset, 60, 0, 0.25 * TAU, POINT_COUNT_HIGH, Color.YELLOW, 2.0 - antialiasing_width_offset, use_antialiasing)
+
+	offset += Vector2(100, 0)
+	draw_arc(margin + offset, 60, 0, 0.25 * TAU, POINT_COUNT_HIGH, Color.YELLOW, 6.0 - antialiasing_width_offset, use_antialiasing)
+
+	# Draw a three quarters of a circle with a low point count, which gives it an angular look.
+	const POINT_COUNT_LOW = 7
+	offset += Vector2(125, 30)
+	draw_arc(margin + offset, 40, -0.25 * TAU, 0.5 * TAU, POINT_COUNT_LOW, Color.YELLOW, line_width_thin, use_antialiasing)
+
+	offset += Vector2(100, 0)
+	draw_arc(margin + offset, 40, -0.25 * TAU, 0.5 * TAU, POINT_COUNT_LOW, Color.YELLOW, 2.0 - antialiasing_width_offset, use_antialiasing)
+
+	offset += Vector2(100, 0)
+	draw_arc(margin + offset, 40, -0.25 * TAU, 0.5 * TAU, POINT_COUNT_LOW, Color.YELLOW, 6.0 - antialiasing_width_offset, use_antialiasing)
+
+	# Draw an horizontally stretched arc.
+	offset += Vector2(200, 0)
+	draw_set_transform(margin + offset, 0.0, Vector2(3.0, 1.0))
+	draw_arc(Vector2(), 40, -0.25 * TAU, 0.5 * TAU, POINT_COUNT_LOW, Color.YELLOW, line_width_thin, use_antialiasing)
+	draw_set_transform(Vector2())

+ 1 - 0
2d/custom_drawing/lines.gd.uid

@@ -0,0 +1 @@
+uid://3gt2v4l0gy1

+ 76 - 0
2d/custom_drawing/meshes.gd

@@ -0,0 +1,76 @@
+# This is a `@tool` script so that the custom 2D drawing can be seen in the editor.
+@tool
+extends Panel
+
+var use_antialiasing := false
+
+
+func _draw() -> void:
+	var margin := Vector2(300, 70)
+
+	var offset := Vector2()
+	var text_mesh := TextMesh.new()
+	text_mesh.text = "TextMesh"
+	# In 2D, 1 unit equals 1 pixel, so the default size at which PrimitiveMeshes are displayed is tiny.
+	# Use much larger mesh size to compensate, or use `draw_set_transform()` before using `draw_mesh()`
+	# to scale the draw command.
+	text_mesh.pixel_size = 2.5
+
+	var noise_texture := NoiseTexture2D.new()
+	noise_texture.seamless = true
+	noise_texture.as_normal_map = true
+	noise_texture.noise = FastNoiseLite.new()
+
+	# FIXME: The mesh needs to be added to a MeshInstance2D (even out-of-tree)
+	# for it to be usable in `draw_mesh()` (otherwise, nothing appears and an error is printed).
+	# Same goes for the texture. I don't know why.
+	var mi2d := MeshInstance2D.new()
+	mi2d.mesh = text_mesh
+	mi2d.texture = noise_texture
+
+	var mi2d2 := MeshInstance2D.new()
+	var sphere_mesh := SphereMesh.new()
+	sphere_mesh.height = 80.0
+	sphere_mesh.radius = 40.0
+	mi2d2.mesh = sphere_mesh
+	mi2d2.texture = noise_texture
+
+	# `draw_set_transform()` is a stateful command: it affects *all* `draw_` methods within this
+	# `_draw()` function after it. This can be used to translate, rotate or scale `draw_` methods
+	# that don't offer dedicated parameters for this (such as `draw_primitive()` not having a position parameter).
+	# To reset back to the initial transform, call `draw_set_transform(Vector2())`.
+	#
+	# Flip drawing on the Y axis so the text appears upright.
+	draw_set_transform(margin + offset, 0.0, Vector2(1, -1))
+	draw_mesh(text_mesh, noise_texture)
+
+	offset += Vector2(150, 0)
+	draw_set_transform(margin + offset)
+	draw_mesh(sphere_mesh, noise_texture)
+
+	var gradient_texture := GradientTexture2D.new()
+	gradient_texture.gradient = Gradient.new()
+	var multi_mesh := MultiMesh.new()
+	multi_mesh.use_colors = true
+	multi_mesh.instance_count = 5
+	multi_mesh.set_instance_transform_2d(0, Transform2D(0.0, Vector2(0, 0)))
+	multi_mesh.set_instance_color(0, Color(1, 0.7, 0.7))
+	multi_mesh.set_instance_transform_2d(1, Transform2D(0.0, Vector2(0, 100)))
+	multi_mesh.set_instance_color(1, Color(0.7, 1, 0.7))
+	multi_mesh.set_instance_transform_2d(2, Transform2D(0.0, Vector2(100, 100)))
+	multi_mesh.set_instance_color(2, Color(0.7, 0.7, 1))
+	multi_mesh.set_instance_transform_2d(3, Transform2D(0.0, Vector2(100, 0)))
+	multi_mesh.set_instance_color(3, Color(1, 1, 0.7))
+	multi_mesh.set_instance_transform_2d(4, Transform2D(0.0, Vector2(50, 50)))
+	multi_mesh.set_instance_color(4, Color(0.7, 1, 1))
+	multi_mesh.mesh = sphere_mesh
+	var mmi2d := MultiMeshInstance2D.new()
+	mmi2d.multimesh = multi_mesh
+	mmi2d.texture = gradient_texture
+
+	# FIXME: The multimesh needs to be added to a MultiMeshInstance2D (even out-of-tree)
+	# for it to be usable in `draw_multimesh()` (otherwise, it crashes).
+	# Same goes for the texture. I don't know why.
+	offset = Vector2(0, 120)
+	draw_set_transform(margin + offset)
+	draw_multimesh(multi_mesh, gradient_texture)

+ 1 - 0
2d/custom_drawing/meshes.gd.uid

@@ -0,0 +1 @@
+uid://dy8ofskb8bg4a

+ 126 - 0
2d/custom_drawing/polygons.gd

@@ -0,0 +1,126 @@
+# This is a `@tool` script so that the custom 2D drawing can be seen in the editor.
+@tool
+extends Panel
+
+var use_antialiasing := false
+
+
+func _draw() -> void:
+	var margin := Vector2(240, 40)
+
+	# Line width of `-1.0` is only usable with draw antialiasing disabled,
+	# as it uses hardware line drawing as opposed to polygon-based line drawing.
+	# Automatically use polygon-based line drawing when needed to avoid runtime warnings.
+	# We also use a line width of `0.5` instead of `1.0` to better match the appearance
+	# of non-antialiased line drawing, as draw antialiasing tends to make lines look thicker.
+	var line_width_thin := 0.5 if use_antialiasing else -1.0
+
+	# Make thick lines 1 pixel thinner when draw antialiasing is enabled,
+	# as draw antialiasing tends to make lines look thicker.
+	var antialiasing_width_offset := 1.0 if use_antialiasing else 0.0
+
+	var points := PackedVector2Array([
+		Vector2(0, 0),
+		Vector2(0, 60),
+		Vector2(60, 90),
+		Vector2(60, 0),
+		Vector2(40, 25),
+		Vector2(10, 40),
+	])
+	var colors := PackedColorArray([
+		Color.WHITE,
+		Color.RED,
+		Color.GREEN,
+		Color.BLUE,
+		Color.MAGENTA,
+		Color.MAGENTA,
+	])
+
+	var offset := Vector2()
+	# `draw_set_transform()` is a stateful command: it affects *all* `draw_` methods within this
+	# `_draw()` function after it. This can be used to translate, rotate or scale `draw_` methods
+	# that don't offer dedicated parameters for this (such as `draw_primitive()` not having a position parameter).
+	# To reset back to the initial transform, call `draw_set_transform(Vector2())`.
+	draw_set_transform(margin + offset)
+	draw_primitive(points.slice(0, 1), colors.slice(0, 1), PackedVector2Array())
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_primitive(points.slice(0, 2), colors.slice(0, 2), PackedVector2Array())
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_primitive(points.slice(0, 3), colors.slice(0, 3), PackedVector2Array())
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_primitive(points.slice(0, 4), colors.slice(0, 4), PackedVector2Array())
+
+	# Draw a polygon with multiple colors that are interpolated between each point.
+	# Colors are specified in the same order as points' positions, but in a different array.
+	offset = Vector2(0, 120)
+	draw_set_transform(margin + offset)
+	draw_polygon(points, colors)
+
+	# Draw a polygon with a single color. Only a points array is needed in this case.
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_colored_polygon(points, Color.YELLOW)
+
+	# Draw a polygon-based line. Each segment is connected to the previous one, which means
+	# `draw_polyline()` always draws a contiguous line.
+	offset = Vector2(0, 240)
+	draw_set_transform(margin + offset)
+	draw_polyline(points, Color.SKY_BLUE, line_width_thin, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_polyline(points, Color.SKY_BLUE, 2.0 - antialiasing_width_offset, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_polyline(points, Color.SKY_BLUE, 6.0 - antialiasing_width_offset, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_polyline_colors(points, colors, line_width_thin, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_polyline_colors(points, colors, 2.0 - antialiasing_width_offset, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_polyline_colors(points, colors, 6.0 - antialiasing_width_offset, use_antialiasing)
+
+	# Draw multiple lines in a single draw command. Unlike `draw_polyline()`,
+	# lines are not connected to the last segment.
+	# This is faster than calling `draw_line()` several times and should be preferred
+	# when drawing dozens of lines or more at once.
+	offset = Vector2(0, 360)
+	draw_set_transform(margin + offset)
+	draw_multiline(points, Color.SKY_BLUE, line_width_thin, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_multiline(points, Color.SKY_BLUE, 2.0 - antialiasing_width_offset, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_multiline(points, Color.SKY_BLUE, 6.0 - antialiasing_width_offset, use_antialiasing)
+
+	# `draw_multiline_colors()` makes it possible to draw lines of different colors in a single
+	# draw command, although gradients are not possible this way (unlike `draw_polygon()` and `draw_polyline()`).
+	# This means the number of supplied colors must be equal to the number of segments, which means
+	# we have to only pass a subset of the colors array in this example.
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_multiline_colors(points, colors.slice(0, 3), line_width_thin, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_multiline_colors(points, colors.slice(0, 3), 2.0 - antialiasing_width_offset, use_antialiasing)
+
+	offset += Vector2(90, 0)
+	draw_set_transform(margin + offset)
+	draw_multiline_colors(points, colors.slice(0, 3), 6.0 - antialiasing_width_offset, use_antialiasing)

+ 1 - 0
2d/custom_drawing/polygons.gd.uid

@@ -0,0 +1 @@
+uid://clsf8dubgyrig

+ 31 - 0
2d/custom_drawing/project.godot

@@ -0,0 +1,31 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+;   [section] ; section goes between []
+;   param=value ; assign values to parameters
+
+config_version=5
+
+[application]
+
+config/name="Custom Drawing in 2D"
+config/description="A demo showing how to draw 2D elements in Godot without using nodes."
+run/main_scene="uid://btxwm0qudsn3t"
+config/features=PackedStringArray("4.4", "Mobile")
+run/low_processor_mode=true
+config/icon="res://icon.svg"
+
+[debug]
+
+gdscript/warnings/untyped_declaration=1
+
+[display]
+
+window/stretch/mode="canvas_items"
+window/stretch/aspect="expand"
+
+[rendering]
+
+renderer/rendering_method="mobile"

+ 146 - 0
2d/custom_drawing/rectangles.gd

@@ -0,0 +1,146 @@
+# This is a `@tool` script so that the custom 2D drawing can be seen in the editor.
+@tool
+extends Panel
+
+var use_antialiasing := false
+
+
+func _draw() -> void:
+	var margin := Vector2(200, 40)
+
+	# Line width of `-1.0` is only usable with draw antialiasing disabled,
+	# as it uses hardware line drawing as opposed to polygon-based line drawing.
+	# Automatically use polygon-based line drawing when needed to avoid runtime warnings.
+	# We also use a line width of `0.5` instead of `1.0` to better match the appearance
+	# of non-antialiased line drawing, as draw antialiasing tends to make lines look thicker.
+	var line_width_thin := 0.5 if use_antialiasing else -1.0
+
+	# Make thick lines 1 pixel thinner when draw antialiasing is enabled,
+	# as draw antialiasing tends to make lines look thicker.
+	var antialiasing_width_offset := 1.0 if use_antialiasing else 0.0
+
+	var offset := Vector2()
+	draw_rect(
+			Rect2(margin + offset, Vector2(100, 50)),
+			Color.PURPLE,
+			false,
+			line_width_thin,
+			use_antialiasing
+	)
+
+	offset += Vector2(120, 0)
+	draw_rect(
+			Rect2(margin + offset, Vector2(100, 50)),
+			Color.PURPLE,
+			false,
+			2.0 - antialiasing_width_offset,
+			use_antialiasing
+	)
+
+	offset += Vector2(120, 0)
+	draw_rect(
+			Rect2(margin + offset, Vector2(100, 50)),
+			Color.PURPLE,
+			false,
+			6.0 - antialiasing_width_offset,
+			use_antialiasing
+	)
+
+	# Draw a filled rectangle. The width parameter is ignored for filled rectangles (it's set to `-1.0` to avoid warnings).
+	# We also reduce the rectangle's size by half the antialiasing width offset.
+	# Otherwise, the rectangle becomes very slightly larger when draw antialiasing is enabled.
+	offset += Vector2(120, 0)
+	draw_rect(
+			Rect2(margin + offset, Vector2(100, 50)).grow(-antialiasing_width_offset * 0.5),
+			Color.PURPLE,
+			true,
+			-1.0,
+			use_antialiasing
+	)
+
+	# `draw_set_transform()` is a stateful command: it affects *all* `draw_` methods within this
+	# `_draw()` function after it. This can be used to translate, rotate or scale `draw_` methods
+	# that don't offer dedicated parameters for this (such as `draw_rect()` not having a rotation parameter).
+	# To reset back to the initial transform, call `draw_set_transform(Vector2())`.
+	offset += Vector2(170, 0)
+	draw_set_transform(margin + offset, deg_to_rad(22.5))
+	draw_rect(
+			Rect2(Vector2(), Vector2(100, 50)),
+			Color.PURPLE,
+			false,
+			line_width_thin,
+			use_antialiasing
+	)
+	offset += Vector2(120, 0)
+	draw_set_transform(margin + offset, deg_to_rad(22.5))
+	draw_rect(
+			Rect2(Vector2(), Vector2(100, 50)),
+			Color.PURPLE,
+			false,
+			2.0 - antialiasing_width_offset,
+			use_antialiasing
+	)
+	offset += Vector2(120, 0)
+	draw_set_transform(margin + offset, deg_to_rad(22.5))
+	draw_rect(
+			Rect2(Vector2(), Vector2(100, 50)),
+			Color.PURPLE,
+			false,
+			6.0 - antialiasing_width_offset,
+			use_antialiasing
+	)
+
+	# `draw_set_transform_matrix()` is a more advanced counterpart of `draw_set_transform()`.
+	# It can be used to apply transforms that are not supported by `draw_set_transform()`, such as
+	# skewing.
+	offset = Vector2(20, 60)
+	var custom_transform := get_transform().translated(margin + offset)
+	# Perform horizontal skewing (the rectangle will appear "slanted").
+	custom_transform.y.x -= 0.5
+	draw_set_transform_matrix(custom_transform)
+	draw_rect(
+		Rect2(Vector2(), Vector2(100, 50)),
+		Color.PURPLE,
+		false,
+		6.0 - antialiasing_width_offset,
+		use_antialiasing
+	)
+	draw_set_transform(Vector2())
+
+	offset = Vector2(0, 250)
+	var style_box_flat := StyleBoxFlat.new()
+	style_box_flat.set_border_width_all(4)
+	style_box_flat.set_corner_radius_all(8)
+	style_box_flat.shadow_size = 1
+	style_box_flat.shadow_offset = Vector2(4, 4)
+	style_box_flat.shadow_color = Color.RED
+	style_box_flat.anti_aliasing = use_antialiasing
+	draw_style_box(style_box_flat, Rect2(margin + offset, Vector2(100, 50)))
+
+	offset += Vector2(130, 0)
+	var style_box_flat_2 := StyleBoxFlat.new()
+	style_box_flat_2.draw_center = false
+	style_box_flat_2.set_border_width_all(4)
+	style_box_flat_2.set_corner_radius_all(8)
+	style_box_flat_2.corner_detail = 1
+	style_box_flat_2.border_color = Color.GREEN
+	style_box_flat_2.anti_aliasing = use_antialiasing
+	draw_style_box(style_box_flat_2, Rect2(margin + offset, Vector2(100, 50)))
+
+	offset += Vector2(160, 0)
+	var style_box_flat_3 := StyleBoxFlat.new()
+	style_box_flat_3.draw_center = false
+	style_box_flat_3.set_border_width_all(4)
+	style_box_flat_3.set_corner_radius_all(8)
+	style_box_flat_3.border_color = Color.CYAN
+	style_box_flat_3.shadow_size = 40
+	style_box_flat_3.shadow_offset = Vector2()
+	style_box_flat_3.shadow_color = Color.CORNFLOWER_BLUE
+	style_box_flat_3.anti_aliasing = use_antialiasing
+	custom_transform = get_transform().translated(margin + offset)
+	# Perform vertical skewing (the rectangle will appear "slanted").
+	custom_transform.x.y -= 0.5
+	draw_set_transform_matrix(custom_transform)
+	draw_style_box(style_box_flat_3, Rect2(Vector2(), Vector2(100, 50)))
+
+	draw_set_transform(Vector2())

+ 1 - 0
2d/custom_drawing/rectangles.gd.uid

@@ -0,0 +1 @@
+uid://cquneapbjf3e0

+ 0 - 0
2d/custom_drawing/screenshots/.gdignore


BIN
2d/custom_drawing/screenshots/custom_drawing.webp


+ 60 - 0
2d/custom_drawing/text.gd

@@ -0,0 +1,60 @@
+# This is a `@tool` script so that the custom 2D drawing can be seen in the editor.
+@tool
+extends Panel
+
+var use_antialiasing := false
+
+
+func _draw() -> void:
+	var font := get_theme_default_font()
+	const FONT_SIZE = 24
+	const STRING = "Hello world!"
+	var margin := Vector2(240, 60)
+
+	var offset := Vector2()
+	var advance := Vector2()
+	for character in STRING:
+		# Draw each character with a random pastel color.
+		# Notice how the advance calculated on the loop's previous iteration is used as an offset here.
+		draw_char(font, margin + offset + advance, character, FONT_SIZE, Color.from_hsv(randf(), 0.4, 1.0))
+
+		# Get the glyph index of the character we've just drawn, so we can retrieve the glyph advance.
+		# This determines the spacing between glyphs so the next character is positioned correctly.
+		var glyph_idx := TextServerManager.get_primary_interface().font_get_glyph_index(
+					get_theme_default_font().get_rids()[0],
+					FONT_SIZE,
+					character.unicode_at(0),
+					0
+		)
+		advance.x += TextServerManager.get_primary_interface().font_get_glyph_advance(
+				get_theme_default_font().get_rids()[0],
+				FONT_SIZE,
+				glyph_idx
+		).x
+
+	offset += Vector2(0, 32)
+	# When drawing a font outline, it must be drawn *before* the main text.
+	# This way, the outline appears behind the main text.
+	draw_string_outline(
+			font,
+			margin + offset,
+			STRING,
+			HORIZONTAL_ALIGNMENT_LEFT,
+			-1,
+			FONT_SIZE,
+			12,
+			Color.ORANGE.darkened(0.6)
+	)
+	# NOTE: Use `draw_multiline_string()` to draw strings that contain line breaks (`\n`) or with
+	# automatic line wrapping based on the specified width.
+	# A width of `-1` is used here, which means "no limit". If width is limited, the end of the string
+	# will be cut off if it doesn't fit within the specified width.
+	draw_string(
+			font,
+			margin + offset,
+			STRING,
+			HORIZONTAL_ALIGNMENT_LEFT,
+			-1,
+			FONT_SIZE,
+			Color.YELLOW
+	)

+ 1 - 0
2d/custom_drawing/text.gd.uid

@@ -0,0 +1 @@
+uid://0kv1wvfyg058

+ 67 - 0
2d/custom_drawing/textures.gd

@@ -0,0 +1,67 @@
+# This is a `@tool` script so that the custom 2D drawing can be seen in the editor.
+@tool
+extends Panel
+
+var use_antialiasing := false
+
+func _draw() -> void:
+	const ICON = preload("res://icon.svg")
+	var margin := Vector2(260, 40)
+
+	var offset := Vector2()
+	# Draw a texture.
+	draw_texture(ICON, margin + offset, Color.WHITE)
+
+	# `draw_set_transform()` is a stateful command: it affects *all* `draw_` methods within this
+	# `_draw()` function after it. This can be used to translate, rotate or scale `draw_` methods
+	# that don't offer dedicated parameters for this (such as `draw_rect()` not having a rotation parameter).
+	# To reset back to the initial transform, call `draw_set_transform(Vector2())`.
+	#
+	# Draw a rotated texture at half the scale of its original pixel size.
+	offset += Vector2(200, 20)
+	draw_set_transform(margin + offset, deg_to_rad(45.0), Vector2(0.5, 0.5))
+	draw_texture(ICON, Vector2(), Color.WHITE)
+	draw_set_transform(Vector2())
+
+	# Draw a stretched texture. In this example, the icon is 128×128 so it will be drawn at 2× scale.
+	offset += Vector2(70, -20)
+	draw_texture_rect(
+			ICON,
+			Rect2(margin + offset, Vector2(256, 256)),
+			false,
+			Color.GREEN
+	)
+
+
+	# Draw a tiled texture. In this example, the icon is 128×128 so it will be drawn twice on each axis.
+	offset += Vector2(270, 0)
+	draw_texture_rect(
+			ICON,
+			Rect2(margin + offset, Vector2(256, 256)),
+			true,
+			Color.GREEN
+	)
+
+	offset = Vector2(0, 300)
+
+	draw_texture_rect_region(
+			ICON,
+			Rect2(margin + offset, Vector2(128, 128)),
+			Rect2(Vector2(32, 32), Vector2(64, 64)),
+			Color.VIOLET
+	)
+
+	# Draw a tiled texture from a region that is larger than the original texture size (128×128).
+	# Transposing is enabled, which will rotate the image by 90 degrees counter-clockwise.
+	# (For more advanced transforms, use `draw_set_transform()` before calling `draw_texture_rect_region()`.)
+	#
+	# For tiling to work with this approach, the CanvasItem in which this `_draw()` method is called
+	# must have its Repeat property set to Enabled in the inspector.
+	offset += Vector2(140, 0)
+	draw_texture_rect_region(
+			ICON,
+			Rect2(margin + offset, Vector2(128, 128)),
+			Rect2(Vector2(), Vector2(512, 512)),
+			Color.VIOLET,
+			true
+	)

+ 1 - 0
2d/custom_drawing/textures.gd.uid

@@ -0,0 +1 @@
+uid://dtxyrnurokare