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 00000000..808d5b9b Binary files /dev/null and b/2d/custom_drawing/screenshots/custom_drawing.webp differ 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