Add a 3D waypoints demo

Co-authored-by: Aaron Franke <arnfranke@yahoo.com>
This commit is contained in:
Hugo Locurcio 2019-07-15 23:31:44 +02:00 committed by Aaron Franke
parent d5dae1cbb7
commit 54a8d37e6c
14 changed files with 513 additions and 0 deletions

19
3d/waypoints/README.md Normal file
View File

@ -0,0 +1,19 @@
# 3D Waypoints
This is an example of displaying GUI elements such as Labels in a 3D world,
by projecting the 3D position onto the screen and displaying the GUI elements
directly, instead of relying on viewports. This results in better readability
and performance for use cases such as showing player names.
Some waypoints showcased in the demo will also snap to the window borders when
outside the player's view.
No Viewport or Sprite3D nodes are used in this demo.
Language: GDScript
Renderer: GLES 2
## Screenshots
![Screenshot](screenshots/waypoints.png)

43
3d/waypoints/camera.gd Normal file
View File

@ -0,0 +1,43 @@
extends Camera
const MOUSE_SENSITIVITY = 0.002
const MOVE_SPEED = 0.6
var rot = Vector3()
var velocity = Vector3()
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _input(event):
# Mouse look (only if the mouse is captured).
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
# Horizontal mouse look.
rot.y -= event.relative.x * MOUSE_SENSITIVITY
# Vertical mouse look.
rot.x = clamp(rot.x - event.relative.y * MOUSE_SENSITIVITY, -1.57, 1.57)
transform.basis = Basis(rot)
if event.is_action_pressed("toggle_mouse_capture"):
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _process(delta):
var motion = Vector3(
Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
0,
Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")
)
# Normalize motion to prevent diagonal movement from being
# `sqrt(2)` times faster than straight movement.
motion = motion.normalized()
velocity += MOVE_SPEED * delta * transform.basis.xform(motion)
velocity *= 0.85
translation += velocity

View File

@ -0,0 +1,9 @@
[gd_resource type="Environment" load_steps=2 format=2]
[sub_resource type="ProceduralSky" id=1]
sun_latitude = 30.0
sun_longitude = 40.0
[resource]
background_mode = 2
background_sky = SubResource( 1 )

BIN
3d/waypoints/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

139
3d/waypoints/main.tscn Normal file
View File

