├── LICENSE ├── README.md └── source ├── addons ├── batch_rename │ ├── interface.tscn │ ├── plugin.cfg │ └── plugin.gd ├── code_color_pick │ ├── interface.tscn │ ├── plugin.cfg │ └── plugin.gd └── drag_n_drop │ ├── interface.tscn │ ├── plugin.cfg │ └── plugin.gd ├── tools ├── AssetBundler.gd ├── DependencyFixer.gd ├── TrackPathFixer.gd ├── audio_library_factory.gd ├── foldify.gd ├── renamer.gd ├── scenefy.gd └── tile_assiner.gd └── utility_nodes └── Geometry2D.gd /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pigdev Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pigdev's Godot Tools 2 | A set of GDScript tools and plugins that helps you improve your workflow in Godot Engine 3 | -------------------------------------------------------------------------------- /source/addons/batch_rename/interface.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="Control" type="Control" index="0"] 4 | 5 | anchor_left = 0.0 6 | anchor_top = 0.0 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | rect_pivot_offset = Vector2( 0, 0 ) 10 | rect_clip_content = false 11 | mouse_filter = 0 12 | mouse_default_cursor_shape = 0 13 | size_flags_horizontal = 1 14 | size_flags_vertical = 1 15 | 16 | [node name="ColorRect" type="ColorRect" parent="." index="0"] 17 | 18 | anchor_left = 0.0 19 | anchor_top = 0.0 20 | anchor_right = 1.0 21 | anchor_bottom = 1.0 22 | rect_pivot_offset = Vector2( 0, 0 ) 23 | rect_clip_content = false 24 | mouse_filter = 0 25 | mouse_default_cursor_shape = 0 26 | size_flags_horizontal = 1 27 | size_flags_vertical = 1 28 | color = Color( 0, 0, 0, 0.470588 ) 29 | 30 | [node name="LineEdit" type="LineEdit" parent="." index="1"] 31 | 32 | anchor_left = 0.5 33 | anchor_top = 0.5 34 | anchor_right = 0.5 35 | anchor_bottom = 0.5 36 | margin_left = -60.0 37 | margin_top = -12.0 38 | margin_right = 59.0 39 | margin_bottom = 12.0 40 | rect_pivot_offset = Vector2( 0, 0 ) 41 | rect_clip_content = false 42 | focus_mode = 2 43 | mouse_filter = 0 44 | mouse_default_cursor_shape = 1 45 | size_flags_horizontal = 1 46 | size_flags_vertical = 1 47 | align = 1 48 | expand_to_text_length = true 49 | focus_mode = 2 50 | context_menu_enabled = true 51 | placeholder_text = "input new name" 52 | placeholder_alpha = 0.6 53 | caret_blink = false 54 | caret_blink_speed = 0.65 55 | caret_position = 0 56 | _sections_unfolded = [ "Placeholder" ] 57 | 58 | 59 | -------------------------------------------------------------------------------- /source/addons/batch_rename/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name = "Batch Rename" 4 | description = "A plugin that takes multiple nodes in the Scene Tree tab and rename them" 5 | author = "Henrique Campos" 6 | version = "0.0.1" 7 | script = "plugin.gd" 8 | -------------------------------------------------------------------------------- /source/addons/batch_rename/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | func _ready(): 5 | get_editor_interface().get_selection().connect("selection_changed", self, 6 | "_on_selection_changed") 7 | set_process_input(false) 8 | 9 | func _on_selection_changed(): 10 | if get_editor_interface().get_selection().get_selected_nodes().size() > 1: 11 | set_process_input(true) 12 | else: 13 | set_process_input(false) 14 | 15 | func _input(event): 16 | if Input.is_key_pressed(KEY_F12): 17 | var r = load("res://addons/batch_rename/interface.tscn").instance() 18 | r.get_node("LineEdit").connect("text_entered", self, 19 | "_on_text_entered", [r]) 20 | get_editor_interface().get_base_control().add_child(r) 21 | 22 | func _on_text_entered(new_text, interface): 23 | var nodes = get_editor_interface().get_selection().get_selected_nodes() 24 | nodes.sort_custom(self, "sort_by_index") 25 | 26 | for n in nodes: 27 | n.name = new_text 28 | 29 | interface.queue_free() 30 | 31 | func sort_by_index(a, b): 32 | return a.get_index() < b.get_index() 33 | -------------------------------------------------------------------------------- /source/addons/code_color_pick/interface.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="ColorPicker" type="ColorPickerButton" index="0"] 4 | 5 | anchor_left = 0.0 6 | anchor_top = 0.0 7 | anchor_right = 0.0 8 | anchor_bottom = 0.0 9 | margin_right = 232.0 10 | margin_bottom = 31.0 11 | rect_pivot_offset = Vector2( 0, 0 ) 12 | rect_clip_content = false 13 | focus_mode = 2 14 | mouse_filter = 0 15 | mouse_default_cursor_shape = 0 16 | size_flags_horizontal = 1 17 | size_flags_vertical = 1 18 | toggle_mode = false 19 | enabled_focus_mode = 2 20 | shortcut = null 21 | group = null 22 | flat = false 23 | align = 1 24 | color = Color( 1, 1, 1, 1 ) 25 | edit_alpha = true 26 | 27 | [node name="Button" type="Button" parent="." index="1"] 28 | 29 | anchor_left = 0.0 30 | anchor_top = 0.0 31 | anchor_right = 0.0 32 | anchor_bottom = 0.0 33 | margin_top = 32.0 34 | margin_right = 233.0 35 | margin_bottom = 64.0 36 | rect_pivot_offset = Vector2( 0, 0 ) 37 | rect_clip_content = false 38 | focus_mode = 2 39 | mouse_filter = 0 40 | mouse_default_cursor_shape = 0 41 | size_flags_horizontal = 1 42 | size_flags_vertical = 1 43 | toggle_mode = false 44 | enabled_focus_mode = 2 45 | shortcut = null 46 | group = null 47 | text = "Confirm" 48 | flat = false 49 | align = 1 50 | 51 | 52 | -------------------------------------------------------------------------------- /source/addons/code_color_pick/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name = "Code Color Picker" 4 | description = "Extends the Script Editor to display a ColorPicker whenever you are about to create a new Color" 5 | author = "Henrique Campos" 6 | version = "0.0.1" 7 | script = "plugin.gd" 8 | -------------------------------------------------------------------------------- /source/addons/code_color_pick/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | func _ready(): 5 | get_editor_interface().get_script_editor().connect("editor_script_changed", 6 | self, "_on_editor_script_changed") 7 | 8 | find_text_editor(get_editor_interface().get_script_editor()) 9 | 10 | func _on_editor_script_changed(new_script): 11 | find_text_editor(get_editor_interface().get_script_editor()) 12 | 13 | func _on_cursor_changed(text_edit): 14 | var line = text_edit.get_line(text_edit.cursor_get_line()) 15 | 16 | if line.ends_with("Color"): 17 | _show_color(text_edit) 18 | 19 | func find_text_editor(node): 20 | for c in node.get_children(): 21 | if c is TextEdit: 22 | if c.is_connected("cursor_changed", self, "_on_cursor_changed"): 23 | c.disconnect("cursor_changed", self, "_on_cursor_changed") 24 | c.connect("cursor_changed", self, "_on_cursor_changed", [c]) 25 | if c.get_child_count() > 0: 26 | find_text_editor(c) 27 | 28 | func _show_color(text_edit): 29 | if has_node("ColorPicker"): 30 | return 31 | 32 | var picker = load("res://addons/code_color_pick/interface.tscn").instance() 33 | text_edit.add_child(picker) 34 | picker.rect_position = text_edit.get_local_mouse_position() 35 | 36 | yield(picker.get_node("Button"), "button_up") 37 | text_edit.insert_text_at_cursor("(" + str(picker.color) + ")") 38 | picker.queue_free() 39 | -------------------------------------------------------------------------------- /source/addons/drag_n_drop/interface.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="Panel" type="Panel" index="0"] 4 | 5 | anchor_left = 0.0 6 | anchor_top = 1.0 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | margin_top = -34.0 10 | rect_pivot_offset = Vector2( 0, 0 ) 11 | rect_clip_content = false 12 | mouse_filter = 0 13 | mouse_default_cursor_shape = 0 14 | size_flags_horizontal = 1 15 | size_flags_vertical = 1 16 | 17 | [node name="Label" type="Label" parent="." index="0"] 18 | 19 | anchor_left = 0.0 20 | anchor_top = 0.0 21 | anchor_right = 1.0 22 | anchor_bottom = 1.0 23 | rect_pivot_offset = Vector2( 0, 0 ) 24 | rect_clip_content = false 25 | mouse_filter = 2 26 | mouse_default_cursor_shape = 0 27 | size_flags_horizontal = 1 28 | size_flags_vertical = 4 29 | text = "Drop to GET, hold SHIFT and drop to SET." 30 | align = 1 31 | valign = 1 32 | percent_visible = 1.0 33 | lines_skipped = 0 34 | max_lines_visible = -1 35 | 36 | 37 | -------------------------------------------------------------------------------- /source/addons/drag_n_drop/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name = "Inspector Drag n Drop" 4 | description = "Extends the Script Editor to accept drag and drop events from the Inspector" 5 | author = "Henrique Campos" 6 | version = "0.0.1" 7 | script = "plugin.gd" 8 | -------------------------------------------------------------------------------- /source/addons/drag_n_drop/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | var current_text_edit = null 5 | signal dropped 6 | 7 | func _ready(): 8 | get_editor_interface().get_script_editor().connect("editor_script_changed", 9 | self, "_on_editor_script_changed") 10 | 11 | find_text_editor(get_editor_interface().get_script_editor()) 12 | 13 | connect("main_screen_changed", self, "_on_main_screen_changed") 14 | 15 | set_process_input(false) 16 | set_process(false) 17 | set_physics_process(false) 18 | 19 | func _on_main_screen_changed(new_screen): 20 | set_process_input(new_screen == "Script") 21 | 22 | func find_text_editor(node): 23 | for c in node.get_children(): 24 | if c is TextEdit: 25 | if c.is_connected("mouse_entered", self, "_on_mouse_entered"): 26 | c.disconnect("mouse_entered", self, "_on_mouse_entered") 27 | c.connect("mouse_entered", self, "_on_mouse_entered", [c]) 28 | if c.get_child_count() > 0: 29 | find_text_editor(c) 30 | 31 | func _on_editor_script_changed(new_script): 32 | find_text_editor(get_editor_interface().get_script_editor()) 33 | 34 | func _on_mouse_entered(text_edit): 35 | current_text_edit = text_edit 36 | 37 | var data = get_viewport().gui_get_drag_data() 38 | if not data: 39 | return 40 | 41 | var i = load("res://addons/drag_n_drop/interface.tscn").instance() 42 | text_edit.add_child(i) 43 | 44 | yield(self, "dropped") 45 | i.queue_free() 46 | 47 | func _input(event): 48 | if current_text_edit == null: 49 | return 50 | if not event is InputEventMouseButton: 51 | return 52 | if event.is_pressed(): 53 | return 54 | 55 | var data = get_viewport().gui_get_drag_data() 56 | if not data: 57 | return 58 | if data.type == "obj_property": 59 | _drop_property(data) 60 | elif data.type == "nodes": 61 | _drop_node(data) 62 | 63 | func _drop_property(data): 64 | var text = "" 65 | 66 | if data.object == get_editor_interface().get_edited_scene_root(): 67 | text = data.property 68 | else: 69 | var path = get_editor_interface().get_edited_scene_root().get_path_to(data.object) 70 | text = "$%s.%s"%[path, data.property] 71 | 72 | if Input.is_key_pressed(KEY_SHIFT): 73 | text = text + " = " 74 | 75 | current_text_edit.insert_text_at_cursor(text) 76 | emit_signal("dropped") 77 | 78 | func _drop_node(data): 79 | var text = "" 80 | var nodes = get_editor_interface().get_selection().get_selected_nodes() 81 | for n in nodes: 82 | var path = get_editor_interface().get_edited_scene_root().get_path_to(n) 83 | if Input.is_key_pressed(KEY_SHIFT): 84 | text = 'onready var %s = get_node("%s")\n'%[n.name.to_lower(), path] 85 | current_text_edit.insert_text_at_cursor(text) 86 | else: 87 | text = 'get_node("%s")'%[path] 88 | current_text_edit.insert_text_at_cursor(text) 89 | break 90 | emit_signal("dropped") 91 | -------------------------------------------------------------------------------- /source/tools/AssetBundler.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorScript 3 | 4 | 5 | func _run(): 6 | var interface = get_editor_interface() 7 | var selected_paths = get_editor_interface().get_selected_paths() 8 | for path in selected_paths: 9 | var directory = DirAccess.open(path.get_base_dir()) 10 | var file_name = path.get_file().replace("." + path.get_extension(), "") 11 | directory.make_dir(file_name) 12 | var target_dir = path.get_base_dir() + "/%s/" % file_name 13 | directory.copy(path, target_dir + path.get_file()) 14 | bundle(path, target_dir) 15 | get_editor_interface().get_resource_filesystem().scan() 16 | 17 | 18 | func bundle(selected_file, target_path): 19 | for dependency in ResourceLoader.get_dependencies(selected_file): 20 | var erasing_start = dependency.find("res://") 21 | var erased = dependency.erase(erasing_start, dependency.length()) 22 | var dependency_path = dependency.replace(erased, "") 23 | var new_dependency_path = target_path + dependency_path.get_file() 24 | var directory = DirAccess.open(target_path.get_base_dir()) 25 | directory.copy(dependency_path, new_dependency_path) 26 | for sub_dependency in ResourceLoader.get_dependencies(dependency_path): 27 | bundle(dependency_path, target_path) 28 | var directory = DirAccess.open(target_path) 29 | directory.copy(selected_file, target_path) 30 | -------------------------------------------------------------------------------- /source/tools/DependencyFixer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorScript 3 | 4 | 5 | func _run(): 6 | var interface = get_editor_interface() 7 | var filesys = interface.get_resource_filesystem() 8 | var dir = filesys.get_filesystem_path(get_editor_interface().get_selected_path()) 9 | 10 | var res = filesys.get_filesystem() 11 | 12 | for f in dir.get_file_count(): 13 | var path = dir.get_file_path(f) 14 | var dependencies = ResourceLoader.get_dependencies(path) 15 | var file = File.new() 16 | 17 | for d in dependencies: 18 | if file.file_exists(d): 19 | continue 20 | fix_dependency(d, res, path) 21 | 22 | for subdir in dir.get_subdir_count(): 23 | subdir = dir.get_subdir(subdir) 24 | for f in subdir.get_file_count(): 25 | var path = subdir.get_file_path(f) 26 | var dependencies = ResourceLoader.get_dependencies(path) 27 | if dependencies.size() < 1: 28 | continue 29 | var file = File.new() 30 | for d in dependencies: 31 | if file.file_exists(d): 32 | continue 33 | fix_dependency(d, res, path) 34 | get_editor_interface().get_resource_filesystem().scan() 35 | 36 | 37 | func fix_dependency(dependency, directory, resource_path): 38 | for subdir in directory.get_subdir_count(): 39 | fix_dependency(dependency, directory.get_subdir(subdir), resource_path) 40 | 41 | for f in directory.get_file_count(): 42 | if not directory.get_file(f) == dependency.get_file(): 43 | continue 44 | var file = File.new() 45 | file.open(resource_path, file.READ) 46 | var text = file.get_as_text() 47 | file.close() 48 | text = text.replace(dependency, directory.get_file_path(f)) 49 | file.open(resource_path, file.WRITE) 50 | file.store_string(text) 51 | file.close() 52 | -------------------------------------------------------------------------------- /source/tools/TrackPathFixer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends AnimationPlayer 3 | 4 | export (String) var undesired_path 5 | export (bool) var fix_it = false setget set_fix_it 6 | 7 | func set_fix_it(value): 8 | if !value: 9 | return 10 | undesired_path = undesired_path.to_lower() 11 | for a in get_animation_list(): 12 | var anim = get_animation(a) 13 | for t in anim.get_track_count(): 14 | var p = str(anim.track_get_path(t)) 15 | p = p.to_lower() 16 | if p.begins_with(undesired_path): 17 | p.erase(0, undesired_path.length() + 1) 18 | print(p) 19 | anim.track_set_path(t, p) 20 | ResourceSaver.save(anim.get_path(), anim) 21 | -------------------------------------------------------------------------------- /source/tools/audio_library_factory.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorScript 3 | 4 | func _run(): 5 | var interface = get_editor_interface() 6 | var filesys = interface.get_resource_filesystem() 7 | var dir = filesys.get_filesystem_path(get_scene().source_directory) 8 | 9 | for f in dir.get_file_count(): 10 | var path = dir.get_file_path(f) 11 | if not filesys.get_file_type(path) == "AudioStreamOGGVorbis": 12 | continue 13 | 14 | var n = AudioStreamPlayer.new() 15 | var node_name = dir.get_file(f) 16 | node_name = node_name.replace(".ogg", "") 17 | n.name = node_name 18 | 19 | var stream = AudioStreamRandomPitch.new() 20 | stream.audio_stream = load(path) 21 | n.stream = stream 22 | 23 | var stream_path = dir.get_file(f) 24 | stream_path = stream_path.replace(".ogg", ".tres") 25 | ResourceSaver.save(dir.get_parent().get_path() + stream_path, stream) 26 | 27 | get_scene().add_child(n) 28 | n.owner = get_scene() 29 | -------------------------------------------------------------------------------- /source/tools/foldify.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorScript 3 | 4 | const PATH = "res://characters/enemies/" 5 | 6 | func _run(): 7 | var interface = get_editor_interface() 8 | var filesys = interface.get_resource_filesystem() 9 | var dir = filesys.get_filesystem_path(PATH) 10 | 11 | for f in dir.get_file_count(): 12 | var path = dir.get_file_path(f) 13 | if not filesys.get_file_type(path) == "StreamTexture": 14 | continue 15 | 16 | var dir_name = dir.get_file(f) 17 | dir_name = dir_name.replace(".png", "") 18 | 19 | #Ad hoc solution to remove file "index" from directory's name 20 | if "0" in dir_name: 21 | dir_name.erase(dir_name.length() - 3, 3) 22 | 23 | var new_path = PATH + dir_name + "/" 24 | create_directory(new_path) 25 | move_file(path, new_path, dir.get_file(f)) 26 | 27 | filesys.scan() 28 | 29 | func create_directory(path): 30 | var dir = Directory.new() 31 | if dir.dir_exists(path): 32 | return 33 | dir.make_dir(path) 34 | 35 | func move_file(path, new_path, file): 36 | var dir = Directory.new() 37 | dir.rename(path, new_path + file) 38 | -------------------------------------------------------------------------------- /source/tools/renamer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | export (String) var new_name = "" 5 | export (bool) var apply = false setget set_apply 6 | 7 | func set_apply(value): 8 | if not value: 9 | return 10 | for c in get_children(): 11 | c.name = new_name + str(c.get_index() + 1) -------------------------------------------------------------------------------- /source/tools/scenefy.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorScript 3 | 4 | const PATH = "res://characters/enemies/" 5 | const SCENE_TYPE = AnimatedSprite 6 | 7 | func _run(): 8 | var interface = get_editor_interface() 9 | var filesys = interface.get_resource_filesystem() 10 | var dir = filesys.get_filesystem_path(PATH) 11 | 12 | for d in dir.get_subdir_count(): 13 | var resource_name = dir.get_subdir(d).get_name() 14 | 15 | var n = SCENE_TYPE.new() 16 | n.name = resource_name 17 | 18 | var sprites = SpriteFrames.new() 19 | sprites.resource_name = resource_name 20 | 21 | sprites.rename_animation("default", "idle") 22 | 23 | var subdir = dir.get_subdir(d) 24 | 25 | for f in subdir.get_file_count(): 26 | if f == subdir.get_file_count() - 1: 27 | sprites.add_animation("dead") 28 | sprites.add_frame("dead", load(subdir.get_file_path(f)), f) 29 | else: 30 | sprites.add_frame("idle", load(subdir.get_file_path(f)), f) 31 | 32 | sprites.set_animation_loop("idle", true) 33 | sprites.set_animation_speed("idle", 4) 34 | sprites.set_animation_loop("dead", false) 35 | sprites.set_animation_speed("dead", 1) 36 | 37 | var sprites_path = dir.get_path() + resource_name + ".tres" 38 | var scene_path = dir.get_path() + resource_name + ".tscn" 39 | 40 | ResourceSaver.save(sprites_path, sprites) 41 | 42 | n.frames = load(sprites_path) 43 | n.animation = "idle" 44 | 45 | var scene = PackedScene.new() 46 | scene.pack(n) 47 | 48 | ResourceSaver.save(scene_path, scene) 49 | 50 | filesys.scan() -------------------------------------------------------------------------------- /source/tools/tile_assiner.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorScript 3 | 4 | func _run(): 5 | var interface = get_editor_interface() 6 | var filesys = interface.get_resource_filesystem() 7 | var dir = filesys.get_filesystem_path(get_scene().tiles_folder) 8 | 9 | for f in dir.get_file_count(): 10 | var path = dir.get_file_path(f) 11 | if not filesys.get_file_type(path) == "StreamTexture": 12 | continue 13 | get_scene().get_child(f).texture = (load(path)) 14 | -------------------------------------------------------------------------------- /source/utility_nodes/Geometry2D.gd: -------------------------------------------------------------------------------- 1 | tool 2 | class_name Geometry2D 3 | extends CollisionShape2D 4 | 5 | export (Color) var color = Color(1, 1, 1, 1) setget set_color 6 | 7 | func set_color(new_color): 8 | color = new_color 9 | update() 10 | 11 | func _draw(): 12 | var offset_position = Vector2(0, 0) 13 | 14 | if shape is CircleShape2D: 15 | draw_circle(offset_position, shape.radius, color) 16 | elif shape is RectangleShape2D: 17 | var rect = Rect2(offset_position - shape.extents, shape.extents * 2.0) 18 | draw_rect(rect, color) 19 | elif shape is CapsuleShape2D: 20 | var upper_circle_position = offset_position + Vector2(0, shape.height * 0.5) 21 | draw_circle(upper_circle_position, shape.radius, color) 22 | 23 | var lower_circle_position = offset_position - Vector2(0, shape.height * 0.5) 24 | draw_circle(lower_circle_position, shape.radius, color) 25 | 26 | var rect_position = offset_position - Vector2(shape.radius, shape.height * 0.5) 27 | var rect = Rect2(rect_position, Vector2(shape.radius * 2, shape.height)) 28 | draw_rect(rect, color) 29 | --------------------------------------------------------------------------------