und wieder 30 Stunden später...

kts
Kai Sellmann 2025-11-08 17:30:24 +01:00
parent 20af1577e5
commit 8a9dd6164a
7775 changed files with 272481 additions and 3878 deletions

View File

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

View File

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

View File

@ -0,0 +1,22 @@
Contributors
============
The main author of this project is [Bastiaan Olij](https://github.com/BastiaanOlij) who manages the source repository found at:
https://github.com/GodotVR/godot-xr-tools
Other people who have helped out by submitting fixes, enhancements, etc are:
- [Florian Jung](https://github.com/Windfisch)
- [RMKD](https://github.com/RMKD)
- [Alessandro Schillaci](https://github.com/silverslade)
- [jtank4](https://github.com/jtank4)
- [Malcolm Nixon](https://github.com/malcolmnixon)
- [Sam Sarette](https://github.com/lunarcloud)
- [Henodude](https://github.com/Henodude)
- [Miodrag Sejic](https://github.com/DigitalN8m4r3)
- [Carlos Padial](https://github.com/surreal6)
- [Julian Todd](https://github.com/goatchurchprime)
- [Kai Tödter](https://github.com/toedter)
- [Sam Sarette](https://github.com/lunarcloud)
- [Squidt](https://github.com/squidt)
Want to be on this list? We would love your help.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018-2023 Bastiaan Olij and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,226 @@
# 4.4.0
- Minimum Godot version changed to 4.2
- Add pickable action_released signal
- Fix custom hand poses calling legacy remove_animation
- Cleaned up StartXR
- Allow grab-points and poses to work with different types of hand trackers
- Add end_xr support to StartXR
- Fixed vignette shader
- Add visibility_changed notifications to Viewport2Din3D hosted scenes
- Invisible Viewport2Din3D now disable physics and viewport updates
- Add SnapPath
- Improvements to collision hands so collision shapes of picked up objects
are added and we no longer have hands collide with dropped objects
# 4.3.3
- Fix Viewport2Din3D property forwarding
# 4.3.2
- Move fade logic into effect
- Added collision fade support
- Added fix for slowly sliding on slopes
- Added fix for ground-control preventing jumping over objects
- Added property forwarding for Viewport2Din3D
- Added fix for open/close poses
- Added rumble manager for haptic feedback
- Fix unreliable wall-walking collision
# 4.3.1
- Fix saving project when using plugin-tools to set physics layers or enable OpenXR
- Fix updating the editor-preview hand-pose
- Fix jumping on slopes
- Fix material warnings by converting binary .material files to .tres files
- Fix staging to use threaded loading while starting the fade
- Fix broken world-grab script
# 4.3.0
- Upgraded project to Godot 4.1 as the new minimum version.
- Added reporting of stage load errors.
- Blend player height changes and prevent the player from standing up under a low ceiling.
- **minor-breakage** Added support for swapping held items between hands.
- Added jog-in-place movement provider.
- Added support for grappling on GridMap instances
- **breakage** Added support for two-handed grabbing.
- Added support for snapping hands to grab-points.
- Added support for world-grab movement.
- Fixed editor errors when using hand physics bones.
- Added support for climbable grab-points.
- Added control of keyboard or gamepad inputs to Viewport2Din3D instances.
# 4.2.1
- Fixed snap-zones showing highlight when disabled.
- Fixed pickup leaving target highlighted after picking up.
- Fixed collision hands getting stuck too far from the real hands.
# 4.2.0
- Environments can now be set normally in scenes loaded through the staging system.
- Fixed issue with not being able to push rigid bodies when colliding with them.
- Fixed player movement on slopes.
- Fixed lag in finger-poke.
- Added initial collision hand support.
- Added support for custom materials for 2D in 3D viewport
- Updated pointer to support visibility properties and events
- Modified virtual keyboard to expose viewport controls and default to unshaded
- Cleaned up teleport and added more properties for customization
- Modified pickup highlighting to support pickables in snap-zones
- Added "UI Objects" layer 23 for viewports to support interaction by pointer and poking
- Fixed player scaling issues with crouching and poke
- **minor-breakage** Added support for passing user data between staged scenes with default handling for spawn-points
- Moved teleport logic to player and added teleport area node
- Change pointer event dispatching
- Added multi-touch on 2D in 3D viewports and virtual-keyboard
- Added option to disable laser-pointers when close to specific bodies/areas
# 4.1.0
- Enhanced grappling to support collision and target layers
- Added Godot Editor XR Tools menu for layers and openxr configuration
- Improved gliding to support roll-turning while flapping
- Added render_target_size_multiplier to StartXR (requires Godot 4.1+)
# 4.0.0
- Conversion to Godot 4
- Fixed footstep resource leak and added jump sounds and footstep signal
- Added grab-point switching to pickable objects
- Added return-to-snap-zone feature
# 3.4.0
- Fixed footstep resource leak and added jump sounds and footstep signal
- Added grab-point switching to pickable objects
- Added return-to-snap-zone feature
# 3.3.0
- Added reset-scene and scene-control functions to scene-base
- Fixed snap-zones stealing objects picked out of other near-by snap-zones
- Improved player body so it can be used to child objects to
- Updated scene/script default physics layers to match recommendations on website
# 3.2.0
- Minimum supported Godot version set to 3.5
- Added glide option for turning with arm-roll
- Added physics gravity effects on the player so they can walk around a planet
- Added wall-walking movement provider
- Cleaned the code to pass gdlint code checks
- Modified to work with both WebXR and OpenXR
- Added enable property to pickable objects
- Added support for snap-on-drop to snap-zones
- Added glide options for gaining altitude when flapping arms
- Added option to disable snap-turn repeating by setting the delay to 0
- Added capability for pointer function to auto-switch between controllers
# 3.1.0
- Improvements to our 2D in 3D viewport for filtering, unshaded, and transparency options
- Fixed editor preview system for our 2D in 3D viewport
- Use value based grip input with threshold
- Improved pointer demo supporting left hand with switching
- Enhanced pointer laser visibility options for colliding with targets
- Implement poke feature (finger interaction)
- Improvements to snap turning
- Moved staging solution into plugin so it can be re-used
- Allow setting different animations for hands
- Added enable/disable to snap-zones
- Added XR settings as Godot editor plugin and the ability to load and save the settings
- Added crouching movement provider
- Modified climbing to use the hand which most recently grabbed the climbing object
- Added enable/disable to pickup function
- Added ability to override hand material
- Added realistic hand models and textures
- Added ability to override hand animations
- Added additional search functions to find nodes
- Added support for viewport 2D in 3D to support 2D scenes instanced in the tree
- Added sprinting movement provider
- Added support for setting hand-poses when the hand enters an area
- Added support for setting grab-points on objects, and the grab-points supporting different hand-poses
# 3.0.0
- Included demo project with test scenes to evaluate features
- Standardized class naming convention for all scripts to "XRTools<PascalCaseName>"
- Standardized file naming convention to "snake_case_name.ext"
- Added many explicit type specifiers in preparation for GDScript 2.0
- Renamed some functions to avoid name-collisions with Godot 4.0
# 2.6.0
- Fixed enforcement of direct-movement maximum speed
- Added editor icons for all nodes
- Added collision bouncing to PlayerBody
# 2.5.0
- Added advanced player height control
- Modified climbing to collapse player to a sphere to allow mounting climbed objects
- Added crouch movement provider
- Added example fall damage detection
- Added moving platform support to player body
- Fixed player height-clamping to work in player-units
- Fixed glide T-pose detection to work in player-units
- Fixed jump detection to work in player-units
- Added valid-layer checking to teleport movement
- Modified hand meshes (blend and glb) to be scaled, so the hand scenes can be 1:1 scaled
- Modified hands to scale with world_scale (required for godot-openxr 1.3.0 and later)
- Added physics hands with PhysicsBody bones
- Fixed disabling of `_process` in XRToolsPickable script
# 2.4.1
- Fixed grab distance
- Fixed snap-zone instance drop and free issue
- Movement provides react properly when disabled
- Hiding grapple target when disabled
# 2.4.0
- Added configuration setting for head height in player body.
- Added Function_JumpDetect_movement to detect jumping via the players body and/or arms
- Improved responsiveness of snap-turning
- Moved flight logic from Function_Direct_movement to Function_Flight_movement
- Added option to disable player sliding on slopes
- Added support for remote grabbing
- Moved turning logic from Function_Direct_movement to Function_Turn_movement
- Fixed movement provider servicing so disabled/bypassed providers can report their finished events
- Added grappling movement provider
- Added snap-zones
# 2.3.0
- Added vignette
- Moved player physics into new PlayerBody asset (breaking change)
- Moved Function_Direct_movement settings for player physics into PlayerBody
- Added Function_Glide_movement to allow the player to glide
- Added Function_Jump_movement to allow the player to jump
- Added Function_Climb_movement to allow the player to climb
- Redid the setup of the hands to make it easier to extend to other gestures
- Improved pickup and throwing logic
# 2.2
- Changed default physics layers to make more sense (minor breaking change)
- Replaced Center On Node property with PickupCenter node you can place
- Made Object_pickable script work by itself and registers as class `XRToolsPickable`
- New Object_interactable convenience script that registers as class `XRToolsInteractable` that reacts to our pointer function
- Removed ducktype switch from pointer, pointer will use signals over ducktyping automatically (minor breaking change)
# 2.1
- added option to highlight object that can be picked up
- added option to snap object to given location (if reset transform is true)
- added callback when shader cache has finished
- using proper UI for layers
- added hand controllers that react on trigger and grip input
- fixed delta on move and slide (breaking change!)
- letting go of an object now adds angular velocity
# 2.0
- Renamed add on to **godot-xr-tools**
- Add enums to our export variables
- Add a switch on pickable objects to keep their current positioning when picked up
- Move direct movement player collision slightly backwards based on player radius
- Added switch between step turning and smooth turning
- Fixed sizing issue with teleport
- Added option to change pickup range
# 1.2
- Assign button to teleport function and no longer need to set origin
- Added pickable object support
- Fixed positioning of direct movement collision shape
- Added strafe and fly mode for directional
- Added ability to enable/disable the movement functions
- Added 2D in 3D viewport for UI
- Improved throwing by assigning linear velocity
# 1.1*
- previous versions were not tracked
* Note that version history before 1.2 was not kept and is thus incomplete

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ocyj01x5mtt7"
path.s3tc="res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.s3tc.ctex"
path.etc2="res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://addons/godot-xr-tools/assets/misc/Hold trigger to continue.png"
dest_files=["res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.s3tc.ctex", "res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

View File

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://clbtsf0ahb3fm"
path.s3tc="res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.s3tc.ctex"
path.etc2="res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://addons/godot-xr-tools/assets/misc/progress_bar.png"
dest_files=["res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.s3tc.ctex", "res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View File

@ -0,0 +1,53 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/audio.svg")
class_name XRToolsAreaAudio
extends AudioStreamPlayer3D
## XRTools Area Audio
##
## This node is attached as a child of a Area3D,
## since all the interactables are actualy Extensions of the Area3D,
## this node will work on those as well
## XRToolsAreaAudioType to associate with this Area Audio
@export var area_audio_type : XRToolsAreaAudioType
@onready var area : Area3D = get_parent()
# Add support for is_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsAreaAudio"
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# Listen for enter
area.body_entered.connect(_on_body_entered)
# Listen for exit
area.body_exited.connect(_on_body_exited)
func _on_body_entered(_body):
if playing:
stop()
stream = area_audio_type.touch_sound
play()
func _on_body_exited(_body):
if playing:
stop()
# This method checks for configuration issues.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
if !area_audio_type:
warnings.append("Area audio type not specified")
# Return warnings
return warnings

View File

@ -0,0 +1 @@
uid://df2nnitjxfgx8

View File

@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://duqehif60vcjg"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/audio/area_audio.gd" id="1_q1jr0"]
[node name="AreaAudio" type="AudioStreamPlayer3D"]
unit_size = 3.0
max_distance = 100.0
script = ExtResource("1_q1jr0")

View File

@ -0,0 +1,28 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/audio.svg")
class_name XRToolsAreaAudioType
extends Resource
## XRTools Area Audio Type Resource
##
## This resource defines the audio stream to play when
## a objects enters it
## Surface name
@export var name : String = ""
## Optional audio stream to play when the player lands on this surface
@export var touch_sound : AudioStream
# This method checks for configuration issues.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
if name == "":
warnings.append("Area audio type must have a name")
# Return warnings
return warnings

View File

@ -0,0 +1 @@
uid://c2nh8s6sb1np0

View File

@ -0,0 +1,79 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/audio.svg")
class_name XRToolsPickableAudio
extends AudioStreamPlayer3D
## XRTools Pickable Audio
##
## This node is attached as a child of a Pickable,
## it plays audio for drop and hit based on velocity,
## along with a audio for when the object is being picked up.
## XRToolsPickableAudioType to associate with this pickable
@export var pickable_audio_type : XRToolsPickableAudioType
## delta throttle is 1/10 of delta
@onready var delta_throttle : float = 0.1
@onready var _pickable : XRToolsPickable = get_parent()
# Add support for is_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsPickableAudio"
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# Listen for when this object enters a body
_pickable.body_entered.connect(_on_body_entered)
# Listen for when this object is picked up or dropped
_pickable.picked_up.connect(_on_picked_up)
_pickable.dropped.connect(_on_dropped)
func _physics_process(_delta):
if !_pickable.sleeping:
if _pickable.linear_velocity.length() > 5:
volume_db = 0
else:
volume_db -= _pickable.linear_velocity.length() * delta_throttle
# Called when this object is picked up
func _on_picked_up(_pickable) -> void:
volume_db = 0
if playing:
stop()
stream = pickable_audio_type.grab_sound
play()
# Called when this object is dropped
func _on_dropped(_pickable) -> void:
for body in _pickable.get_colliding_bodies():
if playing:
stop()
func _on_body_entered(_body):
if playing:
stop()
if _pickable.is_picked_up():
stream = pickable_audio_type.hit_sound
else:
stream = pickable_audio_type.drop_sound
play()
# This method checks for configuration issues.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
if !pickable_audio_type:
warnings.append("Pickable audio type not specified")
# Return warnings
return warnings

View File

@ -0,0 +1 @@
uid://bkrsrl1xjsgl1

View File

@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://bikkxsbo8x7sd"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/audio/pickable_audio.gd" id="1_cfg1k"]
[node name="PickableAudio" type="AudioStreamPlayer3D"]
unit_size = 3.0
max_db = 1.0
max_distance = 100.0
script = ExtResource("1_cfg1k")

View File

@ -0,0 +1,34 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/audio.svg")
class_name XRToolsPickableAudioType
extends Resource
## XRTools Pickable Audio Type Resource
##
## This resource defines the audio streams to play when
## the pickable is being picked up/ dropped/ hit something while being held
## Surface name
@export var name : String = ""
## Optional audio stream to play when the player picks up the pickable
@export var grab_sound : AudioStream
## Optional audio stream to play when the player drops the pickable
@export var drop_sound : AudioStream
## Optional audio stream to play when the item is beign held by the player
@export var hit_sound : AudioStream
# This method checks for configuration issues.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
if name == "":
warnings.append("Pickable audio type must have a name")
# Return warnings
return warnings

View File

@ -0,0 +1 @@
uid://dsyt1g70xpgyg

View File

@ -0,0 +1,32 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/foot.svg")
class_name XRToolsSurfaceAudio
extends Node
## XRTools Surface Audio Node
##
## This node is attached as a child of a StaticObject to give it a surface
## audio type. This will cause the XRToolsMovementFootStep to play the correct
## foot-step sounds when walking on the object.
## XRToolsSurfaceAudioType to associate with this surface
@export var surface_audio_type : XRToolsSurfaceAudioType
# Add support for is_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsSurfaceAudio"
# This method checks for configuration issues.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Verify the camera
if !surface_audio_type:
warnings.append("Surface audio type not specified")
# Return warnings
return warnings

View File

@ -0,0 +1 @@
uid://j07q181fapte

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://c8jtmtuihfujs"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/audio/surface_audio.gd" id="1"]
[node name="SurfaceAudio" type="Node"]
script = ExtResource("1")

View File

@ -0,0 +1,41 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/body.svg")
class_name XRToolsSurfaceAudioType
extends Resource
## XRTools Surface Type Resource
##
## This resource defines a type of surface, and the audio streams to play when
## the user steps on it
## Surface name
@export var name : String = ""
## Optional audio stream to play when the player jumps on this surface
@export var jump_sound : AudioStream
## Optional audio stream to play when the player lands on this surface
@export var hit_sound : AudioStream
## Audio streams to play when the player walks on this surface
@export var walk_sounds :Array[AudioStream] = []
## Walking sound minimum pitch (to randomize steps)
@export_range(0.5, 1.0) var walk_pitch_minimum : float = 0.8
## Walking sound maximum pitch (to randomize steps)
@export_range(1.0, 2.0) var walk_pitch_maximum : float = 1.2
# This method checks for configuration issues.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Verify the camera
if name == "":
warnings.append("Surface audio type must have a name")
# Return warnings
return warnings

View File

@ -0,0 +1 @@
uid://c7qdfmtcwtlga

View File

@ -0,0 +1,54 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/function.svg")
class_name XRToolsDesktopControlerHider
extends Node
## XR Tools Controler Hider
##
## This script hides controler if XR is not active.
var _pointer_disabler := false
var _last_xr_active := true
# XRStart node
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
# Parent controller
@onready var _controller : XRController3D = XRHelpers.get_xr_controller(self)
func _ready() -> void:
if get_parent().has_method("is_xr_class"):
if get_parent().is_xr_class("XRToolsFunctionPointer"):
_pointer_disabler = true
if get_parent() is XRToolsFunctionPointer:
_pointer_disabler = true
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsDesktopControlerHider"
func _process(_delta: float) -> void:
if Engine.is_editor_hint() or !is_inside_tree():
return
if xr_start_node.is_xr_active()==_last_xr_active:
return
if _pointer_disabler:
get_parent().enabled=xr_start_node.is_xr_active()
elif is_instance_valid(_controller):
_controller.visible=xr_start_node.is_xr_active()
_last_xr_active=xr_start_node.is_xr_active()
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Check the controller node
if !XRHelpers.get_xr_controller(self) \
and !XRTools.find_xr_ancestor(self,"*","XRToolsFunctionPointer"):
warnings.append("This node must be within a branch of an XRController3D node")
# Return warnings
return warnings

View File

@ -0,0 +1 @@
uid://bicu6ye6huykx

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://chb848dtavews"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/controler_hider.gd" id="1_6xusf"]
[node name="ControlerHider" type="Node"]
script = ExtResource("1_6xusf")

View File

@ -0,0 +1,484 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/function.svg")
class_name XRToolsDesktopFunctionPointer
extends Node3D
## XR Tools Function Pointer Script
##
## This script implements a pointer function for a players controller. Pointer
## events (entered, exited, pressed, release, and movement) are delivered by
## invoking signals on the target node.
##
## Pointer target nodes commonly extend from [XRToolsInteractableArea] or
## [XRToolsInteractableBody].
## Signal emitted when this object points at another object
signal pointing_event(event)
## Enumeration of laser show modes
enum LaserShow {
HIDE = 0, ## Hide laser
SHOW = 1, ## Show laser
COLLIDE = 2, ## Only show laser on collision
}
## Enumeration of laser length modes
enum LaserLength {
FULL = 0, ## Full length
COLLIDE = 1 ## Draw to collision
}
## Default pointer collision mask of 21:pointable and 23:ui-objects
const DEFAULT_MASK := 0b0000_0000_0101_0000_0000_0000_0000_0000
## Default pointer collision mask of 23:ui-objects
const SUPPRESS_MASK := 0b0000_0000_0100_0000_0000_0000_0000_0000
@export_group("General")
## Pointer enabled
@export var enabled : bool = true: set = set_enabled
## Y Offset for pointer
@export var y_offset : float = -0.013: set = set_y_offset
## Pointer distance
@export var distance : float = 10: set = set_distance
## Active button action
@export var active_button_action : String = "trigger_click"
@export_group("Laser")
## Controls when the laser is visible
@export var show_laser : LaserShow = LaserShow.SHOW: set = set_show_laser
## Controls the length of the laser
@export var laser_length : LaserLength = LaserLength.FULL: set = set_laser_length
## Laser pointer material
@export var laser_material : StandardMaterial3D = null : set = set_laser_material
## Laser pointer material when hitting target
@export var laser_hit_material : StandardMaterial3D = null : set = set_laser_hit_material
@export_group("Target")
## If true, the pointer target is shown
@export var show_target : bool = false: set = set_show_target
## Controls the target radius
@export var target_radius : float = 0.05: set = set_target_radius
## Target material
@export var target_material : StandardMaterial3D = null : set = set_target_material
@export_group("Collision")
## Pointer collision mask
@export_flags_3d_physics var collision_mask : int = DEFAULT_MASK: set = set_collision_mask
## Enable pointer collision with bodies
@export var collide_with_bodies : bool = true: set = set_collide_with_bodies
## Enable pointer collision with areas
@export var collide_with_areas : bool = false: set = set_collide_with_areas
@export_group("Suppression")
## Suppress radius
@export var suppress_radius : float = 0.2: set = set_suppress_radius
## Suppress mask
@export_flags_3d_physics var suppress_mask : int = SUPPRESS_MASK: set = set_suppress_mask
## Current target node
var target : Node3D = null
## Last target node
var last_target : Node3D = null
## Last collision point
var last_collided_at : Vector3 = Vector3.ZERO
# World scale
var _world_scale : float = 1.0
# XRStart Node
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
## Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsDesktopFunctionPointer"
# Called when the node enters the scene tree for the first time.
func _ready():
# Do not initialise if in the editor
if Engine.is_editor_hint():
return
# Read the initial world-scale
_world_scale = XRServer.world_scale
# init our state
_update_y_offset()
_update_distance()
_update_pointer()
_update_target_radius()
_update_target_material()
_update_collision_mask()
_update_collide_with_bodies()
_update_collide_with_areas()
_update_suppress_radius()
_update_suppress_mask()
# Called on each frame to update the pickup
func _process(_delta):
# Do not process if in the editor
if Engine.is_editor_hint() or !is_inside_tree():
return
# Handle world-scale changes
var new_world_scale := XRServer.world_scale
if (_world_scale != new_world_scale):
_world_scale = new_world_scale
_update_y_offset()
set_enabled(!xr_start_node.is_xr_active())
if Input.is_action_just_released(active_button_action):
_on_button_pressed(active_button_action)
await get_tree().process_frame
_on_button_released(active_button_action)
# Find the new pointer target
var new_target : Node3D
var new_at : Vector3
var suppress_area := $SuppressArea
if (enabled and
not $SuppressArea.has_overlapping_bodies() and
not $SuppressArea.has_overlapping_areas() and
$RayCast.is_colliding()):
new_at = $RayCast.get_collision_point()
if target:
# Locked to 'target' even if we're colliding with something else
new_target = target
else:
# Target is whatever the raycast is colliding with
new_target = $RayCast.get_collider()
# If no current or previous collisions then skip
if not new_target and not last_target:
return
# Handle pointer changes
if new_target and not last_target:
# Pointer entered new_target
XRToolsPointerEvent.entered(self, new_target, new_at)
# Pointer moved on new_target for the first time
XRToolsPointerEvent.moved(self, new_target, new_at, new_at)
# Update visible artifacts for hit
_visible_hit(new_at)
elif not new_target and last_target:
# Pointer exited last_target
XRToolsPointerEvent.exited(self, last_target, last_collided_at)
# Update visible artifacts for miss
_visible_miss()
elif new_target != last_target:
# Pointer exited last_target
XRToolsPointerEvent.exited(self, last_target, last_collided_at)
# Pointer entered new_target
XRToolsPointerEvent.entered(self, new_target, new_at)
# Pointer moved on new_target
XRToolsPointerEvent.moved(self, new_target, new_at, new_at)
# Move visible artifacts
_visible_move(new_at)
elif new_at != last_collided_at:
# Pointer moved on new_target
XRToolsPointerEvent.moved(self, new_target, new_at, last_collided_at)
# Move visible artifacts
_visible_move(new_at)
# Update last values
last_target = new_target
last_collided_at = new_at
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Check the controller node
if !XRTools.find_xr_ancestor(self,"*","XRCamera3D"):
warnings.append("This node must be within a branch of an XRCamera3D node")
# Return warnings
return warnings
# Set pointer enabled property
func set_enabled(p_enabled : bool) -> void:
enabled = p_enabled
if is_inside_tree():
_update_pointer()
# Set pointer y_offset property
func set_y_offset(p_offset : float) -> void:
y_offset = p_offset
if is_inside_tree():
_update_y_offset()
# Set pointer distance property
func set_distance(p_new_value : float) -> void:
distance = p_new_value
if is_inside_tree():
_update_distance()
# Set pointer show_laser property
func set_show_laser(p_show : LaserShow) -> void:
show_laser = p_show
if is_inside_tree():
_update_pointer()
# Set pointer laser_length property
func set_laser_length(p_laser_length : LaserLength) -> void:
laser_length = p_laser_length
if is_inside_tree():
_update_pointer()
# Set pointer laser_material property
func set_laser_material(p_laser_material : StandardMaterial3D) -> void:
laser_material = p_laser_material
if is_inside_tree():
_update_pointer()
# Set pointer laser_hit_material property
func set_laser_hit_material(p_laser_hit_material : StandardMaterial3D) -> void:
laser_hit_material = p_laser_hit_material
if is_inside_tree():
_update_pointer()
# Set pointer show_target property
func set_show_target(p_show_target : bool) -> void:
show_target = p_show_target
if is_inside_tree():
$Target.visible = enabled and show_target and last_target
# Set pointer target_radius property
func set_target_radius(p_target_radius : float) -> void:
target_radius = p_target_radius
if is_inside_tree():
_update_target_radius()
# Set pointer target_material property
func set_target_material(p_target_material : StandardMaterial3D) -> void:
target_material = p_target_material
if is_inside_tree():
_update_target_material()
# Set pointer collision_mask property
func set_collision_mask(p_new_mask : int) -> void:
collision_mask = p_new_mask
if is_inside_tree():
_update_collision_mask()
# Set pointer collide_with_bodies property
func set_collide_with_bodies(p_new_value : bool) -> void:
collide_with_bodies = p_new_value
if is_inside_tree():
_update_collide_with_bodies()
# Set pointer collide_with_areas property
func set_collide_with_areas(p_new_value : bool) -> void:
collide_with_areas = p_new_value
if is_inside_tree():
_update_collide_with_areas()
# Set suppress radius property
func set_suppress_radius(p_suppress_radius : float) -> void:
suppress_radius = p_suppress_radius
if is_inside_tree():
_update_suppress_radius()
func set_suppress_mask(p_suppress_mask : int) -> void:
suppress_mask = p_suppress_mask
if is_inside_tree():
_update_suppress_mask()
# Pointer Y offset update handler
func _update_y_offset() -> void:
$Laser.position.y = y_offset * _world_scale
$RayCast.position.y = y_offset * _world_scale
# Pointer distance update handler
func _update_distance() -> void:
$RayCast.target_position.z = -distance
_update_pointer()
# Pointer target radius update handler
func _update_target_radius() -> void:
$Target.mesh.radius = target_radius
$Target.mesh.height = target_radius * 2
# Pointer target_material update handler
func _update_target_material() -> void:
$Target.set_surface_override_material(0, target_material)
# Pointer collision_mask update handler
func _update_collision_mask() -> void:
$RayCast.collision_mask = collision_mask
# Pointer collide_with_bodies update handler
func _update_collide_with_bodies() -> void:
$RayCast.collide_with_bodies = collide_with_bodies
# Pointer collide_with_areas update handler
func _update_collide_with_areas() -> void:
$RayCast.collide_with_areas = collide_with_areas
# Pointer suppress_radius update handler
func _update_suppress_radius() -> void:
$SuppressArea/CollisionShape3D.shape.radius = suppress_radius
# Pointer suppress_mask update handler
func _update_suppress_mask() -> void:
$SuppressArea.collision_mask = suppress_mask
# Pointer visible artifacts update handler
func _update_pointer() -> void:
if enabled and last_target:
_visible_hit(last_collided_at)
else:
_visible_miss()
# Pointer-activation button pressed handler
func _button_pressed() -> void:
if $RayCast.is_colliding():
# Report pressed
target = $RayCast.get_collider()
last_collided_at = $RayCast.get_collision_point()
XRToolsPointerEvent.pressed(self, target, last_collided_at)
# Pointer-activation button released handler
func _button_released() -> void:
if target:
# Report release
XRToolsPointerEvent.released(self, target, last_collided_at)
target = null
last_collided_at = Vector3(0, 0, 0)
# Button pressed handler
func _on_button_pressed(p_button : String) -> void:
if p_button == active_button_action and enabled:
_button_pressed()
# Button released handler
func _on_button_released(p_button : String) -> void:
if p_button == active_button_action and target:
_button_released()
# Update the laser active material
func _update_laser_active_material(hit : bool) -> void:
if hit and laser_hit_material:
$Laser.set_surface_override_material(0, laser_hit_material)
else:
$Laser.set_surface_override_material(0, laser_material)
# Update the visible artifacts to show a hit
func _visible_hit(at : Vector3) -> void:
# Show target if enabled
if show_target:
$Target.global_transform.origin = at
$Target.visible = true
# Control laser visibility
if show_laser != LaserShow.HIDE:
# Ensure the correct laser material is set
_update_laser_active_material(true)
# Adjust laser length
if laser_length == LaserLength.COLLIDE:
var collide_len : float = at.distance_to(global_transform.origin)
$Laser.mesh.size.z = collide_len
$Laser.position.z = collide_len * -0.5
else:
$Laser.mesh.size.z = distance
$Laser.position.z = distance * -0.5
# Show laser
$Laser.visible = true
else:
# Ensure laser is hidden
$Laser.visible = false
# Move the visible pointer artifacts to the target
func _visible_move(at : Vector3) -> void:
# Move target if configured
if show_target:
$Target.global_transform.origin = at
# Adjust laser length if set to collide-length
if laser_length == LaserLength.COLLIDE:
var collide_len : float = at.distance_to(global_transform.origin)
$Laser.mesh.size.z = collide_len
$Laser.position.z = collide_len * -0.5
# Update the visible artifacts to show a miss
func _visible_miss() -> void:
# Ensure target is hidden
$Target.visible = false
# Ensure the correct laser material is set
_update_laser_active_material(false)
# Hide laser if not set to show always
$Laser.visible = show_laser == LaserShow.SHOW
# Restore laser length if set to collide-length
$Laser.mesh.size.z = distance
$Laser.position.z = distance * -0.5

View File

@ -0,0 +1,51 @@
[gd_scene load_steps=6 format=3 uid="uid://42xbeno6pt3y"]
[ext_resource type="Material" path="res://addons/godot-xr-tools/materials/pointer.tres" id="1"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/function_desktop_pointer.gd" id="1_fkfo7"]
[sub_resource type="BoxMesh" id="BoxMesh_ctrty"]
resource_local_to_scene = true
material = ExtResource("1")
size = Vector3(0.002, 0.002, 10)
subdivide_depth = 20
[sub_resource type="SphereMesh" id="SphereMesh_6cghy"]
material = ExtResource("1")
radius = 0.01
height = 0.02
radial_segments = 16
rings = 8
[sub_resource type="SphereShape3D" id="SphereShape3D_8btxb"]
radius = 0.2
[node name="FunctionDesktopPointer" type="Node3D"]
script = ExtResource("1_fkfo7")
y_offset = 0.0
active_button_action = "trigger_left"
show_laser = 0
laser_length = 1
show_target = true
target_radius = 0.01
[node name="RayCast" type="RayCast3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
target_position = Vector3(0, 0, -10)
collision_mask = 5242880
[node name="Laser" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, -5)
visible = false
cast_shadow = 0
mesh = SubResource("BoxMesh_ctrty")
[node name="Target" type="MeshInstance3D" parent="."]
visible = false
mesh = SubResource("SphereMesh_6cghy")
[node name="SuppressArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 4194304
[node name="CollisionShape3D" type="CollisionShape3D" parent="SuppressArea"]
shape = SubResource("SphereShape3D_8btxb")

View File

@ -0,0 +1,51 @@
@tool
class_name XRToolsDesktopMouseCapture
extends XRToolsMovementProvider
## XR Tools Mouse Capture
##
## This script provides support for desktop mouse capture. This script works
## with the PlayerBody attached to the players XROrigin3D.
## Movement provider order
@export var order : int = 1
## Our directional input
@export var escape_action : String = "ui_cancel"
#Last mouse capture status and should it be auto captured
@export var capture : bool = true
# XRStart node
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsDesktopMouseCapture" or super(name)
# Perform jump movement
func physics_movement(_delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Skip if the player body isn't active
var check1 :bool= (xr_start_node.is_xr_active() and Input.mouse_mode==Input.MOUSE_MODE_CAPTURED)
if !player_body.enabled or check1:
return
if Input.is_action_just_pressed("ui_cancel"):
capture=!capture
#print(Input.mouse_mode==Input.MOUSE_MODE_CAPTURED)
if Input.mouse_mode==Input.MOUSE_MODE_CAPTURED and (xr_start_node.is_xr_active() or !capture):
Input.mouse_mode=Input.MOUSE_MODE_VISIBLE
elif (!xr_start_node.is_xr_active() and capture):
Input.mouse_mode=Input.MOUSE_MODE_CAPTURED
return

View File

@ -0,0 +1 @@
uid://cdk1f37eh1w4q

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bkse5rxsx5tb3"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/mouse_capture.gd" id="1_po4v8"]
[node name="MouseCapture" type="Node" groups=["movement_providers"]]
script = ExtResource("1_po4v8")

View File

@ -0,0 +1,84 @@
@tool
class_name XRToolsDesktopMovementCrouch
extends XRToolsMovementProvider
## XR Tools Movement Provider for Crouching
##
## This script works with the [XRToolsPlayerBody] attached to the players
## [XROrigin3D].
##
## While the player presses the crounch button, the height is overridden to
## the specified crouch height.
## Enumeration of crouching modes
enum CrouchType {
HOLD_TO_CROUCH, ## Hold button to crouch
TOGGLE_CROUCH, ## Toggle crouching on button press
}
## Movement provider order
@export var order : int = 10
## Crouch height
@export var crouch_height : float = 1.0
## Crouch button
@export var crouch_button_action : String = "action_crouch"
## Type of crouching
@export var crouch_type : CrouchType = CrouchType.HOLD_TO_CROUCH
## Crouching flag
var _crouching : bool = false
## Crouch button down state
var _crouch_button_down : bool = false
# Controller node
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementCrouch" or super(name)
# Perform jump movement
func physics_movement(_delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Skip if the controller isn't active
if !player_body.enabled or xr_start_node.is_xr_active():
return
# Detect crouch button down and pressed states
var crouch_button_down := Input.is_action_pressed(crouch_button_action)
var crouch_button_pressed := crouch_button_down and !_crouch_button_down
_crouch_button_down = crouch_button_down
# Calculate new crouching state
var crouching := _crouching
match crouch_type:
CrouchType.HOLD_TO_CROUCH:
# Crouch when button down
crouching = crouch_button_down
CrouchType.TOGGLE_CROUCH:
# Toggle when button pressed
if crouch_button_pressed:
crouching = !crouching
# Update crouching state
if crouching != _crouching:
_crouching = crouching
if crouching:
player_body.override_player_height(self, crouch_height)
else:
player_body.override_player_height(self)

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://d2iqcc25yvk61"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/movement_desktop_crouch.gd" id="1_4rbnc"]
[node name="MovementDesktopCrouch" type="Node" groups=["movement_providers"]]
script = ExtResource("1_4rbnc")

View File

@ -0,0 +1,72 @@
@tool
class_name XRToolsDesktopMovementDirect
extends XRToolsMovementProvider
## XR Tools Movement Provider for Direct Movement
##
## This script provides direct movement for the player. This script works
## with the [XRToolsPlayerBody] attached to the players [XROrigin3D].
##
## The player may have multiple [XRToolsMovementDirect] nodes attached to
## different controllers to provide different types of direct movement.
## Movement provider order
@export var order : int = 10
## Movement speed
@export var max_speed : float = 3.0
## If true, the player can strafe
@export var strafe : bool = false
## Input action for movement direction
@export var input_forward : String = "ui_up"
@export var input_backward : String = "ui_down"
@export var input_left : String = "ui_left"
@export var input_right : String = "ui_right"
# XRStart node
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsDesktopMovementDirect" or super(name)
# Perform jump movement
func physics_movement(_delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Skip if the controller isn't active
if !player_body.enabled or xr_start_node.is_xr_active():
return
#Calculate input vector
var input_dir = Input.get_vector(input_left, input_right, input_backward, input_forward)
# Apply forwards/backwards ground control
player_body.ground_control_velocity.y += input_dir.y * max_speed
# Apply left/right ground control
if strafe:
player_body.ground_control_velocity.x += input_dir.x * max_speed
# Clamp ground control
var length := player_body.ground_control_velocity.length()
if length > max_speed:
player_body.ground_control_velocity *= max_speed / length
## Find the right [XRToolsDesktopMovementDirect] node.
##
## This function searches from the specified node for the right controller
## [XRToolsDesktopMovementDirect] assuming the node is a sibling of the [XROrigin3D].
static func find(node : Node) -> XRToolsDesktopMovementDirect:
return XRTools.find_xr_child(
XRHelpers.get_xr_origin(node),
"*",
"XRToolsDesktopMovementDirect") as XRToolsDesktopMovementDirect

View File

@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://6uusxts6n6gm"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/movement_desktop_direct.gd" id="1_e8v0q"]
[node name="MovementDesktopDirect" type="Node" groups=["movement_providers"]]
script = ExtResource("1_e8v0q")
strafe = true

View File

@ -0,0 +1,182 @@
@tool
class_name XRToolsDesktopMovementFlight
extends XRToolsMovementProvider
## XR Tools Movement Provider for Flying
##
## This script provides flying movement for the player. The control parameters
## are intended to support a wide variety of flight mechanics.
##
## Pitch and Bearing input devices are selected which produce a "forwards"
## reference frame. The player controls (forwards/backwards and
## left/right) are applied in relation to this reference frame.
##
## The Speed Scale and Traction parameters allow primitive flight where
## the player is in direct control of their speed (in the reference frame).
## This produces an effect described as the "Mary Poppins Flying Umbrella".
##
## The Acceleration, Drag, and Guidance parameters allow for slightly more
## realisitic flying where the player can accelerate in their reference
## frame. The drag is applied against the global reference and can be used
## to construct a terminal velocity.
##
## The Guidance property attempts to lerp the players velocity into flight
## forwards direction as if the player had guide-fins or wings.
##
## The Exclusive property specifies whether flight is exclusive (no further
## physics effects after flying) or whether additional effects such as
## the default player gravity are applied.
## Signal emitted when flight starts
signal flight_started()
## Signal emitted when flight finishes
signal flight_finished()
## Movement provider order
@export var order : int = 30
## Flight toggle button
@export var flight_button : String = "ui_focus_next"
@export var input_forward : String = "ui_up"
@export var input_backward : String = "ui_down"
@export var input_left : String = "ui_left"
@export var input_right : String = "ui_right"
## Flight speed from control
@export var speed_scale : float = 5.0
## Flight traction pulling flight velocity towards the controlled speed
@export var speed_traction : float = 3.0
## Flight acceleration from control
@export var acceleration_scale : float = 0.0
## Flight drag
@export var drag : float = 0.1
## Guidance effect (virtual fins/wings)
@export var guidance : float = 0.0
## If true, flight movement is exclusive preventing further movement functions
@export var exclusive : bool = true
## Flight button state
var _flight_button : bool = false
# Node references
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
@onready var _camera := XRHelpers.get_xr_camera(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsDesktopMovementFlight" or super(name)
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Process physics movement for flight
func physics_movement(delta: float, player_body: XRToolsPlayerBody, disabled: bool):
# Disable flying if requested, or if no controller
if disabled or !enabled or !player_body.enabled or xr_start_node.is_xr_active():
set_flying(false)
return
# Detect press of flight button
var old_flight_button = _flight_button
_flight_button = Input.is_action_pressed(flight_button)
if _flight_button and !old_flight_button:
set_flying(!is_active)
# Skip if not flying
if !is_active:
return
# Select the pitch vector
var pitch_vector: Vector3
# Use the vertical part of the 'head' forwards vector
pitch_vector = -_camera.transform.basis.z.y * player_body.up_player
# Select the bearing vector
var bearing_vector: Vector3
# Use the horizontal part of the 'head' forwards vector
bearing_vector = -_camera.global_transform.basis.z \
.slide(player_body.up_player)
# Construct the flight bearing
var forwards := (bearing_vector.normalized() + pitch_vector).normalized()
var side := forwards.cross(player_body.up_player)
# Construct the target velocity
var input_dir = Input.get_vector(input_left, input_right, input_backward, input_forward)
var joy_forwards :float= input_dir.y
var joy_side :float= input_dir.x
var heading := forwards * joy_forwards + side * joy_side
# Calculate the flight velocity
var flight_velocity := player_body.velocity
flight_velocity *= 1.0 - drag * delta
flight_velocity = flight_velocity.lerp(heading * speed_scale, speed_traction * delta)
flight_velocity += heading * acceleration_scale * delta
# Apply virtual guidance effect
if guidance > 0.0:
var velocity_forwards := forwards * flight_velocity.length()
flight_velocity = flight_velocity.lerp(velocity_forwards, guidance * delta)
# If exclusive then perform the exclusive move-and-slide
if exclusive:
player_body.velocity = player_body.move_player(flight_velocity)
return true
# Update velocity and return for additional effects
player_body.velocity = flight_velocity
return
func set_flying(active: bool) -> void:
# Skip if no change
if active == is_active:
return
# Update state
is_active = active
# Handle state change
if is_active:
emit_signal("flight_started")
else:
emit_signal("flight_finished")
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Verify the camera
if !XRHelpers.get_xr_camera(self):
warnings.append("Unable to find XRCamera3D")
# Verify the left controller
if !XRHelpers.get_left_controller(self):
warnings.append("Unable to find left XRController3D node")
# Verify the right controller
if !XRHelpers.get_right_controller(self):
warnings.append("Unable to find left XRController3D node")
# Return warnings
return warnings

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://cfa5gsol863rv"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/movement_desktop_flight.gd" id="1_exmlf"]
[node name="MovementDesktopFlight" type="Node" groups=["movement_providers"]]
script = ExtResource("1_exmlf")

View File

@ -0,0 +1,44 @@
@tool
class_name XRToolsDesktopMovementJump
extends XRToolsMovementProvider
## XR Tools Movement Provider for Jumping
##
## This script provides jumping mechanics for the player. This script works
## with the [XRToolsPlayerBody] attached to the players [XROrigin3D].
##
## The player enables jumping by attaching an [XRToolsMovementJump] as a
## child of the appropriate [XRController3D], then configuring the jump button
## and jump velocity.
## Movement provider order
@export var order : int = 20
## Button to trigger jump
@export var jump_button_action : String = "ui_accept"
# Node references
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsDesktopMovementJump" or super(name)
# Perform jump movement
func physics_movement(_delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Skip if the jump controller isn't active
if !player_body.enabled or xr_start_node.is_xr_active():
return
# Request jump if the button is pressed
if Input.is_action_pressed(jump_button_action):
player_body.request_jump()

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dluln6777mmh3"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/movement_desktop_jump.gd" id="1_tccpc"]
[node name="MovementDesktopJump" type="Node" groups=["movement_providers"]]
script = ExtResource("1_tccpc")

View File

@ -0,0 +1,144 @@
@tool
class_name XRToolsDesktopMovementSprint
extends XRToolsMovementProvider
## XR Tools Movement Provider for Sprinting
##
## This script provides sprinting movement for the player. It assumes there is
## a direct movement node in the scene otherwise it will not be functional.
##
## There will not be an error, there just will not be any reason for it to
## have any impact on the player. This node should be a direct child of
## the [XROrigin3D] node rather than to a specific [XRController3D].
## Signal emitted when sprinting starts
signal sprinting_started()
## Signal emitted when sprinting finishes
signal sprinting_finished()
## Enumeration of sprinting modes - toggle or hold button
enum SprintType {
HOLD_TO_SPRINT, ## Hold button to sprint
TOGGLE_SPRINT, ## Toggle sprinting on button press
}
## Type of sprinting
@export var sprint_type : SprintType = SprintType.HOLD_TO_SPRINT
## Sprint speed multiplier (multiplier from speed set by direct movement node(s))
@export_range(1.0, 4.0) var sprint_speed_multiplier : float = 2.0
## Movement provider order
@export var order : int = 11
## Sprint button
@export var sprint_button : String = "action_sprint"
# Sprint button down state
var _sprint_button_down : bool = false
# Variable to hold left controller direct movement node original max speed
var _direct_original_max_speed : float = 0.0
# XRStart node
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
# Variable used to cache left controller direct movement function, if any
@onready var _desktop_direct_move := XRToolsDesktopMovementDirect.find(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsDesktopMovementSprint" or super(name)
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Perform sprinting
func physics_movement(_delta: float, player_body: XRToolsPlayerBody, disabled: bool):
# Skip if the controller isn't active or is not enabled
if !player_body.enabled or xr_start_node.is_xr_active() or disabled == true or !enabled:
set_sprinting(false)
return
# Detect sprint button down and pressed states
var sprint_button_down := Input.is_action_pressed(sprint_button)
var sprint_button_pressed := sprint_button_down and !_sprint_button_down
_sprint_button_down = sprint_button_down
# Calculate new sprinting state
var sprinting := is_active
match sprint_type:
SprintType.HOLD_TO_SPRINT:
# Sprint when button down
sprinting = sprint_button_down
SprintType.TOGGLE_SPRINT:
# Toggle when button pressed
if sprint_button_pressed:
sprinting = !sprinting
# Update sprinting state
if sprinting != is_active:
set_sprinting(sprinting)
# Public function used to set sprinting active or not active
func set_sprinting(active: bool) -> void:
# Skip if no change
if active == is_active:
return
# Update state
is_active = active
# Handle state change
if is_active:
# We are sprinting
emit_signal("sprinting_started")
# Since max speeds could be changed while game is running, check
# now for original max speeds of left and right nodes
if _desktop_direct_move:
_direct_original_max_speed = _desktop_direct_move.max_speed
# Set both controllers' direct movement functions, if appliable, to
# the sprinting speed
if _desktop_direct_move:
_desktop_direct_move.max_speed = \
_direct_original_max_speed * sprint_speed_multiplier
else:
# We are not sprinting
emit_signal("sprinting_finished")
# Set both controllers' direct movement functions, if applicable, to
# their original speeds
if _desktop_direct_move:
_desktop_direct_move.max_speed = _direct_original_max_speed
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Make sure player has at least one direct movement node
if !XRToolsDesktopMovementDirect.find(self):
warnings.append("Player missing XRToolsDesktopMovementDirect node")
# Return warnings
return warnings

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dgm5op1hausw5"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/movement_desktop_sprint.gd" id="1_md0f7"]
[node name="MovementDesktopSprint" type="Node" groups=["movement_providers"]]
script = ExtResource("1_md0f7")

View File

@ -0,0 +1,133 @@
@tool
class_name XRToolsDesktopMovementTurn
extends XRToolsMovementProvider
## XR Tools Movement Provider for Turning
##
## This script provides turning support for the player. This script works
## with the PlayerBody attached to the players XROrigin3D.
## Movement mode
enum TurnMode {
DEFAULT, ## Use turn mode from project/user settings
SNAP, ## Use snap-turning
SMOOTH ## Use smooth-turning
}
## Movement provider order
@export var order : int = 6
## Movement mode property
@export var turn_mode : TurnMode = TurnMode.SMOOTH
## Smooth turn speed in radians per second
@export var smooth_turn_speed : float = 2.0
## Seconds per step (at maximum turn rate)
@export var step_turn_delay : float = 0.2
## Step turn angle in degrees
@export var step_turn_angle : float = 20.0
## Our directional input
@export var input_action : String = "primary"
## Our directional input
@export var clear_mouse_move_when_body_not_active : bool = true
@export var clear_cam_x_when_body_not_active : bool = false
@export var invert_y : bool = true
var plr_body : XRToolsPlayerBody
var mouse_move_vector := Vector2.ZERO
var _last_plr_bd_status := true
# Turn step accumulator
var _turn_step : float = 0.0
# XRStart node
@onready var xr_start_node = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,
"*Staging",
"XRToolsStaging"),"StartXR","Node")
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsDesktopMovementTurn" or super(name)
func _unhandled_input(event):
if !enabled:
return
if event is InputEventMouseMotion:
event.relative*=.1
if invert_y:
event.relative.y *= -1
mouse_move_vector += event.relative
func _process(_delta: float) -> void:
if is_instance_valid(plr_body):
if !plr_body.enabled and !xr_start_node.is_xr_active() and _last_plr_bd_status!=plr_body.enabled:
if clear_mouse_move_when_body_not_active:
mouse_move_vector=Vector2.ZERO
if clear_cam_x_when_body_not_active:
plr_body.camera_node.rotation_degrees.x=0
_last_plr_bd_status=!plr_body.enabled
elif plr_body.enabled:
_last_plr_bd_status=!plr_body.enabled
# Perform jump movement
func physics_movement(delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Skip if the player body isn't active
plr_body=player_body
if !player_body.enabled or xr_start_node.is_xr_active():
if clear_mouse_move_when_body_not_active:
mouse_move_vector=Vector2.ZERO
#if clear_cam_x_when_body_not_active:
# player_body.camera_node.rotation_degrees.x=0
return
var deadzone = 0.1
# if _snap_turning():
# deadzone = XRTools.get_snap_turning_deadzone()
# Read the left/right joystick axis
var left_right := mouse_move_vector.x
#if abs(left_right) <= deadzone:
# # Not turning
# _turn_step = 0.0
# return
# Handle smooth rotation
#if !_snap_turning():
left_right -= deadzone * sign(left_right)
player_body.rotate_player(smooth_turn_speed * delta * left_right)
player_body.camera_node.rotation_degrees.x=clamp(
player_body.camera_node.rotation_degrees.x+smooth_turn_speed * mouse_move_vector.y,
-89.999,
89.999)
mouse_move_vector=Vector2.ZERO
return
# Test if snap turning should be used
func _snap_turning():
#temp removal - IDK if normal controler will be considered to have this as use
return false
# match turn_mode:
# TurnMode.SNAP:
# return true
#
# TurnMode.SMOOTH:
# return false
#
# _:
# return XRToolsUserSettings.snap_turning

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://c4d13r420hnx2"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/movement_desktop_turn.gd" id="1_qv43c"]
[node name="MovementDesktopTurn" type="Node" groups=["movement_providers"]]
script = ExtResource("1_qv43c")

View File

@ -0,0 +1,62 @@
@tool
class_name XRToolsMovementGravityZones
extends XRToolsMovementProvider
# Default wall-walk mask of 4:wall-walk
const DEFAULT_MASK := 0b0000_0000_0000_0000_0000_0000_0000_1000
## Wall walking provider order
@export var order : int = 26
## Set our follow layer mask
@export_flags_3d_physics var follow_mask : int = DEFAULT_MASK
var _gravity_dir := Vector3(0,-9.8,0)
@onready var fly_desktop : XRToolsDesktopMovementFlight = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,"*","XROrigin3D"),
"*",
"XRToolsDesktopMovementFlight")
@onready var fly_xr : XRToolsMovementFlight = XRTools.find_xr_child(
XRTools.find_xr_ancestor(self,"*","XROrigin3D"),
"*",
"XRToolsMovementFlight")
func physics_pre_movement(_delta: float, player_body: XRToolsPlayerBody):
# Test for collision with wall under feet
var gravity_zones1 = get_tree().get_nodes_in_group("gravity_zone1")
var gravity_zones2 = get_tree().get_nodes_in_group("gravity_zone2")
var gravity_zones3 = get_tree().get_nodes_in_group("gravity_zone3")
#_gravity_dir = Vector3(0,-9.8,0)
var grav_z := 0
for zone in gravity_zones3:
if zone.overlaps_body(player_body) and zone is Area3D:
grav_z=3
_gravity_dir=zone.global_transform.basis.y*zone.gravity*-1
if grav_z==0:
for zone in gravity_zones2:
if zone.overlaps_body(player_body) and zone is Area3D:
grav_z=2
_gravity_dir=zone.global_transform.basis.y*zone.gravity*-1
if grav_z==0:
for zone in gravity_zones1:
if zone.overlaps_body(player_body) and zone is Area3D:
grav_z=1
_gravity_dir=zone.global_transform.basis.y*zone.gravity*-1
if grav_z==0:
fly_desktop.set_flying(true)
fly_xr.set_flying(true)
else:
fly_desktop.set_flying(false)
fly_xr.set_flying(false)
# Modify the player gravity
player_body.gravity = _gravity_dir

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/godot-xr-tools/desktop-support/movement_gravity_zones.gd" id="1"]
[node name="MovementGravityZones" type="Node" groups=["movement_providers"]]
script = ExtResource("1")

View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dhnfbf4p0s74"
path="res://.godot/imported/audio.svg-20d7f0b624a1b2ef54f1b4d12970c8d0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-xr-tools/editor/icons/audio.svg"
dest_files=["res://.godot/imported/audio.svg-20d7f0b624a1b2ef54f1b4d12970c8d0.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><path d="m5.640625.250438c-.75 0-.75 1-.75 1l.0028 1.7433557s-.0028.7623199.7656177.7594124l.7315823-.0027681v.75c-.9987826-.28732-2.3899024-.5553882-3.4887954-1.2601516-.4265336-.1870545-.9793234 1.1200236-.9793234 1.1200236 1.4123763 1.0085676 3.5979104.8866743 3.9681188 1.390128v1.75c-.5 1-1.5 2.75-2 3.5h2.25l1.5-2.75 1.25 2.75c.5 0 1.25 0 2 .000003-.5-1.000003-1.25-2.500006-1.75-3.500003v-1.75c.450387-.5452358 2.25264-.15852 3.39846-.4920078.282315-.5425377.182574-.6845166-.297031-1.5473061-1.253436.698062-1.535576.3459793-3.851429.7893139v-.75h.75c.25 0 .7631105-.245671.760209-.7445791l-.010209-1.7554209c0-.5-.25-1-1.25-1l-2.5.75h2.5v1.25c-.9605198.00114-1.745755-.00241-2.5 0v-1.25l2.5-.75z" fill-opacity=".99608" stroke-width=".762656" transform="translate(.609375 -.000438)"/><g fill="none" stroke="#ff8080" stroke-width="1.5"><path d="m3.7388367 3.4147117c-.9448159 1.3559779-.9992656 2.3269247-.4091983 3.6683904"/><path d="m12.545272 4.8349236c.597942-1.5043684.197251-2.2941014-.8486-3.1240651"/></g></g><g transform="translate(.5 -.000438)"><g fill="#fc7f7f"><path d="m12 11.999519c.55228 0 1 .448243 1 1.000481v1.000483.211639.786437 1.000479.0014h-1v-1c-.202186 0-.336627-.491203-.5-.5-1.452695-.07822-1.5-.503591-1.5-1h1 1v-.500438h-1-1c0-.552238.44772-1.000481 1-1.000481z" stroke-width=".999963"/><path d="m1.999832 12v1c0 .55228.44772 1 1 1-.55228 0-1 .44772-1 1v1h1v-1h1v1h1v-1c0-.55228-.44772-1-1-1 .55228 0 1-.44772 1-1v-1h-1v1h-1v-1z"/><path d="m5.999832 12v1 3h1v-1h1v1h1v-1c-.000834-.17579-.047991-.34825-.13672-.5.088728-.15175.13588-.32421.13672-.5v-1c0-.55228-.44772-1-1-1h-1zm1 1h1v1h-1z"/></g><g fill="#ff8080" stroke="#ff8080" stroke-width=".500002"><path d="m11.721643 12.70092h2"/><path d="m11.694618 13.781362h2"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cyg33jxco0rh6"
path="res://.godot/imported/body.svg-324e141d452c32f3136ca97c338025b4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-xr-tools/editor/icons/body.svg"
dest_files=["res://.godot/imported/body.svg-324e141d452c32f3136ca97c338025b4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg40"
sodipodi:docname="foot.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs44" />
<sodipodi:namedview
id="namedview42"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="22.627417"
inkscape:cx="25.654718"
inkscape:cy="13.412932"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg40" />
<path
style="fill:#fd8080;fill-opacity:1;stroke-width:0.0261891"
d="M 10.044819,9.931539 C 9.7799882,9.796189 9.3263332,9.204865 9.1824507,8.8074722 9.1150567,8.6213345 9.1130747,8.5988058 9.1126277,8.0135482 L 9.1121675,7.411198 9.2141197,6.952888 9.3160719,6.4945781 9.3280929,5.3815396 c 0.0083,-0.7685502 0.00101,-1.2913619 -0.02355,-1.6891995 C 9.2520989,2.8428903 9.2589719,1.9354066 9.3196159,1.7019654 9.3950705,1.4115129 9.4953072,1.2147127 9.6248763,1.1026311 l 0.1155004,-0.099912 0.4190263,0.012919 c 0.455795,0.014052 0.654131,0.053122 1.026564,0.2022222 0.54858,0.2196185 1.119659,0.6685668 1.275523,1.0027405 0.173923,0.3728916 0.239826,0.8748577 0.217049,1.6531852 -0.02249,0.7684764 -0.07101,1.0344577 -0.45261,2.4813776 -0.434952,1.6491948 -0.563063,2.0808353 -0.720937,2.4290176 -0.211941,0.4674268 -0.719089,1.0676688 -0.980889,1.1609458 -0.175215,0.06243 -0.339674,0.05776 -0.479284,-0.01359 z"
id="path856" />
<path
style="fill:#fd8080;fill-opacity:1;stroke-width:0.015625"
d="M 5.9752428,13.572626 C 5.825332,13.523052 5.6460658,13.38721 5.4790128,13.196601 4.9595621,12.603902 4.7383842,12.122706 4.5220532,11.114631 4.398494,10.538861 4.3604405,10.389768 4.0920785,9.429995 3.8150524,8.4392364 3.7160202,7.6471402 3.7726775,6.875308 3.8288625,6.1099036 3.9164565,5.844507 4.2308909,5.4869814 4.3533561,5.3477332 4.5254668,5.2179453 4.777465,5.0748125 5.38216,4.731351 5.8813362,4.5862455 6.4581809,4.5862455 c 0.231672,0 0.2836625,0.018714 0.4030472,0.1450784 0.1441095,0.1525344 0.1944306,0.2749974 0.2800073,0.6814355 0.036357,0.1726711 0.042378,0.2436393 0.048891,0.5762416 0.00833,0.4254889 -0.010196,0.7501784 -0.066116,1.1586762 -0.044668,0.3262878 -0.052009,0.4580547 -0.06274,1.1260683 -0.010129,0.6305528 0.012227,1.4086795 0.046016,1.6015625 0.032474,0.185381 0.0839,0.338966 0.2124984,0.634623 0.1235691,0.284096 0.1454697,0.352323 0.1871264,0.582953 0.037078,0.205281 0.038925,0.361544 0.0068,0.575496 -0.066421,0.442379 -0.1842582,0.717659 -0.5233293,1.222544 -0.2556581,0.380682 -0.4087131,0.548019 -0.5739267,0.627484 -0.104405,0.05022 -0.1346894,0.05743 -0.2601551,0.06198 -0.078098,0.0028 -0.1595736,-6.61e-4 -0.181058,-0.0078 z"
id="path902" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bfkcd3fkyahqu"
path="res://.godot/imported/foot.svg-9e361563e010aa07be49bfb25fdb6639.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-xr-tools/editor/icons/foot.svg"
dest_files=["res://.godot/imported/foot.svg-9e361563e010aa07be49bfb25fdb6639.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><path d="m12 11.999519c.55228 0 1 .448243 1 1.000481v1.000483.211639.786437 1.000479.0014h-1v-1c-.202186 0-.336627-.491203-.5-.5-1.452695-.07822-1.5-.503591-1.5-1h1 1v-.500438h-1-1c0-.552238.44772-1.000481 1-1.000481z" stroke-width=".999963"/><path d="m1.999832 12v1c0 .55228.44772 1 1 1-.55228 0-1 .44772-1 1v1h1v-1h1v1h1v-1c0-.55228-.44772-1-1-1 .55228 0 1-.44772 1-1v-1h-1v1h-1v-1z"/><path d="m5.999832 12v1 3h1v-1h1v1h1v-1c-.000834-.17579-.047991-.34825-.13672-.5.088728-.15175.13588-.32421.13672-.5v-1c0-.55228-.44772-1-1-1h-1zm1 1h1v1h-1z"/></g><g stroke="#ff8080"><path d="m11.721643 12.70092h2" fill="#ff8080" stroke-width=".500002"/><path d="m11.694618 13.781362h2" fill="#ff8080" stroke-width=".500002"/><circle cx="-5.853771" cy="-6.970115" fill="none" r="3" stroke-width="1.42857" transform="scale(-1)"/><g fill="#ff8080" transform="matrix(1.5998367 0 0 1.4541212 -8.997551 -.227061)"><path d="m11 2.5h4"/><path d="m13 .5v4"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b5vxil50s0ofi"
path="res://.godot/imported/function.svg-52c5f936037e0f38a4da2b1e16ae67fe.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-xr-tools/editor/icons/function.svg"
dest_files=["res://.godot/imported/function.svg-52c5f936037e0f38a4da2b1e16ae67fe.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m9.5532719 19.032065c.7515921 0 .8020221-.134249 1.3566411-1.096988 1.187157-1.391415 1.959866-2.115403 1.959866-2.115403.956259-1.186302.899466-1.493116.400204-2.10267-1.836473-.08397-.950366 1.07716-2.399132 1.067713-3.6654836.57138-5.8363537 3.182233-5.8211772 3.07349.2193447-1.571653 5.7194162-3.595593 5.7442212-3.670251.388057-1.167938 1.199163-2.981165 1.47376-3.928695.58339-1.7248573-.838773-1.8000802-1.724674-.5736257l-1.918979 4.8317897c-.0690625-.138458.3155477-4.449876.3998932-5.2980728.1599514-1.608499-2.0933718-1.6944154-2.1769669-.2258024-.0537649.944555-.1115382 6.0881672-.147506 4.8181392-.0302181-1.067011-1.3692069-2.513104-1.5668065-4.2277384-.1944963-1.7989751-2.5014442-1.2032339-1.7843238.6443794.4060542 2.461134.8612438 1.917287 1.3050931 3.81199l-1.5451335-2.033556c-.8083872-1.309984-2.2747721-.419846-1.466385.890138l1.7162803 2.711944c.1090999.892326.3001203 1.520805 1.3544509 2.699388.5134486.832581.8190766.71043 1.5706688.71043z" fill="#fc7f7f" fill-opacity=".99608" stroke-width="1.21993" transform="translate(.267457 -7.900915)"/><g fill="#fc7f7f"><path d="m12 11.999519c.55228 0 1 .448243 1 1.000481v1.000483.211639.786437 1.000479.0014h-1v-1c-.202186 0-.336627-.491203-.5-.5-1.452695-.07822-1.5-.503591-1.5-1h1 1v-.500438h-1-1c0-.552238.44772-1.000481 1-1.000481z" stroke-width=".999963"/><path d="m1.999832 12v1c0 .55228.44772 1 1 1-.55228 0-1 .44772-1 1v1h1v-1h1v1h1v-1c0-.55228-.44772-1-1-1 .55228 0 1-.44772 1-1v-1h-1v1h-1v-1z"/><path d="m5.999832 12v1 3h1v-1h1v1h1v-1c-.000834-.17579-.047991-.34825-.13672-.5.088728-.15175.13588-.32421.13672-.5v-1c0-.55228-.44772-1-1-1h-1zm1 1h1v1h-1z"/></g><g fill="#ff8080" stroke="#ff8080" stroke-width=".500002"><path d="m11.721643 12.70092h2"/><path d="m11.694618 13.781362h2"/></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://beko1qhyybx7e"
path="res://.godot/imported/hand.svg-a05486d804ef16320d6cf54e06292b8f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-xr-tools/editor/icons/hand.svg"
dest_files=["res://.godot/imported/hand.svg-a05486d804ef16320d6cf54e06292b8f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g stroke="#ff8080"><path d="m1.4374349 6.2407149c3.327012.02139 9.5648681-.0018 11.4589221-.028063" fill="#ff8080" stroke-width="3"/><g fill="none" stroke-width="2.29484"><path d="m9.324041 2.3242926 4.933526 4.498955"/><path d="m13.437147 5.859164c-1.047728 1.5267675-2.528193 2.753523-3.7347036 4.077989"/></g></g><g transform="translate(.000168)"><g fill="#fc7f7f"><path d="m12 11.999519c.55228 0 1 .448243 1 1.000481v1.000483.211639.786437 1.000479.0014h-1v-1c-.202186 0-.336627-.491203-.5-.5-1.452695-.07822-1.5-.503591-1.5-1h1 1v-.500438h-1-1c0-.552238.44772-1.000481 1-1.000481z" stroke-width=".999963"/><path d="m1.999832 12v1c0 .55228.44772 1 1 1-.55228 0-1 .44772-1 1v1h1v-1h1v1h1v-1c0-.55228-.44772-1-1-1 .55228 0 1-.44772 1-1v-1h-1v1h-1v-1z"/><path d="m5.999832 12v1 3h1v-1h1v1h1v-1c-.000834-.17579-.047991-.34825-.13672-.5.088728-.15175.13588-.32421.13672-.5v-1c0-.55228-.44772-1-1-1h-1zm1 1h1v1h-1z"/></g><g fill="#ff8080" stroke="#ff8080" stroke-width=".500002"><path d="m11.721643 12.70092h2"/><path d="m11.694618 13.781362h2"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://04fn15h4x333"
path="res://.godot/imported/movement_provider.svg-3c994cf0a3775c20f333be563d69fbf8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-xr-tools/editor/icons/movement_provider.svg"
dest_files=["res://.godot/imported/movement_provider.svg-3c994cf0a3775c20f333be563d69fbf8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><path d="m12 11.999519c.55228 0 1 .448243 1 1.000481v1.000483.211639.786437 1.000479.0014h-1v-1c-.202186 0-.336627-.491203-.5-.5-1.452695-.07822-1.5-.503591-1.5-1h1 1v-.500438h-1-1c0-.552238.44772-1.000481 1-1.000481z" stroke-width=".999963"/><path d="m1.999832 12v1c0 .55228.44772 1 1 1-.55228 0-1 .44772-1 1v1h1v-1h1v1h1v-1c0-.55228-.44772-1-1-1 .55228 0 1-.44772 1-1v-1h-1v1h-1v-1z"/><path d="m5.999832 12v1 3h1v-1h1v1h1v-1c-.000834-.17579-.047991-.34825-.13672-.5.088728-.15175.13588-.32421.13672-.5v-1c0-.55228-.44772-1-1-1h-1zm1 1h1v1h-1z"/></g><g stroke="#ff8080"><path d="m11.721643 12.70092h2" fill="#ff8080" stroke-width=".500002"/><path d="m11.694618 13.781362h2" fill="#ff8080" stroke-width=".500002"/><ellipse cx="-8" cy="-5.999998" fill="none" rx="4.285715" ry="4.285713" stroke-width="1.42857" transform="scale(-1)"/></g></svg>

After

Width:  |  Height:  |  Size: 943 B

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b6gwa6o27pbry"
path="res://.godot/imported/node.svg-37d53571b4a4459efefcc791c5402b4f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-xr-tools/editor/icons/node.svg"
dest_files=["res://.godot/imported/node.svg-37d53571b4a4459efefcc791c5402b4f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><path d="m12 11.999519c.55228 0 1 .448243 1 1.000481v1.000483.211639.786437 1.000479.0014h-1v-1c-.202186 0-.336627-.491203-.5-.5-1.452695-.07822-1.5-.503591-1.5-1h1 1v-.500438h-1-1c0-.552238.44772-1.000481 1-1.000481z" stroke-width=".999963"/><path d="m1.999832 12v1c0 .55228.44772 1 1 1-.55228 0-1 .44772-1 1v1h1v-1h1v1h1v-1c0-.55228-.44772-1-1-1 .55228 0 1-.44772 1-1v-1h-1v1h-1v-1z"/><path d="m5.999832 12v1 3h1v-1h1v1h1v-1c-.000834-.17579-.047991-.34825-.13672-.5.088728-.15175.13588-.32421.13672-.5v-1c0-.55228-.44772-1-1-1h-1zm1 1h1v1h-1z"/></g><g stroke="#ff8080"><path d="m11.721643 12.70092h2" fill="#ff8080" stroke-width=".500002"/><path d="m11.694618 13.781362h2" fill="#ff8080" stroke-width=".500002"/><path d="m.98844737 8.4010995c1.83521943-3.2693204 4.32032623 3.4590935 5.09543103-.1041898.5122353-2.3548301 1.5512016-4.7636852 1.5512016-7.1393289 0-.44964593.3393333.8692356.4187169 1.3152833.3581573 2.0123893.7090004 4.0047814 1.1634017 6.0086392 1.3138514 5.7939487.1057884.3417346 2.0036324-1.0154361.144766-.1035335.95577 1.0405657 1.098766 1.1092871.938333.4509487 2.146617-.5105464 3.19809-.8075801" fill="none" stroke="#ff8080" stroke-width="1.52176"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dsexrsb8t0vi2"
path="res://.godot/imported/rumble.svg-104961f6551a931675972887ab17d2bc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot-xr-tools/editor/icons/rumble.svg"
dest_files=["res://.godot/imported/rumble.svg-104961f6551a931675972887ab17d2bc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,79 @@
@tool
class_name XRToolsFade
extends Node3D
## XR Tools Fade Script
##
## This script manages fading the view.
# Dictionary of fade requests
var _faders : Dictionary = {}
# Fade update flag
var _update : bool = false
# Fade mesh
var _mesh : MeshInstance3D
# Fade shader material
var _material : ShaderMaterial
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsFade"
# Called when the fade node is ready
func _ready() -> void:
# Add to the fade_mesh group - in the future this should be replaced with
# static instances.
add_to_group("fade_mesh")
# Get the mesh and material
_mesh = $FadeMesh
_material = _mesh.get_surface_override_material(0)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta : float) -> void:
# Skip if nothing to update
if not _update:
return
# Calculate the cumulative shade color
var fade := Color(1, 1, 1, 0)
var show := false
for whom in _faders:
var color := _faders[whom] as Color
fade = fade.blend(color)
show = true
# Set the shader and show if necessary
_material.set_shader_parameter("albedo", fade)
_mesh.visible = show
_update = false
# Set the fade level
func set_fade_level(p_whom : Variant, p_color : Color) -> void:
# Test if fading is needed
if p_color.a > 0:
# Set the fade level
_faders[p_whom] = p_color
_update = true
elif _faders.erase(p_whom):
# Fade erased
_update = true
## Set the fade level on the fade instance
static func set_fade(p_whom : Variant, p_color : Color) -> void:
# In the future this use of groups should be replaced by static instances.
var tree := Engine.get_main_loop() as SceneTree
for node in tree.get_nodes_in_group("fade_mesh"):
var fade := node as XRToolsFade
if fade:
fade.set_fade_level(p_whom, p_color)

View File

@ -0,0 +1 @@
uid://cg2rbil60ljlx

View File

@ -0,0 +1,13 @@
shader_type spatial;
render_mode depth_test_disabled, skip_vertex_transform, unshaded, cull_disabled;
uniform vec4 albedo : source_color;
void vertex() {
POSITION = vec4(VERTEX.x, -VERTEX.y, 0.0, 1.0);
}
void fragment() {
ALBEDO = albedo.rgb;
ALPHA = albedo.a;
}

View File

@ -0,0 +1 @@
uid://b7si04pqnb1yy

View File

@ -0,0 +1,21 @@
[gd_scene load_steps=5 format=3 uid="uid://wtpox7m5vu2b"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/effects/fade.gd" id="1_6667f"]
[ext_resource type="Shader" path="res://addons/godot-xr-tools/effects/fade.gdshader" id="1_tjen8"]
[sub_resource type="QuadMesh" id="QuadMesh_aqp6r"]
custom_aabb = AABB(-5000, -5000, -5000, 10000, 10000, 10000)
size = Vector2(2, 2)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_xjgy8"]
render_priority = 50
shader = ExtResource("1_tjen8")
shader_parameter/albedo = Color(0, 0, 0, 1)
[node name="Fade" type="Node3D"]
script = ExtResource("1_6667f")
[node name="FadeMesh" type="MeshInstance3D" parent="."]
visible = false
mesh = SubResource("QuadMesh_aqp6r")
surface_material_override/0 = SubResource("ShaderMaterial_xjgy8")

View File

@ -0,0 +1,195 @@
@tool
class_name XRToolsVignette
extends Node3D
@export var radius : float = 1.0: set = set_radius
@export var fade : float = 0.05: set = set_fade
@export var steps : int = 32: set = set_steps
@export var auto_adjust : bool = true: set = set_auto_adjust
@export var auto_inner_radius : float = 0.35
@export var auto_fade_out_factor : float = 1.5
@export var auto_fade_delay : float = 1.0
@export var auto_rotation_limit : float = 20.0: set = set_auto_rotation_limit
@export var auto_velocity_limit : float = 10.0
var material : ShaderMaterial = preload("res://addons/godot-xr-tools/effects/vignette.tres")
var auto_first = true
var fade_delay = 0.0
var origin_node = null
var last_origin_basis : Basis
var last_location : Vector3
@onready var auto_rotation_limit_rad = deg_to_rad(auto_rotation_limit)
func set_radius(new_radius : float) -> void:
radius = new_radius
if is_inside_tree():
_update_radius()
func _update_radius() -> void:
if radius < 1.0:
if material:
material.set_shader_parameter("radius", radius * sqrt(2))
$Mesh.visible = true
else:
$Mesh.visible = false
func set_fade(new_fade : float) -> void:
fade = new_fade
if is_inside_tree():
_update_fade()
func _update_fade() -> void:
if material:
material.set_shader_parameter("fade", fade)
func set_steps(new_steps : int) -> void:
steps = new_steps
if is_inside_tree():
_update_mesh()
func _update_mesh() -> void:
var vertices : PackedVector3Array
var indices : PackedInt32Array
vertices.resize(2 * steps)
indices.resize(6 * steps)
for i in steps:
var v : Vector3 = Vector3.RIGHT.rotated(Vector3.FORWARD, deg_to_rad((360.0 * i) / steps))
vertices[i] = v
vertices[steps+i] = v * 2.0
var off = i * 6
var i2 = ((i + 1) % steps)
indices[off + 0] = steps + i
indices[off + 1] = steps + i2
indices[off + 2] = i2
indices[off + 3] = steps + i
indices[off + 4] = i2
indices[off + 5] = i
# update our mesh
var arr_mesh = ArrayMesh.new()
var arr : Array
arr.resize(ArrayMesh.ARRAY_MAX)
arr[ArrayMesh.ARRAY_VERTEX] = vertices
arr[ArrayMesh.ARRAY_INDEX] = indices
arr_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arr)
arr_mesh.custom_aabb = AABB(Vector3(-1.0, -1.0, -1.0), Vector3(1.0, 1.0, 1.0))
$Mesh.mesh = arr_mesh
$Mesh.set_surface_override_material(0, material)
func set_auto_adjust(new_auto_adjust : bool) -> void:
auto_adjust = new_auto_adjust
if is_inside_tree() and !Engine.is_editor_hint():
_update_auto_adjust()
func _update_auto_adjust() -> void:
# Turn process on if auto adjust is true.
# Note we don't turn it off here, we want to finish fading out the vignette if needed
if auto_adjust:
set_process(true)
func set_auto_rotation_limit(new_auto_rotation_limit : float) -> void:
auto_rotation_limit = new_auto_rotation_limit
auto_rotation_limit_rad = deg_to_rad(auto_rotation_limit)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsVignette"
# Called when the node enters the scene tree for the first time.
func _ready():
if !Engine.is_editor_hint():
origin_node = XRHelpers.get_xr_origin(self)
_update_mesh()
_update_radius()
_update_fade()
_update_auto_adjust()
else:
set_process(false)
# Called on process
func _process(delta):
if Engine.is_editor_hint():
return
if !origin_node:
return
if !auto_adjust:
# set to true for next time this is enabled
auto_first = true
# We are done, turn off process
set_process(false)
return
if auto_first:
# first time we run process since starting, just record transform
last_origin_basis = origin_node.global_transform.basis
last_location = global_transform.origin
auto_first = false
return
# Get our delta transform
var delta_b = origin_node.global_transform.basis * last_origin_basis.inverse()
var delta_v = global_transform.origin - last_location
# Adjust radius based on rotation speed of our origin point (not of head movement).
# We convert our delta rotation to a quaterion.
# A quaternion represents a rotation around an angle.
var q = delta_b.get_rotation_quaternion()
# We get our angle from our w component and then adjust to get a
# rotation speed per second by dividing by delta
var angle = (2 * acos(q.w)) / delta
# Calculate what our radius should be for our rotation speed
var target_radius = 1.0
if auto_rotation_limit > 0:
target_radius = 1.0 - (
clamp(angle / auto_rotation_limit_rad, 0.0, 1.0) * (1.0 - auto_inner_radius))
# Now do the same for speed, this includes players physical speed but there
# isn't much we can do there.
if auto_velocity_limit > 0:
var velocity = delta_v.length() / delta
target_radius = min(target_radius, 1.0 - (
clamp(velocity / auto_velocity_limit, 0.0, 1.0) * (1.0 - auto_inner_radius)))
# if our radius is small then our current we apply it
if target_radius < radius:
set_radius(target_radius)
fade_delay = auto_fade_delay
elif fade_delay > 0.0:
fade_delay -= delta
else:
set_radius(clamp(radius + delta / auto_fade_out_factor, 0.0, 1.0))
last_origin_basis = origin_node.global_transform.basis
last_location = global_transform.origin
# This method verifies the vignette has a valid configuration.
# Specifically it checks the following:
# - XROrigin3D is a parent
# - XRCamera3D is our parent
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Check the origin node
if !XRHelpers.get_xr_origin(self):
warnings.append("Parent node must be in a branch from XROrigin3D")
# check camera node
var parent = get_parent()
if !parent or !parent is XRCamera3D:
warnings.append("Parent node must be an XRCamera3D")
return warnings

View File

@ -0,0 +1 @@
uid://dl5pbpmm3ps1g

View File

@ -0,0 +1,34 @@
shader_type spatial;
render_mode depth_test_disabled, skip_vertex_transform, unshaded, cull_disabled;
uniform vec4 color : source_color = vec4(0.0, 0.0, 0.0, 1.0);
uniform float radius = 1.0;
uniform float fade = 0.05;
varying float dist;
void vertex() {
vec2 v = VERTEX.xy;
dist = length(v);
// outer ring is 2.0, inner ring is 1.0, so this scales purely the inner ring
if (dist < 1.5) {
// Adjust by radius
dist = radius;
v *= dist;
// We don't know our eye center, projecting a center point in the distance gives us a good enough approximation
vec4 eye = PROJECTION_MATRIX * vec4(0.0, 0.0, 100.0, 1.0);
// and we offset our inner circle
v += eye.xy / eye.w;
}
float z = PROJECTION_MATRIX[2][2] < 0.0 ? 0.0 : 1.0;
POSITION = vec4(v, z, 1.0);
}
void fragment() {
ALBEDO = color.rgb;
ALPHA = clamp((dist - radius) / fade, 0.0, 1.0);
}

View File

@ -0,0 +1 @@
uid://dkkqr1jh2i0aq

View File

@ -0,0 +1,10 @@
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://cesiqdvdfojle"]
[ext_resource type="Shader" path="res://addons/godot-xr-tools/effects/vignette.gdshader" id="1_x02h0"]
[resource]
render_priority = 99
shader = ExtResource("1_x02h0")
shader_parameter/color = Color(0, 0, 0, 1)
shader_parameter/radius = 0.282843
shader_parameter/fade = 0.05

View File

@ -0,0 +1,28 @@
[gd_scene load_steps=4 format=3 uid="uid://cc6ngdqie8o8c"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/effects/vignette.gd" id="1"]
[ext_resource type="Material" uid="uid://cesiqdvdfojle" path="res://addons/godot-xr-tools/effects/vignette.tres" id="2_djtaj"]
[sub_resource type="ArrayMesh" id="ArrayMesh_yyajy"]
_surfaces = [{
"aabb": AABB(-2, -2, 0, 4, 4, 1e-05),
"format": 34359742465,
"index_count": 192,
"index_data": PackedByteArray(32, 0, 33, 0, 1, 0, 32, 0, 1, 0, 0, 0, 33, 0, 34, 0, 2, 0, 33, 0, 2, 0, 1, 0, 34, 0, 35, 0, 3, 0, 34, 0, 3, 0, 2, 0, 35, 0, 36, 0, 4, 0, 35, 0, 4, 0, 3, 0, 36, 0, 37, 0, 5, 0, 36, 0, 5, 0, 4, 0, 37, 0, 38, 0, 6, 0, 37, 0, 6, 0, 5, 0, 38, 0, 39, 0, 7, 0, 38, 0, 7, 0, 6, 0, 39, 0, 40, 0, 8, 0, 39, 0, 8, 0, 7, 0, 40, 0, 41, 0, 9, 0, 40, 0, 9, 0, 8, 0, 41, 0, 42, 0, 10, 0, 41, 0, 10, 0, 9, 0, 42, 0, 43, 0, 11, 0, 42, 0, 11, 0, 10, 0, 43, 0, 44, 0, 12, 0, 43, 0, 12, 0, 11, 0, 44, 0, 45, 0, 13, 0, 44, 0, 13, 0, 12, 0, 45, 0, 46, 0, 14, 0, 45, 0, 14, 0, 13, 0, 46, 0, 47, 0, 15, 0, 46, 0, 15, 0, 14, 0, 47, 0, 48, 0, 16, 0, 47, 0, 16, 0, 15, 0, 48, 0, 49, 0, 17, 0, 48, 0, 17, 0, 16, 0, 49, 0, 50, 0, 18, 0, 49, 0, 18, 0, 17, 0, 50, 0, 51, 0, 19, 0, 50, 0, 19, 0, 18, 0, 51, 0, 52, 0, 20, 0, 51, 0, 20, 0, 19, 0, 52, 0, 53, 0, 21, 0, 52, 0, 21, 0, 20, 0, 53, 0, 54, 0, 22, 0, 53, 0, 22, 0, 21, 0, 54, 0, 55, 0, 23, 0, 54, 0, 23, 0, 22, 0, 55, 0, 56, 0, 24, 0, 55, 0, 24, 0, 23, 0, 56, 0, 57, 0, 25, 0, 56, 0, 25, 0, 24, 0, 57, 0, 58, 0, 26, 0, 57, 0, 26, 0, 25, 0, 58, 0, 59, 0, 27, 0, 58, 0, 27, 0, 26, 0, 59, 0, 60, 0, 28, 0, 59, 0, 28, 0, 27, 0, 60, 0, 61, 0, 29, 0, 60, 0, 29, 0, 28, 0, 61, 0, 62, 0, 30, 0, 61, 0, 30, 0, 29, 0, 62, 0, 63, 0, 31, 0, 62, 0, 31, 0, 30, 0, 63, 0, 32, 0, 0, 0, 63, 0, 0, 0, 31, 0),
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 64,
"vertex_data": PackedByteArray(0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 190, 20, 123, 63, 194, 197, 71, 190, 0, 0, 0, 0, 94, 131, 108, 63, 22, 239, 195, 190, 0, 0, 0, 0, 49, 219, 84, 63, 218, 57, 14, 191, 0, 0, 0, 0, 243, 4, 53, 63, 243, 4, 53, 191, 0, 0, 0, 0, 218, 57, 14, 63, 49, 219, 84, 191, 0, 0, 0, 0, 21, 239, 195, 62, 94, 131, 108, 191, 0, 0, 0, 0, 196, 197, 71, 62, 190, 20, 123, 191, 0, 0, 0, 0, 46, 189, 59, 179, 0, 0, 128, 191, 0, 0, 0, 0, 194, 197, 71, 190, 190, 20, 123, 191, 0, 0, 0, 0, 20, 239, 195, 190, 95, 131, 108, 191, 0, 0, 0, 0, 217, 57, 14, 191, 50, 219, 84, 191, 0, 0, 0, 0, 243, 4, 53, 191, 243, 4, 53, 191, 0, 0, 0, 0, 50, 219, 84, 191, 217, 57, 14, 191, 0, 0, 0, 0, 94, 131, 108, 191, 23, 239, 195, 190, 0, 0, 0, 0, 191, 20, 123, 191, 193, 197, 71, 190, 0, 0, 0, 0, 0, 0, 128, 191, 46, 189, 187, 51, 0, 0, 0, 0, 191, 20, 123, 191, 189, 197, 71, 62, 0, 0, 0, 0, 94, 131, 108, 191, 21, 239, 195, 62, 0, 0, 0, 0, 48, 219, 84, 191, 219, 57, 14, 63, 0, 0, 0, 0, 244, 4, 53, 191, 242, 4, 53, 63, 0, 0, 0, 0, 221, 57, 14, 191, 47, 219, 84, 63, 0, 0, 0, 0, 26, 239, 195, 190, 94, 131, 108, 63, 0, 0, 0, 0, 198, 197, 71, 190, 190, 20, 123, 63, 0, 0, 0, 0, 46, 222, 76, 50, 0, 0, 128, 63, 0, 0, 0, 0, 200, 197, 71, 62, 190, 20, 123, 63, 0, 0, 0, 0, 27, 239, 195, 62, 93, 131, 108, 63, 0, 0, 0, 0, 215, 57, 14, 63, 51, 219, 84, 63, 0, 0, 0, 0, 242, 4, 53, 63, 245, 4, 53, 63, 0, 0, 0, 0, 49, 219, 84, 63, 219, 57, 14, 63, 0, 0, 0, 0, 95, 131, 108, 63, 21, 239, 195, 62, 0, 0, 0, 0, 191, 20, 123, 63, 188, 197, 71, 62, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 190, 20, 251, 63, 194, 197, 199, 190, 0, 0, 0, 0, 94, 131, 236, 63, 22, 239, 67, 191, 0, 0, 0, 0, 49, 219, 212, 63, 218, 57, 142, 191, 0, 0, 0, 0, 243, 4, 181, 63, 243, 4, 181, 191, 0, 0, 0, 0, 218, 57, 142, 63, 49, 219, 212, 191, 0, 0, 0, 0, 21, 239, 67, 63, 94, 131, 236, 191, 0, 0, 0, 0, 196, 197, 199, 62, 190, 20, 251, 191, 0, 0, 0, 0, 46, 189, 187, 179, 0, 0, 0, 192, 0, 0, 0, 0, 194, 197, 199, 190, 190, 20, 251, 191, 0, 0, 0, 0, 20, 239, 67, 191, 95, 131, 236, 191, 0, 0, 0, 0, 217, 57, 142, 191, 50, 219, 212, 191, 0, 0, 0, 0, 243, 4, 181, 191, 243, 4, 181, 191, 0, 0, 0, 0, 50, 219, 212, 191, 217, 57, 142, 191, 0, 0, 0, 0, 94, 131, 236, 191, 23, 239, 67, 191, 0, 0, 0, 0, 191, 20, 251, 191, 193, 197, 199, 190, 0, 0, 0, 0, 0, 0, 0, 192, 46, 189, 59, 52, 0, 0, 0, 0, 191, 20, 251, 191, 189, 197, 199, 62, 0, 0, 0, 0, 94, 131, 236, 191, 21, 239, 67, 63, 0, 0, 0, 0, 48, 219, 212, 191, 219, 57, 142, 63, 0, 0, 0, 0, 244, 4, 181, 191, 242, 4, 181, 63, 0, 0, 0, 0, 221, 57, 142, 191, 47, 219, 212, 63, 0, 0, 0, 0, 26, 239, 67, 191, 94, 131, 236, 63, 0, 0, 0, 0, 198, 197, 199, 190, 190, 20, 251, 63, 0, 0, 0, 0, 46, 222, 204, 50, 0, 0, 0, 64, 0, 0, 0, 0, 200, 197, 199, 62, 190, 20, 251, 63, 0, 0, 0, 0, 27, 239, 67, 63, 93, 131, 236, 63, 0, 0, 0, 0, 215, 57, 142, 63, 51, 219, 212, 63, 0, 0, 0, 0, 242, 4, 181, 63, 245, 4, 181, 63, 0, 0, 0, 0, 49, 219, 212, 63, 219, 57, 142, 63, 0, 0, 0, 0, 95, 131, 236, 63, 21, 239, 67, 63, 0, 0, 0, 0, 191, 20, 251, 63, 188, 197, 199, 62, 0, 0, 0, 0)
}]
custom_aabb = AABB(-1, -1, -1, 1, 1, 1)
[node name="Vignette" type="Node3D"]
script = ExtResource("1")
[node name="Mesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1)
visible = false
cast_shadow = 0
ignore_occlusion_culling = true
mesh = SubResource("ArrayMesh_yyajy")
surface_material_override/0 = ExtResource("2_djtaj")

View File

@ -0,0 +1,134 @@
class_name XRToolsPointerEvent
## Types of pointer events
enum Type {
## Pointer entered target
ENTERED,
## Pointer exited target
EXITED,
## Pointer pressed target
PRESSED,
## Pointer released target
RELEASED,
## Pointer moved on target
MOVED
}
## Type of pointer event
var event_type : Type
## Pointer generating event
var pointer : Node3D
## Target of pointer
var target : Node3D
## Point position
var position : Vector3
## Last point position
var last_position : Vector3
## Initialize a new instance of the XRToolsPointerEvent class
func _init(
p_event_type : Type,
p_pointer : Node3D,
p_target : Node3D,
p_position : Vector3,
p_last_position : Vector3) -> void:
event_type = p_event_type
pointer = p_pointer
target = p_target
position = p_position
last_position = p_last_position
## Report a pointer entered event
static func entered(
pointer : Node3D,
target : Node3D,
at : Vector3) -> void:
report(
XRToolsPointerEvent.new(
Type.ENTERED,
pointer,
target,
at,
at))
## Report pointer moved event
static func moved(
pointer : Node3D,
target : Node3D,
to : Vector3,
from : Vector3) -> void:
report(
XRToolsPointerEvent.new(
Type.MOVED,
pointer,
target,
to,
from))
## Report pointer pressed event
static func pressed(
pointer : Node3D,
target : Node3D,
at : Vector3) -> void:
report(
XRToolsPointerEvent.new(
Type.PRESSED,
pointer,
target,
at,
at))
## Report pointer released event
static func released(
pointer : Node3D,
target : Node3D,
at : Vector3) -> void:
report(
XRToolsPointerEvent.new(
Type.RELEASED,
pointer,
target,
at,
at))
## Report a pointer exited event
static func exited(
pointer : Node3D,
target : Node3D,
last : Vector3) -> void:
report(
XRToolsPointerEvent.new(
Type.EXITED,
pointer,
target,
last,
last))
## Report a pointer event
static func report(event : XRToolsPointerEvent) -> void:
# Fire event on pointer
if is_instance_valid(event.pointer):
if event.pointer.has_signal("pointing_event"):
event.pointer.emit_signal("pointing_event", event)
# Fire event/method on the target if it's valid
if is_instance_valid(event.target):
if event.target.has_signal("pointer_event"):
event.target.emit_signal("pointer_event", event)
elif event.target.has_method("pointer_event"):
event.target.pointer_event(event)

View File

@ -0,0 +1 @@
uid://cm3hi0yeo7acb

View File

@ -0,0 +1,92 @@
@tool
class_name XRToolsFallDamage
extends XRToolsMovementProvider
## XR Tools Example Fall Damage Detector
##
## This example script detects the player falling to the ground and
## optionally hitting walls.
##
## It works by tracking the player body velocity to detect velocity
## changes (acceleration) exceeding a threshold.
##
## This doesn't use the usual Acceleration = dV / dT as it doesn't appear
## to work too well considering the "instantaneous" nature of the
## collision. Additionally all it would end up doing is multiplying the
## change in velocity by the physics-frame-rate making it sensitive to
## varying physics timing.
##
## Instead the threshold in terms of delta-velocity makes it easy to work
## out natural values. For example if the player falls under regular gravity
## (9.81 meters per second^2 for 1 second) then hits the ground, they will have fallen around
## 4.9 meters, and will then encounter an instantaneous velocity-change of
## 9.81 meters per second.
##
## This file can handle simple demonstrations, but games will most likely
## want to modify it, for example to ignore damage on certain surfaces.
## Signal invoked when the player takes fall damage
signal player_fall_damage(damage)
## Movement provider order
@export var order : int = 1000
## Ignore damage if player is launched up
@export var ignore_launch : bool = true
## Only take damage on ground
@export var ground_only : bool = false
## Acceleration limit
@export var damage_threshold : float = 8.0
## Previous velocity
var _previous_velocity : Vector3 = Vector3.ZERO
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsFallDamage"
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Set as always active
is_active = true
func physics_movement(_delta: float, player_body: XRToolsPlayerBody, disabled: bool):
# Skip if not enabled
if disabled or !enabled:
_previous_velocity = player_body.velocity
return
# Calculate the instantaneous acceleration
var accel_vec := player_body.velocity - _previous_velocity
_previous_velocity = player_body.velocity
# Ignore launching the player
if ignore_launch:
# Forgive "up" acceleration equal to our "up" speed
var forgive : float = max(0, min(accel_vec.y, player_body.velocity.y))
accel_vec.y -= forgive
# Handle ground-only collisions
if ground_only:
# Ignore if not on ground
if not player_body.on_ground:
return
# Only consider vertical acceleration
accel_vec *= Vector3.UP
# Detect fall damage
var accel := accel_vec.length()
if accel > damage_threshold:
emit_signal("player_fall_damage", accel)

View File

@ -0,0 +1 @@
uid://bu074gqcugr16

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://d2yejwiwab3wv"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/examples/fall_damage.gd" id="1"]
[node name="FallDamage" type="Node" groups=["movement_providers"]]
script = ExtResource("1")

View File

@ -0,0 +1,555 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/function.svg")
class_name XRToolsFunctionGazePointer
extends Node3D
## XR Tools Function Gaze Pointer Script
##
## This script implements a pointer function for a players camera. Pointer
## events (entered, exited, pressed, release, and movement) are delivered by
## invoking signals on the target node.
##
## Pointer target nodes commonly extend from [XRToolsInteractableArea] or
## [XRToolsInteractableBody].
## Signal emitted when this object points at another object
signal pointing_event(event)
## Enumeration of laser show modes
enum LaserShow {
HIDE = 0, ## Hide laser
SHOW = 1, ## Show laser
COLLIDE = 2, ## Only show laser on collision
}
## Enumeration of laser length modes
enum LaserLength {
FULL = 0, ## Full length
COLLIDE = 1 ## Draw to collision
}
## Default pointer collision mask of 21:pointable and 23:ui-objects
const DEFAULT_MASK := 0b0000_0000_0101_0000_0000_0000_0000_0000
## Default pointer collision mask of 23:ui-objects
const SUPPRESS_MASK := 0b0000_0000_0100_0000_0000_0000_0000_0000
@export_group("General")
## Pointer enabled
@export var enabled : bool = true: set = set_enabled
## Y Offset for pointer
@export var y_offset : float = -0.013: set = set_y_offset
## Pointer distance
@export var distance : float = 10: set = set_distance
## Active button action
@export var active_button_action : String = "trigger_click"
@export_group("Laser")
## Controls when the laser is visible
@export var show_laser : LaserShow = LaserShow.SHOW: set = set_show_laser
## Controls the length of the laser
@export var laser_length : LaserLength = LaserLength.FULL: set = set_laser_length
## Laser pointer material
@export var laser_material : StandardMaterial3D = null : set = set_laser_material
## Laser pointer material when hitting target
@export var laser_hit_material : StandardMaterial3D = null : set = set_laser_hit_material
@export_group("Target")
## If true, the pointer target is shown
@export var show_target : bool = false: set = set_show_target
## Controls the target radius
@export var target_radius : float = 0.05: set = set_target_radius
## Target material
@export var target_material : StandardMaterial3D = null : set = set_target_material
@export_group("Collision")
## Pointer collision mask
@export_flags_3d_physics var collision_mask : int = DEFAULT_MASK: set = set_collision_mask
## Enable pointer collision with bodies
@export var collide_with_bodies : bool = true: set = set_collide_with_bodies
## Enable pointer collision with areas
@export var collide_with_areas : bool = false: set = set_collide_with_areas
@export_group("Suppression")
## Suppress radius
@export var suppress_radius : float = 0.2: set = set_suppress_radius
## Suppress mask
@export_flags_3d_physics var suppress_mask : int = SUPPRESS_MASK: set = set_suppress_mask
@export_group("Gaze Pointer")
## send clicks on hold or just move the mouse
@export var click_on_hold : bool = false
## time on hold to release a click
@export var hold_time : float = 2.0
## Color our our visualisation
@export var color : Color = Color(1.0, 1.0, 1.0, 1.0): set = set_color
## Size of the pointer end
@export var size : Vector2 = Vector2(0.3, 0.3): set = set_size
## held time counter
var time_held = 0.0
## hold to click cursor material
var material : ShaderMaterial
## bool for release click
var gaze_pressed = false
## Current target node
var target : Node3D = null
## Last target node
var last_target : Node3D = null
## Last collision point
var last_collided_at : Vector3 = Vector3.ZERO
# World scale
var _world_scale : float = 1.0
# The current camera
var _camera_parent : XRCamera3D
## Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsFunctionGazePointer"
# Called when the node enters the scene tree for the first time.
func _ready():
# Do not initialise if in the editor
if Engine.is_editor_hint():
return
# Read the initial world-scale
_world_scale = XRServer.world_scale
_camera_parent = get_parent() as XRCamera3D
material = $Visualise.get_surface_override_material(0)
if !Engine.is_editor_hint():
_set_time_held(0.0)
_update_size()
_update_color()
# init our state
_update_y_offset()
_update_distance()
_update_pointer()
_update_target_radius()
_update_target_material()
_update_collision_mask()
_update_collide_with_bodies()
_update_collide_with_areas()
_update_suppress_radius()
_update_suppress_mask()
# Called on each frame to update the pickup
func _process(delta):
# Do not process if in the editor
if Engine.is_editor_hint() or !is_inside_tree():
return
# Handle world-scale changes
var new_world_scale := XRServer.world_scale
if (_world_scale != new_world_scale):
_world_scale = new_world_scale
_update_y_offset()
# Find the new pointer target
var new_target : Node3D
var new_at : Vector3
var suppress_area := $SuppressArea
if (enabled and
not $SuppressArea.has_overlapping_bodies() and
not $SuppressArea.has_overlapping_areas() and
$RayCast.is_colliding()):
new_at = $RayCast.get_collision_point()
if target:
# Locked to 'target' even if we're colliding with something else
new_target = target
else:
# Target is whatever the raycast is colliding with
new_target = $RayCast.get_collider()
# hide gaze pointer when pressed
if gaze_pressed:
show_target = false
else:
show_target = true
# If no current or previous collisions then skip
if not new_target and not last_target:
return
# Handle pointer changes
if new_target and not last_target:
# Pointer entered new_target
XRToolsPointerEvent.entered(self, new_target, new_at)
# Pointer moved on new_target for the first time
XRToolsPointerEvent.moved(self, new_target, new_at, new_at)
if click_on_hold and !gaze_pressed:
_set_time_held(time_held + delta)
if time_held > hold_time:
_button_pressed()
# Update visible artifacts for hit
_visible_hit(new_at)
elif not new_target and last_target:
# Pointer exited last_target
XRToolsPointerEvent.exited(self, last_target, last_collided_at)
if click_on_hold:
_set_time_held(0.0)
gaze_pressed = false
# Update visible artifacts for miss
_visible_miss()
elif new_target != last_target:
# Pointer exited last_target
XRToolsPointerEvent.exited(self, last_target, last_collided_at)
if click_on_hold:
_set_time_held(0.0)
gaze_pressed = false
# Pointer entered new_target
XRToolsPointerEvent.entered(self, new_target, new_at)
# Pointer moved on new_target
XRToolsPointerEvent.moved(self, new_target, new_at, new_at)
if click_on_hold and !gaze_pressed:
_set_time_held(time_held + delta)
if time_held > hold_time:
_button_pressed()
# Move visible artifacts
_visible_move(new_at)
elif new_at != last_collided_at:
# Pointer moved on new_target
XRToolsPointerEvent.moved(self, new_target, new_at, last_collided_at)
if click_on_hold and !gaze_pressed:
_set_time_held(time_held + delta)
if time_held > hold_time:
_button_pressed()
# Move visible artifacts
_visible_move(new_at)
# Update last values
last_target = new_target
last_collided_at = new_at
# Set pointer enabled property
func set_enabled(p_enabled : bool) -> void:
enabled = p_enabled
if is_inside_tree():
_update_pointer()
# Set pointer y_offset property
func set_y_offset(p_offset : float) -> void:
y_offset = p_offset
if is_inside_tree():
_update_y_offset()
# Set pointer distance property
func set_distance(p_new_value : float) -> void:
distance = p_new_value
if is_inside_tree():
_update_distance()
# Set pointer show_laser property
func set_show_laser(p_show : LaserShow) -> void:
show_laser = p_show
if is_inside_tree():
_update_pointer()
# Set pointer laser_length property
func set_laser_length(p_laser_length : LaserLength) -> void:
laser_length = p_laser_length
if is_inside_tree():
_update_pointer()
# Set pointer laser_material property
func set_laser_material(p_laser_material : StandardMaterial3D) -> void:
laser_material = p_laser_material
if is_inside_tree():
_update_pointer()
# Set pointer laser_hit_material property
func set_laser_hit_material(p_laser_hit_material : StandardMaterial3D) -> void:
laser_hit_material = p_laser_hit_material
if is_inside_tree():
_update_pointer()
# Set pointer show_target property
func set_show_target(p_show_target : bool) -> void:
show_target = p_show_target
if is_inside_tree():
$Target.visible = enabled and show_target and last_target
# Set pointer target_radius property
func set_target_radius(p_target_radius : float) -> void:
target_radius = p_target_radius
if is_inside_tree():
_update_target_radius()
# Set pointer target_material property
func set_target_material(p_target_material : StandardMaterial3D) -> void:
target_material = p_target_material
if is_inside_tree():
_update_target_material()
# Set pointer collision_mask property
func set_collision_mask(p_new_mask : int) -> void:
collision_mask = p_new_mask
if is_inside_tree():
_update_collision_mask()
# Set pointer collide_with_bodies property
func set_collide_with_bodies(p_new_value : bool) -> void:
collide_with_bodies = p_new_value
if is_inside_tree():
_update_collide_with_bodies()
# Set pointer collide_with_areas property
func set_collide_with_areas(p_new_value : bool) -> void:
collide_with_areas = p_new_value
if is_inside_tree():
_update_collide_with_areas()
# Set suppress radius property
func set_suppress_radius(p_suppress_radius : float) -> void:
suppress_radius = p_suppress_radius
if is_inside_tree():
_update_suppress_radius()
func set_suppress_mask(p_suppress_mask : int) -> void:
suppress_mask = p_suppress_mask
if is_inside_tree():
_update_suppress_mask()
# Pointer Y offset update handler
func _update_y_offset() -> void:
$Laser.position.y = y_offset * _world_scale
$RayCast.position.y = y_offset * _world_scale
# Pointer distance update handler
func _update_distance() -> void:
$RayCast.target_position.z = -distance
_update_pointer()
# Pointer target radius update handler
func _update_target_radius() -> void:
$Target.mesh.radius = target_radius
$Target.mesh.height = target_radius * 2
# Pointer target_material update handler
func _update_target_material() -> void:
$Target.set_surface_override_material(0, target_material)
# Pointer collision_mask update handler
func _update_collision_mask() -> void:
$RayCast.collision_mask = collision_mask
# Pointer collide_with_bodies update handler
func _update_collide_with_bodies() -> void:
$RayCast.collide_with_bodies = collide_with_bodies
# Pointer collide_with_areas update handler
func _update_collide_with_areas() -> void:
$RayCast.collide_with_areas = collide_with_areas
# Pointer suppress_radius update handler
func _update_suppress_radius() -> void:
$SuppressArea/CollisionShape3D.shape.radius = suppress_radius
# Pointer suppress_mask update handler
func _update_suppress_mask() -> void:
$SuppressArea.collision_mask = suppress_mask
# Pointer visible artifacts update handler
func _update_pointer() -> void:
if enabled and last_target:
_visible_hit(last_collided_at)
else:
_visible_miss()
# Pointer-activation button pressed handler
func _button_pressed() -> void:
if $RayCast.is_colliding():
# Report pressed
target = $RayCast.get_collider()
last_collided_at = $RayCast.get_collision_point()
XRToolsPointerEvent.pressed(self, target, last_collided_at)
if click_on_hold:
_set_time_held(0.0)
gaze_pressed = true
XRToolsPointerEvent.released(self, last_target, last_collided_at)
target = null
_set_time_held(0.0)
# Pointer-activation button released handler
func _button_released() -> void:
if target:
# Report release
XRToolsPointerEvent.released(self, target, last_collided_at)
target = null
last_collided_at = Vector3(0, 0, 0)
# Update the laser active material
func _update_laser_active_material(hit : bool) -> void:
if hit and laser_hit_material:
$Laser.set_surface_override_material(0, laser_hit_material)
else:
$Laser.set_surface_override_material(0, laser_material)
# Update the visible artifacts to show a hit
func _visible_hit(at : Vector3) -> void:
# Show target if enabled
if show_target:
$Target.global_transform.origin = at
$Target.visible = true
# Control laser visibility
if show_laser != LaserShow.HIDE:
# Ensure the correct laser material is set
_update_laser_active_material(true)
# Adjust laser length
if laser_length == LaserLength.COLLIDE:
var collide_len : float = at.distance_to(global_transform.origin)
$Laser.mesh.size.z = collide_len
$Laser.position.z = collide_len * -0.5
else:
$Laser.mesh.size.z = distance
$Laser.position.z = distance * -0.5
# Show laser
$Laser.visible = true
else:
# Ensure laser is hidden
$Laser.visible = false
# Move the visible pointer artifacts to the target
func _visible_move(at : Vector3) -> void:
# Move target if configured
if show_target:
$Target.global_transform.origin = at
# Adjust laser length if set to collide-length
if laser_length == LaserLength.COLLIDE:
var collide_len : float = at.distance_to(global_transform.origin)
$Laser.mesh.size.z = collide_len
$Laser.position.z = collide_len * -0.5
$Visualise.global_transform.origin = at
# Update the visible artifacts to show a miss
func _visible_miss() -> void:
# Ensure target is hidden
$Target.visible = false
# Ensure the correct laser material is set
_update_laser_active_material(false)
# Hide laser if not set to show always
$Laser.visible = show_laser == LaserShow.SHOW
# Restore laser length if set to collide-length
$Laser.mesh.size.z = distance
$Laser.position.z = distance * -0.5
#set gaze pointer visualization
func _set_time_held(p_time_held):
time_held = p_time_held
if material:
$Visualise.visible = time_held > 0.0
material.set_shader_parameter("value", time_held/hold_time)
# set gaze pointer size
func set_size(p_size: Vector2):
size = p_size
_update_size()
# update gaze pointer size
func _update_size():
if material: # Note, material won't be set until after we setup our scene
var mesh : QuadMesh = $Visualise.mesh
if mesh.size != size:
mesh.size = size
# updating the size will unset our material, so reset it
$Visualise.set_surface_override_material(0, material)
#set gaze_pointer_color
func set_color(p_color: Color):
color = p_color
_update_color()
#update gaze pointer color
func _update_color():
if material:
material.set_shader_parameter("albedo", color)

View File

@ -0,0 +1 @@
uid://dltkxrqk7i3cs

View File

@ -0,0 +1,66 @@
[gd_scene load_steps=9 format=3 uid="uid://do1wif8rpqtwj"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/function_gaze_pointer.gd" id="1_ipkdr"]
[ext_resource type="Material" path="res://addons/godot-xr-tools/materials/pointer.tres" id="2_ndm62"]
[ext_resource type="Shader" uid="uid://dncfip67nl2sf" path="res://addons/godot-xr-tools/misc/hold_button_gaze_pointer_visualshader.tres" id="3_1p5pd"]
[sub_resource type="BoxMesh" id="1"]
resource_local_to_scene = true
material = ExtResource("2_ndm62")
size = Vector3(0.002, 0.002, 10)
subdivide_depth = 20
[sub_resource type="SphereMesh" id="2"]
material = ExtResource("2_ndm62")
radius = 0.05
height = 0.1
radial_segments = 16
rings = 8
[sub_resource type="SphereShape3D" id="SphereShape3D_k3gfm"]
radius = 0.2
[sub_resource type="QuadMesh" id="QuadMesh_lulcv"]
resource_local_to_scene = true
[sub_resource type="ShaderMaterial" id="ShaderMaterial_ico2c"]
render_priority = -100
shader = ExtResource("3_1p5pd")
shader_parameter/albedo = Color(1, 1, 1, 1)
shader_parameter/value = 0.2
shader_parameter/fade = 0.05
shader_parameter/radius = 0.8
shader_parameter/width = 0.2
[node name="FunctionGazePointer" type="Node3D"]
script = ExtResource("1_ipkdr")
show_laser = 0
show_target = true
[node name="RayCast" type="RayCast3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.013, 0)
target_position = Vector3(0, 0, -10)
collision_mask = 5242880
[node name="Laser" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.013, -5)
visible = false
cast_shadow = 0
mesh = SubResource("1")
[node name="Target" type="MeshInstance3D" parent="."]
visible = false
mesh = SubResource("2")
[node name="SuppressArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 4194304
[node name="CollisionShape3D" type="CollisionShape3D" parent="SuppressArea"]
shape = SubResource("SphereShape3D_k3gfm")
[node name="Visualise" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1)
cast_shadow = 0
mesh = SubResource("QuadMesh_lulcv")
surface_material_override/0 = SubResource("ShaderMaterial_ico2c")

View File

@ -0,0 +1,502 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/function.svg")
class_name XRToolsFunctionPickup
extends Node3D
## XR Tools Function Pickup Script
##
## This script implements picking up of objects. Most pickable
## objects are instances of the [XRToolsPickable] class.
##
## Additionally this script can work in conjunction with the
## [XRToolsMovementProvider] class support climbing. Most climbable objects are
## instances of the [XRToolsClimbable] class.
## Signal emitted when the pickup picks something up
signal has_picked_up(what)
## Signal emitted when the pickup drops something
signal has_dropped
# Default pickup collision mask of 3:pickable and 19:handle
const DEFAULT_GRAB_MASK := 0b0000_0000_0000_0100_0000_0000_0000_0100
# Default pickup collision mask of 3:pickable
const DEFAULT_RANGE_MASK := 0b0000_0000_0000_0000_0000_0000_0000_0100
# Constant for worst-case grab distance
const MAX_GRAB_DISTANCE2: float = 1000000.0
# Class for storing copied collision data
class CopiedCollision extends RefCounted:
var collision_shape : CollisionShape3D
var org_transform : Transform3D
## Pickup enabled property
@export var enabled : bool = true
## Grip controller axis
@export var pickup_axis_action : String = "grip"
## Action controller button
@export var action_button_action : String = "trigger_click"
## Grab distance
@export var grab_distance : float = 0.3: set = _set_grab_distance
## Grab collision mask
@export_flags_3d_physics \
var grab_collision_mask : int = DEFAULT_GRAB_MASK: set = _set_grab_collision_mask
## If true, ranged-grabbing is enabled
@export var ranged_enable : bool = true
## Ranged-grab distance
@export var ranged_distance : float = 5.0: set = _set_ranged_distance
## Ranged-grab angle
@export_range(0.0, 45.0) var ranged_angle : float = 5.0: set = _set_ranged_angle
## Ranged-grab collision mask
@export_flags_3d_physics \
var ranged_collision_mask : int = DEFAULT_RANGE_MASK: set = _set_ranged_collision_mask
## Throw impulse factor
@export var impulse_factor : float = 1.0
## Throw velocity averaging
@export var velocity_samples: int = 5
# Public fields
var closest_object : Node3D = null
var picked_up_object : Node3D = null
var picked_up_ranged : bool = false
var grip_pressed : bool = false
# Private fields
var _object_in_grab_area := Array()
var _object_in_ranged_area := Array()
var _velocity_averager := XRToolsVelocityAverager.new(velocity_samples)
var _grab_area : Area3D
var _grab_collision : CollisionShape3D
var _ranged_area : Area3D
var _ranged_collision : CollisionShape3D
var _active_copied_collisions : Array[CopiedCollision]
## Controller
@onready var _controller := XRHelpers.get_xr_controller(self)
## Collision hand (if applicable)
@onready var _collision_hand : XRToolsCollisionHand = XRToolsCollisionHand.find_ancestor(self)
## Grip threshold (from configuration)
@onready var _grip_threshold : float = XRTools.get_grip_threshold()
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsFunctionPickup"
# Called when the node enters the scene tree for the first time.
func _ready():
# Skip creating grab-helpers if in the editor
if Engine.is_editor_hint():
return
# Create the grab collision shape
_grab_collision = CollisionShape3D.new()
_grab_collision.set_name("GrabCollisionShape")
_grab_collision.shape = SphereShape3D.new()
_grab_collision.shape.radius = grab_distance
# Create the grab area
_grab_area = Area3D.new()
_grab_area.set_name("GrabArea")
_grab_area.collision_layer = 0
_grab_area.collision_mask = grab_collision_mask
_grab_area.add_child(_grab_collision)
_grab_area.area_entered.connect(_on_grab_entered)
_grab_area.body_entered.connect(_on_grab_entered)
_grab_area.area_exited.connect(_on_grab_exited)
_grab_area.body_exited.connect(_on_grab_exited)
add_child(_grab_area)
# Create the ranged collision shape
_ranged_collision = CollisionShape3D.new()
_ranged_collision.set_name("RangedCollisionShape")
_ranged_collision.shape = CylinderShape3D.new()
_ranged_collision.transform.basis = Basis(Vector3.RIGHT, PI/2)
# Create the ranged area
_ranged_area = Area3D.new()
_ranged_area.set_name("RangedArea")
_ranged_area.collision_layer = 0
_ranged_area.collision_mask = ranged_collision_mask
_ranged_area.add_child(_ranged_collision)
_ranged_area.area_entered.connect(_on_ranged_entered)
_ranged_area.body_entered.connect(_on_ranged_entered)
_ranged_area.area_exited.connect(_on_ranged_exited)
_ranged_area.body_exited.connect(_on_ranged_exited)
add_child(_ranged_area)
# Update the colliders
_update_colliders()
# Monitor Grab Button
_controller.connect("button_pressed", _on_button_pressed)
_controller.connect("button_released", _on_button_released)
# Called on each frame to update the pickup
func _process(delta):
# Do not process if in the editor
if Engine.is_editor_hint():
return
# Skip if disabled, or the controller isn't active
if !enabled or !_controller.get_is_active():
return
# Handle our grip
var grip_value = _controller.get_float(pickup_axis_action)
if (grip_pressed and grip_value < (_grip_threshold - 0.1)):
grip_pressed = false
_on_grip_release()
elif (!grip_pressed and grip_value > (_grip_threshold + 0.1)):
grip_pressed = true
_on_grip_pressed()
# Calculate average velocity
if is_instance_valid(picked_up_object) and picked_up_object.is_picked_up():
# Average velocity of picked up object
_velocity_averager.add_transform(delta, picked_up_object.global_transform)
else:
# Average velocity of this pickup
_velocity_averager.add_transform(delta, global_transform)
_update_copied_collisions()
_update_closest_object()
## Find an [XRToolsFunctionPickup] node.
##
## This function searches from the specified node for an [XRToolsFunctionPickup]
## assuming the node is a sibling of the pickup under an [XRController3D].
static func find_instance(node : Node) -> XRToolsFunctionPickup:
return XRTools.find_xr_child(
XRHelpers.get_xr_controller(node),
"*",
"XRToolsFunctionPickup") as XRToolsFunctionPickup
## Find the left [XRToolsFunctionPickup] node.
##
## This function searches from the specified node for the left controller
## [XRToolsFunctionPickup] assuming the node is a sibling of the [XOrigin3D].
static func find_left(node : Node) -> XRToolsFunctionPickup:
return XRTools.find_xr_child(
XRHelpers.get_left_controller(node),
"*",
"XRToolsFunctionPickup") as XRToolsFunctionPickup
## Find the right [XRToolsFunctionPickup] node.
##
## This function searches from the specified node for the right controller
## [XRToolsFunctionPickup] assuming the node is a sibling of the [XROrigin3D].
static func find_right(node : Node) -> XRToolsFunctionPickup:
return XRTools.find_xr_child(
XRHelpers.get_right_controller(node),
"*",
"XRToolsFunctionPickup") as XRToolsFunctionPickup
## Get the [XRController3D] driving this pickup.
func get_controller() -> XRController3D:
return _controller
# Called when the grab distance has been modified
func _set_grab_distance(new_value: float) -> void:
grab_distance = new_value
if is_inside_tree():
_update_colliders()
# Called when the grab collision mask has been modified
func _set_grab_collision_mask(new_value: int) -> void:
grab_collision_mask = new_value
if is_inside_tree() and _grab_area:
_grab_area.collision_mask = new_value
# Called when the ranged-grab distance has been modified
func _set_ranged_distance(new_value: float) -> void:
ranged_distance = new_value
if is_inside_tree():
_update_colliders()
# Called when the ranged-grab angle has been modified
func _set_ranged_angle(new_value: float) -> void:
ranged_angle = new_value
if is_inside_tree():
_update_colliders()
# Called when the ranged-grab collision mask has been modified
func _set_ranged_collision_mask(new_value: int) -> void:
ranged_collision_mask = new_value
if is_inside_tree() and _ranged_collision:
_ranged_collision.collision_mask = new_value
# Update the colliders geometry
func _update_colliders() -> void:
# Update the grab sphere
if _grab_collision:
_grab_collision.shape.radius = grab_distance
# Update the ranged-grab cylinder
if _ranged_collision:
_ranged_collision.shape.radius = tan(deg_to_rad(ranged_angle)) * ranged_distance
_ranged_collision.shape.height = ranged_distance
_ranged_collision.transform.origin.z = -ranged_distance * 0.5
# Called when an object enters the grab sphere
func _on_grab_entered(target: Node3D) -> void:
# reject objects which don't support picking up
if not target.has_method('pick_up'):
return
# ignore objects already known
if _object_in_grab_area.find(target) >= 0:
return
# Add to the list of objects in grab area
_object_in_grab_area.push_back(target)
# Called when an object enters the ranged-grab cylinder
func _on_ranged_entered(target: Node3D) -> void:
# reject objects which don't support picking up rangedly
if not 'can_ranged_grab' in target or not target.can_ranged_grab:
return
# ignore objects already known
if _object_in_ranged_area.find(target) >= 0:
return
# Add to the list of objects in grab area
_object_in_ranged_area.push_back(target)
# Called when an object exits the grab sphere
func _on_grab_exited(target: Node3D) -> void:
_object_in_grab_area.erase(target)
# Called when an object exits the ranged-grab cylinder
func _on_ranged_exited(target: Node3D) -> void:
_object_in_ranged_area.erase(target)
# Update the closest object field with the best choice of grab
func _update_closest_object() -> void:
# Find the closest object we can pickup
var new_closest_obj: Node3D = null
if not picked_up_object:
# Find the closest in grab area
new_closest_obj = _get_closest_grab()
if not new_closest_obj and ranged_enable:
# Find closest in ranged area
new_closest_obj = _get_closest_ranged()
# Skip if no change
if closest_object == new_closest_obj:
return
# remove highlight on old object
if is_instance_valid(closest_object):
closest_object.request_highlight(self, false)
# add highlight to new object
closest_object = new_closest_obj
if is_instance_valid(closest_object):
closest_object.request_highlight(self, true)
# Find the pickable object closest to our hand's grab location
func _get_closest_grab() -> Node3D:
var new_closest_obj: Node3D = null
var new_closest_distance := MAX_GRAB_DISTANCE2
for o in _object_in_grab_area:
# skip objects that can not be picked up
if not o.can_pick_up(self):
continue
# Save if this object is closer than the current best
var distance_squared := global_transform.origin.distance_squared_to(
o.global_transform.origin)
if distance_squared < new_closest_distance:
new_closest_obj = o
new_closest_distance = distance_squared
# Return best object
return new_closest_obj
# Find the rangedly-pickable object closest to our hand's pointing direction
func _get_closest_ranged() -> Node3D:
var new_closest_obj: Node3D = null
var new_closest_angle_dp := cos(deg_to_rad(ranged_angle))
var hand_forwards := -global_transform.basis.z
for o in _object_in_ranged_area:
# skip objects that can not be picked up
if not o.can_pick_up(self):
continue
# Save if this object is closer than the current best
var object_direction: Vector3 = o.global_transform.origin - global_transform.origin
object_direction = object_direction.normalized()
var angle_dp := hand_forwards.dot(object_direction)
if angle_dp > new_closest_angle_dp:
new_closest_obj = o
new_closest_angle_dp = angle_dp
# Return best object
return new_closest_obj
## Drop the currently held object
func drop_object() -> void:
if not is_instance_valid(picked_up_object):
return
# Remove any copied collision objects
_remove_copied_collisions()
# let go of this object
picked_up_object.let_go(
self,
_velocity_averager.linear_velocity() * impulse_factor,
_velocity_averager.angular_velocity())
picked_up_object = null
if _collision_hand:
# Reset the held weight
_collision_hand.set_held_weight(0.0)
emit_signal("has_dropped")
func _pick_up_object(target: Node3D) -> void:
# check if already holding an object
if is_instance_valid(picked_up_object):
# skip if holding the target object
if picked_up_object == target:
return
# holding something else? drop it
drop_object()
# skip if target null or freed
if not is_instance_valid(target):
return
# Handle snap-zone
var snap := target as XRToolsSnapZone
if snap:
target = snap.picked_up_object
snap.drop_object()
# Pick up our target. Note, target may do instant drop_and_free
picked_up_ranged = not _object_in_grab_area.has(target)
picked_up_object = target
target.pick_up(self)
# If object picked up then emit signal
if is_instance_valid(picked_up_object):
_copy_collisions()
picked_up_object.request_highlight(self, false)
emit_signal("has_picked_up", picked_up_object)
# Copy collision shapes on the held object to our collision hand (if applicable).
# If we're two handing an object, both collision hands will get copies.
func _copy_collisions():
if not is_instance_valid(_collision_hand):
return
if not is_instance_valid(picked_up_object) or not picked_up_object is RigidBody3D:
return
for child in picked_up_object.get_children():
if child is CollisionShape3D and not child.disabled:
var copied_collision : CopiedCollision = CopiedCollision.new()
copied_collision.collision_shape = CollisionShape3D.new()
copied_collision.collision_shape.shape = child.shape
copied_collision.org_transform = child.transform
_collision_hand.add_child(copied_collision.collision_shape, false, Node.INTERNAL_MODE_BACK)
copied_collision.collision_shape.global_transform = picked_up_object.global_transform * \
copied_collision.org_transform
_active_copied_collisions.push_back(copied_collision)
# Adjust positions of our collisions to match actual location of object
func _update_copied_collisions():
if is_instance_valid(_collision_hand) and is_instance_valid(picked_up_object):
for copied_collision : CopiedCollision in _active_copied_collisions:
if is_instance_valid(copied_collision.collision_shape):
copied_collision.collision_shape.global_transform = picked_up_object.global_transform * \
copied_collision.org_transform
# Remove copied collision shapes
func _remove_copied_collisions():
if is_instance_valid(_collision_hand):
for copied_collision : CopiedCollision in _active_copied_collisions:
if is_instance_valid(copied_collision.collision_shape):
_collision_hand.remove_child(copied_collision.collision_shape)
copied_collision.collision_shape.queue_free()
_active_copied_collisions.clear()
func _on_button_pressed(p_button) -> void:
if p_button == action_button_action and is_instance_valid(picked_up_object):
if picked_up_object.has_method("action"):
picked_up_object.action()
if picked_up_object.has_method("controller_action"):
picked_up_object.controller_action(_controller)
func _on_button_released(p_button) -> void:
if p_button == action_button_action and is_instance_valid(picked_up_object):
if picked_up_object.has_method("action_release"):
picked_up_object.action_release()
if picked_up_object.has_method("controller_action_release"):
picked_up_object.controller_action_release(_controller)
func _on_grip_pressed() -> void:
if is_instance_valid(picked_up_object) and !picked_up_object.press_to_hold:
drop_object()
elif is_instance_valid(closest_object):
_pick_up_object(closest_object)
func _on_grip_release() -> void:
if is_instance_valid(picked_up_object) and picked_up_object.press_to_hold:
drop_object()

View File

@ -0,0 +1 @@
uid://bjw5378vddn8e

View File

@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://b4ysuy43poobf"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/function_pickup.gd" id="1"]
[node name="FunctionPickup" type="Node3D"]
script = ExtResource("1")
grab_collision_mask = 327684

View File

@ -0,0 +1,511 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/function.svg")
class_name XRToolsFunctionPointer
extends Node3D
## XR Tools Function Pointer Script
##
## This script implements a pointer function for a players controller. Pointer
## events (entered, exited, pressed, release, and movement) are delivered by
## invoking signals on the target node.
##
## Pointer target nodes commonly extend from [XRToolsInteractableArea] or
## [XRToolsInteractableBody].
## Signal emitted when this object points at another object
signal pointing_event(event)
## Enumeration of laser show modes
enum LaserShow {
HIDE = 0, ## Hide laser
SHOW = 1, ## Show laser
COLLIDE = 2, ## Only show laser on collision
}
## Enumeration of laser length modes
enum LaserLength {
FULL = 0, ## Full length
COLLIDE = 1 ## Draw to collision
}
## Default pointer collision mask of 21:pointable and 23:ui-objects
const DEFAULT_MASK := 0b0000_0000_0101_0000_0000_0000_0000_0000
## Default pointer collision mask of 23:ui-objects
const SUPPRESS_MASK := 0b0000_0000_0100_0000_0000_0000_0000_0000
@export_group("General")
## Pointer enabled
@export var enabled : bool = true: set = set_enabled
## Y Offset for pointer
@export var y_offset : float = -0.013: set = set_y_offset
## Pointer distance
@export var distance : float = 10: set = set_distance
## Active button action
@export var active_button_action : String = "trigger_click"
@export_group("Laser")
## Controls when the laser is visible
@export var show_laser : LaserShow = LaserShow.SHOW: set = set_show_laser
## Controls the length of the laser
@export var laser_length : LaserLength = LaserLength.FULL: set = set_laser_length
## Laser pointer material
@export var laser_material : StandardMaterial3D = null : set = set_laser_material
## Laser pointer material when hitting target
@export var laser_hit_material : StandardMaterial3D = null : set = set_laser_hit_material
@export_group("Target")
## If true, the pointer target is shown
@export var show_target : bool = false: set = set_show_target
## Controls the target radius
@export var target_radius : float = 0.05: set = set_target_radius
## Target material
@export var target_material : StandardMaterial3D = null : set = set_target_material
@export_group("Collision")
## Pointer collision mask
@export_flags_3d_physics var collision_mask : int = DEFAULT_MASK: set = set_collision_mask
## Enable pointer collision with bodies
@export var collide_with_bodies : bool = true: set = set_collide_with_bodies
## Enable pointer collision with areas
@export var collide_with_areas : bool = false: set = set_collide_with_areas
@export_group("Suppression")
## Suppress radius
@export var suppress_radius : float = 0.2: set = set_suppress_radius
## Suppress mask
@export_flags_3d_physics var suppress_mask : int = SUPPRESS_MASK: set = set_suppress_mask
## Current target node
var target : Node3D = null
## Last target node
var last_target : Node3D = null
## Last collision point
var last_collided_at : Vector3 = Vector3.ZERO
# World scale
var _world_scale : float = 1.0
# Left controller node
var _controller_left_node : XRController3D
# Right controller node
var _controller_right_node : XRController3D
# Parent controller (if this pointer is childed to a specific controller)
var _controller : XRController3D
# The currently active controller
var _active_controller : XRController3D
## Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsFunctionPointer"
# Called when the node enters the scene tree for the first time.
func _ready():
# Do not initialise if in the editor
if Engine.is_editor_hint():
return
# Read the initial world-scale
_world_scale = XRServer.world_scale
# Check for a parent controller
_controller = XRHelpers.get_xr_controller(self)
if _controller:
# Set as active on the parent controller
_active_controller = _controller
# Get button press feedback from our parent controller
_controller.button_pressed.connect(_on_button_pressed.bind(_controller))
_controller.button_released.connect(_on_button_released.bind(_controller))
else:
# Get the left and right controllers
_controller_left_node = XRHelpers.get_left_controller(self)
_controller_right_node = XRHelpers.get_right_controller(self)
# Start out right hand controller
_active_controller = _controller_right_node
# Get button press feedback from both left and right controllers
_controller_left_node.button_pressed.connect(
_on_button_pressed.bind(_controller_left_node))
_controller_left_node.button_released.connect(
_on_button_released.bind(_controller_left_node))
_controller_right_node.button_pressed.connect(
_on_button_pressed.bind(_controller_right_node))
_controller_right_node.button_released.connect(
_on_button_released.bind(_controller_right_node))
# init our state
_update_y_offset()
_update_distance()
_update_pointer()
_update_target_radius()
_update_target_material()
_update_collision_mask()
_update_collide_with_bodies()
_update_collide_with_areas()
_update_suppress_radius()
_update_suppress_mask()
# Called on each frame to update the pickup
func _process(_delta):
# Do not process if in the editor
if Engine.is_editor_hint() or !is_inside_tree():
return
# Track the active controller (if this pointer is not childed to a controller)
if _controller == null and _active_controller != null:
transform = _active_controller.transform
# Handle world-scale changes
var new_world_scale := XRServer.world_scale
if (_world_scale != new_world_scale):
_world_scale = new_world_scale
_update_y_offset()
# Find the new pointer target
var new_target : Node3D
var new_at : Vector3
var suppress_area := $SuppressArea
if (enabled and
not $SuppressArea.has_overlapping_bodies() and
not $SuppressArea.has_overlapping_areas() and
$RayCast.is_colliding()):
new_at = $RayCast.get_collision_point()
if target:
# Locked to 'target' even if we're colliding with something else
new_target = target
else:
# Target is whatever the raycast is colliding with
new_target = $RayCast.get_collider()
# If no current or previous collisions then skip
if not new_target and not last_target:
return
# Handle pointer changes
if new_target and not last_target:
# Pointer entered new_target
XRToolsPointerEvent.entered(self, new_target, new_at)
# Pointer moved on new_target for the first time
XRToolsPointerEvent.moved(self, new_target, new_at, new_at)
# Update visible artifacts for hit
_visible_hit(new_at)
elif not new_target and last_target:
# Pointer exited last_target
XRToolsPointerEvent.exited(self, last_target, last_collided_at)
# Update visible artifacts for miss
_visible_miss()
elif new_target != last_target:
# Pointer exited last_target
XRToolsPointerEvent.exited(self, last_target, last_collided_at)
# Pointer entered new_target
XRToolsPointerEvent.entered(self, new_target, new_at)
# Pointer moved on new_target
XRToolsPointerEvent.moved(self, new_target, new_at, new_at)
# Move visible artifacts
_visible_move(new_at)
elif new_at != last_collided_at:
# Pointer moved on new_target
XRToolsPointerEvent.moved(self, new_target, new_at, last_collided_at)
# Move visible artifacts
_visible_move(new_at)
# Update last values
last_target = new_target
last_collided_at = new_at
# Set pointer enabled property
func set_enabled(p_enabled : bool) -> void:
enabled = p_enabled
if is_inside_tree():
_update_pointer()
# Set pointer y_offset property
func set_y_offset(p_offset : float) -> void:
y_offset = p_offset
if is_inside_tree():
_update_y_offset()
# Set pointer distance property
func set_distance(p_new_value : float) -> void:
distance = p_new_value
if is_inside_tree():
_update_distance()
# Set pointer show_laser property
func set_show_laser(p_show : LaserShow) -> void:
show_laser = p_show
if is_inside_tree():
_update_pointer()
# Set pointer laser_length property
func set_laser_length(p_laser_length : LaserLength) -> void:
laser_length = p_laser_length
if is_inside_tree():
_update_pointer()
# Set pointer laser_material property
func set_laser_material(p_laser_material : StandardMaterial3D) -> void:
laser_material = p_laser_material
if is_inside_tree():
_update_pointer()
# Set pointer laser_hit_material property
func set_laser_hit_material(p_laser_hit_material : StandardMaterial3D) -> void:
laser_hit_material = p_laser_hit_material
if is_inside_tree():
_update_pointer()
# Set pointer show_target property
func set_show_target(p_show_target : bool) -> void:
show_target = p_show_target
if is_inside_tree():
$Target.visible = enabled and show_target and last_target
# Set pointer target_radius property
func set_target_radius(p_target_radius : float) -> void:
target_radius = p_target_radius
if is_inside_tree():
_update_target_radius()
# Set pointer target_material property
func set_target_material(p_target_material : StandardMaterial3D) -> void:
target_material = p_target_material
if is_inside_tree():
_update_target_material()
# Set pointer collision_mask property
func set_collision_mask(p_new_mask : int) -> void:
collision_mask = p_new_mask
if is_inside_tree():
_update_collision_mask()
# Set pointer collide_with_bodies property
func set_collide_with_bodies(p_new_value : bool) -> void:
collide_with_bodies = p_new_value
if is_inside_tree():
_update_collide_with_bodies()
# Set pointer collide_with_areas property
func set_collide_with_areas(p_new_value : bool) -> void:
collide_with_areas = p_new_value
if is_inside_tree():
_update_collide_with_areas()
# Set suppress radius property
func set_suppress_radius(p_suppress_radius : float) -> void:
suppress_radius = p_suppress_radius
if is_inside_tree():
_update_suppress_radius()
func set_suppress_mask(p_suppress_mask : int) -> void:
suppress_mask = p_suppress_mask
if is_inside_tree():
_update_suppress_mask()
# Pointer Y offset update handler
func _update_y_offset() -> void:
$Laser.position.y = y_offset * _world_scale
$RayCast.position.y = y_offset * _world_scale
# Pointer distance update handler
func _update_distance() -> void:
$RayCast.target_position.z = -distance
_update_pointer()
# Pointer target radius update handler
func _update_target_radius() -> void:
$Target.mesh.radius = target_radius
$Target.mesh.height = target_radius * 2
# Pointer target_material update handler
func _update_target_material() -> void:
$Target.set_surface_override_material(0, target_material)
# Pointer collision_mask update handler
func _update_collision_mask() -> void:
$RayCast.collision_mask = collision_mask
# Pointer collide_with_bodies update handler
func _update_collide_with_bodies() -> void:
$RayCast.collide_with_bodies = collide_with_bodies
# Pointer collide_with_areas update handler
func _update_collide_with_areas() -> void:
$RayCast.collide_with_areas = collide_with_areas
# Pointer suppress_radius update handler
func _update_suppress_radius() -> void:
$SuppressArea/CollisionShape3D.shape.radius = suppress_radius
# Pointer suppress_mask update handler
func _update_suppress_mask() -> void:
$SuppressArea.collision_mask = suppress_mask
# Pointer visible artifacts update handler
func _update_pointer() -> void:
if enabled and last_target:
_visible_hit(last_collided_at)
else:
_visible_miss()
# Pointer-activation button pressed handler
func _button_pressed() -> void:
if $RayCast.is_colliding():
# Report pressed
target = $RayCast.get_collider()
last_collided_at = $RayCast.get_collision_point()
XRToolsPointerEvent.pressed(self, target, last_collided_at)
# Pointer-activation button released handler
func _button_released() -> void:
if target:
# Report release
XRToolsPointerEvent.released(self, target, last_collided_at)
target = null
last_collided_at = Vector3(0, 0, 0)
# Button pressed handler
func _on_button_pressed(p_button : String, controller : XRController3D) -> void:
if p_button == active_button_action and enabled:
if controller == _active_controller:
_button_pressed()
else:
_active_controller = controller
# Button released handler
func _on_button_released(p_button : String, _controller : XRController3D) -> void:
if p_button == active_button_action and target:
_button_released()
# Update the laser active material
func _update_laser_active_material(hit : bool) -> void:
if hit and laser_hit_material:
$Laser.set_surface_override_material(0, laser_hit_material)
else:
$Laser.set_surface_override_material(0, laser_material)
# Update the visible artifacts to show a hit
func _visible_hit(at : Vector3) -> void:
# Show target if enabled
if show_target:
$Target.global_transform.origin = at
$Target.visible = true
# Control laser visibility
if show_laser != LaserShow.HIDE:
# Ensure the correct laser material is set
_update_laser_active_material(true)
# Adjust laser length
if laser_length == LaserLength.COLLIDE:
var collide_len : float = at.distance_to(global_transform.origin)
$Laser.mesh.size.z = collide_len
$Laser.position.z = collide_len * -0.5
else:
$Laser.mesh.size.z = distance
$Laser.position.z = distance * -0.5
# Show laser
$Laser.visible = true
else:
# Ensure laser is hidden
$Laser.visible = false
# Move the visible pointer artifacts to the target
func _visible_move(at : Vector3) -> void:
# Move target if configured
if show_target:
$Target.global_transform.origin = at
# Adjust laser length if set to collide-length
if laser_length == LaserLength.COLLIDE:
var collide_len : float = at.distance_to(global_transform.origin)
$Laser.mesh.size.z = collide_len
$Laser.position.z = collide_len * -0.5
# Update the visible artifacts to show a miss
func _visible_miss() -> void:
# Ensure target is hidden
$Target.visible = false
# Ensure the correct laser material is set
_update_laser_active_material(false)
# Hide laser if not set to show always
$Laser.visible = show_laser == LaserShow.SHOW
# Restore laser length if set to collide-length
$Laser.mesh.size.z = distance
$Laser.position.z = distance * -0.5

View File

@ -0,0 +1 @@
uid://drdsa6gk8fm3p

View File

@ -0,0 +1,44 @@
[gd_scene load_steps=6 format=3 uid="uid://cqhw276realc"]
[ext_resource type="Material" path="res://addons/godot-xr-tools/materials/pointer.tres" id="1"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/function_pointer.gd" id="2"]
[sub_resource type="BoxMesh" id="1"]
resource_local_to_scene = true
material = ExtResource("1")
size = Vector3(0.002, 0.002, 10)
subdivide_depth = 20
[sub_resource type="SphereMesh" id="2"]
material = ExtResource("1")
radius = 0.05
height = 0.1
radial_segments = 16
rings = 8
[sub_resource type="SphereShape3D" id="SphereShape3D_k3gfm"]
radius = 0.2
[node name="FunctionPointer" type="Node3D"]
script = ExtResource("2")
[node name="RayCast" type="RayCast3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.013, 0)
target_position = Vector3(0, 0, -10)
collision_mask = 5242880
[node name="Laser" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.013, -5)
cast_shadow = 0
mesh = SubResource("1")
[node name="Target" type="MeshInstance3D" parent="."]
visible = false
mesh = SubResource("2")
[node name="SuppressArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 4194304
[node name="CollisionShape3D" type="CollisionShape3D" parent="SuppressArea"]
shape = SubResource("SphereShape3D_k3gfm")

View File

@ -0,0 +1,109 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/hand.svg")
class_name XRToolsFunctionPoseDetector
extends Node3D
## XR Tools Function Pose Area
##
## This area works with the XRToolsHandPoseArea to control the pose
## of the VR hands.
# Default pose detector collision mask of 22:pose-area
const DEFAULT_MASK := 0b0000_0000_0010_0000_0000_0000_0000_0000
## Collision mask to detect hand pose areas
@export_flags_3d_physics var collision_mask : int = DEFAULT_MASK: set = set_collision_mask
## Hand controller
@onready var _controller := XRHelpers.get_xr_controller(self)
## Hand to control
@onready var _hand := XRToolsHand.find_instance(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsFunctionPoseDetector"
# Called when the node enters the scene tree for the first time.
func _ready():
# Connect signals (if controller and hand are valid)
if _controller and _hand:
if $SenseArea.area_entered.connect(_on_area_entered):
push_error("Unable to connect area_entered signal")
if $SenseArea.area_exited.connect(_on_area_exited):
push_error("Unable to connect area_exited signal")
# Update collision mask
_update_collision_mask()
# This method verifies the pose area has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
if !XRHelpers.get_xr_controller(self):
warnings.append("Node must be within a branch of an XRController3D node")
# Verify hand can be found
if !XRToolsHand.find_instance(self):
warnings.append("Node must be a within a branch of an XRController node with a hand")
# Pass basic validation
return warnings
func set_collision_mask(mask : int) -> void:
collision_mask = mask
if is_inside_tree():
_update_collision_mask()
func _update_collision_mask() -> void:
$SenseArea.collision_mask = collision_mask
## Signal handler called when this XRToolsFunctionPoseArea enters an area
func _on_area_entered(area : Area3D) -> void:
# Igjnore if the area is not a hand-pose area
var pose_area := area as XRToolsHandPoseArea
if !pose_area:
return
# Get the positional tracker
var tracker := XRServer.get_tracker(_controller.tracker) as XRPositionalTracker
# Set the appropriate poses
if tracker.hand == XRPositionalTracker.TRACKER_HAND_LEFT and pose_area.left_pose:
_hand.add_pose_override(
pose_area,
pose_area.pose_priority,
pose_area.left_pose)
# Disable grabpoints in this pose_area
pose_area.disable_grab_points()
elif tracker.hand == XRPositionalTracker.TRACKER_HAND_RIGHT and pose_area.right_pose:
_hand.add_pose_override(
pose_area,
pose_area.pose_priority,
pose_area.right_pose)
# Disable grabpoints in this pose_area
pose_area.disable_grab_points()
## Signal handler called when this XRToolsFunctionPoseArea leaves an area
func _on_area_exited(area : Area3D) -> void:
# Ignore if the area is not a hand-pose area
var pose_area := area as XRToolsHandPoseArea
if !pose_area:
return
# Remove any overrides set from this hand-pose area
_hand.remove_pose_override(pose_area)
# Enable previously disabled grabpoints
pose_area.enable_grab_points()

View File

@ -0,0 +1,19 @@
[gd_scene load_steps=3 format=3 uid="uid://bft3xyxs31ci3"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/function_pose_detector.gd" id="1"]
[sub_resource type="CapsuleShape3D" id="1"]
radius = 0.08
height = 0.24
[node name="FunctionPoseDetector" type="Node3D"]
script = ExtResource("1")
[node name="SenseArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 2097152
monitorable = false
[node name="CollisionShape" type="CollisionShape3D" parent="SenseArea"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, -0.04, 0.08)
shape = SubResource("1")

Some files were not shown because too many files have changed in this diff Show More