add vr template project
@ -1,4 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
3
.gitattributes
vendored
@ -1,2 +1,3 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
|
||||
/img/** export-ignore
|
||||
|
||||
5
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
# Godot 4+ specific ignores
|
||||
.import/
|
||||
.godot/
|
||||
/android/
|
||||
build/
|
||||
android/
|
||||
|
||||
17
.gitignore_1
@ -1,17 +0,0 @@
|
||||
# ---> Godot
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
|
||||
# Godot-specific ignores
|
||||
.import/
|
||||
export.cfg
|
||||
export_presets.cfg
|
||||
|
||||
# Imported translations (automatically generated from CSV files)
|
||||
*.translation
|
||||
|
||||
# Mono-specific ignores
|
||||
.mono/
|
||||
data_*/
|
||||
mono_crash.*.json
|
||||
|
||||
20
LICENSE
@ -1,9 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 guillaumev
|
||||
Copyright (c) 2023 Godot XR
|
||||
|
||||
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:
|
||||
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 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.
|
||||
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.
|
||||
|
||||
74
README.md
@ -1,3 +1,73 @@
|
||||
# GodotVR
|
||||
# Godot XR Template
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
This repository contains a template Godot project for building a simple VR game.
|
||||
|
||||
|
||||
## Versions
|
||||
|
||||
Official releases are tagged and can be found [here](https://github.com/GodotVR/godot-xr-template/releases).
|
||||
|
||||
The following branches are in active development:
|
||||
| Branch | Description | Godot version |
|
||||
|----------|-------------------------------|-----------------|
|
||||
| main | Current development branch | Godot 4.2+ |
|
||||
| 4.1 | Godot 4.1 development branch | Godot 4.1 |
|
||||
| 3.x | Godot 3.x development branch | Godot 3.5+ |
|
||||
|
||||
|
||||
# Assets
|
||||
|
||||
This project uses the following assets:
|
||||
- [Godot XR Tools](https://godotengine.org/asset-library/asset/1515)
|
||||
- [OpenXR Loaders](https://github.com/GodotVR/godot_openxr_loaders)
|
||||
|
||||
|
||||
# Getting Started
|
||||
|
||||
Start by downloading this asset from github; or by installing it from the Godot
|
||||
Asset Library.
|
||||
|
||||
The game should be playable with a splash screen and two example scenes the player
|
||||
can move between.
|
||||
|
||||
The game should be customized by:
|
||||
- Modifying the splash-screen texture to represent the game
|
||||
- Modifying the icon.png for the game
|
||||
- Add game state variables to the game_state.gd singleton class
|
||||
- Replacing the demo zones with zones suitable to the game
|
||||
|
||||
|
||||
# Exporting to Android
|
||||
|
||||
The template contains a copy of the XR loaders plugin
|
||||
and preconfigured exports for android based headsets that support OpenXR.
|
||||
|
||||
Before this can be used you do need to install the android build template.
|
||||
Select the menu `Editor->Manage Export Templates...` to download the templates.
|
||||
Select the menu `Project->Install Android Build Template...` to install the template.
|
||||
|
||||
Make sure you set the correct entry in the export templates to runable
|
||||
if you want to use one click deploy to your device.
|
||||
|
||||
Please refer to the official documentation for Godots prerequisits for exporting to android:
|
||||
https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_android.html
|
||||
|
||||
# Recommended Asset Locations
|
||||
|
||||
Common areas to find assets are:
|
||||
- [Godot Asset Library](https://godotengine.org/asset-library/asset)
|
||||
- [AmbientCG](https://ambientcg.com/) for object and sky textures
|
||||
- [FreePD.com](https://freepd.com/) for sound tracks
|
||||
- [FreeSound](https://freesound.org/) for sound effects
|
||||
- [Kenney.nl](https://kenney.nl/)
|
||||
|
||||
|
||||
# More Information
|
||||
|
||||
Information on the Godot XR Tools can be found on [the website](https://godotvr.github.io/godot-xr-tools/).
|
||||
|
||||
Projet VR en Godot
|
||||
24
VERSIONS.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 4.3.0
|
||||
- Upgrade Godot XR Tools to 4.3.3
|
||||
- Upgrade Godot OpenXR Vendors to 2.0.4
|
||||
- Added WebXR Export
|
||||
|
||||
# 4.2.0
|
||||
- Upgrade to Godot 4.2
|
||||
- Upgrade Godot XR Tools to 4.3.0
|
||||
- Fixed minor compatibility breaks introduced with two-handed grabbing
|
||||
- Configured crate and rock for two-handed grabbing
|
||||
|
||||
# 4.1.0
|
||||
- Upgrade project to Godot 4.1+
|
||||
- Upgrade Godot XR Tools to 4.2.1
|
||||
- Upgrade Godot OpenXR Loaders to 1.1.0
|
||||
- Replaced the Kenney Prototype Textures with some simpler textures
|
||||
- Added start zone with dummy game-select screen
|
||||
- Switched demo to three zones showing zone spawn-points
|
||||
|
||||
# 4.0.0
|
||||
- Initial version of the Godot 4.0 version of this template
|
||||
- Uses Godot XR Tools v4.1.0
|
||||
- Uses Godot OpenXR Loaders v1.0.0
|
||||
|
||||
21
addons/godot-xr-tools/CONTRIBUTORS.md
Normal file
@ -0,0 +1,21 @@
|
||||
Contributors
|
||||
============
|
||||
|
||||
The main author of this project is [Bastiaan Olij](https://github.com/BastiaanOlij) who manages the source repository found at:
|
||||
https://github.com/GodotVR/godot-xr-tools
|
||||
|
||||
Other people who have helped out by submitting fixes, enhancements, etc are:
|
||||
- [Florian Jung](https://github.com/Windfisch)
|
||||
- [RMKD](https://github.com/RMKD)
|
||||
- [Alessandro Schillaci](https://github.com/silverslade)
|
||||
- [jtank4](https://github.com/jtank4)
|
||||
- [Malcolm Nixon](https://github.com/malcolmnixon)
|
||||
- [Sam Sarette](https://github.com/lunarcloud)
|
||||
- [Henodude](https://github.com/Henodude)
|
||||
- [Miodrag Sejic](https://github.com/DigitalN8m4r3)
|
||||
- [Carlos Padial](https://github.com/surreal6)
|
||||
- [Julian Todd](https://github.com/goatchurchprime)
|
||||
- [Kai Tödter](https://github.com/toedter)
|
||||
- [Sam Sarette](https://github.com/lunarcloud)
|
||||
|
||||
Want to be on this list? We would love your help.
|
||||
21
addons/godot-xr-tools/LICENSE
Normal 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.
|
||||
212
addons/godot-xr-tools/VERSIONS.md
Normal file
@ -0,0 +1,212 @@
|
||||
# 4.3.3
|
||||
- Fix Viewport2Din3D property forwarding
|
||||
|
||||
# 4.3.2
|
||||
- Move fade logic into effect
|
||||
- Added collision fade support
|
||||
- Added fix for slowly sliding on slopes
|
||||
- Added fix for ground-control preventing jumping over objects
|
||||
- Added property forwarding for Viewport2Din3D
|
||||
- Added fix for open/close poses
|
||||
- Added rumble manager for haptic feedback
|
||||
- Fix unreliable wall-walking collision
|
||||
|
||||
# 4.3.1
|
||||
- Fix saving project when using plugin-tools to set physics layers or enable OpenXR
|
||||
- Fix updating the editor-preview hand-pose
|
||||
- Fix jumping on slopes
|
||||
- Fix material warnings by converting binary .material files to .tres files
|
||||
- Fix staging to use threaded loading while starting the fade
|
||||
- Fix broken world-grab script
|
||||
|
||||
# 4.3.0
|
||||
- Upgraded project to Godot 4.1 as the new minimum version.
|
||||
- Added reporting of stage load errors.
|
||||
- Blend player height changes and prevent the player from standing up under a low ceiling.
|
||||
- **minor-breakage** Added support for swapping held items between hands.
|
||||
- Added jog-in-place movement provider.
|
||||
- Added support for grappling on GridMap instances
|
||||
- **breakage** Added support for two-handed grabbing.
|
||||
- Added support for snapping hands to grab-points.
|
||||
- Added support for world-grab movement.
|
||||
- Fixed editor errors when using hand physics bones.
|
||||
- Added support for climbable grab-points.
|
||||
- Added control of keyboard or gamepad inputs to Viewport2Din3D instances.
|
||||
|
||||
# 4.2.1
|
||||
- Fixed snap-zones showing highlight when disabled.
|
||||
- Fixed pickup leaving target highlighted after picking up.
|
||||
- Fixed collision hands getting stuck too far from the real hands.
|
||||
|
||||
# 4.2.0
|
||||
- Environments can now be set normally in scenes loaded through the staging system.
|
||||
- Fixed issue with not being able to push rigid bodies when colliding with them.
|
||||
- Fixed player movement on slopes.
|
||||
- Fixed lag in finger-poke.
|
||||
- Added initial collision hand support.
|
||||
- Added support for custom materials for 2D in 3D viewport
|
||||
- Updated pointer to support visibility properties and events
|
||||
- Modified virtual keyboard to expose viewport controls and default to unshaded
|
||||
- Cleaned up teleport and added more properties for customization
|
||||
- Modified pickup highlighting to support pickables in snap-zones
|
||||
- Added "UI Objects" layer 23 for viewports to support interaction by pointer and poking
|
||||
- Fixed player scaling issues with crouching and poke
|
||||
- **minor-breakage** Added support for passing user data between staged scenes with default handling for spawn-points
|
||||
- Moved teleport logic to player and added teleport area node
|
||||
- Change pointer event dispatching
|
||||
- Added multi-touch on 2D in 3D viewports and virtual-keyboard
|
||||
- Added option to disable laser-pointers when close to specific bodies/areas
|
||||
|
||||
# 4.1.0
|
||||
- Enhanced grappling to support collision and target layers
|
||||
- Added Godot Editor XR Tools menu for layers and openxr configuration
|
||||
- Improved gliding to support roll-turning while flapping
|
||||
- Added render_target_size_multiplier to StartXR (requires Godot 4.1+)
|
||||
|
||||
# 4.0.0
|
||||
- Conversion to Godot 4
|
||||
- Fixed footstep resource leak and added jump sounds and footstep signal
|
||||
- Added grab-point switching to pickable objects
|
||||
- Added return-to-snap-zone feature
|
||||
|
||||
# 3.4.0
|
||||
- Fixed footstep resource leak and added jump sounds and footstep signal
|
||||
- Added grab-point switching to pickable objects
|
||||
- Added return-to-snap-zone feature
|
||||
|
||||
# 3.3.0
|
||||
- Added reset-scene and scene-control functions to scene-base
|
||||
- Fixed snap-zones stealing objects picked out of other near-by snap-zones
|
||||
- Improved player body so it can be used to child objects to
|
||||
- Updated scene/script default physics layers to match recommendations on website
|
||||
|
||||
# 3.2.0
|
||||
- Minimum supported Godot version set to 3.5
|
||||
- Added glide option for turning with arm-roll
|
||||
- Added physics gravity effects on the player so they can walk around a planet
|
||||
- Added wall-walking movement provider
|
||||
- Cleaned the code to pass gdlint code checks
|
||||
- Modified to work with both WebXR and OpenXR
|
||||
- Added enable property to pickable objects
|
||||
- Added support for snap-on-drop to snap-zones
|
||||
- Added glide options for gaining altitude when flapping arms
|
||||
- Added option to disable snap-turn repeating by setting the delay to 0
|
||||
- Added capability for pointer function to auto-switch between controllers
|
||||
|
||||
# 3.1.0
|
||||
- Improvements to our 2D in 3D viewport for filtering, unshaded, and transparency options
|
||||
- Fixed editor preview system for our 2D in 3D viewport
|
||||
- Use value based grip input with threshold
|
||||
- Improved pointer demo supporting left hand with switching
|
||||
- Enhanced pointer laser visibility options for colliding with targets
|
||||
- Implement poke feature (finger interaction)
|
||||
- Improvements to snap turning
|
||||
- Moved staging solution into plugin so it can be re-used
|
||||
- Allow setting different animations for hands
|
||||
- Added enable/disable to snap-zones
|
||||
- Added XR settings as Godot editor plugin and the ability to load and save the settings
|
||||
- Added crouching movement provider
|
||||
- Modified climbing to use the hand which most recently grabbed the climbing object
|
||||
- Added enable/disable to pickup function
|
||||
- Added ability to override hand material
|
||||
- Added realistic hand models and textures
|
||||
- Added ability to override hand animations
|
||||
- Added additional search functions to find nodes
|
||||
- Added support for viewport 2D in 3D to support 2D scenes instanced in the tree
|
||||
- Added sprinting movement provider
|
||||
- Added support for setting hand-poses when the hand enters an area
|
||||
- Added support for setting grab-points on objects, and the grab-points supporting different hand-poses
|
||||
|
||||
# 3.0.0
|
||||
- Included demo project with test scenes to evaluate features
|
||||
- Standardized class naming convention for all scripts to "XRTools<PascalCaseName>"
|
||||
- Standardized file naming convention to "snake_case_name.ext"
|
||||
- Added many explicit type specifiers in preparation for GDScript 2.0
|
||||
- Renamed some functions to avoid name-collisions with Godot 4.0
|
||||
|
||||
# 2.6.0
|
||||
- Fixed enforcement of direct-movement maximum speed
|
||||
- Added editor icons for all nodes
|
||||
- Added collision bouncing to PlayerBody
|
||||
|
||||
# 2.5.0
|
||||
- Added advanced player height control
|
||||
- Modified climbing to collapse player to a sphere to allow mounting climbed objects
|
||||
- Added crouch movement provider
|
||||
- Added example fall damage detection
|
||||
- Added moving platform support to player body
|
||||
- Fixed player height-clamping to work in player-units
|
||||
- Fixed glide T-pose detection to work in player-units
|
||||
- Fixed jump detection to work in player-units
|
||||
- Added valid-layer checking to teleport movement
|
||||
- Modified hand meshes (blend and glb) to be scaled, so the hand scenes can be 1:1 scaled
|
||||
- Modified hands to scale with world_scale (required for godot-openxr 1.3.0 and later)
|
||||
- Added physics hands with PhysicsBody bones
|
||||
- Fixed disabling of `_process` in XRToolsPickable script
|
||||
|
||||
# 2.4.1
|
||||
- Fixed grab distance
|
||||
- Fixed snap-zone instance drop and free issue
|
||||
- Movement provides react properly when disabled
|
||||
- Hiding grapple target when disabled
|
||||
|
||||
# 2.4.0
|
||||
- Added configuration setting for head height in player body.
|
||||
- Added Function_JumpDetect_movement to detect jumping via the players body and/or arms
|
||||
- Improved responsiveness of snap-turning
|
||||
- Moved flight logic from Function_Direct_movement to Function_Flight_movement
|
||||
- Added option to disable player sliding on slopes
|
||||
- Added support for remote grabbing
|
||||
- Moved turning logic from Function_Direct_movement to Function_Turn_movement
|
||||
- Fixed movement provider servicing so disabled/bypassed providers can report their finished events
|
||||
- Added grappling movement provider
|
||||
- Added snap-zones
|
||||
|
||||
# 2.3.0
|
||||
- Added vignette
|
||||
- Moved player physics into new PlayerBody asset (breaking change)
|
||||
- Moved Function_Direct_movement settings for player physics into PlayerBody
|
||||
- Added Function_Glide_movement to allow the player to glide
|
||||
- Added Function_Jump_movement to allow the player to jump
|
||||
- Added Function_Climb_movement to allow the player to climb
|
||||
- Redid the setup of the hands to make it easier to extend to other gestures
|
||||
- Improved pickup and throwing logic
|
||||
|
||||
# 2.2
|
||||
- Changed default physics layers to make more sense (minor breaking change)
|
||||
- Replaced Center On Node property with PickupCenter node you can place
|
||||
- Made Object_pickable script work by itself and registers as class `XRToolsPickable`
|
||||
- New Object_interactable convenience script that registers as class `XRToolsInteractable` that reacts to our pointer function
|
||||
- Removed ducktype switch from pointer, pointer will use signals over ducktyping automatically (minor breaking change)
|
||||
|
||||
# 2.1
|
||||
- added option to highlight object that can be picked up
|
||||
- added option to snap object to given location (if reset transform is true)
|
||||
- added callback when shader cache has finished
|
||||
- using proper UI for layers
|
||||
- added hand controllers that react on trigger and grip input
|
||||
- fixed delta on move and slide (breaking change!)
|
||||
- letting go of an object now adds angular velocity
|
||||
|
||||
# 2.0
|
||||
- Renamed add on to **godot-xr-tools**
|
||||
- Add enums to our export variables
|
||||
- Add a switch on pickable objects to keep their current positioning when picked up
|
||||
- Move direct movement player collision slightly backwards based on player radius
|
||||
- Added switch between step turning and smooth turning
|
||||
- Fixed sizing issue with teleport
|
||||
- Added option to change pickup range
|
||||
|
||||
# 1.2
|
||||
- Assign button to teleport function and no longer need to set origin
|
||||
- Added pickable object support
|
||||
- Fixed positioning of direct movement collision shape
|
||||
- Added strafe and fly mode for directional
|
||||
- Added ability to enable/disable the movement functions
|
||||
- Added 2D in 3D viewport for UI
|
||||
- Improved throwing by assigning linear velocity
|
||||
|
||||
# 1.1*
|
||||
- previous versions were not tracked
|
||||
|
||||
* Note that version history before 1.2 was not kept and is thus incomplete
|
||||
BIN
addons/godot-xr-tools/assets/misc/Hold trigger to continue.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ocyj01x5mtt7"
|
||||
path.s3tc="res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.etc2.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/assets/misc/Hold trigger to continue.png"
|
||||
dest_files=["res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.s3tc.ctex", "res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.etc2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
addons/godot-xr-tools/assets/misc/progress_bar.png
Normal file
|
After Width: | Height: | Size: 698 B |
42
addons/godot-xr-tools/assets/misc/progress_bar.png.import
Normal file
@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://clbtsf0ahb3fm"
|
||||
path.s3tc="res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.etc2.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/assets/misc/progress_bar.png"
|
||||
dest_files=["res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.s3tc.ctex", "res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.etc2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
53
addons/godot-xr-tools/audio/area_audio.gd
Normal 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
|
||||
1
addons/godot-xr-tools/audio/area_audio.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://3ubmnwtroeyj
|
||||
8
addons/godot-xr-tools/audio/area_audio.tscn
Normal 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")
|
||||
28
addons/godot-xr-tools/audio/area_audio_type.gd
Normal 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
|
||||
1
addons/godot-xr-tools/audio/area_audio_type.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://ybufuv32l11d
|
||||
79
addons/godot-xr-tools/audio/pickable_audio.gd
Normal 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
|
||||
1
addons/godot-xr-tools/audio/pickable_audio.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://con7hh0okbfet
|
||||
9
addons/godot-xr-tools/audio/pickable_audio.tscn
Normal 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")
|
||||
34
addons/godot-xr-tools/audio/pickable_audio_type.gd
Normal 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
|
||||
1
addons/godot-xr-tools/audio/pickable_audio_type.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bhwhw1dabi6x6
|
||||
32
addons/godot-xr-tools/audio/surface_audio.gd
Normal 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
|
||||
1
addons/godot-xr-tools/audio/surface_audio.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://co1j2kp0d6wkm
|
||||
6
addons/godot-xr-tools/audio/surface_audio.tscn
Normal 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 )
|
||||
41
addons/godot-xr-tools/audio/surface_audio_type.gd
Normal 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
|
||||
1
addons/godot-xr-tools/audio/surface_audio_type.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dceg7vsw3c5h
|
||||
121
addons/godot-xr-tools/editor/icons/LICENSE
Normal 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.
|
||||
48
addons/godot-xr-tools/editor/icons/audio.svg
Normal file
|
After Width: | Height: | Size: 19 KiB |
43
addons/godot-xr-tools/editor/icons/audio.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dhnfbf4p0s74"
|
||||
path="res://.godot/imported/audio.svg-20d7f0b624a1b2ef54f1b4d12970c8d0.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/editor/icons/audio.svg"
|
||||
dest_files=["res://.godot/imported/audio.svg-20d7f0b624a1b2ef54f1b4d12970c8d0.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
addons/godot-xr-tools/editor/icons/body.svg
Normal 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 |
@ -2,22 +2,24 @@
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cp1p2n6u52638"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
uid="uid://cyg33jxco0rh6"
|
||||
path="res://.godot/imported/body.svg-324e141d452c32f3136ca97c338025b4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
source_file="res://addons/godot-xr-tools/editor/icons/body.svg"
|
||||
dest_files=["res://.godot/imported/body.svg-324e141d452c32f3136ca97c338025b4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
@ -25,6 +27,10 @@ mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
43
addons/godot-xr-tools/editor/icons/foot.svg
Normal 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 |
43
addons/godot-xr-tools/editor/icons/foot.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bfkcd3fkyahqu"
|
||||
path="res://.godot/imported/foot.svg-9e361563e010aa07be49bfb25fdb6639.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/editor/icons/foot.svg"
|
||||
dest_files=["res://.godot/imported/foot.svg-9e361563e010aa07be49bfb25fdb6639.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
addons/godot-xr-tools/editor/icons/function.svg
Normal 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 |
43
addons/godot-xr-tools/editor/icons/function.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b5vxil50s0ofi"
|
||||
path="res://.godot/imported/function.svg-52c5f936037e0f38a4da2b1e16ae67fe.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/editor/icons/function.svg"
|
||||
dest_files=["res://.godot/imported/function.svg-52c5f936037e0f38a4da2b1e16ae67fe.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
addons/godot-xr-tools/editor/icons/hand.svg
Normal 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 |
43
addons/godot-xr-tools/editor/icons/hand.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://beko1qhyybx7e"
|
||||
path="res://.godot/imported/hand.svg-a05486d804ef16320d6cf54e06292b8f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/editor/icons/hand.svg"
|
||||
dest_files=["res://.godot/imported/hand.svg-a05486d804ef16320d6cf54e06292b8f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
addons/godot-xr-tools/editor/icons/movement_provider.svg
Normal 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 |
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://04fn15h4x333"
|
||||
path="res://.godot/imported/movement_provider.svg-3c994cf0a3775c20f333be563d69fbf8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/editor/icons/movement_provider.svg"
|
||||
dest_files=["res://.godot/imported/movement_provider.svg-3c994cf0a3775c20f333be563d69fbf8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
addons/godot-xr-tools/editor/icons/node.svg
Normal 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 |
43
addons/godot-xr-tools/editor/icons/node.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b6gwa6o27pbry"
|
||||
path="res://.godot/imported/node.svg-37d53571b4a4459efefcc791c5402b4f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/editor/icons/node.svg"
|
||||
dest_files=["res://.godot/imported/node.svg-37d53571b4a4459efefcc791c5402b4f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
addons/godot-xr-tools/editor/icons/rumble.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><path d="m12 11.999519c.55228 0 1 .448243 1 1.000481v1.000483.211639.786437 1.000479.0014h-1v-1c-.202186 0-.336627-.491203-.5-.5-1.452695-.07822-1.5-.503591-1.5-1h1 1v-.500438h-1-1c0-.552238.44772-1.000481 1-1.000481z" stroke-width=".999963"/><path d="m1.999832 12v1c0 .55228.44772 1 1 1-.55228 0-1 .44772-1 1v1h1v-1h1v1h1v-1c0-.55228-.44772-1-1-1 .55228 0 1-.44772 1-1v-1h-1v1h-1v-1z"/><path d="m5.999832 12v1 3h1v-1h1v1h1v-1c-.000834-.17579-.047991-.34825-.13672-.5.088728-.15175.13588-.32421.13672-.5v-1c0-.55228-.44772-1-1-1h-1zm1 1h1v1h-1z"/></g><g stroke="#ff8080"><path d="m11.721643 12.70092h2" fill="#ff8080" stroke-width=".500002"/><path d="m11.694618 13.781362h2" fill="#ff8080" stroke-width=".500002"/><path d="m.98844737 8.4010995c1.83521943-3.2693204 4.32032623 3.4590935 5.09543103-.1041898.5122353-2.3548301 1.5512016-4.7636852 1.5512016-7.1393289 0-.44964593.3393333.8692356.4187169 1.3152833.3581573 2.0123893.7090004 4.0047814 1.1634017 6.0086392 1.3138514 5.7939487.1057884.3417346 2.0036324-1.0154361.144766-.1035335.95577 1.0405657 1.098766 1.1092871.938333.4509487 2.146617-.5105464 3.19809-.8075801" fill="none" stroke="#ff8080" stroke-width="1.52176"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
43
addons/godot-xr-tools/editor/icons/rumble.svg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dsexrsb8t0vi2"
|
||||
path="res://.godot/imported/rumble.svg-104961f6551a931675972887ab17d2bc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot-xr-tools/editor/icons/rumble.svg"
|
||||
dest_files=["res://.godot/imported/rumble.svg-104961f6551a931675972887ab17d2bc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
79
addons/godot-xr-tools/effects/fade.gd
Normal file
@ -0,0 +1,79 @@
|
||||
@tool
|
||||
class_name XRToolsFade
|
||||
extends Node3D
|
||||
|
||||
|
||||
## XR Tools Fade Script
|
||||
##
|
||||
## This script manages fading the view.
|
||||
|
||||
|
||||
# Dictionary of fade requests
|
||||
var _faders : Dictionary = {}
|
||||
|
||||
# Fade update flag
|
||||
var _update : bool = false
|
||||
|
||||
# Fade mesh
|
||||
var _mesh : MeshInstance3D
|
||||
|
||||
# Fade shader material
|
||||
var _material : ShaderMaterial
|
||||
|
||||
|
||||
# Add support for is_xr_class on XRTools classes
|
||||
func is_xr_class(name : String) -> bool:
|
||||
return name == "XRToolsFade"
|
||||
|
||||
|
||||
# Called when the fade node is ready
|
||||
func _ready() -> void:
|
||||
# Add to the fade_mesh group - in the future this should be replaced with
|
||||
# static instances.
|
||||
add_to_group("fade_mesh")
|
||||
|
||||
# Get the mesh and material
|
||||
_mesh = $FadeMesh
|
||||
_material = _mesh.get_surface_override_material(0)
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(_delta : float) -> void:
|
||||
# Skip if nothing to update
|
||||
if not _update:
|
||||
return
|
||||
|
||||
# Calculate the cumulative shade color
|
||||
var fade := Color(1, 1, 1, 0)
|
||||
var show := false
|
||||
for whom in _faders:
|
||||
var color := _faders[whom] as Color
|
||||
fade = fade.blend(color)
|
||||
show = true
|
||||
|
||||
# Set the shader and show if necessary
|
||||
_material.set_shader_parameter("albedo", fade)
|
||||
_mesh.visible = show
|
||||
_update = false
|
||||
|
||||
|
||||
# Set the fade level
|
||||
func set_fade_level(p_whom : Variant, p_color : Color) -> void:
|
||||
# Test if fading is needed
|
||||
if p_color.a > 0:
|
||||
# Set the fade level
|
||||
_faders[p_whom] = p_color
|
||||
_update = true
|
||||
elif _faders.erase(p_whom):
|
||||
# Fade erased
|
||||
_update = true
|
||||
|
||||
|
||||
## Set the fade level on the fade instance
|
||||
static func set_fade(p_whom : Variant, p_color : Color) -> void:
|
||||
# In the future this use of groups should be replaced by static instances.
|
||||
var tree := Engine.get_main_loop() as SceneTree
|
||||
for node in tree.get_nodes_in_group("fade_mesh"):
|
||||
var fade := node as XRToolsFade
|
||||
if fade:
|
||||
fade.set_fade_level(p_whom, p_color)
|
||||
1
addons/godot-xr-tools/effects/fade.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://rq4v3tvv27ap
|
||||
13
addons/godot-xr-tools/effects/fade.gdshader
Normal file
@ -0,0 +1,13 @@
|
||||
shader_type spatial;
|
||||
render_mode depth_test_disabled, skip_vertex_transform, unshaded, cull_disabled;
|
||||
|
||||
uniform vec4 albedo : source_color;
|
||||
|
||||
void vertex() {
|
||||
POSITION = vec4(VERTEX.x, -VERTEX.y, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
ALBEDO = albedo.rgb;
|
||||
ALPHA = albedo.a;
|
||||
}
|
||||
1
addons/godot-xr-tools/effects/fade.gdshader.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dnhcvacjaicgm
|
||||
21
addons/godot-xr-tools/effects/fade.tscn
Normal file
@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://wtpox7m5vu2b"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/godot-xr-tools/effects/fade.gd" id="1_6667f"]
|
||||
[ext_resource type="Shader" path="res://addons/godot-xr-tools/effects/fade.gdshader" id="1_tjen8"]
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_aqp6r"]
|
||||
custom_aabb = AABB(-5000, -5000, -5000, 10000, 10000, 10000)
|
||||
size = Vector2(2, 2)
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_xjgy8"]
|
||||
render_priority = 50
|
||||
shader = ExtResource("1_tjen8")
|
||||
shader_parameter/albedo = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="Fade" type="Node3D"]
|
||||
script = ExtResource("1_6667f")
|
||||
|
||||
[node name="FadeMesh" type="MeshInstance3D" parent="."]
|
||||
visible = false
|
||||
mesh = SubResource("QuadMesh_aqp6r")
|
||||
surface_material_override/0 = SubResource("ShaderMaterial_xjgy8")
|
||||
195
addons/godot-xr-tools/effects/vignette.gd
Normal 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
|
||||
1
addons/godot-xr-tools/effects/vignette.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dpai27qonyr5
|
||||
34
addons/godot-xr-tools/effects/vignette.gdshader
Normal 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);
|
||||
}
|
||||
1
addons/godot-xr-tools/effects/vignette.gdshader.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bcmamo5d8n7ml
|
||||
10
addons/godot-xr-tools/effects/vignette.tres
Normal 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
|
||||
27
addons/godot-xr-tools/effects/vignette.tscn
Normal 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")
|
||||
134
addons/godot-xr-tools/events/pointer_event.gd
Normal 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)
|
||||
1
addons/godot-xr-tools/events/pointer_event.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c61u73bu13koe
|
||||
92
addons/godot-xr-tools/examples/fall_damage.gd
Normal 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)
|
||||
1
addons/godot-xr-tools/examples/fall_damage.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dki1bcn0dkky1
|
||||
6
addons/godot-xr-tools/examples/fall_damage.tscn
Normal 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")
|
||||
432
addons/godot-xr-tools/functions/function_pickup.gd
Normal file
@ -0,0 +1,432 @@
|
||||
@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(
|
||||
self,
|
||||
_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)
|
||||
|
||||
# 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()
|
||||
1
addons/godot-xr-tools/functions/function_pickup.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://vsaf06fyyrny
|
||||
7
addons/godot-xr-tools/functions/function_pickup.tscn
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b4ysuy43poobf"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/function_pickup.gd" id="1"]
|
||||
|
||||
[node name="FunctionPickup" type="Node3D"]
|
||||
script = ExtResource("1")
|
||||
grab_collision_mask = 327684
|
||||
511
addons/godot-xr-tools/functions/function_pointer.gd
Normal 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
|
||||
1
addons/godot-xr-tools/functions/function_pointer.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cdw2wrrc3gswm
|
||||
44
addons/godot-xr-tools/functions/function_pointer.tscn
Normal 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")
|
||||
106
addons/godot-xr-tools/functions/function_pose_detector.gd
Normal file
@ -0,0 +1,106 @@
|
||||
@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)
|
||||
# Disable grabpoints in this pose_area
|
||||
pose_area.disable_grab_points()
|
||||
elif _controller.tracker == "right_hand" and pose_area.right_pose:
|
||||
_hand.add_pose_override(
|
||||
pose_area,
|
||||
pose_area.pose_priority,
|
||||
pose_area.right_pose)
|
||||
# Disable grabpoints in this pose_area
|
||||
pose_area.disable_grab_points()
|
||||
|
||||
|
||||
## Signal handler called when this XRToolsFunctionPoseArea leaves an area
|
||||
func _on_area_exited(area : Area3D) -> void:
|
||||
# Ignore if the area is not a hand-pose area
|
||||
var pose_area := area as XRToolsHandPoseArea
|
||||
if !pose_area:
|
||||
return
|
||||
|
||||
# Remove any overrides set from this hand-pose area
|
||||
_hand.remove_pose_override(pose_area)
|
||||
|
||||
# Enable previously disabled grabpoints
|
||||
pose_area.enable_grab_points()
|
||||
@ -0,0 +1 @@
|
||||
uid://d2jom3fo13olp
|
||||
19
addons/godot-xr-tools/functions/function_pose_detector.tscn
Normal 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")
|
||||
495
addons/godot-xr-tools/functions/function_teleport.gd
Normal 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)
|
||||
1
addons/godot-xr-tools/functions/function_teleport.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://jhb4tpwa1lvr
|
||||
37
addons/godot-xr-tools/functions/function_teleport.tscn
Normal 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")
|
||||
283
addons/godot-xr-tools/functions/movement_climb.gd
Normal file
@ -0,0 +1,283 @@
|
||||
@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_hand := XRToolsHand.find_left(self)
|
||||
|
||||
# Right collision hand
|
||||
@onready var _right_hand := XRToolsHand.find_right(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)
|
||||
elif _left_hand:
|
||||
_left_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)
|
||||
elif _right_hand:
|
||||
_right_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)
|
||||
if _left_hand:
|
||||
_left_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)
|
||||
if _right_hand:
|
||||
_right_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
|
||||
1
addons/godot-xr-tools/functions/movement_climb.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bu7tsscmjtlwg
|
||||
6
addons/godot-xr-tools/functions/movement_climb.tscn
Normal 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")
|
||||
92
addons/godot-xr-tools/functions/movement_crouch.gd
Normal 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
|
||||
1
addons/godot-xr-tools/functions/movement_crouch.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cst6mgqvm5r2r
|
||||
6
addons/godot-xr-tools/functions/movement_crouch.tscn
Normal 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")
|
||||
87
addons/godot-xr-tools/functions/movement_direct.gd
Normal 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
|
||||
1
addons/godot-xr-tools/functions/movement_direct.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dvdxgvv454u1w
|
||||
6
addons/godot-xr-tools/functions/movement_direct.tscn
Normal 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")
|
||||
229
addons/godot-xr-tools/functions/movement_flight.gd
Normal 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
|
||||
1
addons/godot-xr-tools/functions/movement_flight.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bb478fjvmuiqv
|
||||
6
addons/godot-xr-tools/functions/movement_flight.tscn
Normal 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")
|
||||
245
addons/godot-xr-tools/functions/movement_footstep.gd
Normal 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
|
||||
1
addons/godot-xr-tools/functions/movement_footstep.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://doy24syo5j1dr
|
||||
8
addons/godot-xr-tools/functions/movement_footstep.tscn
Normal 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="."]
|
||||
236
addons/godot-xr-tools/functions/movement_glide.gd
Normal 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
|
||||
1
addons/godot-xr-tools/functions/movement_glide.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cggp85ntwryci
|
||||
6
addons/godot-xr-tools/functions/movement_glide.tscn
Normal 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")
|
||||
241
addons/godot-xr-tools/functions/movement_grapple.gd
Normal file
@ -0,0 +1,241 @@
|
||||
@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 the grappling line and target
|
||||
func _physics_process(_delta : float):
|
||||
# Skip if running in the editor
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
# If pointing grappler at target then show the target
|
||||
if enabled and not 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
|
||||
|
||||
# If actively grappling then update and show the grappling 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
|
||||
|
||||
|
||||
# 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:
|
||||
# Test if the raycast hit a collider
|
||||
var target = _grapple_raycast.get_collider()
|
||||
if not is_instance_valid(target):
|
||||
return false
|
||||
|
||||
# Check collider 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
|
||||
1
addons/godot-xr-tools/functions/movement_grapple.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dox53pkah4wf8
|
||||
28
addons/godot-xr-tools/functions/movement_grapple.tscn
Normal 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
|
||||
156
addons/godot-xr-tools/functions/movement_jog.gd
Normal file
@ -0,0 +1,156 @@
|
||||
@tool
|
||||
class_name XRToolsMovementJog
|
||||
extends XRToolsMovementProvider
|
||||
|
||||
|
||||
## XR Tools Movement Provider for Jog Movement
|
||||
##
|
||||
## This script provides jog-in-place movement for the player. This script
|
||||
## works with the [XRToolsPlayerBody] attached to the players [XROrigin3D].
|
||||
##
|
||||
## The implementation uses filtering of the controller Y velocities to measure
|
||||
## the approximate frequency of jog arm-swings; and uses that to
|
||||
## switch between stopped, slow, and fast movement speeds.
|
||||
|
||||
|
||||
## Speed mode enumeration
|
||||
enum SpeedMode {
|
||||
STOPPED, ## Not jogging
|
||||
SLOW, ## Jogging slowly
|
||||
FAST ## Jogging fast
|
||||
}
|
||||
|
||||
|
||||
## Jog arm-swing frequency in Hz to trigger slow movement
|
||||
const JOG_SLOW_FREQ := 3.5
|
||||
|
||||
## Jog arm-swing frequency in Hz to trigger fast movement
|
||||
const JOG_FAST_FREQ := 5.5
|
||||
|
||||
|
||||
## Movement provider order
|
||||
@export var order : int = 10
|
||||
|
||||
## Slow jogging speed in meters-per-second
|
||||
@export var slow_speed : float = 1.0
|
||||
|
||||
## Fast jogging speed in meters-per-second
|
||||
@export var fast_speed : float = 3.0
|
||||
|
||||
|
||||
# Jog arm-swing "stroke" detector "confidence-hat" signal
|
||||
var _conf_hat := 0.0
|
||||
|
||||
# Current jog arm-swing "stroke" duration
|
||||
var _current_stroke := 0.0
|
||||
|
||||
# Last jog arm-swing "stroke" total duration
|
||||
var _last_stroke := 0.0
|
||||
|
||||
# Current jog-speed mode
|
||||
var _speed_mode := SpeedMode.STOPPED
|
||||
|
||||
|
||||
# Left controller
|
||||
@onready var _left_controller := XRHelpers.get_left_controller(self)
|
||||
|
||||
# Right controller
|
||||
@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 == "XRToolsMovementJog" or super(name)
|
||||
|
||||
|
||||
# Perform jump movement
|
||||
func physics_movement(delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
|
||||
# Skip if the either controller is inactive
|
||||
if !_left_controller.get_is_active() or !_right_controller.get_is_active():
|
||||
_speed_mode = SpeedMode.STOPPED
|
||||
return
|
||||
|
||||
# Get the arm-swing stroke frequency in Hz
|
||||
var freq := _get_stroke_frequency(delta)
|
||||
|
||||
# Transition between stopped/slow/fast speed-modes based on thresholds.
|
||||
# This thresholding has some hysteresis to make speed changes smoother.
|
||||
if freq == 0:
|
||||
_speed_mode = SpeedMode.STOPPED
|
||||
elif freq < JOG_SLOW_FREQ:
|
||||
_speed_mode = min(_speed_mode, SpeedMode.SLOW)
|
||||
elif freq < JOG_FAST_FREQ:
|
||||
_speed_mode = max(_speed_mode, SpeedMode.SLOW)
|
||||
else:
|
||||
_speed_mode = SpeedMode.FAST
|
||||
|
||||
# Pick the speed in meters-per-second based on the current speed-mode.
|
||||
var speed := 0.0
|
||||
if _speed_mode == SpeedMode.SLOW:
|
||||
speed = slow_speed
|
||||
elif _speed_mode == SpeedMode.FAST:
|
||||
speed = fast_speed
|
||||
|
||||
# Contribute to the player body speed - with clamping to the maximum speed
|
||||
player_body.ground_control_velocity.y += speed
|
||||
var length := player_body.ground_control_velocity.length()
|
||||
if length > fast_speed:
|
||||
player_body.ground_control_velocity *= fast_speed / length
|
||||
|
||||
|
||||
# Get the frequency of the last arm-swing "stroke" in Hz.
|
||||
func _get_stroke_frequency(delta : float) -> float:
|
||||
# Get the controller velocities
|
||||
var vl := _left_controller.get_pose().linear_velocity.y
|
||||
var vr := _right_controller.get_pose().linear_velocity.y
|
||||
|
||||
# Calculate the arm-swing "stroke" confidence. This is done by multiplying
|
||||
# the left and right controller vertical velocities. As these velocities
|
||||
# are highly anti-correlated while "jogging" the result is a confidence
|
||||
# signal with a high "peak" on every jog "stroke".
|
||||
var conf := vl * -vr
|
||||
|
||||
# Test for the confidence valley between strokes. This is used to signal
|
||||
# when to measure the duration between strokes.
|
||||
var valley := conf < _conf_hat
|
||||
|
||||
# Update confidence-hat. The confidence-hat signal has a fast-rise and
|
||||
# slow-decay. Rising with each jog arm-swing "stroke" and then taking time
|
||||
# to decay. The magnitude of the "confidence-hat" can be used as a good
|
||||
# indicator of when the user is jogging; and the difference between the
|
||||
# "confidence" and "confidence-hat" signals can be used to identify the
|
||||
# duration of a jog arm-swing "stroke".
|
||||
if valley:
|
||||
# Gently decay when in the confidence valley.
|
||||
_conf_hat = lerpf(_conf_hat, 0.0, delta * 2)
|
||||
else:
|
||||
# Quickly ramp confidence-hat to confidence
|
||||
_conf_hat = lerpf(_conf_hat, conf, delta * 20)
|
||||
|
||||
# If the "confidence-hat" signal is too low then the user is not jogging.
|
||||
# The stroke date-data is cleared and a stroke frequency of 0Hz is returned.
|
||||
if _conf_hat < 0.5:
|
||||
_current_stroke = 0.0
|
||||
_last_stroke = 0.0
|
||||
return 0.0
|
||||
|
||||
# Track the jog arm-swing "stroke" duration.
|
||||
if valley:
|
||||
# In the valley between jog arm-swing "strokes"
|
||||
_current_stroke += delta
|
||||
elif _current_stroke > 0.1:
|
||||
# Save the measured jog arm-swing "stroke" duration.
|
||||
_last_stroke = _current_stroke
|
||||
_current_stroke = 0.0
|
||||
|
||||
# If no previous jog arm-swing "stroke" duration to report, so return 0Hz.
|
||||
if _last_stroke < 0.1:
|
||||
return 0.0
|
||||
|
||||
# If the current jog arm-swing "stroke" is taking longer (slower) than 2Hz
|
||||
# then truncate to 0Hz.
|
||||
if _current_stroke > 0.75:
|
||||
return 0.0
|
||||
|
||||
# Return the last jog arm-swing "stroke" in Hz.
|
||||
return 1.0 / _last_stroke
|
||||
1
addons/godot-xr-tools/functions/movement_jog.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://di2bo8dq6hm17
|
||||
6
addons/godot-xr-tools/functions/movement_jog.tscn
Normal file
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://chcuj3jysipk8"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_jog.gd" id="1_k4cao"]
|
||||
|
||||
[node name="MovementJog" type="Node" groups=["movement_providers"]]
|
||||
script = ExtResource("1_k4cao")
|
||||
52
addons/godot-xr-tools/functions/movement_jump.gd
Normal 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
|
||||
1
addons/godot-xr-tools/functions/movement_jump.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c3vabh1khy0ch
|
||||