@ -0,0 +1,139 @@
[gd_scene load_steps=17 format=2]
[ext_resource path="res://camera.gd" type="Script" id=1]
[ext_resource path="res://waypoint.tscn" type="PackedScene" id=2]
[ext_resource path="res://noto_sans_regular.ttf" type="DynamicFontData" id=3]
[sub_resource type="SpatialMaterial" id=1]
albedo_color = Color( 0.6, 0.564706, 0.423529, 1 )
[sub_resource type="CubeMesh" id=2]
material = SubResource( 1 )
size = Vector3( 16, 2, 16 )
[sub_resource type="SpatialMaterial" id=3]
albedo_color = Color( 0.788235, 0.788235, 0.788235, 1 )
[sub_resource type="CubeMesh" id=4]
material = SubResource( 3 )
size = Vector3( 4, 1.5, 4 )
[sub_resource type="SpatialMaterial" id=5]
albedo_color = Color( 0.25098, 0.470588, 0.996078, 1 )
[sub_resource type="CubeMesh" id=6]
material = SubResource( 5 )
size = Vector3( 1, 1, 1 )
[sub_resource type="SpatialMaterial" id=7]
albedo_color = Color( 0.435294, 0.917647, 0.380392, 1 )
[sub_resource type="CubeMesh" id=8]
material = SubResource( 7 )
size = Vector3( 1, 1, 1 )
[sub_resource type="SpatialMaterial" id=9]
albedo_color = Color( 0.862745, 0.764706, 0.12549, 1 )
[sub_resource type="CubeMesh" id=10]
material = SubResource( 9 )
size = Vector3( 1, 1, 1 )
[sub_resource type="SpatialMaterial" id=11]
albedo_color = Color( 0.996078, 0.266667, 0.25098, 1 )
[sub_resource type="CubeMesh" id=12]
material = SubResource( 11 )
size = Vector3( 1, 1, 1 )
[sub_resource type="DynamicFont" id=13]
font_data = ExtResource( 3 )
[node name="Main" type="Spatial"]
[node name="Camera" type="Camera" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 8 )
fov = 75.0
script = ExtResource( 1 )
[node name="Ground" type="MeshInstance" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0 )
mesh = SubResource( 2 )
material/0 = null
[node name="WhiteCube" type="MeshInstance" parent="."]
mesh = SubResource( 4 )
material/0 = null
[node name="BlueCube" type="MeshInstance" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, -5 )
mesh = SubResource( 6 )
material/0 = null
[node name="WaypointAnchor" type="Position3D" parent="BlueCube"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0 )
[node name="Waypoint" parent="BlueCube/WaypointAnchor" instance=ExtResource( 2 )]
modulate = Color( 0.501961, 0.764706, 1, 1 )
text = "Blue Waypoint"
[node name="GreenCube" type="MeshInstance" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5 )
mesh = SubResource( 8 )
material/0 = null
[node name="WaypointAnchor" type="Position3D" parent="GreenCube"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0 )
[node name="Waypoint" parent="GreenCube/WaypointAnchor" instance=ExtResource( 2 )]
modulate = Color( 0.419608, 1, 0.427451, 1 )
text = "Green Waypoint"
[node name="YellowCube" type="MeshInstance" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, 5 )
mesh = SubResource( 10 )
material/0 = null
[node name="WaypointAnchor" type="Position3D" parent="YellowCube"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0 )
[node name="Waypoint" parent="YellowCube/WaypointAnchor" instance=ExtResource( 2 )]
modulate = Color( 1, 0.992157, 0.419608, 1 )
text = "Yellow Waypoint (non-sticky)"
sticky = false
[node name="RedCube" type="MeshInstance" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, -5 )
mesh = SubResource( 12 )
material/0 = null
[node name="WaypointAnchor" type="Position3D" parent="RedCube"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0 )
[node name="Waypoint" parent="RedCube/WaypointAnchor" instance=ExtResource( 2 )]
modulate = Color( 1, 0.466667, 0.427451, 1 )
text = "Red Waypoint"
[node name="DirectionalLight" type="DirectionalLight" parent="."]
transform = Transform( -0.642788, -0.383022, 0.663414, 0, 0.866025, 0.5, -0.766044, 0.321394, -0.556671, 0, 6, -9 )
light_energy = 0.9
shadow_enabled = true
shadow_bias = 0.06
directional_shadow_blend_splits = true
directional_shadow_normal_bias = 0.0
directional_shadow_bias_split_scale = 0.7
directional_shadow_max_distance = 60.0
[node name="Label" type="Label" parent="."]
margin_left = 10.0
margin_top = 10.0
margin_right = 50.0
margin_bottom = 24.0
custom_fonts/font = SubResource( 13 )
custom_colors/font_color_shadow = Color( 0, 0, 0, 0.501961 )
custom_constants/shadow_offset_x = 1
custom_constants/shadow_offset_y = 1
text = "Press Esc or F10 to toggle mouse capture"
__meta__ = {
"_edit_use_anchors_": false
}

Binary file not shown.

View File

@ -0,0 +1,77 @@
; 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=4
_global_script_classes=[ ]
_global_script_class_icons={
}
[application]
config/name="3D Waypoints"
config/description="This is an example of displaying GUI elements such as Labels in a 3D world, without relying on viewports. This results in better readability and performance for use cases such as showing player names."
run/main_scene="res://main.tscn"
config/icon="res://icon.png"
[display]
window/stretch/mode="2d"
window/stretch/aspect="expand"
[input]
move_forward={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":90,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
]
}
move_back={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
]
}
move_left={
"deadzone": 0.51,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":81,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
]
}
move_right={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
]
}
toggle_mouse_capture={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777253,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"unicode":0,"echo":false,"script":null)
]
}
[rendering]
quality/driver/driver_name="GLES2"
vram_compression/import_etc=true
vram_compression/import_etc2=false
quality/filters/msaa=2
environment/default_environment="res://default_env.tres"

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

116
3d/waypoints/waypoint.gd Normal file
View File

