From f78efc69ee1eca12f0b2e119e3c2f7c828f2c587 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Fri, 30 May 2025 17:29:06 +0200 Subject: [PATCH] Add a custom drawing in 2D demo (#1185) --- 2d/custom_drawing/README.md | 23 ++ 2d/custom_drawing/animation.gd | 43 +++ 2d/custom_drawing/animation.gd.uid | 1 + 2d/custom_drawing/animation_slice.gd | 36 +++ 2d/custom_drawing/animation_slice.gd.uid | 1 + 2d/custom_drawing/custom_drawing.gd | 14 + 2d/custom_drawing/custom_drawing.gd.uid | 1 + 2d/custom_drawing/custom_drawing.tscn | 272 ++++++++++++++++++ 2d/custom_drawing/icon.svg | 1 + 2d/custom_drawing/icon.svg.import | 37 +++ 2d/custom_drawing/lines.gd | 89 ++++++ 2d/custom_drawing/lines.gd.uid | 1 + 2d/custom_drawing/meshes.gd | 76 +++++ 2d/custom_drawing/meshes.gd.uid | 1 + 2d/custom_drawing/polygons.gd | 126 ++++++++ 2d/custom_drawing/polygons.gd.uid | 1 + 2d/custom_drawing/project.godot | 31 ++ 2d/custom_drawing/rectangles.gd | 146 ++++++++++ 2d/custom_drawing/rectangles.gd.uid | 1 + 2d/custom_drawing/screenshots/.gdignore | 0 .../screenshots/custom_drawing.webp | Bin 0 -> 47766 bytes 2d/custom_drawing/text.gd | 60 ++++ 2d/custom_drawing/text.gd.uid | 1 + 2d/custom_drawing/textures.gd | 67 +++++ 2d/custom_drawing/textures.gd.uid | 1 + 25 files changed, 1030 insertions(+) create mode 100644 2d/custom_drawing/README.md create mode 100644 2d/custom_drawing/animation.gd create mode 100644 2d/custom_drawing/animation.gd.uid create mode 100644 2d/custom_drawing/animation_slice.gd create mode 100644 2d/custom_drawing/animation_slice.gd.uid create mode 100644 2d/custom_drawing/custom_drawing.gd create mode 100644 2d/custom_drawing/custom_drawing.gd.uid create mode 100644 2d/custom_drawing/custom_drawing.tscn create mode 100644 2d/custom_drawing/icon.svg create mode 100644 2d/custom_drawing/icon.svg.import create mode 100644 2d/custom_drawing/lines.gd create mode 100644 2d/custom_drawing/lines.gd.uid create mode 100644 2d/custom_drawing/meshes.gd create mode 100644 2d/custom_drawing/meshes.gd.uid create mode 100644 2d/custom_drawing/polygons.gd create mode 100644 2d/custom_drawing/polygons.gd.uid create mode 100644 2d/custom_drawing/project.godot create mode 100644 2d/custom_drawing/rectangles.gd create mode 100644 2d/custom_drawing/rectangles.gd.uid create mode 100644 2d/custom_drawing/screenshots/.gdignore create mode 100644 2d/custom_drawing/screenshots/custom_drawing.webp create mode 100644 2d/custom_drawing/text.gd create mode 100644 2d/custom_drawing/text.gd.uid create mode 100644 2d/custom_drawing/textures.gd create mode 100644 2d/custom_drawing/textures.gd.uid diff --git a/2d/custom_drawing/README.md b/2d/custom_drawing/README.md new file mode 100644 index 00000000..add9ba3a --- /dev/null +++ b/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) diff --git a/2d/custom_drawing/animation.gd b/2d/custom_drawing/animation.gd new file mode 100644 index 00000000..083e9614 --- /dev/null +++ b/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 + ) diff --git a/2d/custom_drawing/animation.gd.uid b/2d/custom_drawing/animation.gd.uid new file mode 100644 index 00000000..f50b1e65 --- /dev/null +++ b/2d/custom_drawing/animation.gd.uid @@ -0,0 +1 @@ +uid://b8d4d0s3gujbp diff --git a/2d/custom_drawing/animation_slice.gd b/2d/custom_drawing/animation_slice.gd new file mode 100644 index 00000000..d2e6fe75 --- /dev/null +++ b/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() diff --git a/2d/custom_drawing/animation_slice.gd.uid b/2d/custom_drawing/animation_slice.gd.uid new file mode 100644 index 00000000..51c77daa --- /dev/null +++ b/2d/custom_drawing/animation_slice.gd.uid @@ -0,0 +1 @@ +uid://wksdrvv65620 diff --git a/2d/custom_drawing/custom_drawing.gd b/2d/custom_drawing/custom_drawing.gd new file mode 100644 index 00000000..4d2c4511 --- /dev/null +++ b/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() diff --git a/2d/custom_drawing/custom_drawing.gd.uid b/2d/custom_drawing/custom_drawing.gd.uid new file mode 100644 index 00000000..7c300a51 --- /dev/null +++ b/2d/custom_drawing/custom_drawing.gd.uid @@ -0,0 +1 @@ +uid://cinaeqsrawkbw diff --git a/2d/custom_drawing/custom_drawing.tscn b/2d/custom_drawing/custom_drawing.tscn new file mode 100644 index 00000000..2368ac2f --- /dev/null +++ b/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"] diff --git a/2d/custom_drawing/icon.svg b/2d/custom_drawing/icon.svg new file mode 100644 index 00000000..575a1978 --- /dev/null +++ b/2d/custom_drawing/icon.svg @@ -0,0 +1 @@ + diff --git a/2d/custom_drawing/icon.svg.import b/2d/custom_drawing/icon.svg.import new file mode 100644 index 00000000..e811e87f --- /dev/null +++ b/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 diff --git a/2d/custom_drawing/lines.gd b/2d/custom_drawing/lines.gd new file mode 100644 index 00000000..80404313 --- /dev/null +++ b/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()) diff --git a/2d/custom_drawing/lines.gd.uid b/2d/custom_drawing/lines.gd.uid new file mode 100644 index 00000000..34127534 --- /dev/null +++ b/2d/custom_drawing/lines.gd.uid @@ -0,0 +1 @@ +uid://3gt2v4l0gy1 diff --git a/2d/custom_drawing/meshes.gd b/2d/custom_drawing/meshes.gd new file mode 100644 index 00000000..b404b63f --- /dev/null +++ b/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) diff --git a/2d/custom_drawing/meshes.gd.uid b/2d/custom_drawing/meshes.gd.uid new file mode 100644 index 00000000..ae554c2c --- /dev/null +++ b/2d/custom_drawing/meshes.gd.uid @@ -0,0 +1 @@ +uid://dy8ofskb8bg4a diff --git a/2d/custom_drawing/polygons.gd b/2d/custom_drawing/polygons.gd new file mode 100644 index 00000000..fb769288 --- /dev/null +++ b/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) diff --git a/2d/custom_drawing/polygons.gd.uid b/2d/custom_drawing/polygons.gd.uid new file mode 100644 index 00000000..4d77e747 --- /dev/null +++ b/2d/custom_drawing/polygons.gd.uid @@ -0,0 +1 @@ +uid://clsf8dubgyrig diff --git a/2d/custom_drawing/project.godot b/2d/custom_drawing/project.godot new file mode 100644 index 00000000..14b9b0c0 --- /dev/null +++ b/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" diff --git a/2d/custom_drawing/rectangles.gd b/2d/custom_drawing/rectangles.gd new file mode 100644 index 00000000..f808836c --- /dev/null +++ b/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()) diff --git a/2d/custom_drawing/rectangles.gd.uid b/2d/custom_drawing/rectangles.gd.uid new file mode 100644 index 00000000..7ea8311f --- /dev/null +++ b/2d/custom_drawing/rectangles.gd.uid @@ -0,0 +1 @@ +uid://cquneapbjf3e0 diff --git a/2d/custom_drawing/screenshots/.gdignore b/2d/custom_drawing/screenshots/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/2d/custom_drawing/screenshots/custom_drawing.webp b/2d/custom_drawing/screenshots/custom_drawing.webp new file mode 100644 index 0000000000000000000000000000000000000000..808d5b9be76dfc45dd354dc30378686f44126084 GIT binary patch literal 47766 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 R$W*%g)RZvwA&AL_{U1CM|BV0u literal 0 HcmV?d00001 diff --git a/2d/custom_drawing/text.gd b/2d/custom_drawing/text.gd new file mode 100644 index 00000000..a6f10b8c --- /dev/null +++ b/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 + ) diff --git a/2d/custom_drawing/text.gd.uid b/2d/custom_drawing/text.gd.uid new file mode 100644 index 00000000..c11fa4ff --- /dev/null +++ b/2d/custom_drawing/text.gd.uid @@ -0,0 +1 @@ +uid://0kv1wvfyg058 diff --git a/2d/custom_drawing/textures.gd b/2d/custom_drawing/textures.gd new file mode 100644 index 00000000..b24d4a5c --- /dev/null +++ b/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 + ) diff --git a/2d/custom_drawing/textures.gd.uid b/2d/custom_drawing/textures.gd.uid new file mode 100644 index 00000000..0aa397f6 --- /dev/null +++ b/2d/custom_drawing/textures.gd.uid @@ -0,0 +1 @@ +uid://dtxyrnurokare