add initial template

This commit is contained in:
Nitwel 2023-10-16 19:10:20 +02:00
parent e954af2da3
commit df8928464c
355 changed files with 30095 additions and 0 deletions

View File

@ -0,0 +1,20 @@
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)
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,177 @@
# 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,36 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,36 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,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,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,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,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,6 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/godot-xr-tools/audio/surface_audio.gd" type="Script" 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,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,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,37 @@
[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/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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,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,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() {
vec3 v = VERTEX;
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.xy += eye.xy / eye.z;
}
// looks like this is broken in Godot 4...
POSITION = vec4(v, 1.0);
}
void fragment() {
ALBEDO = color.rgb;
ALPHA = clamp((dist - radius) / fade, 0.0, 1.0);
}

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 = 0
shader = ExtResource("1_x02h0")
shader_parameter/color = Color(0, 0, 0, 1)
shader_parameter/radius = 0.2
shader_parameter/fade = 0.05

View File

@ -0,0 +1,27 @@
[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": 4097,
"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,
"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,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,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,431 @@
@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
## 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
## Controller
@onready var _controller := XRHelpers.get_xr_controller(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_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_collision:
_grab_collision.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
# let go of this object
picked_up_object.let_go(
_velocity_averager.linear_velocity() * impulse_factor,
_velocity_averager.angular_velocity())
picked_up_object = null
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, _controller)
# If object picked up then emit signal
if is_instance_valid(picked_up_object):
picked_up_object.request_highlight(self, false)
emit_signal("has_picked_up", picked_up_object)
func _on_button_pressed(p_button) -> void:
if p_button == action_button_action:
if is_instance_valid(picked_up_object) and picked_up_object.has_method("action"):
picked_up_object.action()
func _on_button_released(_p_button) -> void:
pass
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,6 @@
[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")

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,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,99 @@
@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
# Set the appropriate poses
if _controller.tracker == "left_hand" and pose_area.left_pose:
_hand.add_pose_override(
pose_area,
pose_area.pose_priority,
pose_area.left_pose)
elif _controller.tracker == "right_hand" and pose_area.right_pose:
_hand.add_pose_override(
pose_area,
pose_area.pose_priority,
pose_area.right_pose)
## 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)

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")

View File

@ -0,0 +1,495 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/function.svg")
class_name XRToolsFunctionTeleport
extends Node3D
## XR Tools Function Teleport Script
##
## This script provides teleport functionality.
##
## Add this scene as a sub scene of your [XRController3D] node to implement
## a teleport function on that controller.
# Default teleport collision mask of all
const DEFAULT_MASK := 0b1111_1111_1111_1111_1111_1111_1111_1111
# Default material
# gdlint:ignore = load-constant-name
const _DefaultMaterial := preload("res://addons/godot-xr-tools/materials/capsule.tres")
## If true, teleporting is enabled
@export var enabled : bool = true: set = set_enabled
## Teleport button action
@export var teleport_button_action : String = "trigger_click"
## Teleport rotation action
@export var rotation_action : String = "primary"
# Teleport Path Group
@export_group("Visuals")
## Teleport allowed color property
@export var can_teleport_color : Color = Color(0.0, 1.0, 0.0, 1.0)
## Teleport denied color property
@export var cant_teleport_color : Color = Color(1.0, 0.0, 0.0, 1.0)
## Teleport no-collision color property
@export var no_collision_color : Color = Color(45.0 / 255.0, 80.0 / 255.0, 220.0 / 255.0, 1.0)
## Teleport-arc strength
@export var strength : float = 5.0
## Teleport texture
@export var arc_texture : Texture2D \
= preload("res://addons/godot-xr-tools/images/teleport_arrow.png") \
: set = set_arc_texture
## Target texture
@export var target_texture : Texture2D \
= preload("res://addons/godot-xr-tools/images/teleport_target.png") \
: set = set_target_texture
# Player Group
@export_group("Player")
## Player height property
@export var player_height : float = 1.8: set = set_player_height
## Player radius property
@export var player_radius : float = 0.4: set = set_player_radius
## Player scene
@export var player_scene : PackedScene: set = set_player_scene
# Target Group
@export_group("Collision")
## Maximum floor slope
@export var max_slope : float = 20.0
## Collision mask
@export_flags_3d_physics var collision_mask : int = 1023
## Valid teleport layer mask
@export_flags_3d_physics var valid_teleport_mask : int = DEFAULT_MASK
## Player capsule material (ignored for custom player scenes)
var player_material : StandardMaterial3D = _DefaultMaterial : set = set_player_material
var is_on_floor : bool = true
var is_teleporting : bool = false
var can_teleport : bool = true
var teleport_rotation : float = 0.0;
var floor_normal : Vector3 = Vector3.UP
var last_target_transform : Transform3D = Transform3D()
var collision_shape : Shape3D
var step_size : float = 0.5
# Custom player scene
var player : Node3D
# World scale
@onready var ws : float = XRServer.world_scale
## Capsule shown when not using a custom player mesh
@onready var capsule : MeshInstance3D = $Target/Player_figure/Capsule
## [XRToolsPlayerBody] node.
@onready var player_body := XRToolsPlayerBody.find_instance(self)
## [XRController3D] node.
@onready var controller := XRHelpers.get_xr_controller(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsFunctionTeleport"
# 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
# It's inactive when we start
$Teleport.visible = false
$Target.visible = false
# Scale to our world scale
$Teleport.mesh.size = Vector2(0.05 * ws, 1.0)
$Target.mesh.size = Vector2(ws, ws)
$Target/Player_figure.scale = Vector3(ws, ws, ws)
# get our capsule shape
collision_shape = CapsuleShape3D.new()
# Apply properties
_update_arc_texture()
_update_target_texture()
_update_player_scene()
_update_player_height()
_update_player_radius()
_update_player_material()
func _physics_process(delta):
# Do not process physics if in the editor
if Engine.is_editor_hint():
return
# Skip if required nodes are missing
if !player_body or !controller:
return
# if we're not enabled no point in doing mode
if !enabled:
# reset these
is_teleporting = false;
$Teleport.visible = false
$Target.visible = false
# and stop this from running until we enable again
set_physics_process(false)
return
# check if our world scale has changed..
var new_ws := XRServer.world_scale
if ws != new_ws:
ws = new_ws
$Teleport.mesh.size = Vector2(0.05 * ws, 1.0)
$Target.mesh.size = Vector2(ws, ws)
$Target/Player_figure.scale = Vector3(ws, ws, ws)
if controller and controller.get_is_active() and \
controller.is_button_pressed(teleport_button_action):
if !is_teleporting:
is_teleporting = true
$Teleport.visible = true
$Target.visible = true
teleport_rotation = 0.0
# get our physics engine state
var state := get_world_3d().direct_space_state
var query := PhysicsShapeQueryParameters3D.new()
# init stuff about our query that doesn't change
query.collision_mask = collision_mask
query.margin = collision_shape.margin
query.shape_rid = collision_shape.get_rid()
# make a transform for offsetting our shape, it's always
# lying on its side by default...
var shape_transform := Transform3D(
Basis(),
Vector3(0.0, player_height / 2.0, 0.0))
# update location
var teleport_global_transform : Transform3D = $Teleport.global_transform
var target_global_origin := teleport_global_transform.origin
var up := player_body.up_player
var down := -up.normalized() / ws
############################################################
# New teleport logic
# We're going to use test move in steps to find out where we hit something...
# This can be optimised loads by determining the lenght based on the angle
# between sections extending the length when we're in a flat part of the arch
# Where we do get a collission we may want to fine tune the collision
var cast_length := 0.0
var fine_tune := 1.0
var hit_something := false
var max_slope_cos := cos(deg_to_rad(max_slope))
for i in range(1,26):
var new_cast_length := cast_length + (step_size / fine_tune)
var global_target := Vector3(0.0, 0.0, -new_cast_length)
# our quadratic values
var t := global_target.z / strength
var t2 := t * t
# target to world space
global_target = teleport_global_transform * global_target
# adjust for gravity
global_target += down * t2
# test our new location for collisions
query.transform = Transform3D(
player_body.global_transform.basis,
global_target) * shape_transform
var cast_result := state.collide_shape(query, 10)
if cast_result.is_empty():
# we didn't collide with anything so check our next section...
cast_length = new_cast_length
target_global_origin = global_target
elif (fine_tune <= 16.0):
# try again with a small step size
fine_tune *= 2.0
else:
# if we don't collide make sure we keep using our current origin point
var collided_at := target_global_origin
# check for collision
var step_delta := global_target - target_global_origin
if up.dot(step_delta) > 0:
# if we're moving up, we hit the ceiling of something, we
# don't really care what
is_on_floor = false
else:
# now we cast a ray downwards to see if we're on a surface
var ray_query := PhysicsRayQueryParameters3D.new()
ray_query.from = target_global_origin + (up * 0.5 * player_height)
ray_query.to = target_global_origin - (up * 1.1 * player_height)
ray_query.collision_mask = collision_mask
var intersects := state.intersect_ray(ray_query)
if intersects.is_empty():
is_on_floor = false
else:
# did we collide with a floor or a wall?
floor_normal = intersects["normal"]
var dot := up.dot(floor_normal)
if dot > max_slope_cos:
is_on_floor = true
else:
is_on_floor = false
# Update our collision point if it's moved enough, this
# solves a little bit of jittering
var diff : Vector3 = collided_at - intersects["position"]
if diff.length() > 0.1:
collided_at = intersects["position"]
# Fail if the hit target isn't in our valid mask
var collider_mask : int = intersects["collider"].collision_layer
if not valid_teleport_mask & collider_mask:
is_on_floor = false
# we are colliding, find our if we're colliding on a wall or
# floor, one we can do, the other nope...
cast_length += (collided_at - target_global_origin).length()
target_global_origin = collided_at
hit_something = true
break
# and just update our shader
$Teleport.get_surface_override_material(0).set_shader_parameter("scale_t", 1.0 / strength)
$Teleport.get_surface_override_material(0).set_shader_parameter("down", down)
$Teleport.get_surface_override_material(0).set_shader_parameter("length", cast_length)
if hit_something:
var color := can_teleport_color
var normal := up
if is_on_floor:
# if we're on the floor we'll reorientate our target to match.
normal = floor_normal
can_teleport = true
else:
can_teleport = false
color = cant_teleport_color
# check our axis to see if we need to rotate
teleport_rotation += (delta * controller.get_vector2(rotation_action).x * -4.0)
# update target and colour
var target_basis := Basis()
target_basis.y = normal
target_basis.x = teleport_global_transform.basis.x.slide(normal).normalized()
target_basis.z = target_basis.x.cross(target_basis.y)
target_basis = target_basis.rotated(normal, teleport_rotation)
last_target_transform.basis = target_basis
last_target_transform.origin = target_global_origin + up * 0.001
$Target.global_transform = last_target_transform
$Teleport.get_surface_override_material(0).set_shader_parameter("mix_color", color)
$Target.get_surface_override_material(0).albedo_color = color
$Target.visible = can_teleport
else:
can_teleport = false
$Target.visible = false
$Teleport.get_surface_override_material(0).set_shader_parameter("mix_color", no_collision_color)
elif is_teleporting:
if can_teleport:
# Make our target using the players up vector
var new_transform := last_target_transform
new_transform.basis.y = player_body.up_player
new_transform.basis.x = new_transform.basis.y.cross(new_transform.basis.z).normalized()
new_transform.basis.z = new_transform.basis.x.cross(new_transform.basis.y).normalized()
# Teleport the player
player_body.teleport(new_transform)
# and disable
is_teleporting = false;
$Teleport.visible = false
$Target.visible = false
# This method verifies the teleport has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Verify we can find the XRToolsPlayerBody
if !XRToolsPlayerBody.find_instance(self):
warnings.append("This node must be within a branch of an XRToolsPlayerBody node")
# Verify we can find the XRController3D
if !XRHelpers.get_xr_controller(self):
warnings.append("This node must be within a branch of an XRController3D node")
# Return warnings
return warnings
# Provide custom property information
func _get_property_list() -> Array[Dictionary]:
return [
{
"name" : "Player",
"type" : TYPE_NIL,
"usage" : PROPERTY_USAGE_GROUP
},
{
"name" : "player_material",
"class_name" : "StandardMaterial3D",
"type" : TYPE_OBJECT,
"usage" : PROPERTY_USAGE_NO_EDITOR if player_scene else PROPERTY_USAGE_DEFAULT,
"hint" : PROPERTY_HINT_RESOURCE_TYPE,
"hint_string" : "StandardMaterial3D"
}
]
# Allow revert of custom properties
func _property_can_revert(property : StringName) -> bool:
return property == "player_material"
# Provide revert values for custom properties
func _property_get_revert(property : StringName): # Variant
if property == "player_material":
return _DefaultMaterial
# Set enabled property
func set_enabled(new_value : bool) -> void:
enabled = new_value
if enabled:
# make sure our physics process is on
set_physics_process(true)
else:
# we turn this off in physics process just in case we want to do some cleanup
pass
# Set the arc texture
func set_arc_texture(p_arc_texture : Texture2D) -> void:
arc_texture = p_arc_texture
if is_inside_tree():
_update_arc_texture()
# Set the target texture
func set_target_texture(p_target_texture : Texture2D) -> void:
target_texture = p_target_texture
if is_inside_tree():
_update_target_texture()
# Set player height property
func set_player_height(p_height : float) -> void:
player_height = p_height
if is_inside_tree():
_update_player_height()
# Set player radius property
func set_player_radius(p_radius : float) -> void:
player_radius = p_radius
if is_inside_tree():
_update_player_radius()
# Set the player scene
func set_player_scene(p_player_scene : PackedScene) -> void:
player_scene = p_player_scene
notify_property_list_changed()
if is_inside_tree():
_update_player_scene()
# Set the player material
func set_player_material(p_player_material : StandardMaterial3D) -> void:
player_material = p_player_material
if is_inside_tree():
_update_player_material()
# Update arc texture
func _update_arc_texture():
var material : ShaderMaterial = $Teleport.get_surface_override_material(0)
if material and arc_texture:
material.set_shader_parameter("arrow_texture", arc_texture)
# Update target texture
func _update_target_texture():
var material : StandardMaterial3D = $Target.get_surface_override_material(0)
if material and target_texture:
material.albedo_texture = target_texture
# Player height update handler
func _update_player_height() -> void:
if collision_shape:
collision_shape.height = player_height - (2.0 * player_radius)
if capsule:
capsule.mesh.height = player_height
capsule.position = Vector3(0.0, player_height/2.0, 0.0)
# Player radius update handler
func _update_player_radius():
if collision_shape:
collision_shape.height = player_height
collision_shape.radius = player_radius
if capsule:
capsule.mesh.height = player_height
capsule.mesh.radius = player_radius
# Update the player scene
func _update_player_scene() -> void:
# Free the current player
if player:
player.queue_free()
player = null
# If specified, instantiate a new player
if player_scene:
player = player_scene.instantiate()
$Target/Player_figure.add_child(player)
# Show the capsule mesh only if we have no player
capsule.visible = player == null
# Update player material
func _update_player_material():
if player_material:
capsule.set_surface_override_material(0, player_material)

View File

@ -0,0 +1,37 @@
[gd_scene load_steps=8 format=3 uid="uid://fiul51tsyoop"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/function_teleport.gd" id="1"]
[ext_resource type="Material" uid="uid://bk72wfw25ff0v" path="res://addons/godot-xr-tools/materials/teleport.tres" id="2"]
[ext_resource type="Material" path="res://addons/godot-xr-tools/materials/target.tres" id="3"]
[ext_resource type="Material" path="res://addons/godot-xr-tools/materials/capsule.tres" id="4"]
[sub_resource type="PlaneMesh" id="1"]
size = Vector2(0.05, 1)
subdivide_depth = 40
[sub_resource type="PlaneMesh" id="2"]
size = Vector2(1, 1)
[sub_resource type="CapsuleMesh" id="3"]
radius = 0.4
height = 1.8
[node name="FunctionTeleport" type="Node3D"]
script = ExtResource("1")
player_material = ExtResource("4")
[node name="Teleport" type="MeshInstance3D" parent="."]
mesh = SubResource("1")
surface_material_override/0 = ExtResource("2")
[node name="Target" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, -4.92359)
mesh = SubResource("2")
surface_material_override/0 = ExtResource("3")
[node name="Player_figure" type="Marker3D" parent="Target"]
[node name="Capsule" type="MeshInstance3D" parent="Target/Player_figure"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
mesh = SubResource("3")
surface_material_override/0 = ExtResource("4")

View File

@ -0,0 +1,269 @@
@tool
class_name XRToolsMovementClimb
extends XRToolsMovementProvider
## XR Tools Movement Provider for Climbing
##
## This script provides climbing movement for the player. To add climbing
## support, the player must also have [XRToolsFunctionPickup] nodes attached
## to the left and right controllers, and an [XRToolsPlayerBody] under the
## [XROrigin3D].
##
## Climbable objects can inherit from the climbable scene, or be [StaticBody]
## objects with the [XRToolsClimbable] script attached to them.
##
## When climbing, the global velocity of the [XRToolsPlayerBody] is averaged,
## and upon release the velocity is applied to the [XRToolsPlayerBody] with an
## optional fling multiplier, so the player can fling themselves up walls if
## desired.
## Signal invoked when the player starts climing
signal player_climb_start
## Signal invoked when the player ends climbing
signal player_climb_end
## Distance at which grabs snap
const SNAP_DISTANCE : float = 1.0
## Movement provider order
@export var order : int = 15
## Push forward when flinging
@export var forward_push : float = 1.0
## Velocity multiplier when flinging up walls
@export var fling_multiplier : float = 1.0
## Averages for velocity measurement
@export var velocity_averages : int = 5
# Left climbing handle
var _left_handle : Node3D
# Right climbing handle
var _right_handle : Node3D
# Dominant handle (moving the player)
var _dominant : Node3D
# Velocity averager
@onready var _averager := XRToolsVelocityAveragerLinear.new(velocity_averages)
# Left pickup node
@onready var _left_pickup_node := XRToolsFunctionPickup.find_left(self)
# Right pickup node
@onready var _right_pickup_node := XRToolsFunctionPickup.find_right(self)
# Left controller
@onready var _left_controller := XRHelpers.get_left_controller(self)
# Right controller
@onready var _right_controller := XRHelpers.get_right_controller(self)
# Left collision hand
@onready var _left_collision_hand := XRToolsCollisionHand.find_left(self)
# Right collision hand
@onready var _right_collision_hand := XRToolsCollisionHand.find_right(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementClimb" or super(name)
## Called when the node enters the scene tree for the first time.
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Do not initialise if in the editor
if Engine.is_editor_hint():
return
# Connect pickup funcitons
if _left_pickup_node.connect("has_picked_up", _on_left_picked_up):
push_error("Unable to connect left picked up signal")
if _right_pickup_node.connect("has_picked_up", _on_right_picked_up):
push_error("Unable to connect right picked up signal")
if _left_pickup_node.connect("has_dropped", _on_left_dropped):
push_error("Unable to connect left dropped signal")
if _right_pickup_node.connect("has_dropped", _on_right_dropped):
push_error("Unable to connect right dropped signal")
## Perform player physics movement
func physics_movement(delta: float, player_body: XRToolsPlayerBody, disabled: bool):
# Disable climbing if requested
if disabled or !enabled:
_set_climbing(false, player_body)
return
# Check for climbing handles being deleted while held
if not is_instance_valid(_left_handle):
_left_handle = null
if not is_instance_valid(_right_handle):
_right_handle = null
if not is_instance_valid(_dominant):
_dominant = null
# Snap grabs if too far
if _left_handle:
var left_pickup_pos := _left_controller.global_position
var left_grab_pos = _left_handle.global_position
if left_pickup_pos.distance_to(left_grab_pos) > SNAP_DISTANCE:
_left_pickup_node.drop_object()
if _right_handle:
var right_pickup_pos := _right_controller.global_position
var right_grab_pos := _right_handle.global_position
if right_pickup_pos.distance_to(right_grab_pos) > SNAP_DISTANCE:
_right_pickup_node.drop_object()
# Update climbing
_set_climbing(_dominant != null, player_body)
# Skip if not actively climbing
if !is_active:
return
# Calculate how much the player has moved
var offset := Vector3.ZERO
if _dominant == _left_handle:
var left_pickup_pos := _left_controller.global_position
var left_grab_pos := _left_handle.global_position
offset = left_pickup_pos - left_grab_pos
elif _dominant == _right_handle:
var right_pickup_pos := _right_controller.global_position
var right_grab_pos := _right_handle.global_position
offset = right_pickup_pos - right_grab_pos
# Move the player by the offset
var old_position := player_body.global_position
player_body.move_and_collide(-offset)
player_body.velocity = Vector3.ZERO
# Update the players average-velocity data
var distance := player_body.global_position - old_position
_averager.add_distance(delta, distance)
# Report exclusive motion performed (to bypass gravity)
return true
## Start or stop climbing
func _set_climbing(active: bool, player_body: XRToolsPlayerBody) -> void:
# Skip if no change
if active == is_active:
return
# Update state
is_active = active
# Handle state change
if is_active:
_averager.clear()
player_body.override_player_height(self, 0.0)
emit_signal("player_climb_start")
else:
# Calculate the forward direction (based on camera-forward)
var dir_forward = -player_body.camera_node.global_transform.basis.z \
.slide(player_body.up_player) \
.normalized()
# Set player velocity based on averaged velocity, fling multiplier,
# and a forward push
var velocity := _averager.velocity()
player_body.velocity = (velocity * fling_multiplier) + (dir_forward * forward_push)
player_body.override_player_height(self)
emit_signal("player_climb_end")
## Handler for left controller picked up
func _on_left_picked_up(what : Node3D) -> void:
# Get the climbable
var climbable = what as XRToolsClimbable
if not climbable:
return
# Get the handle
_left_handle = climbable.get_grab_handle(_left_pickup_node)
if not _left_handle:
return
# Switch dominance to the left handle
_dominant = _left_handle
# If collision hands present then target the handle
if _left_collision_hand:
_left_collision_hand.add_target_override(_left_handle, 0)
## Handler for right controller picked up
func _on_right_picked_up(what : Node3D) -> void:
# Get the climbable
var climbable = what as XRToolsClimbable
if not climbable:
return
# Get the handle
_right_handle = climbable.get_grab_handle(_right_pickup_node)
if not _right_handle:
return
# Switch dominance to the right handle
_dominant = _right_handle
# If collision hands present then target the handle
if _right_collision_hand:
_right_collision_hand.add_target_override(_right_handle, 0)
## Handler for left controller dropped
func _on_left_dropped() -> void:
# If collision hands present then clear handle target
if _left_collision_hand:
_left_collision_hand.remove_target_override(_left_handle)
# Release handle and transfer dominance
_left_handle = null
_dominant = _right_handle
## Handler for righ controller dropped
func _on_right_dropped() -> void:
# If collision hands present then clear handle target
if _right_collision_hand:
_right_collision_hand.remove_target_override(_right_handle)
# Release handle and transfer dominance
_right_handle = null
_dominant = _left_handle
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Verify the left controller pickup
if !XRToolsFunctionPickup.find_left(self):
warnings.append("Unable to find left XRToolsFunctionPickup node")
# Verify the right controller pickup
if !XRToolsFunctionPickup.find_right(self):
warnings.append("Unable to find right XRToolsFunctionPickup node")
# Verify velocity averages
if velocity_averages < 2:
warnings.append("Minimum of 2 velocity averages needed")
# Return warnings
return warnings

View File

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

View File

@ -0,0 +1,92 @@
@tool
class_name XRToolsMovementCrouch
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 = "primary_click"
## 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 _controller := XRHelpers.get_xr_controller(self)
# 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 !_controller.get_is_active():
return
# Detect crouch button down and pressed states
var crouch_button_down := _controller.is_button_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)
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Check the controller node
if !XRHelpers.get_xr_controller(self):
warnings.append("This node must be within a branch of an XRController3D node")
# Return warnings
return warnings

View File

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

View File

@ -0,0 +1,87 @@
@tool
class_name XRToolsMovementDirect
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_action : String = "primary"
# Controller node
@onready var _controller := XRHelpers.get_xr_controller(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementDirect" or super(name)
# Perform jump movement
func physics_movement(_delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Skip if the controller isn't active
if !_controller.get_is_active():
return
## get input action with deadzone correction applied
var dz_input_action = XRToolsUserSettings.get_adjusted_vector2(_controller, input_action)
player_body.ground_control_velocity.y += dz_input_action.y * max_speed
if strafe:
player_body.ground_control_velocity.x += dz_input_action.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
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Check the controller node
if !XRHelpers.get_xr_controller(self):
warnings.append("This node must be within a branch of an XRController3D node")
# Return warnings
return warnings
## Find the left [XRToolsMovementDirect] node.
##
## This function searches from the specified node for the left controller
## [XRToolsMovementDirect] assuming the node is a sibling of the [XROrigin3D].
static func find_left(node : Node) -> XRToolsMovementDirect:
return XRTools.find_xr_child(
XRHelpers.get_left_controller(node),
"*",
"XRToolsMovementDirect") as XRToolsMovementDirect
## Find the right [XRToolsMovementDirect] node.
##
## This function searches from the specified node for the right controller
## [XRToolsMovementDirect] assuming the node is a sibling of the [XROrigin3D].
static func find_right(node : Node) -> XRToolsMovementDirect:
return XRTools.find_xr_child(
XRHelpers.get_right_controller(node),
"*",
"XRToolsMovementDirect") as XRToolsMovementDirect

View File

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

View File

@ -0,0 +1,229 @@
@tool
class_name XRToolsMovementFlight
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()
## Enumeration of controller to use for flight
enum FlightController {
LEFT, ## Use left controller
RIGHT, ## Use right controler
}
## Enumeration of pitch control input
enum FlightPitch {
HEAD, ## Head controls pitch
CONTROLLER, ## Controller controls pitch
}
## Enumeration of bearing control input
enum FlightBearing {
HEAD, ## Head controls bearing
CONTROLLER, ## Controller controls bearing
BODY, ## Body controls bearing
}
## Movement provider order
@export var order : int = 30
## Flight controller
@export var controller : FlightController = FlightController.LEFT
## Flight toggle button
@export var flight_button : String = "by_button"
## Flight pitch control
@export var pitch : FlightPitch = FlightPitch.CONTROLLER
## Flight bearing control
@export var bearing : FlightBearing = FlightBearing.CONTROLLER
## 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
## Flight controller
var _controller : XRController3D
# Node references
@onready var _camera := XRHelpers.get_xr_camera(self)
@onready var _left_controller := XRHelpers.get_left_controller(self)
@onready var _right_controller := XRHelpers.get_right_controller(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementFlight" or super(name)
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Get the flight controller
if controller == FlightController.LEFT:
_controller = _left_controller
else:
_controller = _right_controller
# 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 !_controller.get_is_active():
set_flying(false)
return
# Detect press of flight button
var old_flight_button = _flight_button
_flight_button = _controller.is_button_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
if pitch == FlightPitch.HEAD:
# Use the vertical part of the 'head' forwards vector
pitch_vector = -_camera.transform.basis.z.y * player_body.up_player
else:
# Use the vertical part of the 'controller' forwards vector
pitch_vector = -_controller.transform.basis.z.y * player_body.up_player
# Select the bearing vector
var bearing_vector: Vector3
if bearing == FlightBearing.HEAD:
# Use the horizontal part of the 'head' forwards vector
bearing_vector = -_camera.global_transform.basis.z \
.slide(player_body.up_player)
elif bearing == FlightBearing.CONTROLLER:
# Use the horizontal part of the 'controller' forwards vector
bearing_vector = -_controller.global_transform.basis.z \
.slide(player_body.up_player)
else:
# Use the horizontal part of the 'body' forwards vector
var left := _left_controller.global_transform.origin
var right := _right_controller.global_transform.origin
var left_to_right := right - left
bearing_vector = left_to_right \
.rotated(player_body.up_player, PI/2) \
.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 joy_forwards := _controller.get_vector2("primary").y
var joy_side := _controller.get_vector2("primary").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_body(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://kyhaogt0a4q8"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_flight.gd" id="1"]
[node name="MovementFlight" type="Node" groups=["movement_providers"]]
script = ExtResource("1")

View File

@ -0,0 +1,245 @@
@tool
class_name XRToolsMovementFootstep
extends XRToolsMovementProvider
## XR Tools Movement Provider for Footsteps
##
## This movement provider detects walking on different surfaces.
## It plays audio sounds associated with the surface the player is
## currently walking on.
## Signal emitted when a footstep is generated
signal footstep(name)
# Number of audio players to pool
const AUDIO_POOL_SIZE := 3
## Movement provider order
@export var order : int = 1001
## Default XRToolsSurfaceAudioType when not overridden
@export var default_surface_audio_type : XRToolsSurfaceAudioType
## Speed at which the player is considered walking
@export var walk_speed := 0.4
## Step per meter by time
@export var steps_per_meter = 1.0
# step time
var step_time = 0.0
# Last on_ground state of the player
var _old_on_ground := true
# Node representing the location of the players foot
var _foot_spatial : Node3D
# Pool of idle AudioStreamPlayer3D nodes
var _audio_pool_idle : Array[AudioStreamPlayer3D]
# Last ground node
var _ground_node : Node
# Surface audio type associated with last ground node
var _ground_node_audio_type : XRToolsSurfaceAudioType
## PlayerBody - Player Physics Body Script
@onready var player_body := XRToolsPlayerBody.find_instance(self)
# Add support for is_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementFootstep" or super(name)
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Construct the foot spatial - we will move it around as the player moves.
_foot_spatial = Node3D.new()
_foot_spatial.name = "FootSpatial"
add_child(_foot_spatial)
# Make the array of players in _audio_pool_idle
for i in AUDIO_POOL_SIZE:
var player = $PlayerSettings.duplicate()
player.name = "PlayerCopy%d" % (i + 1)
_foot_spatial.add_child(player)
_audio_pool_idle.append(player)
player.finished.connect(_on_player_finished.bind(player))
# Set as always active
is_active = true
# Listen for the player jumping
player_body.player_jumped.connect(_on_player_jumped)
# This method checks for configuration issues.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Verify player settings node exists
if not $PlayerSettings:
warnings.append("Missing player settings node")
# Return warnings
return warnings
func physics_movement(_delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Update the spatial location of the foot
_update_foot_spatial()
# Update the ground audio information
_update_ground_audio()
# Skip if footsteps have been disabled
if not enabled:
step_time = 0
return
# Detect landing on ground
if not _old_on_ground and player_body.on_ground:
# Play the ground hit sound
_play_ground_hit()
# Update the old on_ground state
_old_on_ground = player_body.on_ground
if not player_body.on_ground:
step_time = 0 # Reset when not on ground
return
# Handle slow/stopped
if player_body.ground_control_velocity.length() < walk_speed:
step_time = 0 # Reset when slow/stopped
return
# Count up the step timer, and skip if not take a step yet
step_time += _delta * player_body.ground_control_velocity.length()
if step_time > steps_per_meter:
_play_step_sound()
step_time = 0
# Update the foot spatial to be where the players foot is
func _update_foot_spatial() -> void:
# Project the players camera down to the XZ plane (real-world space)
var local_foot := player_body.camera_node.position.slide(Vector3.UP)
# Move the foot_spatial to the local foot in the global origin space
_foot_spatial.global_position = player_body.origin_node.global_transform * local_foot
# Update the ground audio information
func _update_ground_audio() -> void:
# Skip if no change
if player_body.ground_node == _ground_node:
return
# Save the new ground node
_ground_node = player_body.ground_node
# Handle no ground
if not _ground_node:
_ground_node_audio_type = null
return
# Find the surface audio for the ground (if any)
var ground_audio : XRToolsSurfaceAudio = XRTools.find_xr_child(
_ground_node, "*", "XRToolsSurfaceAudio")
if ground_audio:
_ground_node_audio_type = ground_audio.surface_audio_type
else:
_ground_node_audio_type = default_surface_audio_type
# Called when the player jumps
func _on_player_jumped() -> void:
# Skip if no jump sound
if not _ground_node_audio_type:
return
# Play the jump sound
_play_sound(
_ground_node_audio_type.name,
_ground_node_audio_type.jump_sound)
# Play the hit sound made when the player lands on the ground
func _play_ground_hit() -> void:
# Skip if no hit sound
if not _ground_node_audio_type:
return
# Play the hit sound
_play_sound(
_ground_node_audio_type.name,
_ground_node_audio_type.hit_sound)
# Play a step sound for the current ground
func _play_step_sound() -> void:
# Skip if no walk audio
if not _ground_node_audio_type or _ground_node_audio_type.walk_sounds.size() == 0:
return
# Pick the sound index
var idx := randi() % _ground_node_audio_type.walk_sounds.size()
# Pick the playback pitck
var pitch := randf_range(
_ground_node_audio_type.walk_pitch_minimum,
_ground_node_audio_type.walk_pitch_maximum)
# Play the walk sound
_play_sound(
_ground_node_audio_type.name,
_ground_node_audio_type.walk_sounds[idx],
pitch)
# Play the specified audio stream at the requested pitch using an
# AudioStreamPlayer3D in the idle pool of players.
func _play_sound(name : String, stream : AudioStream, pitch : float = 1.0) -> void:
# Skip if no stream provided
if not stream:
return
# Emit the footstep signal
footstep.emit(name)
# Verify we have an audio player
if _audio_pool_idle.size() == 0:
push_warning("XRToolsMovementFootstep idle audio pool empty")
return
# Play the sound
var player : AudioStreamPlayer3D = _audio_pool_idle.pop_front()
player.stream = stream
player.pitch_scale = pitch
player.play()
# Called when an AudioStreamPlayer3D in our pool finishes playing its sound
func _on_player_finished(player : AudioStreamPlayer3D) -> void:
_audio_pool_idle.append(player)
## Find an [XRToolsMovementFootstep] node.
##
## This function searches from the specified node for an [XRToolsMovementFootstep]
## assuming the node is a sibling of the body under an [ARVROrigin].
static func find_instance(node: Node) -> XRToolsMovementFootstep:
return XRTools.find_xr_child(
XRHelpers.get_xr_origin(node),
"*",
"XRToolsMovementFootstep") as XRToolsMovementFootstep

View File

@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://0xlsitpu17r1"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_footstep.gd" id="1"]
[node name="MovementFootstep" type="Node" groups=["movement_providers"]]
script = ExtResource("1")
[node name="PlayerSettings" type="AudioStreamPlayer3D" parent="."]

View File

@ -0,0 +1,236 @@
@tool
class_name XRToolsMovementGlide
extends XRToolsMovementProvider
## XR Tools Movement Provider for Gliding
##
## This script provides glide mechanics for the player. This script works
## with the [XRToolsPlayerBody] attached to the players [XROrigin3D].
##
## The player enables flying by moving the controllers apart further than
## 'glide_detect_distance'.
##
## When gliding, the players fall speed will slew to 'glide_fall_speed' and
## the velocity will slew to 'glide_forward_speed' in the direction the
## player is facing.
##
## Gliding is an exclusive motion operation, and so gliding should be ordered
## after any Direct movement providers responsible for turning.
## Signal invoked when the player starts gliding
signal player_glide_start
## Signal invoked when the player ends gliding
signal player_glide_end
## Signal invoked when the player flaps
signal player_flapped
## Movement provider order
@export var order : int = 35
## Controller separation distance to register as glide
@export var glide_detect_distance : float = 1.0
## Minimum falling speed to be considered gliding
@export var glide_min_fall_speed : float = -1.5
## Glide falling speed
@export var glide_fall_speed : float = -2.0
## Glide forward speed
@export var glide_forward_speed : float = 12.0
## Slew rate to transition to gliding
@export var horizontal_slew_rate : float = 1.0
## Slew rate to transition to gliding
@export var vertical_slew_rate : float = 2.0
## glide rotate with roll angle
@export var turn_with_roll : bool = false
## Smooth turn speed in radians per second
@export var roll_turn_speed : float = 1
## Add vertical impulse by flapping controllers
@export var wings_impulse : bool = false
## Minimum velocity for flapping
@export var flap_min_speed : float = 0.3
## Flapping force multiplier
@export var wings_force : float = 1.0
## Minimum distance from controllers to ARVRCamera to rearm flaps.
## if set to 0, you need to reach head level with hands to rearm flaps
@export var rearm_distance_offset : float = 0.2
## Flap activated (when both controllers are near the ARVRCamera height)
var flap_armed : bool = false
## Last controllers position to calculate flapping velocity
var last_local_left_position : Vector3
var last_local_right_position : Vector3
# True if the controller positions are valid
var _has_controller_positions : bool = false
# Left controller
@onready var _left_controller := XRHelpers.get_left_controller(self)
# Right controller
@onready var _right_controller := XRHelpers.get_right_controller(self)
# ARVRCamera
@onready var _camera_node := XRHelpers.get_xr_camera(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementGlide" or super(name)
func physics_movement(delta: float, player_body: XRToolsPlayerBody, disabled: bool):
# Skip if disabled or either controller is off
if disabled or !enabled or \
!_left_controller.get_is_active() or \
!_right_controller.get_is_active():
_set_gliding(false)
return
# If on the ground, then not gliding
if player_body.on_ground:
_set_gliding(false)
return
# Get the controller left and right global horizontal positions
var left_position := _left_controller.global_transform.origin
var right_position := _right_controller.global_transform.origin
# Set default wings impulse to zero
var wings_impulse_velocity := 0.0
# If wings impulse is active, calculate flapping impulse
if wings_impulse:
# Check controllers position relative to head
var cam_local_y := _camera_node.position.y
var left_hand_over_head = cam_local_y < _left_controller.position.y + rearm_distance_offset
var right_hand_over_head = cam_local_y < _right_controller.position.y + rearm_distance_offset
if left_hand_over_head && right_hand_over_head:
flap_armed = true
if flap_armed:
# Get controller local positions
var local_left_position := _left_controller.position
var local_right_position := _right_controller.position
# Store last frame controller positions for the first step
if not _has_controller_positions:
_has_controller_positions = true
last_local_left_position = local_left_position
last_local_right_position = local_right_position
# Calculate controllers velocity only when flapping downwards
var left_wing_velocity = 0.0
var right_wing_velocity = 0.0
if local_left_position.y < last_local_left_position.y:
left_wing_velocity = local_left_position.distance_to(last_local_left_position) / delta
if local_right_position.y < last_local_right_position.y:
right_wing_velocity = local_right_position.distance_to(last_local_right_position) / delta
# Calculate wings impulse
if left_wing_velocity > flap_min_speed && right_wing_velocity > flap_min_speed:
wings_impulse_velocity = (left_wing_velocity + right_wing_velocity) / 2
wings_impulse_velocity = wings_impulse_velocity * wings_force * delta * 50
emit_signal("player_flapped")
flap_armed = false
# Store controller position for next frame
last_local_left_position = local_left_position
last_local_right_position = local_right_position
# Calculate global left to right controller vector
var left_to_right := right_position - left_position
if turn_with_roll:
var angle = -left_to_right.dot(player_body.up_player)
player_body.rotate_player(roll_turn_speed * delta * angle)
# If not falling, then not gliding
var vertical_velocity := player_body.velocity.dot(player_body.up_gravity)
vertical_velocity += wings_impulse_velocity
if vertical_velocity >= glide_min_fall_speed && wings_impulse_velocity == 0.0:
_set_gliding(false)
return
# Set gliding based on hand separation
var separation := left_to_right.length() / XRServer.world_scale
_set_gliding(separation >= glide_detect_distance)
# Skip if not gliding
if !is_active:
return
# Lerp the vertical velocity to glide_fall_speed
vertical_velocity = lerp(vertical_velocity, glide_fall_speed, vertical_slew_rate * delta)
# Lerp the horizontal velocity towards forward_speed
var horizontal_velocity := player_body.velocity.slide(player_body.up_gravity)
var dir_forward := left_to_right \
.rotated(player_body.up_gravity, PI/2) \
.slide(player_body.up_gravity) \
.normalized()
var forward_velocity := dir_forward * glide_forward_speed
horizontal_velocity = horizontal_velocity.lerp(forward_velocity, horizontal_slew_rate * delta)
# Perform the glide
var glide_velocity := horizontal_velocity + vertical_velocity * player_body.up_gravity
player_body.velocity = player_body.move_body(glide_velocity)
# Report exclusive motion performed (to bypass gravity)
return true
# Set the gliding state and fire any signals
func _set_gliding(active: bool) -> void:
# Skip if no change
if active == is_active:
return
# Update the is_gliding flag
is_active = active;
# Report transition
if is_active:
emit_signal("player_glide_start")
else:
emit_signal("player_glide_end")
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# 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 right XRController3D node")
# Check glide parameters
if glide_min_fall_speed > 0:
warnings.append("Glide minimum fall speed must be zero or less")
if glide_fall_speed > 0:
warnings.append("Glide fall speed must be zero or less")
if glide_min_fall_speed < glide_fall_speed:
warnings.append("Glide fall speed must be faster than minimum fall speed")
# Return warnings
return warnings

View File

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

View File

@ -0,0 +1,243 @@
@tool
class_name XRToolsMovementGrapple
extends XRToolsMovementProvider
## XR Tools Movement Provider for Grapple Movement
##
## This script provide simple grapple based movement - "bat hook" style
## where the player flings a rope to the target and swings on it.
## This script works with the [XRToolsPlayerBody] attached to the players
## [XROrigin3D].
## Signal emitted when grapple starts
signal grapple_started()
## Signal emitted when grapple finishes
signal grapple_finished()
## Grapple state
enum GrappleState {
IDLE, ## Grapple is idle
FIRED, ## Grapple is fired
WINCHING, ## Grapple is winching
}
# Default grapple collision mask of 1-5 (world)
const DEFAULT_COLLISION_MASK := 0b0000_0000_0000_0000_0000_0000_0001_1111
# Default grapple enable mask of 5:grapple-target
const DEFAULT_ENABLE_MASK := 0b0000_0000_0000_0000_0000_0000_0001_0000
## Movement provider order
@export var order : int = 20
## Grapple length - use to adjust maximum distance for possible grapple hooking.
@export var grapple_length : float = 15.0
## Grapple collision mask
@export_flags_3d_physics var grapple_collision_mask : int = DEFAULT_COLLISION_MASK:
set = _set_grapple_collision_mask
## Grapple enable mask
@export_flags_3d_physics var grapple_enable_mask : int = DEFAULT_ENABLE_MASK
## Impulse speed applied to the player on first grapple
@export var impulse_speed : float = 10.0
## Winch speed applied to the player while the grapple is held
@export var winch_speed : float = 2.0
## Probably need to add export variables for line size, maybe line material at
## some point so dev does not need to make children editable to do this.
## For now, right click on grapple node and make children editable to edit these
## facets.
@export var rope_width : float = 0.02
## Air friction while grappling
@export var friction : float = 0.1
## Grapple button (triggers grappling movement). Be sure this button does not
## conflict with other functions.
@export var grapple_button_action : String = "trigger_click"
# Hook related variables
var hook_object : Node3D = null
var hook_local := Vector3(0,0,0)
var hook_point := Vector3(0,0,0)
# Grapple button state
var _grapple_button := false
# Get line creation nodes
@onready var _line_helper : Node3D = $LineHelper
@onready var _line : CSGCylinder3D = $LineHelper/Line
# Get Controller node - consider way to universalize this if user wanted to
# attach this to a gun instead of player's hand. Could consider variable to
# select controller instead.
@onready var _controller := XRHelpers.get_xr_controller(self)
# Get Raycast node
@onready var _grapple_raycast : RayCast3D = $Grapple_RayCast
# Get Grapple Target Node
@onready var _grapple_target : Node3D = $Grapple_Target
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementGrapple" or super(name)
# Function run when node is added to scene
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Skip if running in the editor
if Engine.is_editor_hint():
return
# Ensure grapple length is valid
var min_hook_length := 1.5 * XRServer.world_scale
if grapple_length < min_hook_length:
grapple_length = min_hook_length
# Set ray-cast
_grapple_raycast.target_position = Vector3(0, 0, -grapple_length) * XRServer.world_scale
_grapple_raycast.collision_mask = grapple_collision_mask
# Deal with line
_line.radius = rope_width
_line.hide()
# Update grapple display objects
func _process(_delta: float):
# Skip if running in the editor
if Engine.is_editor_hint():
return
# Update grapple line
if is_active:
var line_length := (hook_point - _controller.global_transform.origin).length()
_line_helper.look_at(hook_point, Vector3.UP)
_line.height = line_length
_line.position.z = line_length / -2
_line.visible = true
else:
_line.visible = false
# Update grapple target
if enabled and !is_active and _is_raycast_valid():
_grapple_target.global_transform.origin = _grapple_raycast.get_collision_point()
_grapple_target.global_transform = _grapple_target.global_transform.orthonormalized()
_grapple_target.visible = true
else:
_grapple_target.visible = false
# Perform grapple movement
func physics_movement(delta: float, player_body: XRToolsPlayerBody, disabled: bool):
# Disable if requested
if disabled or !enabled or !_controller.get_is_active():
_set_grappling(false)
return
# Update grapple button
var old_grapple_button := _grapple_button
_grapple_button = _controller.is_button_pressed(grapple_button_action)
# Enable/disable grappling
var do_impulse := false
if is_active and !_grapple_button:
_set_grappling(false)
elif _grapple_button and !old_grapple_button and _is_raycast_valid():
hook_object = _grapple_raycast.get_collider()
hook_point = _grapple_raycast.get_collision_point()
hook_local = hook_point * hook_object.global_transform
do_impulse = true
_set_grappling(true)
# Skip if not grappling
if !is_active:
return
# Get hook direction
hook_point = hook_object.global_transform * hook_local
var hook_vector := hook_point - _controller.global_transform.origin
var hook_length := hook_vector.length()
var hook_direction := hook_vector / hook_length
# Apply gravity
player_body.velocity += player_body.gravity * delta
# Select the grapple speed
var speed := impulse_speed if do_impulse else winch_speed
if hook_length < 1.0:
speed = 0.0
# Ensure velocity is at least winch_speed towards hook
var vdot = player_body.velocity.dot(hook_direction)
if vdot < speed:
player_body.velocity += hook_direction * (speed - vdot)
# Scale down velocity
player_body.velocity *= 1.0 - friction * delta
# Perform exclusive movement as we have dealt with gravity
player_body.velocity = player_body.move_body(player_body.velocity)
return true
# Called when the grapple collision mask has been modified
func _set_grapple_collision_mask(new_value: int) -> void:
grapple_collision_mask = new_value
if is_inside_tree() and _grapple_raycast:
_grapple_raycast.collision_mask = new_value
# Set the grappling state and fire any signals
func _set_grappling(active: bool) -> void:
# Skip if no change
if active == is_active:
return
# Update the is_active flag
is_active = active;
# Report transition
if is_active:
emit_signal("grapple_started")
else:
emit_signal("grapple_finished")
# Test if the raycast is striking a valid target
func _is_raycast_valid() -> bool:
# Fail if raycast not colliding
if not _grapple_raycast.is_colliding():
return false
# Get the target of the raycast
var target : CollisionObject3D = _grapple_raycast.get_collider()
# Check tartget layer
return true if target.collision_layer & grapple_enable_mask else false
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Check the controller node
if !XRHelpers.get_xr_controller(self):
warnings.append("This node must be within a branch of an XRController3D node")
# Return warnings
return warnings

View File

@ -0,0 +1,28 @@
[gd_scene load_steps=4 format=3 uid="uid://c78tjrtiyqna8"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_grapple.gd" id="1"]
[ext_resource type="Material" path="res://addons/godot-xr-tools/materials/pointer.tres" id="2_n6olo"]
[sub_resource type="BoxMesh" id="1"]
resource_local_to_scene = true
size = Vector3(0.05, 0.05, 0.05)
subdivide_depth = 20
[node name="MovementGrapple" type="Node3D" groups=["movement_providers"]]
script = ExtResource("1")
[node name="Grapple_RayCast" type="RayCast3D" parent="."]
collision_mask = 3
debug_shape_custom_color = Color(0.862745, 0.278431, 0.278431, 1)
debug_shape_thickness = 1
[node name="Grapple_Target" type="MeshInstance3D" parent="."]
visible = false
mesh = SubResource("1")
surface_material_override/0 = ExtResource("2_n6olo")
[node name="LineHelper" type="Node3D" parent="."]
[node name="Line" type="CSGCylinder3D" parent="LineHelper"]
transform = Transform3D(1.91069e-15, 4.37114e-08, 1, 1, -4.37114e-08, 0, 4.37114e-08, 1, -4.37114e-08, 0, 0, 0)
radius = 0.02

View File

@ -0,0 +1,52 @@
@tool
class_name XRToolsMovementJump
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 = "trigger_click"
# Node references
@onready var _controller := XRHelpers.get_xr_controller(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementJump" 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 !_controller.get_is_active():
return
# Request jump if the button is pressed
if _controller.is_button_pressed(jump_button_action):
player_body.request_jump()
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Check the controller node
if !XRHelpers.get_xr_controller(self):
warnings.append("This node must be within a branch of an XRController3D node")
# Return warnings
return warnings

View File

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

View File

@ -0,0 +1,197 @@
@tool
class_name XRToolsMovementPhysicalJump
extends XRToolsMovementProvider
## XR Tools Movement Provider for Player Physical Jump Detection
##
## This script can detect jumping based on either the players body jumping,
## or by the player swinging their arms up.
##
## The player body jumping is detected by putting the cameras instantaneous
## Y velocity (in the tracking space) into a sliding-window averager. If the
## average Y velocity exceeds a threshold parameter then the player has
## jumped.
##
## The player arms jumping is detected by putting both controllers instantaneous
## Y velocity (in the tracking space) into a sliding-window averager. If both
## average Y velocities exceed a threshold parameter then the player has
## jumped.
## Movement provider order
@export var order : int = 20
## If true, jumps are detected via the players body (through the camera)
@export var body_jump_enable : bool = true
## If true, the player jump is as high as the physical jump(no ground physics)
@export var body_jump_player_only : bool = false
## Body jump detection threshold (M/S^2)
@export var body_jump_threshold : float = 2.5
## If true, jumps are detected via the players arms (through the controllers)
@export var arms_jump_enable : bool = false
## Arms jump detection threshold (M/S^2)
@export var arms_jump_threshold : float = 5.0
# Node Positions
var _camera_position : float = 0.0
var _controller_left_position : float = 0.0
var _controller_right_position : float = 0.0
# Node Velocities
var _camera_velocity : SlidingAverage = SlidingAverage.new(5)
var _controller_left_velocity : SlidingAverage = SlidingAverage.new(5)
var _controller_right_velocity : SlidingAverage = SlidingAverage.new(5)
# Node references
@onready var _origin_node := XRHelpers.get_xr_origin(self)
@onready var _camera_node := XRHelpers.get_xr_camera(self)
@onready var _controller_left_node := XRHelpers.get_left_controller(self)
@onready var _controller_right_node := XRHelpers.get_right_controller(self)
# Sliding Average class
class SlidingAverage:
# Sliding window size
var _size: int
# Sum of items in the window
var _sum := 0.0
# Position
var _pos := 0
# Data window
var _data := Array()
# Constructor
func _init(size: int):
# Set the size and fill the array
_size = size
for i in size:
_data.push_back(0.0)
# Update the average
func update(entry: float) -> float:
# Add the new entry and subtract the old
_sum += entry
_sum -= _data[_pos]
# Store the new entry in the array and circularly advance the index
_data[_pos] = entry;
_pos = (_pos + 1) % _size
# Return the average
return _sum / _size
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementPhysicalJump" or super(name)
# Perform jump detection
func physics_movement(delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Handle detecting body jump
if body_jump_enable:
_detect_body_jump(delta, player_body)
# Handle detecting arms jump
if arms_jump_enable:
_detect_arms_jump(delta, player_body)
# 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_origin(self):
warnings.append("This node must be within a branch of an XROrigin3D node")
# 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
# Detect the player jumping with their body (using the headset camera)
func _detect_body_jump(delta: float, player_body: XRToolsPlayerBody) -> void:
# Get the camera instantaneous velocity
var new_camera_pos := _camera_node.transform.origin.y
var camera_vel := (new_camera_pos - _camera_position) / delta
_camera_position = new_camera_pos
# Ignore zero moves (either not tracking, or no update since last physics)
if abs(camera_vel) < 0.001:
return;
# Correct for world-scale (convert to player units)
camera_vel /= XRServer.world_scale
# Clamp the camera instantaneous velocity to +/- 2x the jump threshold
camera_vel = clamp(camera_vel, -2.0 * body_jump_threshold, 2.0 * body_jump_threshold)
# Get the averaged velocity
camera_vel = _camera_velocity.update(camera_vel)
# Detect a jump
if camera_vel >= body_jump_threshold:
player_body.request_jump(body_jump_player_only)
# Detect the player jumping with their arms (using the controllers)
func _detect_arms_jump(delta: float, player_body: XRToolsPlayerBody) -> void:
# Skip if either of the controllers is disabled
if !_controller_left_node.get_is_active() or !_controller_right_node.get_is_active():
return
# Get the controllers instantaneous velocity
var new_controller_left_pos := _controller_left_node.transform.origin.y
var new_controller_right_pos := _controller_right_node.transform.origin.y
var controller_left_vel := (new_controller_left_pos - _controller_left_position) / delta
var controller_right_vel := (new_controller_right_pos - _controller_right_position) / delta
_controller_left_position = new_controller_left_pos
_controller_right_position = new_controller_right_pos
# Ignore zero moves (either not tracking, or no update since last physics)
if abs(controller_left_vel) <= 0.001 and abs(controller_right_vel) <= 0.001:
return
# Correct for world-scale (convert to player units)
controller_left_vel /= XRServer.world_scale
controller_right_vel /= XRServer.world_scale
# Clamp the controller instantaneous velocity to +/- 2x the jump threshold
controller_left_vel = clamp(
controller_left_vel,
-2.0 * arms_jump_threshold,
2.0 * arms_jump_threshold)
controller_right_vel = clamp(
controller_right_vel,
-2.0 * arms_jump_threshold,
2.0 * arms_jump_threshold)
# Get the averaged velocity
controller_left_vel = _controller_left_velocity.update(controller_left_vel)
controller_right_vel = _controller_right_velocity.update(controller_right_vel)
# Detect a jump
if controller_left_vel >= arms_jump_threshold and controller_right_vel >= arms_jump_threshold:
player_body.request_jump()

View File

@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://ckt118vcpmr6q"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_physical_jump.gd" id="1"]
[node name="MovementPhysicalJump" type="Node" groups=["movement_providers"]]
script = ExtResource("1")
arms_jump_enable = true

View File

@ -0,0 +1,93 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/movement_provider.svg")
class_name XRToolsMovementProvider
extends Node
## XR Tools Movement Provider base class
##
## This movement provider class is the base class of all movement providers.
## Movement providers are invoked by the [XRToolsPlayerBody] object in order
## to apply motion to the player.
##
## Movement provider implementations should:
## - Export an 'order' integer to control order of processing
## - Override the physics_movement method to impelment motion
## Player body scene
const PLAYER_BODY := preload("res://addons/godot-xr-tools/player/player_body.tscn")
## Enable movement provider
@export var enabled : bool = true
## If true, the movement provider is actively performing a move
var is_active := false
# If missing we need to add our [XRToolsPlayerBody]
func _create_player_body_node():
# get our origin node
var xr_origin = XRHelpers.get_xr_origin(self)
if !xr_origin:
return
# Double check if it hasn't already been created by another movement function
var player_body := XRToolsPlayerBody.find_instance(self)
if !player_body:
# create our XRToolsPlayerBody node and add it into our tree
player_body = PLAYER_BODY.instantiate()
player_body.set_name("PlayerBody")
xr_origin.add_child(player_body)
player_body.set_owner(get_tree().get_edited_scene_root())
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementProvider"
# Function run when node is added to scene
func _ready():
# If we're in the editor, help the user out by creating our XRToolsPlayerBody node
# automatically when needed.
if Engine.is_editor_hint():
var player_body = XRToolsPlayerBody.find_instance(self)
if !player_body:
# This call needs to be deferred, we can't add nodes during scene construction
call_deferred("_create_player_body_node")
## Override this method to perform pre-movement updates to the PlayerBody
func physics_pre_movement(_delta: float, _player_body: XRToolsPlayerBody):
pass
## Override this method to apply motion to the PlayerBody
func physics_movement(_delta: float, _player_body: XRToolsPlayerBody, _disabled: bool):
pass
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Verify we're within the tree of an XROrigin3D node
if !XRHelpers.get_xr_origin(self):
warnings.append("This node must be within a branch on an XROrigin3D node")
if !XRToolsPlayerBody.find_instance(self):
warnings.append("Missing PlayerBody node on the XROrigin3D")
# Verify movement provider is in the correct group
if !is_in_group("movement_providers"):
warnings.append("Movement provider not in 'movement_providers' group")
# Verify order property exists
if !"order" in self:
warnings.append("Movement provider does not expose an order property")
# Return warnings
return warnings

View File

@ -0,0 +1,169 @@
@tool
class_name XRToolsMovementSprint
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 controller to use for triggering sprinting. This allows the
## developer to assign the sprint button to either controller.
enum SprintController {
LEFT, ## Use left controller
RIGHT, ## Use right controler
}
## 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 controller
@export var controller : SprintController = SprintController.LEFT
## Sprint button
@export var sprint_button : String = "primary_click"
# Sprint controller
var _controller : XRController3D
# Sprint button down state
var _sprint_button_down : bool = false
# Variable to hold left controller direct movement node original max speed
var _left_controller_original_max_speed : float = 0.0
# Variable to hold right controller direct movement node original max speed
var _right_controller_original_max_speed : float = 0.0
# Variable used to cache left controller direct movement function, if any
@onready var _left_controller_direct_move := XRToolsMovementDirect.find_left(self)
# Variable used to cache right controller direct movement function, if any
@onready var _right_controller_direct_move := XRToolsMovementDirect.find_right(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementSprint" or super(name)
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Get the sprinting controller
if controller == SprintController.LEFT:
_controller = XRHelpers.get_left_controller(self)
else:
_controller = XRHelpers.get_right_controller(self)
# Perform sprinting
func physics_movement(_delta: float, _player_body: XRToolsPlayerBody, disabled: bool):
# Skip if the controller isn't active or is not enabled
if !_controller.get_is_active() or disabled == true or !enabled:
set_sprinting(false)
return
# Detect sprint button down and pressed states
var sprint_button_down := _controller.is_button_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 _left_controller_direct_move:
_left_controller_original_max_speed = _left_controller_direct_move.max_speed
if _right_controller_direct_move:
_right_controller_original_max_speed = _right_controller_direct_move.max_speed
# Set both controllers' direct movement functions, if appliable, to
# the sprinting speed
if _left_controller_direct_move:
_left_controller_direct_move.max_speed = \
_left_controller_original_max_speed * sprint_speed_multiplier
if _right_controller_direct_move:
_right_controller_direct_move.max_speed = \
_right_controller_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 _left_controller_direct_move:
_left_controller_direct_move.max_speed = _left_controller_original_max_speed
if _right_controller_direct_move:
_right_controller_direct_move.max_speed = _right_controller_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 !XRToolsMovementDirect.find_left(self) and !XRToolsMovementDirect.find_right(self):
warnings.append("Player missing XRToolsMovementDirect nodes")
# Return warnings
return warnings

View File

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

View File

@ -0,0 +1,111 @@
@tool
class_name XRToolsMovementTurn
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 = 5
## Movement mode property
@export var turn_mode : TurnMode = TurnMode.DEFAULT
## 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"
# Turn step accumulator
var _turn_step : float = 0.0
# Controller node
@onready var _controller := XRHelpers.get_xr_controller(self)
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementTurn" or super(name)
# Perform jump movement
func physics_movement(delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Skip if the controller isn't active
if !_controller.get_is_active():
return
var deadzone = 0.1
if _snap_turning():
deadzone = XRTools.get_snap_turning_deadzone()
# Read the left/right joystick axis
var left_right := _controller.get_vector2(input_action).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)
return
# Disable repeat snap turning if delay is zero
if step_turn_delay == 0.0 and _turn_step < 0.0:
return
# Update the next turn-step delay
_turn_step -= abs(left_right) * delta
if _turn_step >= 0.0:
return
# Turn one step in the requested direction
if step_turn_delay != 0.0:
_turn_step = step_turn_delay
player_body.rotate_player(deg_to_rad(step_turn_angle) * sign(left_right))
# This method verifies the movement provider has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()
# Check the controller node
if !XRHelpers.get_xr_controller(self):
warnings.append("Unable to find XRController3D node")
# Return warnings
return warnings
# Test if snap turning should be used
func _snap_turning():
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://b6bk2pj8vbj28"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_turn.gd" id="1"]
[node name="MovementTurn" type="Node" groups=["movement_providers"]]
script = ExtResource("1")

View File

@ -0,0 +1,44 @@
@tool
class_name XRToolsMovementWallWalk
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 = 25
## Set our follow layer mask
@export_flags_3d_physics var follow_mask : int = DEFAULT_MASK
## Wall stick distance
@export var stick_distance : float = 1.0
## Wall stick strength
@export var stick_strength : float = 9.8
func physics_pre_movement(_delta: float, player_body: XRToolsPlayerBody):
# Test for collision with wall under feet
var wall_collision := player_body.move_and_collide(
player_body.up_player * -stick_distance, true, true, true)
if !wall_collision:
return
# Get the wall information
var wall_node := wall_collision.get_collider()
var wall_normal := wall_collision.get_normal()
# Skip if the wall node doesn't have a collision layer
if not "collision_layer" in wall_node:
return
# Skip if the wall doesn't match the follow layer
var wall_layer : int = wall_node.collision_layer
if (wall_layer & follow_mask) == 0:
return
# Modify the player gravity
player_body.gravity = -wall_normal * stick_strength

View File

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

View File

@ -0,0 +1,135 @@
@tool
class_name XRToolsMovementWind
extends XRToolsMovementProvider
## XR Tools Movement Provider for Wind
##
## This script provides wind mechanics for the player. This script works
## with the [XRToolsPlayerBody] attached to the players [XROrigin3D].
##
## When the player enters an [XRToolsWindArea], the wind pushes the player
## around, and can even lift the player into the air.
## Signal invoked when changing active wind areas
signal wind_area_changed(active_wind_area)
# Default wind area collision mask of 20:player-body
const DEFAULT_MASK := 0b0000_0000_0000_1000_0000_0000_0000_0000
## Movement provider order
@export var order : int = 25
## Drag multiplier for the player
@export var drag_multiplier : float = 1.0
# Set our collision mask
@export_flags_3d_physics var collision_mask : int = DEFAULT_MASK: set = set_collision_mask
# Wind detection area
var _sense_area : Area3D
# Array of wind areas the player is in
var _in_wind_areas := Array()
# Currently active wind area
var _active_wind_area : XRToolsWindArea
# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsMovementWind" or super(name)
# Called when the node enters the scene tree for the first time.
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Skip if running in the editor
if Engine.is_editor_hint():
return
# Skip if we don't have a camera
var camera := XRHelpers.get_xr_camera(self)
if !camera:
return
# Construct the sphere shape
var sphere_shape := SphereShape3D.new()
sphere_shape.radius = 0.3
# Construct the collision shape
var collision_shape := CollisionShape3D.new()
collision_shape.set_name("WindSensorShape")
collision_shape.shape = sphere_shape
# Construct the sense area
_sense_area = Area3D.new()
_sense_area.set_name("WindSensorArea")
_sense_area.collision_mask = collision_mask
_sense_area.add_child(collision_shape)
# Add the sense area to the camera
camera.add_child(_sense_area)
# Subscribe to area notifications
_sense_area.area_entered.connect(_on_area_entered)
_sense_area.area_exited.connect(_on_area_exited)
func set_collision_mask(new_mask: int) -> void:
collision_mask = new_mask
if is_inside_tree() and _sense_area:
_sense_area.collision_mask = collision_mask
func _on_area_entered(area: Area3D):
# Skip if not wind area
var wind_area = area as XRToolsWindArea
if !wind_area:
return
# Save area and set active
_in_wind_areas.push_front(wind_area)
_active_wind_area = wind_area
# Report the wind area change
emit_signal("wind_area_changed", _active_wind_area)
func _on_area_exited(area: Area3D):
# Erase from the wind area
_in_wind_areas.erase(area)
# If we didn't leave the active wind area then we're done
if area != _active_wind_area:
return
# Select a new active wind area
if _in_wind_areas.is_empty():
_active_wind_area = null
else:
_active_wind_area = _in_wind_areas.front()
# Report the wind area change
emit_signal("wind_area_changed", _active_wind_area)
# Perform wind movement
func physics_movement(delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
# Skip if no active wind area
if !_active_wind_area:
return
# Calculate the global wind velocity of the wind area
var wind_velocity := _active_wind_area.global_transform.basis * _active_wind_area.wind_vector
# Drag the player into the wind
var drag_factor := _active_wind_area.drag * drag_multiplier * delta
drag_factor = clamp(drag_factor, 0.0, 1.0)
player_body.velocity = player_body.velocity.lerp(wind_velocity, drag_factor)

View File

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

View File

@ -0,0 +1,17 @@
The Hand Mesh was made using the Makehuman Community Edition
It was slightly modified inside Blender 3D and textured inside Substance Painter
If there should be concerns regarding License issues
https://github.com/makehumancommunity/makehuman/blob/master/LICENSE.md
under point D of the makehuman communtiy edition license
https://github.com/makehumancommunity/makehuman/blob/master/LICENSE.md#d-concerning-the-output-from-makehuman
As the assets have been released under CC0, there is no limitation on what you can do with this combined output.
The MakeHuman project makes no claim whatsoever over output such as:
Exports to files (FBX, OBJ, DAE, MHX2...)
Exports via direct integration (import via MPFB)
Graphical data generated via scripting or plugins
Renderings
Screenshots
Saved model files

View File

@ -0,0 +1,126 @@
To the extent possible under law, DigitalN8m4r3 aka Miodrag Sejic
has waived all copyright and related or neighboring rights to Hand Models for the Godot XR Tools.
This work is published from: Austria.
Date: 7th November 2022
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.

View File

@ -0,0 +1,81 @@
[gd_scene load_steps=37 format=3 uid="uid://the6y7swe6j0"]
[ext_resource type="Animation" uid="uid://dgfeikrugfewi" path="res://addons/godot-xr-tools/hands/animations/left/Cup.res" id="1_7svyl"]
[ext_resource type="Animation" uid="uid://dlxa6f6hwurka" path="res://addons/godot-xr-tools/hands/animations/left/Default pose.res" id="2_ykhfd"]
[ext_resource type="Animation" uid="uid://dqa0h82y3qn1t" path="res://addons/godot-xr-tools/hands/animations/left/Grip 1.res" id="3_y80ds"]
[ext_resource type="Animation" uid="uid://di384xtde8ydf" path="res://addons/godot-xr-tools/hands/animations/left/Grip 2.res" id="4_1nbr8"]
[ext_resource type="Animation" uid="uid://dd67rufxwj2u" path="res://addons/godot-xr-tools/hands/animations/left/Grip 3.res" id="5_28lp1"]
[ext_resource type="Animation" uid="uid://db62hs5s4n2b3" path="res://addons/godot-xr-tools/hands/animations/left/Grip 4.res" id="6_uj648"]
[ext_resource type="Animation" uid="uid://bediglpx0rj7i" path="res://addons/godot-xr-tools/hands/animations/left/Grip 5.res" id="7_yxjeh"]
[ext_resource type="Animation" uid="uid://nq3xh1olqipq" path="res://addons/godot-xr-tools/hands/animations/left/Grip Shaft.res" id="8_i0ro5"]
[ext_resource type="Animation" uid="uid://plad1r85f7ws" path="res://addons/godot-xr-tools/hands/animations/left/Grip.res" id="9_811qe"]
[ext_resource type="Animation" uid="uid://bi1l6lre2w2lp" path="res://addons/godot-xr-tools/hands/animations/left/Hold.res" id="10_o5u3q"]
[ext_resource type="Animation" uid="uid://c3e6h0rv2uw2d" path="res://addons/godot-xr-tools/hands/animations/left/Horns.res" id="11_i2b8g"]
[ext_resource type="Animation" uid="uid://dfekure1r6q13" path="res://addons/godot-xr-tools/hands/animations/left/Metal.res" id="12_yoig6"]
[ext_resource type="Animation" uid="uid://b0rhk4r0r0t32" path="res://addons/godot-xr-tools/hands/animations/left/Middle.res" id="13_kvhwh"]
[ext_resource type="Animation" uid="uid://f5k0gh4qnmv5" path="res://addons/godot-xr-tools/hands/animations/left/OK.res" id="14_ofab3"]
[ext_resource type="Animation" uid="uid://1nlkfvitq7ku" path="res://addons/godot-xr-tools/hands/animations/left/Peace.res" id="15_a4q3y"]
[ext_resource type="Animation" uid="uid://dhjb0e334tfwl" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Flat.res" id="16_ixe2d"]
[ext_resource type="Animation" uid="uid://dkjsnihi81b7p" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Large.res" id="17_oifug"]
[ext_resource type="Animation" uid="uid://bn0fdhe2jwq3h" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Middle.res" id="18_2ic2e"]
[ext_resource type="Animation" uid="uid://bo1b8w0s4ci81" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Ring.res" id="19_4a8v4"]
[ext_resource type="Animation" uid="uid://m5x2m8x3tcel" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Tight.res" id="20_ugc01"]
[ext_resource type="Animation" uid="uid://fi23m6i7orhw" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Up.res" id="21_l3kgg"]
[ext_resource type="Animation" uid="uid://c8qmcuyaltdnw" path="res://addons/godot-xr-tools/hands/animations/left/PingPong.res" id="22_j83er"]
[ext_resource type="Animation" uid="uid://bqnoubqq7ogwu" path="res://addons/godot-xr-tools/hands/animations/left/Pinky.res" id="23_sm4sd"]
[ext_resource type="Animation" uid="uid://ddbo6ioa282en" path="res://addons/godot-xr-tools/hands/animations/left/Pistol.res" id="24_r4kss"]
[ext_resource type="Animation" uid="uid://brkptjihht3ae" path="res://addons/godot-xr-tools/hands/animations/left/Ring.res" id="25_2ncut"]
[ext_resource type="Animation" uid="uid://cnng6xumhw7cx" path="res://addons/godot-xr-tools/hands/animations/left/Rounded.res" id="26_02lfv"]
[ext_resource type="Animation" uid="uid://cevirj0eagdrq" path="res://addons/godot-xr-tools/hands/animations/left/Sign 1.res" id="27_o5si3"]
[ext_resource type="Animation" uid="uid://cc6phxovf1ban" path="res://addons/godot-xr-tools/hands/animations/left/Sign 2.res" id="28_ble5k"]
[ext_resource type="Animation" uid="uid://ohthjp8qbcc4" path="res://addons/godot-xr-tools/hands/animations/left/Sign 3.res" id="29_twq1x"]
[ext_resource type="Animation" uid="uid://dmx42g64577g5" path="res://addons/godot-xr-tools/hands/animations/left/Sign 4.res" id="30_eyeuv"]
[ext_resource type="Animation" uid="uid://dhsoxntrktx0p" path="res://addons/godot-xr-tools/hands/animations/left/Sign 5.res" id="31_50jrs"]
[ext_resource type="Animation" uid="uid://c0u2a3yc2vhg8" path="res://addons/godot-xr-tools/hands/animations/left/Sign_Point.res" id="32_7v122"]
[ext_resource type="Animation" uid="uid://4g211my0hoiw" path="res://addons/godot-xr-tools/hands/animations/left/Straight.res" id="33_m3lif"]
[ext_resource type="Animation" uid="uid://d06l7hygl4qt3" path="res://addons/godot-xr-tools/hands/animations/left/Surfer.res" id="34_b3p10"]
[ext_resource type="Animation" uid="uid://bxei4oebd4hu3" path="res://addons/godot-xr-tools/hands/animations/left/Thumb.res" id="35_huikq"]
[sub_resource type="AnimationLibrary" id="AnimationLibrary_kw48d"]
_data = {
"Cup": ExtResource("1_7svyl"),
"Default pose": ExtResource("2_ykhfd"),
"Grip": ExtResource("9_811qe"),
"Grip 1": ExtResource("3_y80ds"),
"Grip 2": ExtResource("4_1nbr8"),
"Grip 3": ExtResource("5_28lp1"),
"Grip 4": ExtResource("6_uj648"),
"Grip 5": ExtResource("7_yxjeh"),
"Grip Shaft": ExtResource("8_i0ro5"),
"Hold": ExtResource("10_o5u3q"),
"Horns": ExtResource("11_i2b8g"),
"Metal": ExtResource("12_yoig6"),
"Middle": ExtResource("13_kvhwh"),
"OK": ExtResource("14_ofab3"),
"Peace": ExtResource("15_a4q3y"),
"Pinch Flat": ExtResource("16_ixe2d"),
"Pinch Large": ExtResource("17_oifug"),
"Pinch Middle": ExtResource("18_2ic2e"),
"Pinch Ring": ExtResource("19_4a8v4"),
"Pinch Tight": ExtResource("20_ugc01"),
"Pinch Up": ExtResource("21_l3kgg"),
"PingPong": ExtResource("22_j83er"),
"Pinky": ExtResource("23_sm4sd"),
"Pistol": ExtResource("24_r4kss"),
"Ring": ExtResource("25_2ncut"),
"Rounded": ExtResource("26_02lfv"),
"Sign 1": ExtResource("27_o5si3"),
"Sign 2": ExtResource("28_ble5k"),
"Sign 3": ExtResource("29_twq1x"),
"Sign 4": ExtResource("30_eyeuv"),
"Sign 5": ExtResource("31_50jrs"),
"Sign_Point": ExtResource("32_7v122"),
"Straight": ExtResource("33_m3lif"),
"Surfer": ExtResource("34_b3p10"),
"Thumb": ExtResource("35_huikq")
}
[node name="AnimationPlayer" type="AnimationPlayer"]
libraries = {
"": SubResource("AnimationLibrary_kw48d")
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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