@ -0,0 +1,116 @@
extends Control
# Some margin to keep the marker away from the screen's corners.
const MARGIN = 8
onready var camera = get_viewport().get_camera()
onready var parent = get_parent()
onready var label = $Label
onready var marker = $Marker
# The waypoint's text.
export var text = "Waypoint" setget set_text
# If `true`, the waypoint sticks to the viewport's edges when moving off-screen.
export var sticky = true
func _ready() -> void:
self.text = text
if not parent is Spatial:
push_error("The waypoint's parent node must inherit from Spatial.")
func _process(_delta):
var parent_translation = parent.global_transform.origin
var camera_transform = camera.global_transform
var camera_translation = camera_transform.origin
# We would use "camera.is_position_behind(parent_translation)", except
# that it also accounts for the near clip plane, which we don't want.
var is_behind = camera_transform.basis.z.dot(parent_translation - camera_translation) > 0
# Fade the waypoint when the camera gets close.
var distance = camera_translation.distance_to(parent_translation)
modulate.a = clamp(range_lerp(distance, 0, 2, 0, 1), 0, 1 )
var unprojected_position = camera.unproject_position(parent_translation)
# `get_size_override()` will return a valid size only if the stretch mode is `2d`.
# Otherwise, the viewport size is used directly.
var viewport_base_size = (
get_viewport().get_size_override() if get_viewport().get_size_override() > Vector2(0, 0)
else get_viewport().size
)
if not sticky:
# For non-sticky waypoints, we don't need to clamp and calculate
# the position if the waypoint goes off screen.
rect_position = unprojected_position
visible = not is_behind
return
# We need to handle the axes differently.
# For the screen's X axis, the projected position is useful to us,
# but we need to force it to the side if it's also behind.
if is_behind:
if unprojected_position.x < viewport_base_size.x / 2:
unprojected_position.x = viewport_base_size.x - MARGIN
else:
unprojected_position.x = MARGIN
# For the screen's Y axis, the projected position is NOT useful to us
# because we don't want to indicate to the user that they need to look
# up or down to see something behind them. Instead, here we approximate
# the correct position using difference of the X axis Euler angles
# (up/down rotation) and the ratio of that with the camera's FOV.
# This will be slightly off from the theoretical "ideal" position.
if is_behind or unprojected_position.x < MARGIN or \
unprojected_position.x > viewport_base_size.x - MARGIN:
var look = camera_transform.looking_at(parent_translation, Vector3.UP)
var diff = angle_diff(look.basis.get_euler().x, camera_transform.basis.get_euler().x)
unprojected_position.y = viewport_base_size.y * (0.5 + (diff / deg2rad(camera.fov)))
rect_position = Vector2(
clamp(unprojected_position.x, MARGIN, viewport_base_size.x - MARGIN),
clamp(unprojected_position.y, MARGIN, viewport_base_size.y - MARGIN)
)
label.visible = true
rect_rotation = 0
# Used to display a diagonal arrow when the waypoint is displayed in
# one of the screen corners.
var overflow = 0
if rect_position.x <= MARGIN:
# Left overflow.
overflow = -45
label.visible = false
rect_rotation = 90
elif rect_position.x >= viewport_base_size.x - MARGIN:
# Right overflow.
overflow = 45
label.visible = false
rect_rotation = 270
if rect_position.y <= MARGIN:
# Top overflow.
label.visible = false
rect_rotation = 180 + overflow
elif rect_position.y >= viewport_base_size.y - MARGIN:
# Bottom overflow.
label.visible = false
rect_rotation = -overflow
func set_text(p_text):
text = p_text
# The label's text can only be set once the node is ready.
if is_inside_tree():
label.text = p_text
static func angle_diff(from, to):
var diff = fmod(to - from, TAU)
return fmod(2.0 * diff, TAU) - diff

View File

@ -0,0 +1 @@
<svg height="128" viewBox="0 0 33.866666 33.866666" width="128" xmlns="http://www.w3.org/2000/svg"><path d="m2.5 3.6166886h28.8667346l-14.433367 27.0625224z" fill="#fff" stroke="#000" stroke-width="3"/></svg>

After

Width:  |  Height:  |  Size: 209 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/waypoint.svg-de0ee5a99654d0ef48e907a9ea3ae741.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://waypoint.svg"
dest_files=[ "res://.import/waypoint.svg-de0ee5a99654d0ef48e907a9ea3ae741.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=true
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -0,0 +1,41 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://waypoint.gd" type="Script" id=1]
[ext_resource path="res://waypoint.svg" type="Texture" id=2]
[ext_resource path="res://noto_sans_regular.ttf" type="DynamicFontData" id=3]
[sub_resource type="DynamicFont" id=1]
font_data = ExtResource( 3 )
[node name="Waypoint" type="Control"]
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Label" type="Label" parent="."]
margin_left = -200.0
margin_top = -40.0
margin_right = 200.0
margin_bottom = -17.0
custom_fonts/font = SubResource( 1 )
custom_colors/font_color_shadow = Color( 0, 0, 0, 0.501961 )
custom_constants/shadow_offset_x = 1
custom_constants/shadow_offset_y = 1
text = "Waypoint"
align = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Marker" type="TextureRect" parent="."]
margin_left = -8.0
margin_top = -16.0
margin_right = 120.0
margin_bottom = 112.0
rect_scale = Vector2( 0.125, 0.125 )
texture = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false,
"_editor_description_": "An high-resolution texture is used and scaled down so the demo looks good at higher resolutions."
}