├── example.png ├── auto-example.png ├── .gitattributes ├── addons └── Tracker2D │ ├── tests │ ├── add_by_name.tscn │ ├── add_by_class.tscn │ ├── add_by_group.tscn │ ├── test_scene.tscn │ └── test_scene.gd │ ├── plugin.cfg │ ├── tracker_2d.tscn │ ├── plugin.gd │ ├── rect.gd │ ├── position_info_overlay.tscn │ ├── position_info_box_theme.tres │ ├── tracker_2d.gd │ ├── position_info_overlay.gd │ ├── position_info_box.tscn │ └── position_info_box.gd ├── .gitignore ├── example.png.import ├── auto-example.png.import ├── icon.svg.import ├── project.godot ├── LICENSE ├── icon.svg └── README.md /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danboo/Godot-Tracker2D-Plugin/HEAD/example.png -------------------------------------------------------------------------------- /auto-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danboo/Godot-Tracker2D-Plugin/HEAD/auto-example.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /addons/Tracker2D/tests/add_by_name.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://b37ljwlhyesxb"] 2 | 3 | [node name="auto_track_test_name" type="Node2D"] 4 | -------------------------------------------------------------------------------- /addons/Tracker2D/tests/add_by_class.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://11cu8ycnf2nb"] 2 | 3 | [node name="auto_track_test_class" type="PointLight2D"] 4 | -------------------------------------------------------------------------------- /addons/Tracker2D/tests/add_by_group.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://droi0rkk6llu6"] 2 | 3 | [node name="auto_track_test_group" type="Node2D" groups=["auto_track_test_group"]] 4 | -------------------------------------------------------------------------------- /addons/Tracker2D/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Tracker2D" 4 | description="A plugin for Godot 4 that allows visually tracking 2D node information, such as positions, rotations and property values." 5 | author="Dan Boorstein" 6 | version="1.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot-specific ignores 2 | .import/ 3 | .godot/ 4 | .godot/editor/ 5 | export.cfg 6 | export_presets.cfg 7 | 8 | # Imported translations (automatically generated from CSV files) 9 | *.translation 10 | 11 | # Mono-specific ignores 12 | .mono/ 13 | data_*/ 14 | -------------------------------------------------------------------------------- /addons/Tracker2D/tracker_2d.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://bl4o2qqik8q7k"] 2 | 3 | [ext_resource type="Script" path="res://addons/Tracker2D/tracker_2d.gd" id="1_yg1mo"] 4 | 5 | [node name="Tracker2D" type="Node2D" groups=["tracker_2d_nodes"]] 6 | script = ExtResource("1_yg1mo") 7 | -------------------------------------------------------------------------------- /addons/Tracker2D/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | const AUTOLOAD_NAME : String = "Tracker2D_Overlay" 5 | 6 | func _enter_tree() -> void: 7 | add_autoload_singleton(AUTOLOAD_NAME, "res://addons/Tracker2D/position_info_overlay.tscn") 8 | 9 | func _exit_tree() -> void: 10 | remove_autoload_singleton(AUTOLOAD_NAME) 11 | -------------------------------------------------------------------------------- /addons/Tracker2D/rect.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var _tracker : Tracker2D 4 | var _parent : Node2D 5 | 6 | func _process(delta): 7 | global_rotation = _parent.global_rotation 8 | global_scale = _parent.global_scale 9 | 10 | func _draw(): 11 | if _tracker.display_rect && _parent._edit_use_rect(): 12 | var rect : Rect2 = _parent._edit_get_rect() 13 | draw_rect(rect, Color.WHITE, false) 14 | 15 | func set_tracker(value : Tracker2D) -> void: 16 | _tracker = value 17 | _parent = _tracker.get_parent() 18 | -------------------------------------------------------------------------------- /addons/Tracker2D/position_info_overlay.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://pb7dkt4qho2x"] 2 | 3 | [ext_resource type="Script" path="res://addons/Tracker2D/position_info_overlay.gd" id="1_604hk"] 4 | 5 | [node name="position_info_overlay" type="CanvasLayer"] 6 | layer = 128 7 | script = ExtResource("1_604hk") 8 | 9 | [node name="position_info_overlay" type="Control" parent="."] 10 | layout_mode = 3 11 | anchors_preset = 15 12 | anchor_right = 1.0 13 | anchor_bottom = 1.0 14 | grow_horizontal = 2 15 | grow_vertical = 2 16 | mouse_filter = 2 17 | -------------------------------------------------------------------------------- /example.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cvk00qr7074w6" 6 | path="res://.godot/imported/example.png-5dd84d06bb6eb6f43df0df83dd0703ba.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://example.png" 14 | dest_files=["res://.godot/imported/example.png-5dd84d06bb6eb6f43df0df83dd0703ba.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/lossy_quality=0.7 20 | compress/hdr_compression=1 21 | compress/bptc_ldr=0 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /auto-example.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://vpb7vrhhx7pl" 6 | path="res://.godot/imported/auto-example.png-ffe28ade2f7aad1e56a46ea923b99172.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://auto-example.png" 14 | dest_files=["res://.godot/imported/auto-example.png-ffe28ade2f7aad1e56a46ea923b99172.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/lossy_quality=0.7 20 | compress/hdr_compression=1 21 | compress/bptc_ldr=0 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/Tracker2D/tests/test_scene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bl3663sybkeg8"] 2 | 3 | [ext_resource type="Script" path="res://addons/Tracker2D/tests/test_scene.gd" id="1_3dluf"] 4 | [ext_resource type="Texture2D" uid="uid://bibtqiobruk1f" path="res://icon.svg" id="2_5vqry"] 5 | 6 | [node name="test_scene" type="Node2D"] 7 | script = ExtResource("1_3dluf") 8 | 9 | [node name="rotator" type="Node2D" parent="."] 10 | 11 | [node name="tracked_node" type="Node2D" parent="rotator"] 12 | 13 | [node name="occluded_sprite" type="Sprite2D" parent="rotator"] 14 | modulate = Color(1, 0, 0.0901961, 1) 15 | position = Vector2(100, 100) 16 | scale = Vector2(1.5, 1.5) 17 | texture = ExtResource("2_5vqry") 18 | 19 | [node name="occluding_sprite" type="Sprite2D" parent="."] 20 | position = Vector2(-200, 200) 21 | scale = Vector2(2, 2) 22 | texture = ExtResource("2_5vqry") 23 | 24 | [node name="Camera2D" type="Camera2D" parent="."] 25 | current = true 26 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bibtqiobruk1f" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/lossy_quality=0.7 20 | compress/hdr_compression=1 21 | compress/bptc_ldr=0 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/Tracker2D/position_info_box_theme.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=4 format=3 uid="uid://jqhxocgoy8gg"] 2 | 3 | [sub_resource type="StyleBoxLine" id="StyleBoxLine_euoqn"] 4 | color = Color(0.392157, 0.392157, 0.517647, 0.517647) 5 | 6 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pj0g6"] 7 | bg_color = Color(0.129412, 0.129412, 0.258824, 0.8) 8 | corner_radius_top_left = 2 9 | corner_radius_top_right = 2 10 | corner_radius_bottom_right = 2 11 | corner_radius_bottom_left = 2 12 | shadow_color = Color(0.129412, 0.129412, 0.258824, 0.258824) 13 | shadow_size = 4 14 | shadow_offset = Vector2(2, 2) 15 | 16 | [sub_resource type="SystemFont" id="SystemFont_rets4"] 17 | font_names = PackedStringArray("Consolas", "Lucida Console", "Monospace", "Courier New") 18 | 19 | [resource] 20 | default_font = SubResource("SystemFont_rets4") 21 | HSeparator/constants/separation = 0 22 | HSeparator/styles/separator = SubResource("StyleBoxLine_euoqn") 23 | Label/constants/line_spacing = 0 24 | Label/font_sizes/font_size = 10 25 | PanelContainer/styles/panel = SubResource("StyleBoxFlat_pj0g6") 26 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | _global_script_classes=[{ 12 | "base": "Control", 13 | "class": &"InfoBox", 14 | "language": &"GDScript", 15 | "path": "res://addons/Tracker2D/position_info_box.gd" 16 | }, { 17 | "base": "Node2D", 18 | "class": &"Tracker2D", 19 | "language": &"GDScript", 20 | "path": "res://addons/Tracker2D/tracker_2d.gd" 21 | }] 22 | _global_script_class_icons={ 23 | "InfoBox": "", 24 | "Tracker2D": "" 25 | } 26 | 27 | [application] 28 | 29 | config/name="Godot-Tracker2D-Plugin" 30 | run/main_scene="res://addons/Tracker2D/tests/test_scene.tscn" 31 | config/features=PackedStringArray("4.0", "Forward Plus") 32 | config/icon="res://icon.svg" 33 | 34 | [autoload] 35 | 36 | Tracker2D_Overlay="*res://addons/Tracker2D/position_info_overlay.tscn" 37 | 38 | [editor_plugins] 39 | 40 | enabled=PackedStringArray("res://addons/Tracker2D/plugin.cfg") 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dan Boorstein 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 | -------------------------------------------------------------------------------- /addons/Tracker2D/tracker_2d.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | class_name Tracker2D 3 | 4 | var info_box_scene : PackedScene = preload("position_info_box.tscn") 5 | 6 | enum MARKER_STYLE { Cross, Point, Circle, None } 7 | enum ROTATION_UNITS { Radians, Degrees } 8 | 9 | @export_group("Position") 10 | @export var display_position : bool = true 11 | @export var display_global_position : bool = true 12 | @export_range(0, 9, 1) var position_decimals : int = 1 13 | 14 | @export_group("Rotation") 15 | @export var display_rotation : bool = true 16 | @export var display_global_rotation : bool = true 17 | @export var rotation_units : ROTATION_UNITS = ROTATION_UNITS.Radians 18 | @export_range(0, 9, 1) var rotation_decimals : int = 1 19 | 20 | @export_group("", "") 21 | @export var display_name : bool = true 22 | @export var marker_style : MARKER_STYLE = MARKER_STYLE.Cross 23 | @export var display_rect : bool = false 24 | @export var tracked_properties : Array[String] 25 | 26 | @onready var _parent : Node = get_parent() 27 | 28 | func _ready(): 29 | ## disable in released games 30 | if OS.has_feature("standalone"): 31 | queue_free() 32 | return 33 | 34 | Tracker2D_Overlay.add_info_box( self ) 35 | -------------------------------------------------------------------------------- /addons/Tracker2D/position_info_overlay.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | @export var auto_track_names : Array[String] = [] 4 | @export var auto_track_classes : Array[String] = [] 5 | @export var auto_track_groups : Array[String] = [] 6 | 7 | var tracker2d_scene : PackedScene = preload("res://addons/Tracker2D/tracker_2d.tscn") 8 | var info_box_scene : PackedScene = preload("position_info_box.tscn") 9 | 10 | func _ready(): 11 | if ! OS.has_feature("standalone"): 12 | get_tree().node_added.connect(_handle_node_added) 13 | 14 | func _process(delta): 15 | pass 16 | 17 | func add_info_box( _tracker : Tracker2D ) -> void: 18 | var info_box : InfoBox = info_box_scene.instantiate() 19 | info_box.set_tracker( _tracker ) 20 | $position_info_overlay.add_child( info_box ) 21 | 22 | func _handle_node_added( node : Node ) -> void: 23 | 24 | ## try not to infinitely recurse 25 | if node is InfoBox or node.owner is InfoBox: 26 | return 27 | if node is Tracker2D or node.owner is Tracker2D: 28 | return 29 | 30 | var auto_track : bool = false 31 | 32 | if auto_track_names.has(node.name): 33 | auto_track = true 34 | elif auto_track_classes.has(node.get_class()): 35 | auto_track = true 36 | else: 37 | for group in node.get_groups(): 38 | if auto_track_groups.has(group): 39 | auto_track = true 40 | break 41 | 42 | if auto_track: 43 | var tracker : Tracker2D = tracker2d_scene.instantiate() 44 | node.add_child(tracker) 45 | -------------------------------------------------------------------------------- /addons/Tracker2D/tests/test_scene.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var tracker_scene : PackedScene = preload("../tracker_2d.tscn") 4 | 5 | func _ready(): 6 | ## set auto track arrays for testing purposes 7 | Tracker2D_Overlay.auto_track_names = [ "auto_track_test_name" ] 8 | Tracker2D_Overlay.auto_track_classes = [ "PointLight2D" ] 9 | Tracker2D_Overlay.auto_track_groups = [ "auto_track_test_group" ] 10 | 11 | ## build test scenes 12 | test_node2d() 13 | test_sprite() 14 | test_removal() 15 | test_auto_track_name() 16 | test_auto_track_class() 17 | test_auto_track_group() 18 | 19 | func _process(delta): 20 | $rotator.global_rotation += 0.1 * delta 21 | 22 | func test_sprite() -> void: 23 | var sprite_tracker : Tracker2D = tracker_scene.instantiate() 24 | sprite_tracker.display_rect = true 25 | sprite_tracker.tracked_properties = [ "modulate" ] 26 | $rotator/occluded_sprite.add_child( sprite_tracker ) 27 | 28 | func test_node2d() -> void: 29 | var node1 = Node2D.new(); 30 | node1.name = "no_rect" 31 | $rotator.add_child( node1 ); 32 | node1.position = Vector2(-100,-100) 33 | 34 | var tracker : Tracker2D = tracker_scene.instantiate() 35 | node1.add_child( tracker ) 36 | 37 | func test_removal() -> void: 38 | var node1 = Node2D.new(); 39 | node1.name = "removal" 40 | $rotator.add_child( node1 ); 41 | node1.position = Vector2(100,-100) 42 | 43 | var tracker : Tracker2D = tracker_scene.instantiate() 44 | node1.add_child( tracker ) 45 | 46 | get_tree().create_timer(3).timeout.connect( func (): node1.queue_free() ) 47 | 48 | func test_auto_track_name() -> void: 49 | var scene : PackedScene = preload("res://addons/Tracker2D/tests/add_by_name.tscn") 50 | var node : Node2D = scene.instantiate() 51 | $rotator.add_child(node) 52 | node.position = Vector2(-100, 100) 53 | 54 | func test_auto_track_class() -> void: 55 | var scene : PackedScene = preload("res://addons/Tracker2D/tests/add_by_class.tscn") 56 | var node : PointLight2D = scene.instantiate() 57 | $rotator.add_child(node) 58 | node.position = Vector2(-100, 0) 59 | 60 | func test_auto_track_group() -> void: 61 | var scene : PackedScene = preload("res://addons/Tracker2D/tests/add_by_group.tscn") 62 | var node : Node2D = scene.instantiate() 63 | $rotator.add_child(node) 64 | node.position = Vector2(100, 0) 65 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/Tracker2D/position_info_box.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://b2ykdacd2wswg"] 2 | 3 | [ext_resource type="Theme" uid="uid://jqhxocgoy8gg" path="res://addons/Tracker2D/position_info_box_theme.tres" id="1_j3wqo"] 4 | [ext_resource type="Script" path="res://addons/Tracker2D/position_info_box.gd" id="1_jt23q"] 5 | [ext_resource type="Script" path="res://addons/Tracker2D/rect.gd" id="3_a5vor"] 6 | 7 | [node name="position_info_box" type="Control"] 8 | layout_mode = 3 9 | anchors_preset = 0 10 | mouse_filter = 2 11 | theme = ExtResource("1_j3wqo") 12 | script = ExtResource("1_jt23q") 13 | 14 | [node name="rect" type="Node2D" parent="."] 15 | unique_name_in_owner = true 16 | scale = Vector2(0.2, 0.2) 17 | script = ExtResource("3_a5vor") 18 | 19 | [node name="main_panel" type="PanelContainer" parent="."] 20 | unique_name_in_owner = true 21 | offset_left = 4.0 22 | offset_top = 4.0 23 | offset_right = 104.0 24 | offset_bottom = 68.0 25 | mouse_filter = 2 26 | 27 | [node name="MarginContainer" type="MarginContainer" parent="main_panel"] 28 | offset_right = 119.0 29 | offset_bottom = 97.0 30 | size_flags_horizontal = 4 31 | size_flags_vertical = 0 32 | mouse_filter = 2 33 | theme_override_constants/margin_left = 5 34 | theme_override_constants/margin_top = 5 35 | theme_override_constants/margin_right = 5 36 | theme_override_constants/margin_bottom = 5 37 | 38 | [node name="VBoxContainer" type="VBoxContainer" parent="main_panel/MarginContainer"] 39 | offset_left = 5.0 40 | offset_top = 5.0 41 | offset_right = 114.0 42 | offset_bottom = 92.0 43 | size_flags_horizontal = 4 44 | size_flags_vertical = 0 45 | mouse_filter = 2 46 | 47 | [node name="name_value" type="Label" parent="main_panel/MarginContainer/VBoxContainer"] 48 | unique_name_in_owner = true 49 | offset_right = 109.0 50 | offset_bottom = 11.0 51 | text = "node_name" 52 | horizontal_alignment = 1 53 | 54 | [node name="position_separator" type="HSeparator" parent="main_panel/MarginContainer/VBoxContainer"] 55 | unique_name_in_owner = true 56 | offset_top = 15.0 57 | offset_right = 109.0 58 | offset_bottom = 15.0 59 | mouse_filter = 2 60 | 61 | [node name="GridContainer" type="GridContainer" parent="main_panel/MarginContainer/VBoxContainer"] 62 | offset_top = 19.0 63 | offset_right = 109.0 64 | offset_bottom = 45.0 65 | mouse_filter = 2 66 | columns = 2 67 | 68 | [node name="position_label" type="Label" parent="main_panel/MarginContainer/VBoxContainer/GridContainer"] 69 | unique_name_in_owner = true 70 | offset_right = 33.0 71 | offset_bottom = 11.0 72 | text = "pos:" 73 | horizontal_alignment = 2 74 | 75 | [node name="position_value" type="Label" parent="main_panel/MarginContainer/VBoxContainer/GridContainer"] 76 | unique_name_in_owner = true 77 | offset_left = 37.0 78 | offset_right = 109.0 79 | offset_bottom = 11.0 80 | text = "-123.0, 543.2" 81 | 82 | [node name="global_position_label" type="Label" parent="main_panel/MarginContainer/VBoxContainer/GridContainer"] 83 | unique_name_in_owner = true 84 | offset_top = 15.0 85 | offset_right = 33.0 86 | offset_bottom = 26.0 87 | text = "g-pos:" 88 | horizontal_alignment = 2 89 | 90 | [node name="global_position_value" type="Label" parent="main_panel/MarginContainer/VBoxContainer/GridContainer"] 91 | unique_name_in_owner = true 92 | offset_left = 37.0 93 | offset_top = 15.0 94 | offset_right = 109.0 95 | offset_bottom = 26.0 96 | text = "-111.1, 111.1" 97 | 98 | [node name="rotation_separator" type="HSeparator" parent="main_panel/MarginContainer/VBoxContainer"] 99 | unique_name_in_owner = true 100 | offset_top = 49.0 101 | offset_right = 109.0 102 | offset_bottom = 49.0 103 | mouse_filter = 2 104 | 105 | [node name="GridContainer2" type="GridContainer" parent="main_panel/MarginContainer/VBoxContainer"] 106 | offset_top = 53.0 107 | offset_right = 109.0 108 | offset_bottom = 79.0 109 | mouse_filter = 2 110 | columns = 2 111 | 112 | [node name="rotation_label" type="Label" parent="main_panel/MarginContainer/VBoxContainer/GridContainer2"] 113 | unique_name_in_owner = true 114 | offset_right = 33.0 115 | offset_bottom = 11.0 116 | text = "rot:" 117 | horizontal_alignment = 2 118 | 119 | [node name="rotation_value" type="Label" parent="main_panel/MarginContainer/VBoxContainer/GridContainer2"] 120 | unique_name_in_owner = true 121 | offset_left = 37.0 122 | offset_right = 70.0 123 | offset_bottom = 11.0 124 | text = "-123.0" 125 | 126 | [node name="global_rotation_label" type="Label" parent="main_panel/MarginContainer/VBoxContainer/GridContainer2"] 127 | unique_name_in_owner = true 128 | offset_top = 15.0 129 | offset_right = 33.0 130 | offset_bottom = 26.0 131 | text = "g-rot:" 132 | horizontal_alignment = 2 133 | 134 | [node name="global_rotation_value" type="Label" parent="main_panel/MarginContainer/VBoxContainer/GridContainer2"] 135 | unique_name_in_owner = true 136 | offset_left = 37.0 137 | offset_top = 15.0 138 | offset_right = 70.0 139 | offset_bottom = 26.0 140 | text = "-123.0" 141 | 142 | [node name="property_separator" type="HSeparator" parent="main_panel/MarginContainer/VBoxContainer"] 143 | unique_name_in_owner = true 144 | offset_top = 83.0 145 | offset_right = 109.0 146 | offset_bottom = 83.0 147 | mouse_filter = 2 148 | 149 | [node name="tracked_properties" type="VBoxContainer" parent="main_panel/MarginContainer/VBoxContainer"] 150 | unique_name_in_owner = true 151 | offset_top = 87.0 152 | offset_right = 109.0 153 | offset_bottom = 87.0 154 | mouse_filter = 2 155 | -------------------------------------------------------------------------------- /addons/Tracker2D/position_info_box.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | class_name InfoBox 4 | 5 | var _tracker : Tracker2D 6 | var _parent : Node2D 7 | var _market_style : int = Tracker2D.MARKER_STYLE.Cross 8 | var _position_decimals : int = 1 9 | var _rotation_decimals : int = 1 10 | var _rotation_units : int = Tracker2D.ROTATION_UNITS.Radians 11 | var _tracked_properties : Array[String] = [] 12 | 13 | # Called when the node enters the scene tree for the first time. 14 | func _ready() -> void: 15 | process_priority = 1 16 | 17 | %rect.set_tracker(_tracker) 18 | set_name_visible(_tracker.display_name) 19 | set_position_visible(_tracker.display_position) 20 | set_global_position_visible(_tracker.display_global_position) 21 | set_position_decimals(_tracker.position_decimals) 22 | set_rotation_visible(_tracker.display_rotation) 23 | set_global_rotation_visible(_tracker.display_global_rotation) 24 | set_rotation_decimals(_tracker.rotation_decimals) 25 | set_rotation_units(_tracker.rotation_units) 26 | set_tracked_properties(_tracker.tracked_properties) 27 | set_marker_style(_tracker.marker_style) 28 | 29 | _tracker.tree_exiting.connect( func (): queue_free() ) 30 | 31 | %position_separator.visible = %position_value.visible || %global_position_value.visible 32 | %rotation_separator.visible = %rotation_value.visible || %global_rotation_value.visible 33 | %property_separator.visible = _tracked_properties.size() > 0 34 | 35 | %main_panel.size = Vector2.ZERO 36 | 37 | # Called every frame. 'delta' is the elapsed time since the previous frame. 38 | func _process(delta) -> void: 39 | 40 | global_position = get_canvas_transform().affine_inverse() \ 41 | * _parent.get_canvas_transform() \ 42 | * _parent.global_position 43 | 44 | update_name_value() 45 | update_position_value() 46 | update_global_position_value() 47 | update_rotation_value() 48 | update_global_rotation_value() 49 | update_tracked_properties() 50 | 51 | func _draw() -> void: 52 | match _market_style: 53 | Tracker2D.MARKER_STYLE.Cross: 54 | _draw_cross() 55 | Tracker2D.MARKER_STYLE.Point: 56 | _draw_point() 57 | Tracker2D.MARKER_STYLE.Circle: 58 | _draw_empty_circle() 59 | Tracker2D.MARKER_STYLE.None: 60 | pass 61 | 62 | func _draw_point() -> void: 63 | draw_circle(Vector2.ZERO, 2, Color.WHITE) 64 | 65 | func _draw_empty_circle() -> void: 66 | draw_arc(Vector2.ZERO, 4, 0, TAU, 32, Color.WHITE) 67 | 68 | func _draw_cross() -> void: 69 | draw_line(Vector2(3, 0), Vector2(10, 0), Color.WHITE) 70 | draw_line(Vector2(0, 3), Vector2(0, 10), Color.WHITE) 71 | draw_line(Vector2(-3, 0), Vector2(-10, 0), Color.WHITE) 72 | draw_line(Vector2(0, -3), Vector2(0, -10), Color.WHITE) 73 | 74 | func set_tracker(value : Tracker2D) -> void: 75 | _tracker = value 76 | _parent = _tracker.get_parent() 77 | 78 | func set_marker_style(value : Tracker2D.MARKER_STYLE) -> void: 79 | _market_style = value 80 | 81 | func set_tracked_properties(value : Array[String]) -> void: 82 | _tracked_properties = value 83 | for property in _tracked_properties: 84 | var property_label : Label = Label.new() 85 | property_label.mouse_filter = Control.MOUSE_FILTER_IGNORE 86 | %tracked_properties.add_child( property_label ) 87 | 88 | func set_rotation_units(value : int) -> void: 89 | _rotation_units = value 90 | 91 | func set_rotation_decimals(value : int) -> void: 92 | _rotation_decimals = value 93 | 94 | func set_position_decimals(value : int) -> void: 95 | _position_decimals = value 96 | 97 | func set_name_visible(value : bool) -> void: 98 | %name_value.visible = value 99 | 100 | func set_position_visible(value : bool) -> void: 101 | %position_label.visible = value 102 | %position_value.visible = value 103 | 104 | func set_global_position_visible(value : bool) -> void: 105 | %global_position_label.visible = value 106 | %global_position_value.visible = value 107 | 108 | func set_rotation_visible(value : bool) -> void: 109 | %rotation_label.visible = value 110 | %rotation_value.visible = value 111 | 112 | func set_global_rotation_visible(value : bool) -> void: 113 | %global_rotation_label.visible = value 114 | %global_rotation_value.visible = value 115 | 116 | func update_name_value() -> void: 117 | %name_value.text = str( _parent.name, " (", _parent.get_class(), ")") 118 | 119 | func update_position_value() -> void: 120 | %position_value.text = _format_position(_parent.position) 121 | 122 | func update_global_position_value() -> void: 123 | %global_position_value.text = _format_position(_parent.global_position) 124 | 125 | func update_rotation_value() -> void: 126 | %rotation_value.text = _format_rotation(_parent.rotation) 127 | 128 | func update_global_rotation_value() -> void: 129 | %global_rotation_value.text = _format_rotation(_parent.global_rotation) 130 | 131 | func update_tracked_properties() -> void: 132 | for i in _tracked_properties.size(): 133 | %tracked_properties.get_child(i).text \ 134 | = str( _tracked_properties[i], ": ", _parent.get( _tracked_properties[i] ) ) 135 | 136 | func _format_position( vec2 : Vector2 ) -> String: 137 | var format_string = str('%.0', _position_decimals, 'f') 138 | return str( format_string % vec2.x, ', ', format_string % vec2.y) 139 | 140 | func _format_rotation( rot : float ) -> String: 141 | var format_string = str('%.0', _rotation_decimals, 'f') 142 | if _rotation_units == Tracker2D.ROTATION_UNITS.Degrees: 143 | rot = rad_to_deg(rot) 144 | return str( format_string % rot ) 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tracker2D 2 | 3 | ## Description 4 | 5 | `Tracker2D` is a Godot 4.0 plugin that lets you visually track node position and orientation along 6 | with a textual representation of various properties. In some respects it is combination of 7 | `Marker2D` and the "Remote" scene view, but is visible during game execution instead of in the 8 | editor, and makes the visual association of nodes and their properties immediately apparent. 9 | 10 | It adds a marker (e.g., a cross) to the origin of the tracked node, draws a bounding box if 11 | appropriate, and adds an information panel that reports live property values. By default it 12 | reports the `position`, `rotation`, `global_position`, and `global_rotation` properties, 13 | but you can specify an array of user-defined properties to track in addition to the defaults. 14 | 15 | ### Example 16 | 17 | ![Example](example.png "Example") 18 | 19 | In the example above, the red-tinted Godot logo has a `Tracker2D` as a child, configured to also 20 | report the `modulate` value. 21 | 22 | The larger, untinted Godot logo is intended to show that the `Tracker2D` information (e.g, the 23 | bounding box) is drawn on top of the scene, so it won't be occluded by other game visuals. 24 | 25 | ## Installation Instructions 26 | 27 | 1. Copy the plugin directory contents into your project as `res://addons/Tracker2D/*`. 28 | 2. Enable the plugin via the menus: `Project` -> `Project Settings...` -> `Plugins` -> `Enable` 29 | 30 | ## Basic Usage Instructions 31 | 32 | The simplest usage is to instantiate a `Tracker2D` node, configure its properties as desired, and 33 | add it as a child of a node you want to track. 34 | 35 | ### Example via Scripting: 36 | 37 | ```gdscript 38 | var tracker_2d_scene : PackedScene = preload("res://addons/Tracker2D/tracker_2d.tscn") 39 | var tracker_2d : Tracker2D = tracker_2d_scene.instantiate() 40 | tracker_2d.display_position = false 41 | tracker_2d.tracked_properties = [ "modulate" ] 42 | node_of_interest.add_child(tracker_2d) 43 | ``` 44 | 45 | ### Example via "Scene" UI: 46 | 47 | Locate node of interest and use right-click -> "Instantiate Child Scene" to add an instance of 48 | `res://addons/Tracker2D/tracker_2d.tscn` as a child. Use the "Inspector" of the `Tracker2D` node to 49 | configure its properties as desired. 50 | 51 | ## Tracker2D Configuration 52 | 53 | The following configuration properties available on `Tracker2D` nodes and can be customzed via GDScript 54 | or as exported variables: 55 | 56 | `display_name` - a `bool`, display the node name when `true` 57 | 58 | `marker_style` - an `enum`, defined as `MARKER_STYLE { Cross, Point, Circle, None }`, `Cross` by default 59 | 60 | `display_rect` - a `bool`, display the bounding rectangle when `true` 61 | 62 | `tracked_properties` - an `Array[String]`, list the custom node properties to be displayed 63 | 64 | `display_position` - a `bool`, display the `position` when `true` 65 | 66 | `display_global_position` - a `bool`, display the `global_position` when `true` 67 | 68 | `position_decimals` - an `int` in the range of `[0,9]`, number of digits displayed right of the decimal for position values 69 | 70 | `display_rotation` - a `bool`, display the `rotation` when `true` 71 | 72 | `display_global_rotation` - a `bool`, display the `global_rotation` when `true` 73 | 74 | `rotation_decimals` - an `int` in the range of `[0,9]`, number of digits displayed right of the decimal for rotation values 75 | 76 | `rotation_units` - an `enum`, defined as `ROTATION_UNITS { Radians, Degrees }`, `Radians` by default 77 | 78 | ## Auto-Tracking Nodes by Name, Class or Group 79 | 80 | There may be situations where you want to track many nodes that share some characteristic, but 81 | manually adding a `Tracker2D` instance to each is inconvenient. To help in such a case there is 82 | support for automatically tracking nodes based on their name, class or groups. This is done by 83 | monitoring the `SceneTree.node_added` signal and evaluating added nodes against your criteria. 84 | 85 | Note that the `node_added` signal only triggers for nodes added dynamically after the game's initial 86 | scene is fully loaded. Additionally, the class criteria is evaluated against the builtin class of 87 | the node, *not* a user-defined class if present. 88 | 89 | There are a few ways to setup the auto-tracking criteria. 90 | 91 | ### Auto-Tracking via UI Configuration 92 | 93 | Open `res://addons/Tracker2D/position_info_overlay.tscn`, select the root node of the scene, use 94 | the exported arrays to configure the names, classes and groups you want to track. 95 | 96 | ![Auto Example](auto-example.png "Auto Example") 97 | 98 | ### Auto-Tracking via Scripting 99 | 100 | In a `_ready` function of a script loaded during the main scene for your game you can assign a 101 | list of values to the corresponding arrays for auto-tracking exposed via the `Tracker2D_Overlay` 102 | autoload-singleton. 103 | 104 | ```gdscript 105 | func _ready(): 106 | Tracker2D_Overlay.auto_track_names = [ "auto_track_test_name" ] 107 | Tracker2D_Overlay.auto_track_classes = [ "PointLight2D" ] 108 | Tracker2D_Overlay.auto_track_groups = [ "auto_track_test_group" ] 109 | ``` 110 | 111 | ## Changes 112 | 113 | ## TODO 114 | 115 | - add a way to configure displayed properties of auto-tracked nodes (repeat tracker2d params in overlay singleton?) 116 | - instead of a rect, draw an antialiased polyline 117 | - add window, viewport and screen position options 118 | - update info based on timer instead of per frame 119 | - add option to keep info boxes in viewport (change BG color when tracked origin is outside viewport) 120 | - add test cases: 121 | - test rect drawing for scaling changes 122 | --------------------------------------------------------------------------------