├── Demo_Scene.tscn ├── LICENSE ├── README.md ├── assets └── images │ └── hud │ ├── direction_arrow.png │ └── icon_grace.png ├── default_env.tres ├── icon.png ├── project.godot └── src └── 3d_helpers ├── 3d_Target_Tracker.gd └── 3d_Target_Tracker.tscn /Demo_Scene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://src/3d_helpers/3d_Target_Tracker.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://assets/images/hud/icon_grace.png" type="Texture" id=2] 5 | 6 | [sub_resource type="Animation" id=1] 7 | resource_name = "move" 8 | length = 10.0 9 | loop = true 10 | tracks/0/type = "value" 11 | tracks/0/path = NodePath(".:translation") 12 | tracks/0/interp = 1 13 | tracks/0/loop_wrap = true 14 | tracks/0/imported = false 15 | tracks/0/enabled = true 16 | tracks/0/keys = { 17 | "times": PoolRealArray( 0, 3, 7, 10 ), 18 | "transitions": PoolRealArray( 1, 1, 1, 1 ), 19 | "update": 0, 20 | "values": [ Vector3( 0, 0, 0 ), Vector3( 10, 4, 0 ), Vector3( -10, -6, 0 ), Vector3( 0, 0, 0 ) ] 21 | } 22 | 23 | [sub_resource type="SpatialMaterial" id=2] 24 | albedo_color = Color( 1, 0, 0.937255, 1 ) 25 | 26 | [sub_resource type="SphereMesh" id=3] 27 | material = SubResource( 2 ) 28 | radius = 0.25 29 | height = 0.5 30 | radial_segments = 32 31 | rings = 16 32 | 33 | [node name="Demo_Scene" type="Spatial"] 34 | 35 | [node name="Camera" type="Camera" parent="."] 36 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.8794 ) 37 | 38 | [node name="Actor" type="KinematicBody" parent="."] 39 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -10, -6, 0 ) 40 | 41 | [node name="Target_Tracker" parent="Actor" instance=ExtResource( 1 )] 42 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0 ) 43 | mode = "show_always" 44 | image = ExtResource( 2 ) 45 | 46 | [node name="AnimationPlayer" type="AnimationPlayer" parent="Actor"] 47 | autoplay = "move" 48 | anims/move = SubResource( 1 ) 49 | 50 | [node name="Actor_Mesh" type="MeshInstance" parent="Actor"] 51 | mesh = SubResource( 3 ) 52 | material/0 = null 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 smix8 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 | 2 | # Godot 3D Target Tracker and Direction Indicator 3 | 3D Target Tracker and Direction Indicator for Godot 3.2 4 | 5 | No longer developed so archived. 6 | 7 | ![off_screen_indi](https://user-images.githubusercontent.com/52464204/73356491-c70aec00-429a-11ea-97cd-5ab27ac89fdd.gif) 8 | 9 | ## Features | Examples: 10 | - Transfers parent 3d position to 2d screenspace and displays icon with direction arrow 11 | - Useful for indicating the position of all kind of 3d game objects on the players screen 12 | - Different modes to change behaviour and show target while onscreen, offscreen or always 13 | - Offscreen mode for e.g. enemies behind the player, grenades that are thrown close under the feet 14 | - Onscreen mode for e.g. to show worldmap locations and quest givers in front of the player 15 | - Always mode for e.g. to track party members position or indicate important 3d audio effects for deaf players 16 | - Ruby colors! 17 | 18 | ## Setup | Usage 19 | The example project comes with a minimalistic `Demo_Scene` that shows the intended Node setup. In the folder directory `src/3d_helpers` you find a `3d_Target_Tracker.tscn` scene file. Add this `3d_Target_Tracker` scene as a Child to any 3d `Node` you want to track ( must have a 3d transform / inherit from `Spatial` ) 20 | 21 | To make the direction arrow work correctly move the `3d_Target_Tracker` node in 3d view to a position higher than the target node. Otherwise the arrow will point at the toe on the ground on a large monster instead of its head when it is close to the screen border. When the target is on screen the direction arrow will automatically switch to aim down at the targets ground position to avoid pointing in the wrong direction when the 2d positions overlap. 22 | 23 | Use the nodes export variables in the inspector to adjust settings. Use the bool `is_tracked` variable to toggle the display on and off from code. e.g. to start the tracking only when an enemy turns aggressive towards the player 24 | 25 | ## License 26 | MIT 27 | 28 | ## Known Issues 29 | Script works without setting a bounding box for the parent model by using the center origin ( 0, 0 ) to decide if it is considered on screen or not which works fine for most 3d models. 30 | 31 | If that doesn't work for your large scale model you might want to modify the script and work with a `VisibilityNotifer` and bounding box to solve this issue for your model. This solution is not provided with this examples. -------------------------------------------------------------------------------- /assets/images/hud/direction_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smix8/Godot3DTargetTracker/30abc26a5afa939109b0663d0a6f1b13381fbc22/assets/images/hud/direction_arrow.png -------------------------------------------------------------------------------- /assets/images/hud/icon_grace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smix8/Godot3DTargetTracker/30abc26a5afa939109b0663d0a6f1b13381fbc22/assets/images/hud/icon_grace.png -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smix8/Godot3DTargetTracker/30abc26a5afa939109b0663d0a6f1b13381fbc22/icon.png -------------------------------------------------------------------------------- /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=4 10 | 11 | _global_script_classes=[ ] 12 | _global_script_class_icons={ 13 | 14 | } 15 | 16 | [application] 17 | 18 | config/name="3d Tracker" 19 | run/main_scene="res://Demo_Scene.tscn" 20 | config/icon="res://icon.png" 21 | 22 | [display] 23 | 24 | window/size/width=1920 25 | window/size/height=1080 26 | window/size/test_width=1280 27 | window/size/test_height=720 28 | window/stretch/mode="2d" 29 | window/stretch/aspect="expand" 30 | 31 | [rendering] 32 | 33 | environment/default_environment="res://default_env.tres" 34 | -------------------------------------------------------------------------------- /src/3d_helpers/3d_Target_Tracker.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | ################################################## 4 | ### Helper Node to track parent 3D Node on/offscreen 5 | ### Shows sprite image with directional arrow for the parents position that stays inside the screen border 6 | 7 | export(String, "show_offscreen", "show_onscreen", "show_always") var mode = "show_offscreen" 8 | 9 | export(Texture) var image setget _set_image 10 | 11 | export(Vector2) var screen_border_offset = Vector2( 20.0, 20.0 ) 12 | ### amount of forced spacing between portrait image position and screen border in pixels 13 | ### overwriten if set lower than the size of the used image 14 | 15 | export(bool) var is_tracked = true setget _set_tracked 16 | ### toggle this variable external to active the display for the parent actor 17 | ### e.g. on enemies when they become aggressive towards the player 18 | ### e.g. on party members when they join the player party 19 | 20 | var target_node: Spatial 21 | ### 3d node to track, _process() gets parent node automatically 22 | 23 | var windowsize 24 | onready var pointer = $Sprite/Pointer 25 | onready var animate = $Sprite/AnimationPlayer 26 | onready var sprite = $Sprite 27 | 28 | 29 | func _ready(): 30 | 31 | sprite.visible = false 32 | set_process(false) 33 | 34 | 35 | ### artist bailout, if used image is larger than offset from the script use image size instead 36 | var rect = sprite.get_rect().size 37 | if screen_border_offset.x < rect.x: 38 | screen_border_offset.x = rect.x 39 | if screen_border_offset.y < rect.y: 40 | screen_border_offset.y = rect.y 41 | 42 | if is_tracked == true: 43 | set_process(true) 44 | 45 | 46 | 47 | func _process(delta): 48 | 49 | if not target_node: 50 | target_node = get_parent() 51 | if not target_node: 52 | print("### error, can't find parent node for tracker") 53 | return 54 | 55 | var viewport_rect = sprite.get_viewport_rect() 56 | 57 | var current_camera: Camera = get_tree().get_root().get_camera() 58 | if not current_camera: 59 | print("### error, can't find current scene camera") 60 | return 61 | 62 | ### project the targets 3d position to our 2d screen 63 | var target_2d_position: Vector2 = current_camera.unproject_position(get_global_transform().origin) 64 | 65 | 66 | ### keep our indicatior within the screens at all time 67 | sprite.position.x = clamp(target_2d_position.x, screen_border_offset.x, viewport_rect.size.x - screen_border_offset.x) 68 | sprite.position.y = clamp(target_2d_position.y, screen_border_offset.y, viewport_rect.size.y - screen_border_offset.y) 69 | 70 | if viewport_rect.has_point(target_2d_position): 71 | target_2d_position = current_camera.unproject_position(target_node.get_global_transform().origin) 72 | 73 | ### rotate our arrow image to the target direction over time 74 | pointer.look_at( target_2d_position ) 75 | 76 | 77 | if mode == "show_always": 78 | if not sprite.visible and not animate.is_playing(): 79 | animate.play("show") 80 | return 81 | 82 | elif mode == "show_offscreen": 83 | ### check if target position is inside our viewport screen 84 | if viewport_rect.has_point(target_2d_position): 85 | if sprite.visible and not animate.is_playing(): 86 | animate.play("hide") 87 | else: 88 | if not sprite.visible and not animate.is_playing(): 89 | animate.play("show") 90 | return 91 | 92 | elif mode == "show_onscreen": 93 | ### check if target position is inside our viewport screen 94 | if viewport_rect.has_point(target_2d_position): 95 | if not sprite.visible and not animate.is_playing(): 96 | animate.play("show") 97 | else: 98 | if sprite.visible and not animate.is_playing(): 99 | animate.play("hide") 100 | return 101 | 102 | else: 103 | print("error - set mode for target indicator unknown") 104 | return 105 | 106 | 107 | 108 | func _set_tracked(tracked : bool): 109 | is_tracked = tracked 110 | if tracked == true: 111 | set_process(true) 112 | else: 113 | if visible: 114 | animate.play("hide") 115 | yield(animate, "animation_finished") 116 | set_process(false) 117 | 118 | 119 | func _set_image( texture: Texture ): 120 | $Sprite.texture = texture 121 | -------------------------------------------------------------------------------- /src/3d_helpers/3d_Target_Tracker.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://src/3d_helpers/3d_Target_Tracker.gd" type="Script" id=1] 4 | [ext_resource path="res://assets/images/hud/direction_arrow.png" type="Texture" id=2] 5 | 6 | [sub_resource type="Animation" id=1] 7 | resource_name = "hide" 8 | length = 0.5 9 | tracks/0/type = "value" 10 | tracks/0/path = NodePath(".:modulate") 11 | tracks/0/interp = 1 12 | tracks/0/loop_wrap = true 13 | tracks/0/imported = false 14 | tracks/0/enabled = true 15 | tracks/0/keys = { 16 | "times": PoolRealArray( 0, 0.5 ), 17 | "transitions": PoolRealArray( 1, 1 ), 18 | "update": 0, 19 | "values": [ Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 0 ) ] 20 | } 21 | tracks/1/type = "value" 22 | tracks/1/path = NodePath(".:visible") 23 | tracks/1/interp = 1 24 | tracks/1/loop_wrap = true 25 | tracks/1/imported = false 26 | tracks/1/enabled = true 27 | tracks/1/keys = { 28 | "times": PoolRealArray( 0, 0.5 ), 29 | "transitions": PoolRealArray( 1, 1 ), 30 | "update": 1, 31 | "values": [ true, false ] 32 | } 33 | 34 | [sub_resource type="Animation" id=2] 35 | resource_name = "reset" 36 | length = 0.1 37 | tracks/0/type = "value" 38 | tracks/0/path = NodePath(".:visible") 39 | tracks/0/interp = 1 40 | tracks/0/loop_wrap = true 41 | tracks/0/imported = false 42 | tracks/0/enabled = true 43 | tracks/0/keys = { 44 | "times": PoolRealArray( 0 ), 45 | "transitions": PoolRealArray( 1 ), 46 | "update": 1, 47 | "values": [ true ] 48 | } 49 | tracks/1/type = "value" 50 | tracks/1/path = NodePath(".:modulate") 51 | tracks/1/interp = 1 52 | tracks/1/loop_wrap = true 53 | tracks/1/imported = false 54 | tracks/1/enabled = true 55 | tracks/1/keys = { 56 | "times": PoolRealArray( 0 ), 57 | "transitions": PoolRealArray( 1 ), 58 | "update": 0, 59 | "values": [ Color( 1, 1, 1, 1 ) ] 60 | } 61 | 62 | [sub_resource type="Animation" id=3] 63 | length = 0.5 64 | tracks/0/type = "value" 65 | tracks/0/path = NodePath(".:modulate") 66 | tracks/0/interp = 1 67 | tracks/0/loop_wrap = true 68 | tracks/0/imported = false 69 | tracks/0/enabled = true 70 | tracks/0/keys = { 71 | "times": PoolRealArray( 0, 0.5 ), 72 | "transitions": PoolRealArray( 1, 1 ), 73 | "update": 0, 74 | "values": [ Color( 1, 1, 1, 0 ), Color( 1, 1, 1, 1 ) ] 75 | } 76 | tracks/1/type = "value" 77 | tracks/1/path = NodePath(".:visible") 78 | tracks/1/interp = 1 79 | tracks/1/loop_wrap = true 80 | tracks/1/imported = false 81 | tracks/1/enabled = true 82 | tracks/1/keys = { 83 | "times": PoolRealArray( 0, 0.5 ), 84 | "transitions": PoolRealArray( 1, 1 ), 85 | "update": 1, 86 | "values": [ true, true ] 87 | } 88 | 89 | [node name="Target_Tracker" type="Spatial"] 90 | script = ExtResource( 1 ) 91 | 92 | [node name="Sprite" type="Sprite" parent="."] 93 | 94 | [node name="Pointer" type="Position2D" parent="Sprite"] 95 | self_modulate = Color( 1, 1, 1, 0 ) 96 | 97 | [node name="Arrow" type="Sprite" parent="Sprite/Pointer"] 98 | position = Vector2( 90, 0 ) 99 | scale = Vector2( 0.1, 0.5 ) 100 | texture = ExtResource( 2 ) 101 | 102 | [node name="AnimationPlayer" type="AnimationPlayer" parent="Sprite"] 103 | anims/hide = SubResource( 1 ) 104 | anims/reset = SubResource( 2 ) 105 | anims/show = SubResource( 3 ) 106 | --------------------------------------------------------------------------------