add initial template
20
addons/godot-xr-tools/CONTRIBUTORS.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Contributors
|
||||||
|
============
|
||||||
|
|
||||||
|
The main author of this project is [Bastiaan Olij](https://github.com/BastiaanOlij) who manages the source repository found at:
|
||||||
|
https://github.com/GodotVR/godot-xr-tools
|
||||||
|
|
||||||
|
Other people who have helped out by submitting fixes, enhancements, etc are:
|
||||||
|
- [Florian Jung](https://github.com/Windfisch)
|
||||||
|
- [RMKD](https://github.com/RMKD)
|
||||||
|
- [Alessandro Schillaci](https://github.com/silverslade)
|
||||||
|
- [jtank4](https://github.com/jtank4)
|
||||||
|
- [Malcolm Nixon](https://github.com/malcolmnixon)
|
||||||
|
- [Sam Sarette](https://github.com/lunarcloud)
|
||||||
|
- [Henodude](https://github.com/Henodude)
|
||||||
|
- [Miodrag Sejic](https://github.com/DigitalN8m4r3)
|
||||||
|
- [Carlos Padial](https://github.com/surreal6)
|
||||||
|
- [Julian Todd](https://github.com/goatchurchprime)
|
||||||
|
- [Kai Tödter](https://github.com/toedter)
|
||||||
|
|
||||||
|
Want to be on this list? We would love your help.
|
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.
|
177
addons/godot-xr-tools/VERSIONS.md
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
# 4.2.1
|
||||||
|
- Fixed snap-zones showing highlight when disabled.
|
||||||
|
- Fixed pickup leaving target highlighted after picking up.
|
||||||
|
- Fixed collision hands getting stuck too far from the real hands.
|
||||||
|
|
||||||
|
# 4.2.0
|
||||||
|
- Environments can now be set normally in scenes loaded through the staging system.
|
||||||
|
- Fixed issue with not being able to push rigid bodies when colliding with them.
|
||||||
|
- Fixed player movement on slopes.
|
||||||
|
- Fixed lag in finger-poke.
|
||||||
|
- Added initial collision hand support.
|
||||||
|
- Added support for custom materials for 2D in 3D viewport
|
||||||
|
- Updated pointer to support visibility properties and events
|
||||||
|
- Modified virtual keyboard to expose viewport controls and default to unshaded
|
||||||
|
- Cleaned up teleport and added more properties for customization
|
||||||
|
- Modified pickup highlighting to support pickables in snap-zones
|
||||||
|
- Added "UI Objects" layer 23 for viewports to support interaction by pointer and poking
|
||||||
|
- Fixed player scaling issues with crouching and poke
|
||||||
|
- **minor-breakage** Added support for passing user data between staged scenes with default handling for spawn-points
|
||||||
|
- Moved teleport logic to player and added teleport area node
|
||||||
|
- Change pointer event dispatching
|
||||||
|
- Added multi-touch on 2D in 3D viewports and virtual-keyboard
|
||||||
|
- Added option to disable laser-pointers when close to specific bodies/areas
|
||||||
|
|
||||||
|
# 4.1.0
|
||||||
|
- Enhanced grappling to support collision and target layers
|
||||||
|
- Added Godot Editor XR Tools menu for layers and openxr configuration
|
||||||
|
- Improved gliding to support roll-turning while flapping
|
||||||
|
- Added render_target_size_multiplier to StartXR (requires Godot 4.1+)
|
||||||
|
|
||||||
|
# 4.0.0
|
||||||
|
- Conversion to Godot 4
|
||||||
|
- Fixed footstep resource leak and added jump sounds and footstep signal
|
||||||
|
- Added grab-point switching to pickable objects
|
||||||
|
- Added return-to-snap-zone feature
|
||||||
|
|
||||||
|
# 3.4.0
|
||||||
|
- Fixed footstep resource leak and added jump sounds and footstep signal
|
||||||
|
- Added grab-point switching to pickable objects
|
||||||
|
- Added return-to-snap-zone feature
|
||||||
|
|
||||||
|
# 3.3.0
|
||||||
|
- Added reset-scene and scene-control functions to scene-base
|
||||||
|
- Fixed snap-zones stealing objects picked out of other near-by snap-zones
|
||||||
|
- Improved player body so it can be used to child objects to
|
||||||
|
- Updated scene/script default physics layers to match recommendations on website
|
||||||
|
|
||||||
|
# 3.2.0
|
||||||
|
- Minimum supported Godot version set to 3.5
|
||||||
|
- Added glide option for turning with arm-roll
|
||||||
|
- Added physics gravity effects on the player so they can walk around a planet
|
||||||
|
- Added wall-walking movement provider
|
||||||
|
- Cleaned the code to pass gdlint code checks
|
||||||
|
- Modified to work with both WebXR and OpenXR
|
||||||
|
- Added enable property to pickable objects
|
||||||
|
- Added support for snap-on-drop to snap-zones
|
||||||
|
- Added glide options for gaining altitude when flapping arms
|
||||||
|
- Added option to disable snap-turn repeating by setting the delay to 0
|
||||||
|
- Added capability for pointer function to auto-switch between controllers
|
||||||
|
|
||||||
|
# 3.1.0
|
||||||
|
- Improvements to our 2D in 3D viewport for filtering, unshaded, and transparency options
|
||||||
|
- Fixed editor preview system for our 2D in 3D viewport
|
||||||
|
- Use value based grip input with threshold
|
||||||
|
- Improved pointer demo supporting left hand with switching
|
||||||
|
- Enhanced pointer laser visibility options for colliding with targets
|
||||||
|
- Implement poke feature (finger interaction)
|
||||||
|
- Improvements to snap turning
|
||||||
|
- Moved staging solution into plugin so it can be re-used
|
||||||
|
- Allow setting different animations for hands
|
||||||
|
- Added enable/disable to snap-zones
|
||||||
|
- Added XR settings as Godot editor plugin and the ability to load and save the settings
|
||||||
|
- Added crouching movement provider
|
||||||
|
- Modified climbing to use the hand which most recently grabbed the climbing object
|
||||||
|
- Added enable/disable to pickup function
|
||||||
|
- Added ability to override hand material
|
||||||
|
- Added realistic hand models and textures
|
||||||
|
- Added ability to override hand animations
|
||||||
|
- Added additional search functions to find nodes
|
||||||
|
- Added support for viewport 2D in 3D to support 2D scenes instanced in the tree
|
||||||
|
- Added sprinting movement provider
|
||||||
|
- Added support for setting hand-poses when the hand enters an area
|
||||||
|
- Added support for setting grab-points on objects, and the grab-points supporting different hand-poses
|
||||||
|
|
||||||
|
# 3.0.0
|
||||||
|
- Included demo project with test scenes to evaluate features
|
||||||
|
- Standardized class naming convention for all scripts to "XRTools<PascalCaseName>"
|
||||||
|
- Standardized file naming convention to "snake_case_name.ext"
|
||||||
|
- Added many explicit type specifiers in preparation for GDScript 2.0
|
||||||
|
- Renamed some functions to avoid name-collisions with Godot 4.0
|
||||||
|
|
||||||
|
# 2.6.0
|
||||||
|
- Fixed enforcement of direct-movement maximum speed
|
||||||
|
- Added editor icons for all nodes
|
||||||
|
- Added collision bouncing to PlayerBody
|
||||||
|
|
||||||
|
# 2.5.0
|
||||||
|
- Added advanced player height control
|
||||||
|
- Modified climbing to collapse player to a sphere to allow mounting climbed objects
|
||||||
|
- Added crouch movement provider
|
||||||
|
- Added example fall damage detection
|
||||||
|
- Added moving platform support to player body
|
||||||
|
- Fixed player height-clamping to work in player-units
|
||||||
|
- Fixed glide T-pose detection to work in player-units
|
||||||
|
- Fixed jump detection to work in player-units
|
||||||
|
- Added valid-layer checking to teleport movement
|
||||||
|
- Modified hand meshes (blend and glb) to be scaled, so the hand scenes can be 1:1 scaled
|
||||||
|
- Modified hands to scale with world_scale (required for godot-openxr 1.3.0 and later)
|
||||||
|
- Added physics hands with PhysicsBody bones
|
||||||
|
- Fixed disabling of `_process` in XRToolsPickable script
|
||||||
|
|
||||||
|
# 2.4.1
|
||||||
|
- Fixed grab distance
|
||||||
|
- Fixed snap-zone instance drop and free issue
|
||||||
|
- Movement provides react properly when disabled
|
||||||
|
- Hiding grapple target when disabled
|
||||||
|
|
||||||
|
# 2.4.0
|
||||||
|
- Added configuration setting for head height in player body.
|
||||||
|
- Added Function_JumpDetect_movement to detect jumping via the players body and/or arms
|
||||||
|
- Improved responsiveness of snap-turning
|
||||||
|
- Moved flight logic from Function_Direct_movement to Function_Flight_movement
|
||||||
|
- Added option to disable player sliding on slopes
|
||||||
|
- Added support for remote grabbing
|
||||||
|
- Moved turning logic from Function_Direct_movement to Function_Turn_movement
|
||||||
|
- Fixed movement provider servicing so disabled/bypassed providers can report their finished events
|
||||||
|
- Added grappling movement provider
|
||||||
|
- Added snap-zones
|
||||||
|
|
||||||
|
# 2.3.0
|
||||||
|
- Added vignette
|
||||||
|
- Moved player physics into new PlayerBody asset (breaking change)
|
||||||
|
- Moved Function_Direct_movement settings for player physics into PlayerBody
|
||||||
|
- Added Function_Glide_movement to allow the player to glide
|
||||||
|
- Added Function_Jump_movement to allow the player to jump
|
||||||
|
- Added Function_Climb_movement to allow the player to climb
|
||||||
|
- Redid the setup of the hands to make it easier to extend to other gestures
|
||||||
|
- Improved pickup and throwing logic
|
||||||
|
|
||||||
|
# 2.2
|
||||||
|
- Changed default physics layers to make more sense (minor breaking change)
|
||||||
|
- Replaced Center On Node property with PickupCenter node you can place
|
||||||
|
- Made Object_pickable script work by itself and registers as class `XRToolsPickable`
|
||||||
|
- New Object_interactable convenience script that registers as class `XRToolsInteractable` that reacts to our pointer function
|
||||||
|
- Removed ducktype switch from pointer, pointer will use signals over ducktyping automatically (minor breaking change)
|
||||||
|
|
||||||
|
# 2.1
|
||||||
|
- added option to highlight object that can be picked up
|
||||||
|
- added option to snap object to given location (if reset transform is true)
|
||||||
|
- added callback when shader cache has finished
|
||||||
|
- using proper UI for layers
|
||||||
|
- added hand controllers that react on trigger and grip input
|
||||||
|
- fixed delta on move and slide (breaking change!)
|
||||||
|
- letting go of an object now adds angular velocity
|
||||||
|
|
||||||
|
# 2.0
|
||||||
|
- Renamed add on to **godot-xr-tools**
|
||||||
|
- Add enums to our export variables
|
||||||
|
- Add a switch on pickable objects to keep their current positioning when picked up
|
||||||
|
- Move direct movement player collision slightly backwards based on player radius
|
||||||
|
- Added switch between step turning and smooth turning
|
||||||
|
- Fixed sizing issue with teleport
|
||||||
|
- Added option to change pickup range
|
||||||
|
|
||||||
|
# 1.2
|
||||||
|
- Assign button to teleport function and no longer need to set origin
|
||||||
|
- Added pickable object support
|
||||||
|
- Fixed positioning of direct movement collision shape
|
||||||
|
- Added strafe and fly mode for directional
|
||||||
|
- Added ability to enable/disable the movement functions
|
||||||
|
- Added 2D in 3D viewport for UI
|
||||||
|
- Improved throwing by assigning linear velocity
|
||||||
|
|
||||||
|
# 1.1*
|
||||||
|
- previous versions were not tracked
|
||||||
|
|
||||||
|
* Note that version history before 1.2 was not kept and is thus incomplete
|
BIN
addons/godot-xr-tools/assets/misc/Hold trigger to continue.png
Normal file
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,36 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ocyj01x5mtt7"
|
||||||
|
path.s3tc="res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/assets/misc/Hold trigger to continue.png"
|
||||||
|
dest_files=["res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.s3tc.ctex", "res://.godot/imported/Hold trigger to continue.png-ce0a3a4de13c262f7015326bad2cb09d.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
BIN
addons/godot-xr-tools/assets/misc/progress_bar.png
Normal file
After Width: | Height: | Size: 698 B |
36
addons/godot-xr-tools/assets/misc/progress_bar.png.import
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://clbtsf0ahb3fm"
|
||||||
|
path.s3tc="res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/assets/misc/progress_bar.png"
|
||||||
|
dest_files=["res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.s3tc.ctex", "res://.godot/imported/progress_bar.png-2ef3cbffca173889900be004fdeb1700.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
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
|
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
|
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
|
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
|
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
|
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
|
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 |
37
addons/godot-xr-tools/editor/icons/audio.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dhnfbf4p0s74"
|
||||||
|
path="res://.godot/imported/audio.svg-20d7f0b624a1b2ef54f1b4d12970c8d0.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/editor/icons/audio.svg"
|
||||||
|
dest_files=["res://.godot/imported/audio.svg-20d7f0b624a1b2ef54f1b4d12970c8d0.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
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 |
37
addons/godot-xr-tools/editor/icons/body.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cyg33jxco0rh6"
|
||||||
|
path="res://.godot/imported/body.svg-324e141d452c32f3136ca97c338025b4.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/editor/icons/body.svg"
|
||||||
|
dest_files=["res://.godot/imported/body.svg-324e141d452c32f3136ca97c338025b4.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
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 |
37
addons/godot-xr-tools/editor/icons/foot.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bfkcd3fkyahqu"
|
||||||
|
path="res://.godot/imported/foot.svg-9e361563e010aa07be49bfb25fdb6639.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/editor/icons/foot.svg"
|
||||||
|
dest_files=["res://.godot/imported/foot.svg-9e361563e010aa07be49bfb25fdb6639.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
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 |
37
addons/godot-xr-tools/editor/icons/function.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b5vxil50s0ofi"
|
||||||
|
path="res://.godot/imported/function.svg-52c5f936037e0f38a4da2b1e16ae67fe.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/editor/icons/function.svg"
|
||||||
|
dest_files=["res://.godot/imported/function.svg-52c5f936037e0f38a4da2b1e16ae67fe.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
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 |
37
addons/godot-xr-tools/editor/icons/hand.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://beko1qhyybx7e"
|
||||||
|
path="res://.godot/imported/hand.svg-a05486d804ef16320d6cf54e06292b8f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/editor/icons/hand.svg"
|
||||||
|
dest_files=["res://.godot/imported/hand.svg-a05486d804ef16320d6cf54e06292b8f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
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,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://04fn15h4x333"
|
||||||
|
path="res://.godot/imported/movement_provider.svg-3c994cf0a3775c20f333be563d69fbf8.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/editor/icons/movement_provider.svg"
|
||||||
|
dest_files=["res://.godot/imported/movement_provider.svg-3c994cf0a3775c20f333be563d69fbf8.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
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 |
37
addons/godot-xr-tools/editor/icons/node.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b6gwa6o27pbry"
|
||||||
|
path="res://.godot/imported/node.svg-37d53571b4a4459efefcc791c5402b4f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/godot-xr-tools/editor/icons/node.svg"
|
||||||
|
dest_files=["res://.godot/imported/node.svg-37d53571b4a4459efefcc791c5402b4f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
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
|
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);
|
||||||
|
}
|
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)
|
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)
|
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")
|
431
addons/godot-xr-tools/functions/function_pickup.gd
Normal file
|
@ -0,0 +1,431 @@
|
||||||
|
@tool
|
||||||
|
@icon("res://addons/godot-xr-tools/editor/icons/function.svg")
|
||||||
|
class_name XRToolsFunctionPickup
|
||||||
|
extends Node3D
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Function Pickup Script
|
||||||
|
##
|
||||||
|
## This script implements picking up of objects. Most pickable
|
||||||
|
## objects are instances of the [XRToolsPickable] class.
|
||||||
|
##
|
||||||
|
## Additionally this script can work in conjunction with the
|
||||||
|
## [XRToolsMovementProvider] class support climbing. Most climbable objects are
|
||||||
|
## instances of the [XRToolsClimbable] class.
|
||||||
|
|
||||||
|
|
||||||
|
## Signal emitted when the pickup picks something up
|
||||||
|
signal has_picked_up(what)
|
||||||
|
|
||||||
|
## Signal emitted when the pickup drops something
|
||||||
|
signal has_dropped
|
||||||
|
|
||||||
|
|
||||||
|
# Default pickup collision mask of 3:pickable and 19:handle
|
||||||
|
const DEFAULT_GRAB_MASK := 0b0000_0000_0000_0100_0000_0000_0000_0100
|
||||||
|
|
||||||
|
# Default pickup collision mask of 3:pickable
|
||||||
|
const DEFAULT_RANGE_MASK := 0b0000_0000_0000_0000_0000_0000_0000_0100
|
||||||
|
|
||||||
|
# Constant for worst-case grab distance
|
||||||
|
const MAX_GRAB_DISTANCE2: float = 1000000.0
|
||||||
|
|
||||||
|
|
||||||
|
## Pickup enabled property
|
||||||
|
@export var enabled : bool = true
|
||||||
|
|
||||||
|
## Grip controller axis
|
||||||
|
@export var pickup_axis_action : String = "grip"
|
||||||
|
|
||||||
|
## Action controller button
|
||||||
|
@export var action_button_action : String = "trigger_click"
|
||||||
|
|
||||||
|
## Grab distance
|
||||||
|
@export var grab_distance : float = 0.3: set = _set_grab_distance
|
||||||
|
|
||||||
|
## Grab collision mask
|
||||||
|
@export_flags_3d_physics \
|
||||||
|
var grab_collision_mask : int = DEFAULT_GRAB_MASK: set = _set_grab_collision_mask
|
||||||
|
|
||||||
|
## If true, ranged-grabbing is enabled
|
||||||
|
@export var ranged_enable : bool = true
|
||||||
|
|
||||||
|
## Ranged-grab distance
|
||||||
|
@export var ranged_distance : float = 5.0: set = _set_ranged_distance
|
||||||
|
|
||||||
|
## Ranged-grab angle
|
||||||
|
@export_range(0.0, 45.0) var ranged_angle : float = 5.0: set = _set_ranged_angle
|
||||||
|
|
||||||
|
## Ranged-grab collision mask
|
||||||
|
@export_flags_3d_physics \
|
||||||
|
var ranged_collision_mask : int = DEFAULT_RANGE_MASK: set = _set_ranged_collision_mask
|
||||||
|
|
||||||
|
## Throw impulse factor
|
||||||
|
@export var impulse_factor : float = 1.0
|
||||||
|
|
||||||
|
## Throw velocity averaging
|
||||||
|
@export var velocity_samples: int = 5
|
||||||
|
|
||||||
|
|
||||||
|
# Public fields
|
||||||
|
var closest_object : Node3D = null
|
||||||
|
var picked_up_object : Node3D = null
|
||||||
|
var picked_up_ranged : bool = false
|
||||||
|
var grip_pressed : bool = false
|
||||||
|
|
||||||
|
# Private fields
|
||||||
|
var _object_in_grab_area := Array()
|
||||||
|
var _object_in_ranged_area := Array()
|
||||||
|
var _velocity_averager := XRToolsVelocityAverager.new(velocity_samples)
|
||||||
|
var _grab_area : Area3D
|
||||||
|
var _grab_collision : CollisionShape3D
|
||||||
|
var _ranged_area : Area3D
|
||||||
|
var _ranged_collision : CollisionShape3D
|
||||||
|
|
||||||
|
|
||||||
|
## Controller
|
||||||
|
@onready var _controller := XRHelpers.get_xr_controller(self)
|
||||||
|
|
||||||
|
## Grip threshold (from configuration)
|
||||||
|
@onready var _grip_threshold : float = XRTools.get_grip_threshold()
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsFunctionPickup"
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready():
|
||||||
|
# Skip creating grab-helpers if in the editor
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create the grab collision shape
|
||||||
|
_grab_collision = CollisionShape3D.new()
|
||||||
|
_grab_collision.set_name("GrabCollisionShape")
|
||||||
|
_grab_collision.shape = SphereShape3D.new()
|
||||||
|
_grab_collision.shape.radius = grab_distance
|
||||||
|
|
||||||
|
# Create the grab area
|
||||||
|
_grab_area = Area3D.new()
|
||||||
|
_grab_area.set_name("GrabArea")
|
||||||
|
_grab_area.collision_layer = 0
|
||||||
|
_grab_area.collision_mask = grab_collision_mask
|
||||||
|
_grab_area.add_child(_grab_collision)
|
||||||
|
_grab_area.area_entered.connect(_on_grab_entered)
|
||||||
|
_grab_area.body_entered.connect(_on_grab_entered)
|
||||||
|
_grab_area.area_exited.connect(_on_grab_exited)
|
||||||
|
_grab_area.body_exited.connect(_on_grab_exited)
|
||||||
|
add_child(_grab_area)
|
||||||
|
|
||||||
|
# Create the ranged collision shape
|
||||||
|
_ranged_collision = CollisionShape3D.new()
|
||||||
|
_ranged_collision.set_name("RangedCollisionShape")
|
||||||
|
_ranged_collision.shape = CylinderShape3D.new()
|
||||||
|
_ranged_collision.transform.basis = Basis(Vector3.RIGHT, PI/2)
|
||||||
|
|
||||||
|
# Create the ranged area
|
||||||
|
_ranged_area = Area3D.new()
|
||||||
|
_ranged_area.set_name("RangedArea")
|
||||||
|
_ranged_area.collision_layer = 0
|
||||||
|
_ranged_area.collision_mask = ranged_collision_mask
|
||||||
|
_ranged_area.add_child(_ranged_collision)
|
||||||
|
_ranged_area.area_entered.connect(_on_ranged_entered)
|
||||||
|
_ranged_area.body_entered.connect(_on_ranged_entered)
|
||||||
|
_ranged_area.area_exited.connect(_on_ranged_exited)
|
||||||
|
_ranged_area.body_exited.connect(_on_ranged_exited)
|
||||||
|
add_child(_ranged_area)
|
||||||
|
|
||||||
|
# Update the colliders
|
||||||
|
_update_colliders()
|
||||||
|
|
||||||
|
# Monitor Grab Button
|
||||||
|
_controller.connect("button_pressed", _on_button_pressed)
|
||||||
|
_controller.connect("button_released", _on_button_released)
|
||||||
|
|
||||||
|
|
||||||
|
# Called on each frame to update the pickup
|
||||||
|
func _process(delta):
|
||||||
|
# Do not process if in the editor
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Skip if disabled, or the controller isn't active
|
||||||
|
if !enabled or !_controller.get_is_active():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle our grip
|
||||||
|
var grip_value = _controller.get_float(pickup_axis_action)
|
||||||
|
if (grip_pressed and grip_value < (_grip_threshold - 0.1)):
|
||||||
|
grip_pressed = false
|
||||||
|
_on_grip_release()
|
||||||
|
elif (!grip_pressed and grip_value > (_grip_threshold + 0.1)):
|
||||||
|
grip_pressed = true
|
||||||
|
_on_grip_pressed()
|
||||||
|
|
||||||
|
# Calculate average velocity
|
||||||
|
if is_instance_valid(picked_up_object) and picked_up_object.is_picked_up():
|
||||||
|
# Average velocity of picked up object
|
||||||
|
_velocity_averager.add_transform(delta, picked_up_object.global_transform)
|
||||||
|
else:
|
||||||
|
# Average velocity of this pickup
|
||||||
|
_velocity_averager.add_transform(delta, global_transform)
|
||||||
|
|
||||||
|
_update_closest_object()
|
||||||
|
|
||||||
|
|
||||||
|
## Find an [XRToolsFunctionPickup] node.
|
||||||
|
##
|
||||||
|
## This function searches from the specified node for an [XRToolsFunctionPickup]
|
||||||
|
## assuming the node is a sibling of the pickup under an [XRController3D].
|
||||||
|
static func find_instance(node : Node) -> XRToolsFunctionPickup:
|
||||||
|
return XRTools.find_xr_child(
|
||||||
|
XRHelpers.get_xr_controller(node),
|
||||||
|
"*",
|
||||||
|
"XRToolsFunctionPickup") as XRToolsFunctionPickup
|
||||||
|
|
||||||
|
|
||||||
|
## Find the left [XRToolsFunctionPickup] node.
|
||||||
|
##
|
||||||
|
## This function searches from the specified node for the left controller
|
||||||
|
## [XRToolsFunctionPickup] assuming the node is a sibling of the [XOrigin3D].
|
||||||
|
static func find_left(node : Node) -> XRToolsFunctionPickup:
|
||||||
|
return XRTools.find_xr_child(
|
||||||
|
XRHelpers.get_left_controller(node),
|
||||||
|
"*",
|
||||||
|
"XRToolsFunctionPickup") as XRToolsFunctionPickup
|
||||||
|
|
||||||
|
|
||||||
|
## Find the right [XRToolsFunctionPickup] node.
|
||||||
|
##
|
||||||
|
## This function searches from the specified node for the right controller
|
||||||
|
## [XRToolsFunctionPickup] assuming the node is a sibling of the [XROrigin3D].
|
||||||
|
static func find_right(node : Node) -> XRToolsFunctionPickup:
|
||||||
|
return XRTools.find_xr_child(
|
||||||
|
XRHelpers.get_right_controller(node),
|
||||||
|
"*",
|
||||||
|
"XRToolsFunctionPickup") as XRToolsFunctionPickup
|
||||||
|
|
||||||
|
|
||||||
|
## Get the [XRController3D] driving this pickup.
|
||||||
|
func get_controller() -> XRController3D:
|
||||||
|
return _controller
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the grab distance has been modified
|
||||||
|
func _set_grab_distance(new_value: float) -> void:
|
||||||
|
grab_distance = new_value
|
||||||
|
if is_inside_tree():
|
||||||
|
_update_colliders()
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the grab collision mask has been modified
|
||||||
|
func _set_grab_collision_mask(new_value: int) -> void:
|
||||||
|
grab_collision_mask = new_value
|
||||||
|
if is_inside_tree() and _grab_collision:
|
||||||
|
_grab_collision.collision_mask = new_value
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the ranged-grab distance has been modified
|
||||||
|
func _set_ranged_distance(new_value: float) -> void:
|
||||||
|
ranged_distance = new_value
|
||||||
|
if is_inside_tree():
|
||||||
|
_update_colliders()
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the ranged-grab angle has been modified
|
||||||
|
func _set_ranged_angle(new_value: float) -> void:
|
||||||
|
ranged_angle = new_value
|
||||||
|
if is_inside_tree():
|
||||||
|
_update_colliders()
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the ranged-grab collision mask has been modified
|
||||||
|
func _set_ranged_collision_mask(new_value: int) -> void:
|
||||||
|
ranged_collision_mask = new_value
|
||||||
|
if is_inside_tree() and _ranged_collision:
|
||||||
|
_ranged_collision.collision_mask = new_value
|
||||||
|
|
||||||
|
|
||||||
|
# Update the colliders geometry
|
||||||
|
func _update_colliders() -> void:
|
||||||
|
# Update the grab sphere
|
||||||
|
if _grab_collision:
|
||||||
|
_grab_collision.shape.radius = grab_distance
|
||||||
|
|
||||||
|
# Update the ranged-grab cylinder
|
||||||
|
if _ranged_collision:
|
||||||
|
_ranged_collision.shape.radius = tan(deg_to_rad(ranged_angle)) * ranged_distance
|
||||||
|
_ranged_collision.shape.height = ranged_distance
|
||||||
|
_ranged_collision.transform.origin.z = -ranged_distance * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
# Called when an object enters the grab sphere
|
||||||
|
func _on_grab_entered(target: Node3D) -> void:
|
||||||
|
# reject objects which don't support picking up
|
||||||
|
if not target.has_method('pick_up'):
|
||||||
|
return
|
||||||
|
|
||||||
|
# ignore objects already known
|
||||||
|
if _object_in_grab_area.find(target) >= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add to the list of objects in grab area
|
||||||
|
_object_in_grab_area.push_back(target)
|
||||||
|
|
||||||
|
|
||||||
|
# Called when an object enters the ranged-grab cylinder
|
||||||
|
func _on_ranged_entered(target: Node3D) -> void:
|
||||||
|
# reject objects which don't support picking up rangedly
|
||||||
|
if not 'can_ranged_grab' in target or not target.can_ranged_grab:
|
||||||
|
return
|
||||||
|
|
||||||
|
# ignore objects already known
|
||||||
|
if _object_in_ranged_area.find(target) >= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add to the list of objects in grab area
|
||||||
|
_object_in_ranged_area.push_back(target)
|
||||||
|
|
||||||
|
|
||||||
|
# Called when an object exits the grab sphere
|
||||||
|
func _on_grab_exited(target: Node3D) -> void:
|
||||||
|
_object_in_grab_area.erase(target)
|
||||||
|
|
||||||
|
|
||||||
|
# Called when an object exits the ranged-grab cylinder
|
||||||
|
func _on_ranged_exited(target: Node3D) -> void:
|
||||||
|
_object_in_ranged_area.erase(target)
|
||||||
|
|
||||||
|
|
||||||
|
# Update the closest object field with the best choice of grab
|
||||||
|
func _update_closest_object() -> void:
|
||||||
|
# Find the closest object we can pickup
|
||||||
|
var new_closest_obj: Node3D = null
|
||||||
|
if not picked_up_object:
|
||||||
|
# Find the closest in grab area
|
||||||
|
new_closest_obj = _get_closest_grab()
|
||||||
|
if not new_closest_obj and ranged_enable:
|
||||||
|
# Find closest in ranged area
|
||||||
|
new_closest_obj = _get_closest_ranged()
|
||||||
|
|
||||||
|
# Skip if no change
|
||||||
|
if closest_object == new_closest_obj:
|
||||||
|
return
|
||||||
|
|
||||||
|
# remove highlight on old object
|
||||||
|
if is_instance_valid(closest_object):
|
||||||
|
closest_object.request_highlight(self, false)
|
||||||
|
|
||||||
|
# add highlight to new object
|
||||||
|
closest_object = new_closest_obj
|
||||||
|
if is_instance_valid(closest_object):
|
||||||
|
closest_object.request_highlight(self, true)
|
||||||
|
|
||||||
|
|
||||||
|
# Find the pickable object closest to our hand's grab location
|
||||||
|
func _get_closest_grab() -> Node3D:
|
||||||
|
var new_closest_obj: Node3D = null
|
||||||
|
var new_closest_distance := MAX_GRAB_DISTANCE2
|
||||||
|
for o in _object_in_grab_area:
|
||||||
|
# skip objects that can not be picked up
|
||||||
|
if not o.can_pick_up(self):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Save if this object is closer than the current best
|
||||||
|
var distance_squared := global_transform.origin.distance_squared_to(
|
||||||
|
o.global_transform.origin)
|
||||||
|
if distance_squared < new_closest_distance:
|
||||||
|
new_closest_obj = o
|
||||||
|
new_closest_distance = distance_squared
|
||||||
|
|
||||||
|
# Return best object
|
||||||
|
return new_closest_obj
|
||||||
|
|
||||||
|
|
||||||
|
# Find the rangedly-pickable object closest to our hand's pointing direction
|
||||||
|
func _get_closest_ranged() -> Node3D:
|
||||||
|
var new_closest_obj: Node3D = null
|
||||||
|
var new_closest_angle_dp := cos(deg_to_rad(ranged_angle))
|
||||||
|
var hand_forwards := -global_transform.basis.z
|
||||||
|
for o in _object_in_ranged_area:
|
||||||
|
# skip objects that can not be picked up
|
||||||
|
if not o.can_pick_up(self):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Save if this object is closer than the current best
|
||||||
|
var object_direction: Vector3 = o.global_transform.origin - global_transform.origin
|
||||||
|
object_direction = object_direction.normalized()
|
||||||
|
var angle_dp := hand_forwards.dot(object_direction)
|
||||||
|
if angle_dp > new_closest_angle_dp:
|
||||||
|
new_closest_obj = o
|
||||||
|
new_closest_angle_dp = angle_dp
|
||||||
|
|
||||||
|
# Return best object
|
||||||
|
return new_closest_obj
|
||||||
|
|
||||||
|
|
||||||
|
## Drop the currently held object
|
||||||
|
func drop_object() -> void:
|
||||||
|
if not is_instance_valid(picked_up_object):
|
||||||
|
return
|
||||||
|
|
||||||
|
# let go of this object
|
||||||
|
picked_up_object.let_go(
|
||||||
|
_velocity_averager.linear_velocity() * impulse_factor,
|
||||||
|
_velocity_averager.angular_velocity())
|
||||||
|
picked_up_object = null
|
||||||
|
emit_signal("has_dropped")
|
||||||
|
|
||||||
|
|
||||||
|
func _pick_up_object(target: Node3D) -> void:
|
||||||
|
# check if already holding an object
|
||||||
|
if is_instance_valid(picked_up_object):
|
||||||
|
# skip if holding the target object
|
||||||
|
if picked_up_object == target:
|
||||||
|
return
|
||||||
|
# holding something else? drop it
|
||||||
|
drop_object()
|
||||||
|
|
||||||
|
# skip if target null or freed
|
||||||
|
if not is_instance_valid(target):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle snap-zone
|
||||||
|
var snap := target as XRToolsSnapZone
|
||||||
|
if snap:
|
||||||
|
target = snap.picked_up_object
|
||||||
|
snap.drop_object()
|
||||||
|
|
||||||
|
# Pick up our target. Note, target may do instant drop_and_free
|
||||||
|
picked_up_ranged = not _object_in_grab_area.has(target)
|
||||||
|
picked_up_object = target
|
||||||
|
target.pick_up(self, _controller)
|
||||||
|
|
||||||
|
# If object picked up then emit signal
|
||||||
|
if is_instance_valid(picked_up_object):
|
||||||
|
picked_up_object.request_highlight(self, false)
|
||||||
|
emit_signal("has_picked_up", picked_up_object)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_button_pressed(p_button) -> void:
|
||||||
|
if p_button == action_button_action:
|
||||||
|
if is_instance_valid(picked_up_object) and picked_up_object.has_method("action"):
|
||||||
|
picked_up_object.action()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_button_released(_p_button) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _on_grip_pressed() -> void:
|
||||||
|
if is_instance_valid(picked_up_object) and !picked_up_object.press_to_hold:
|
||||||
|
drop_object()
|
||||||
|
elif is_instance_valid(closest_object):
|
||||||
|
_pick_up_object(closest_object)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_grip_release() -> void:
|
||||||
|
if is_instance_valid(picked_up_object) and picked_up_object.press_to_hold:
|
||||||
|
drop_object()
|
6
addons/godot-xr-tools/functions/function_pickup.tscn
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://b4ysuy43poobf"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/function_pickup.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="FunctionPickup" type="Node3D"]
|
||||||
|
script = ExtResource("1")
|
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
|
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")
|
99
addons/godot-xr-tools/functions/function_pose_detector.gd
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
@tool
|
||||||
|
@icon("res://addons/godot-xr-tools/editor/icons/hand.svg")
|
||||||
|
class_name XRToolsFunctionPoseDetector
|
||||||
|
extends Node3D
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Function Pose Area
|
||||||
|
##
|
||||||
|
## This area works with the XRToolsHandPoseArea to control the pose
|
||||||
|
## of the VR hands.
|
||||||
|
|
||||||
|
|
||||||
|
# Default pose detector collision mask of 22:pose-area
|
||||||
|
const DEFAULT_MASK := 0b0000_0000_0010_0000_0000_0000_0000_0000
|
||||||
|
|
||||||
|
|
||||||
|
## Collision mask to detect hand pose areas
|
||||||
|
@export_flags_3d_physics var collision_mask : int = DEFAULT_MASK: set = set_collision_mask
|
||||||
|
|
||||||
|
|
||||||
|
## Hand controller
|
||||||
|
@onready var _controller := XRHelpers.get_xr_controller(self)
|
||||||
|
|
||||||
|
## Hand to control
|
||||||
|
@onready var _hand := XRToolsHand.find_instance(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsFunctionPoseDetector"
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready():
|
||||||
|
# Connect signals (if controller and hand are valid)
|
||||||
|
if _controller and _hand:
|
||||||
|
if $SenseArea.area_entered.connect(_on_area_entered):
|
||||||
|
push_error("Unable to connect area_entered signal")
|
||||||
|
if $SenseArea.area_exited.connect(_on_area_exited):
|
||||||
|
push_error("Unable to connect area_exited signal")
|
||||||
|
|
||||||
|
# Update collision mask
|
||||||
|
_update_collision_mask()
|
||||||
|
|
||||||
|
|
||||||
|
# This method verifies the pose area has a valid configuration.
|
||||||
|
func _get_configuration_warnings() -> PackedStringArray:
|
||||||
|
var warnings := PackedStringArray()
|
||||||
|
|
||||||
|
if !XRHelpers.get_xr_controller(self):
|
||||||
|
warnings.append("Node must be within a branch of an XRController3D node")
|
||||||
|
|
||||||
|
# Verify hand can be found
|
||||||
|
if !XRToolsHand.find_instance(self):
|
||||||
|
warnings.append("Node must be a within a branch of an XRController node with a hand")
|
||||||
|
|
||||||
|
# Pass basic validation
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
|
||||||
|
func set_collision_mask(mask : int) -> void:
|
||||||
|
collision_mask = mask
|
||||||
|
if is_inside_tree():
|
||||||
|
_update_collision_mask()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_collision_mask() -> void:
|
||||||
|
$SenseArea.collision_mask = collision_mask
|
||||||
|
|
||||||
|
|
||||||
|
## Signal handler called when this XRToolsFunctionPoseArea enters an area
|
||||||
|
func _on_area_entered(area : Area3D) -> void:
|
||||||
|
# Igjnore if the area is not a hand-pose area
|
||||||
|
var pose_area := area as XRToolsHandPoseArea
|
||||||
|
if !pose_area:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set the appropriate poses
|
||||||
|
if _controller.tracker == "left_hand" and pose_area.left_pose:
|
||||||
|
_hand.add_pose_override(
|
||||||
|
pose_area,
|
||||||
|
pose_area.pose_priority,
|
||||||
|
pose_area.left_pose)
|
||||||
|
elif _controller.tracker == "right_hand" and pose_area.right_pose:
|
||||||
|
_hand.add_pose_override(
|
||||||
|
pose_area,
|
||||||
|
pose_area.pose_priority,
|
||||||
|
pose_area.right_pose)
|
||||||
|
|
||||||
|
|
||||||
|
## Signal handler called when this XRToolsFunctionPoseArea leaves an area
|
||||||
|
func _on_area_exited(area : Area3D) -> void:
|
||||||
|
# Ignore if the area is not a hand-pose area
|
||||||
|
var pose_area := area as XRToolsHandPoseArea
|
||||||
|
if !pose_area:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remove any overrides set from this hand-pose area
|
||||||
|
_hand.remove_pose_override(pose_area)
|
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)
|
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")
|
269
addons/godot-xr-tools/functions/movement_climb.gd
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
@tool
|
||||||
|
class_name XRToolsMovementClimb
|
||||||
|
extends XRToolsMovementProvider
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Movement Provider for Climbing
|
||||||
|
##
|
||||||
|
## This script provides climbing movement for the player. To add climbing
|
||||||
|
## support, the player must also have [XRToolsFunctionPickup] nodes attached
|
||||||
|
## to the left and right controllers, and an [XRToolsPlayerBody] under the
|
||||||
|
## [XROrigin3D].
|
||||||
|
##
|
||||||
|
## Climbable objects can inherit from the climbable scene, or be [StaticBody]
|
||||||
|
## objects with the [XRToolsClimbable] script attached to them.
|
||||||
|
##
|
||||||
|
## When climbing, the global velocity of the [XRToolsPlayerBody] is averaged,
|
||||||
|
## and upon release the velocity is applied to the [XRToolsPlayerBody] with an
|
||||||
|
## optional fling multiplier, so the player can fling themselves up walls if
|
||||||
|
## desired.
|
||||||
|
|
||||||
|
|
||||||
|
## Signal invoked when the player starts climing
|
||||||
|
signal player_climb_start
|
||||||
|
|
||||||
|
## Signal invoked when the player ends climbing
|
||||||
|
signal player_climb_end
|
||||||
|
|
||||||
|
|
||||||
|
## Distance at which grabs snap
|
||||||
|
const SNAP_DISTANCE : float = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
## Movement provider order
|
||||||
|
@export var order : int = 15
|
||||||
|
|
||||||
|
## Push forward when flinging
|
||||||
|
@export var forward_push : float = 1.0
|
||||||
|
|
||||||
|
## Velocity multiplier when flinging up walls
|
||||||
|
@export var fling_multiplier : float = 1.0
|
||||||
|
|
||||||
|
## Averages for velocity measurement
|
||||||
|
@export var velocity_averages : int = 5
|
||||||
|
|
||||||
|
|
||||||
|
# Left climbing handle
|
||||||
|
var _left_handle : Node3D
|
||||||
|
|
||||||
|
# Right climbing handle
|
||||||
|
var _right_handle : Node3D
|
||||||
|
|
||||||
|
# Dominant handle (moving the player)
|
||||||
|
var _dominant : Node3D
|
||||||
|
|
||||||
|
|
||||||
|
# Velocity averager
|
||||||
|
@onready var _averager := XRToolsVelocityAveragerLinear.new(velocity_averages)
|
||||||
|
|
||||||
|
# Left pickup node
|
||||||
|
@onready var _left_pickup_node := XRToolsFunctionPickup.find_left(self)
|
||||||
|
|
||||||
|
# Right pickup node
|
||||||
|
@onready var _right_pickup_node := XRToolsFunctionPickup.find_right(self)
|
||||||
|
|
||||||
|
# Left controller
|
||||||
|
@onready var _left_controller := XRHelpers.get_left_controller(self)
|
||||||
|
|
||||||
|
# Right controller
|
||||||
|
@onready var _right_controller := XRHelpers.get_right_controller(self)
|
||||||
|
|
||||||
|
# Left collision hand
|
||||||
|
@onready var _left_collision_hand := XRToolsCollisionHand.find_left(self)
|
||||||
|
|
||||||
|
# Right collision hand
|
||||||
|
@onready var _right_collision_hand := XRToolsCollisionHand.find_right(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsMovementClimb" or super(name)
|
||||||
|
|
||||||
|
|
||||||
|
## Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready():
|
||||||
|
# In Godot 4 we must now manually call our super class ready function
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Do not initialise if in the editor
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Connect pickup funcitons
|
||||||
|
if _left_pickup_node.connect("has_picked_up", _on_left_picked_up):
|
||||||
|
push_error("Unable to connect left picked up signal")
|
||||||
|
if _right_pickup_node.connect("has_picked_up", _on_right_picked_up):
|
||||||
|
push_error("Unable to connect right picked up signal")
|
||||||
|
if _left_pickup_node.connect("has_dropped", _on_left_dropped):
|
||||||
|
push_error("Unable to connect left dropped signal")
|
||||||
|
if _right_pickup_node.connect("has_dropped", _on_right_dropped):
|
||||||
|
push_error("Unable to connect right dropped signal")
|
||||||
|
|
||||||
|
|
||||||
|
## Perform player physics movement
|
||||||
|
func physics_movement(delta: float, player_body: XRToolsPlayerBody, disabled: bool):
|
||||||
|
# Disable climbing if requested
|
||||||
|
if disabled or !enabled:
|
||||||
|
_set_climbing(false, player_body)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check for climbing handles being deleted while held
|
||||||
|
if not is_instance_valid(_left_handle):
|
||||||
|
_left_handle = null
|
||||||
|
if not is_instance_valid(_right_handle):
|
||||||
|
_right_handle = null
|
||||||
|
if not is_instance_valid(_dominant):
|
||||||
|
_dominant = null
|
||||||
|
|
||||||
|
# Snap grabs if too far
|
||||||
|
if _left_handle:
|
||||||
|
var left_pickup_pos := _left_controller.global_position
|
||||||
|
var left_grab_pos = _left_handle.global_position
|
||||||
|
if left_pickup_pos.distance_to(left_grab_pos) > SNAP_DISTANCE:
|
||||||
|
_left_pickup_node.drop_object()
|
||||||
|
if _right_handle:
|
||||||
|
var right_pickup_pos := _right_controller.global_position
|
||||||
|
var right_grab_pos := _right_handle.global_position
|
||||||
|
if right_pickup_pos.distance_to(right_grab_pos) > SNAP_DISTANCE:
|
||||||
|
_right_pickup_node.drop_object()
|
||||||
|
|
||||||
|
# Update climbing
|
||||||
|
_set_climbing(_dominant != null, player_body)
|
||||||
|
|
||||||
|
# Skip if not actively climbing
|
||||||
|
if !is_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate how much the player has moved
|
||||||
|
var offset := Vector3.ZERO
|
||||||
|
if _dominant == _left_handle:
|
||||||
|
var left_pickup_pos := _left_controller.global_position
|
||||||
|
var left_grab_pos := _left_handle.global_position
|
||||||
|
offset = left_pickup_pos - left_grab_pos
|
||||||
|
elif _dominant == _right_handle:
|
||||||
|
var right_pickup_pos := _right_controller.global_position
|
||||||
|
var right_grab_pos := _right_handle.global_position
|
||||||
|
offset = right_pickup_pos - right_grab_pos
|
||||||
|
|
||||||
|
# Move the player by the offset
|
||||||
|
var old_position := player_body.global_position
|
||||||
|
player_body.move_and_collide(-offset)
|
||||||
|
player_body.velocity = Vector3.ZERO
|
||||||
|
|
||||||
|
# Update the players average-velocity data
|
||||||
|
var distance := player_body.global_position - old_position
|
||||||
|
_averager.add_distance(delta, distance)
|
||||||
|
|
||||||
|
# Report exclusive motion performed (to bypass gravity)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Start or stop climbing
|
||||||
|
func _set_climbing(active: bool, player_body: XRToolsPlayerBody) -> void:
|
||||||
|
# Skip if no change
|
||||||
|
if active == is_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update state
|
||||||
|
is_active = active
|
||||||
|
|
||||||
|
# Handle state change
|
||||||
|
if is_active:
|
||||||
|
_averager.clear()
|
||||||
|
player_body.override_player_height(self, 0.0)
|
||||||
|
emit_signal("player_climb_start")
|
||||||
|
else:
|
||||||
|
# Calculate the forward direction (based on camera-forward)
|
||||||
|
var dir_forward = -player_body.camera_node.global_transform.basis.z \
|
||||||
|
.slide(player_body.up_player) \
|
||||||
|
.normalized()
|
||||||
|
|
||||||
|
# Set player velocity based on averaged velocity, fling multiplier,
|
||||||
|
# and a forward push
|
||||||
|
var velocity := _averager.velocity()
|
||||||
|
player_body.velocity = (velocity * fling_multiplier) + (dir_forward * forward_push)
|
||||||
|
|
||||||
|
player_body.override_player_height(self)
|
||||||
|
emit_signal("player_climb_end")
|
||||||
|
|
||||||
|
|
||||||
|
## Handler for left controller picked up
|
||||||
|
func _on_left_picked_up(what : Node3D) -> void:
|
||||||
|
# Get the climbable
|
||||||
|
var climbable = what as XRToolsClimbable
|
||||||
|
if not climbable:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the handle
|
||||||
|
_left_handle = climbable.get_grab_handle(_left_pickup_node)
|
||||||
|
if not _left_handle:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Switch dominance to the left handle
|
||||||
|
_dominant = _left_handle
|
||||||
|
|
||||||
|
# If collision hands present then target the handle
|
||||||
|
if _left_collision_hand:
|
||||||
|
_left_collision_hand.add_target_override(_left_handle, 0)
|
||||||
|
|
||||||
|
|
||||||
|
## Handler for right controller picked up
|
||||||
|
func _on_right_picked_up(what : Node3D) -> void:
|
||||||
|
# Get the climbable
|
||||||
|
var climbable = what as XRToolsClimbable
|
||||||
|
if not climbable:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the handle
|
||||||
|
_right_handle = climbable.get_grab_handle(_right_pickup_node)
|
||||||
|
if not _right_handle:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Switch dominance to the right handle
|
||||||
|
_dominant = _right_handle
|
||||||
|
|
||||||
|
# If collision hands present then target the handle
|
||||||
|
if _right_collision_hand:
|
||||||
|
_right_collision_hand.add_target_override(_right_handle, 0)
|
||||||
|
|
||||||
|
|
||||||
|
## Handler for left controller dropped
|
||||||
|
func _on_left_dropped() -> void:
|
||||||
|
# If collision hands present then clear handle target
|
||||||
|
if _left_collision_hand:
|
||||||
|
_left_collision_hand.remove_target_override(_left_handle)
|
||||||
|
|
||||||
|
# Release handle and transfer dominance
|
||||||
|
_left_handle = null
|
||||||
|
_dominant = _right_handle
|
||||||
|
|
||||||
|
|
||||||
|
## Handler for righ controller dropped
|
||||||
|
func _on_right_dropped() -> void:
|
||||||
|
# If collision hands present then clear handle target
|
||||||
|
if _right_collision_hand:
|
||||||
|
_right_collision_hand.remove_target_override(_right_handle)
|
||||||
|
|
||||||
|
# Release handle and transfer dominance
|
||||||
|
_right_handle = null
|
||||||
|
_dominant = _left_handle
|
||||||
|
|
||||||
|
|
||||||
|
# This method verifies the movement provider has a valid configuration.
|
||||||
|
func _get_configuration_warnings() -> PackedStringArray:
|
||||||
|
var warnings := super()
|
||||||
|
|
||||||
|
# Verify the left controller pickup
|
||||||
|
if !XRToolsFunctionPickup.find_left(self):
|
||||||
|
warnings.append("Unable to find left XRToolsFunctionPickup node")
|
||||||
|
|
||||||
|
# Verify the right controller pickup
|
||||||
|
if !XRToolsFunctionPickup.find_right(self):
|
||||||
|
warnings.append("Unable to find right XRToolsFunctionPickup node")
|
||||||
|
|
||||||
|
# Verify velocity averages
|
||||||
|
if velocity_averages < 2:
|
||||||
|
warnings.append("Minimum of 2 velocity averages needed")
|
||||||
|
|
||||||
|
# Return warnings
|
||||||
|
return warnings
|
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
|
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
|
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
|
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
|
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
|
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")
|
243
addons/godot-xr-tools/functions/movement_grapple.gd
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
@tool
|
||||||
|
class_name XRToolsMovementGrapple
|
||||||
|
extends XRToolsMovementProvider
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Movement Provider for Grapple Movement
|
||||||
|
##
|
||||||
|
## This script provide simple grapple based movement - "bat hook" style
|
||||||
|
## where the player flings a rope to the target and swings on it.
|
||||||
|
## This script works with the [XRToolsPlayerBody] attached to the players
|
||||||
|
## [XROrigin3D].
|
||||||
|
|
||||||
|
|
||||||
|
## Signal emitted when grapple starts
|
||||||
|
signal grapple_started()
|
||||||
|
|
||||||
|
## Signal emitted when grapple finishes
|
||||||
|
signal grapple_finished()
|
||||||
|
|
||||||
|
|
||||||
|
## Grapple state
|
||||||
|
enum GrappleState {
|
||||||
|
IDLE, ## Grapple is idle
|
||||||
|
FIRED, ## Grapple is fired
|
||||||
|
WINCHING, ## Grapple is winching
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Default grapple collision mask of 1-5 (world)
|
||||||
|
const DEFAULT_COLLISION_MASK := 0b0000_0000_0000_0000_0000_0000_0001_1111
|
||||||
|
|
||||||
|
# Default grapple enable mask of 5:grapple-target
|
||||||
|
const DEFAULT_ENABLE_MASK := 0b0000_0000_0000_0000_0000_0000_0001_0000
|
||||||
|
|
||||||
|
|
||||||
|
## Movement provider order
|
||||||
|
@export var order : int = 20
|
||||||
|
|
||||||
|
## Grapple length - use to adjust maximum distance for possible grapple hooking.
|
||||||
|
@export var grapple_length : float = 15.0
|
||||||
|
|
||||||
|
## Grapple collision mask
|
||||||
|
@export_flags_3d_physics var grapple_collision_mask : int = DEFAULT_COLLISION_MASK:
|
||||||
|
set = _set_grapple_collision_mask
|
||||||
|
|
||||||
|
## Grapple enable mask
|
||||||
|
@export_flags_3d_physics var grapple_enable_mask : int = DEFAULT_ENABLE_MASK
|
||||||
|
|
||||||
|
## Impulse speed applied to the player on first grapple
|
||||||
|
@export var impulse_speed : float = 10.0
|
||||||
|
|
||||||
|
## Winch speed applied to the player while the grapple is held
|
||||||
|
@export var winch_speed : float = 2.0
|
||||||
|
|
||||||
|
## Probably need to add export variables for line size, maybe line material at
|
||||||
|
## some point so dev does not need to make children editable to do this.
|
||||||
|
## For now, right click on grapple node and make children editable to edit these
|
||||||
|
## facets.
|
||||||
|
@export var rope_width : float = 0.02
|
||||||
|
|
||||||
|
## Air friction while grappling
|
||||||
|
@export var friction : float = 0.1
|
||||||
|
|
||||||
|
## Grapple button (triggers grappling movement). Be sure this button does not
|
||||||
|
## conflict with other functions.
|
||||||
|
@export var grapple_button_action : String = "trigger_click"
|
||||||
|
|
||||||
|
# Hook related variables
|
||||||
|
var hook_object : Node3D = null
|
||||||
|
var hook_local := Vector3(0,0,0)
|
||||||
|
var hook_point := Vector3(0,0,0)
|
||||||
|
|
||||||
|
# Grapple button state
|
||||||
|
var _grapple_button := false
|
||||||
|
|
||||||
|
# Get line creation nodes
|
||||||
|
@onready var _line_helper : Node3D = $LineHelper
|
||||||
|
@onready var _line : CSGCylinder3D = $LineHelper/Line
|
||||||
|
|
||||||
|
# Get Controller node - consider way to universalize this if user wanted to
|
||||||
|
# attach this to a gun instead of player's hand. Could consider variable to
|
||||||
|
# select controller instead.
|
||||||
|
@onready var _controller := XRHelpers.get_xr_controller(self)
|
||||||
|
|
||||||
|
# Get Raycast node
|
||||||
|
@onready var _grapple_raycast : RayCast3D = $Grapple_RayCast
|
||||||
|
|
||||||
|
# Get Grapple Target Node
|
||||||
|
@onready var _grapple_target : Node3D = $Grapple_Target
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsMovementGrapple" or super(name)
|
||||||
|
|
||||||
|
|
||||||
|
# Function run when node is added to scene
|
||||||
|
func _ready():
|
||||||
|
# In Godot 4 we must now manually call our super class ready function
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Skip if running in the editor
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ensure grapple length is valid
|
||||||
|
var min_hook_length := 1.5 * XRServer.world_scale
|
||||||
|
if grapple_length < min_hook_length:
|
||||||
|
grapple_length = min_hook_length
|
||||||
|
|
||||||
|
# Set ray-cast
|
||||||
|
_grapple_raycast.target_position = Vector3(0, 0, -grapple_length) * XRServer.world_scale
|
||||||
|
_grapple_raycast.collision_mask = grapple_collision_mask
|
||||||
|
|
||||||
|
# Deal with line
|
||||||
|
_line.radius = rope_width
|
||||||
|
_line.hide()
|
||||||
|
|
||||||
|
|
||||||
|
# Update grapple display objects
|
||||||
|
func _process(_delta: float):
|
||||||
|
# Skip if running in the editor
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update grapple line
|
||||||
|
if is_active:
|
||||||
|
var line_length := (hook_point - _controller.global_transform.origin).length()
|
||||||
|
_line_helper.look_at(hook_point, Vector3.UP)
|
||||||
|
_line.height = line_length
|
||||||
|
_line.position.z = line_length / -2
|
||||||
|
_line.visible = true
|
||||||
|
else:
|
||||||
|
_line.visible = false
|
||||||
|
|
||||||
|
# Update grapple target
|
||||||
|
if enabled and !is_active and _is_raycast_valid():
|
||||||
|
_grapple_target.global_transform.origin = _grapple_raycast.get_collision_point()
|
||||||
|
_grapple_target.global_transform = _grapple_target.global_transform.orthonormalized()
|
||||||
|
_grapple_target.visible = true
|
||||||
|
else:
|
||||||
|
_grapple_target.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
# Perform grapple movement
|
||||||
|
func physics_movement(delta: float, player_body: XRToolsPlayerBody, disabled: bool):
|
||||||
|
# Disable if requested
|
||||||
|
if disabled or !enabled or !_controller.get_is_active():
|
||||||
|
_set_grappling(false)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update grapple button
|
||||||
|
var old_grapple_button := _grapple_button
|
||||||
|
_grapple_button = _controller.is_button_pressed(grapple_button_action)
|
||||||
|
|
||||||
|
# Enable/disable grappling
|
||||||
|
var do_impulse := false
|
||||||
|
if is_active and !_grapple_button:
|
||||||
|
_set_grappling(false)
|
||||||
|
elif _grapple_button and !old_grapple_button and _is_raycast_valid():
|
||||||
|
hook_object = _grapple_raycast.get_collider()
|
||||||
|
hook_point = _grapple_raycast.get_collision_point()
|
||||||
|
hook_local = hook_point * hook_object.global_transform
|
||||||
|
do_impulse = true
|
||||||
|
_set_grappling(true)
|
||||||
|
|
||||||
|
# Skip if not grappling
|
||||||
|
if !is_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get hook direction
|
||||||
|
hook_point = hook_object.global_transform * hook_local
|
||||||
|
var hook_vector := hook_point - _controller.global_transform.origin
|
||||||
|
var hook_length := hook_vector.length()
|
||||||
|
var hook_direction := hook_vector / hook_length
|
||||||
|
|
||||||
|
# Apply gravity
|
||||||
|
player_body.velocity += player_body.gravity * delta
|
||||||
|
|
||||||
|
# Select the grapple speed
|
||||||
|
var speed := impulse_speed if do_impulse else winch_speed
|
||||||
|
if hook_length < 1.0:
|
||||||
|
speed = 0.0
|
||||||
|
|
||||||
|
# Ensure velocity is at least winch_speed towards hook
|
||||||
|
var vdot = player_body.velocity.dot(hook_direction)
|
||||||
|
if vdot < speed:
|
||||||
|
player_body.velocity += hook_direction * (speed - vdot)
|
||||||
|
|
||||||
|
# Scale down velocity
|
||||||
|
player_body.velocity *= 1.0 - friction * delta
|
||||||
|
|
||||||
|
# Perform exclusive movement as we have dealt with gravity
|
||||||
|
player_body.velocity = player_body.move_body(player_body.velocity)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the grapple collision mask has been modified
|
||||||
|
func _set_grapple_collision_mask(new_value: int) -> void:
|
||||||
|
grapple_collision_mask = new_value
|
||||||
|
if is_inside_tree() and _grapple_raycast:
|
||||||
|
_grapple_raycast.collision_mask = new_value
|
||||||
|
|
||||||
|
|
||||||
|
# Set the grappling state and fire any signals
|
||||||
|
func _set_grappling(active: bool) -> void:
|
||||||
|
# Skip if no change
|
||||||
|
if active == is_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update the is_active flag
|
||||||
|
is_active = active;
|
||||||
|
|
||||||
|
# Report transition
|
||||||
|
if is_active:
|
||||||
|
emit_signal("grapple_started")
|
||||||
|
else:
|
||||||
|
emit_signal("grapple_finished")
|
||||||
|
|
||||||
|
|
||||||
|
# Test if the raycast is striking a valid target
|
||||||
|
func _is_raycast_valid() -> bool:
|
||||||
|
# Fail if raycast not colliding
|
||||||
|
if not _grapple_raycast.is_colliding():
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Get the target of the raycast
|
||||||
|
var target : CollisionObject3D = _grapple_raycast.get_collider()
|
||||||
|
|
||||||
|
# Check tartget layer
|
||||||
|
return true if target.collision_layer & grapple_enable_mask else false
|
||||||
|
|
||||||
|
|
||||||
|
# This method verifies the movement provider has a valid configuration.
|
||||||
|
func _get_configuration_warnings() -> PackedStringArray:
|
||||||
|
var warnings := super()
|
||||||
|
|
||||||
|
# Check the controller node
|
||||||
|
if !XRHelpers.get_xr_controller(self):
|
||||||
|
warnings.append("This node must be within a branch of an XRController3D node")
|
||||||
|
|
||||||
|
# Return warnings
|
||||||
|
return warnings
|
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
|
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
|
6
addons/godot-xr-tools/functions/movement_jump.tscn
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://c2q5phg8w08o"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_jump.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="MovementJump" type="Node" groups=["movement_providers"]]
|
||||||
|
script = ExtResource("1")
|
197
addons/godot-xr-tools/functions/movement_physical_jump.gd
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
@tool
|
||||||
|
class_name XRToolsMovementPhysicalJump
|
||||||
|
extends XRToolsMovementProvider
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Movement Provider for Player Physical Jump Detection
|
||||||
|
##
|
||||||
|
## This script can detect jumping based on either the players body jumping,
|
||||||
|
## or by the player swinging their arms up.
|
||||||
|
##
|
||||||
|
## The player body jumping is detected by putting the cameras instantaneous
|
||||||
|
## Y velocity (in the tracking space) into a sliding-window averager. If the
|
||||||
|
## average Y velocity exceeds a threshold parameter then the player has
|
||||||
|
## jumped.
|
||||||
|
##
|
||||||
|
## The player arms jumping is detected by putting both controllers instantaneous
|
||||||
|
## Y velocity (in the tracking space) into a sliding-window averager. If both
|
||||||
|
## average Y velocities exceed a threshold parameter then the player has
|
||||||
|
## jumped.
|
||||||
|
|
||||||
|
|
||||||
|
## Movement provider order
|
||||||
|
@export var order : int = 20
|
||||||
|
|
||||||
|
## If true, jumps are detected via the players body (through the camera)
|
||||||
|
@export var body_jump_enable : bool = true
|
||||||
|
|
||||||
|
## If true, the player jump is as high as the physical jump(no ground physics)
|
||||||
|
@export var body_jump_player_only : bool = false
|
||||||
|
|
||||||
|
## Body jump detection threshold (M/S^2)
|
||||||
|
@export var body_jump_threshold : float = 2.5
|
||||||
|
|
||||||
|
## If true, jumps are detected via the players arms (through the controllers)
|
||||||
|
@export var arms_jump_enable : bool = false
|
||||||
|
|
||||||
|
## Arms jump detection threshold (M/S^2)
|
||||||
|
@export var arms_jump_threshold : float = 5.0
|
||||||
|
|
||||||
|
|
||||||
|
# Node Positions
|
||||||
|
var _camera_position : float = 0.0
|
||||||
|
var _controller_left_position : float = 0.0
|
||||||
|
var _controller_right_position : float = 0.0
|
||||||
|
|
||||||
|
# Node Velocities
|
||||||
|
var _camera_velocity : SlidingAverage = SlidingAverage.new(5)
|
||||||
|
var _controller_left_velocity : SlidingAverage = SlidingAverage.new(5)
|
||||||
|
var _controller_right_velocity : SlidingAverage = SlidingAverage.new(5)
|
||||||
|
|
||||||
|
|
||||||
|
# Node references
|
||||||
|
@onready var _origin_node := XRHelpers.get_xr_origin(self)
|
||||||
|
@onready var _camera_node := XRHelpers.get_xr_camera(self)
|
||||||
|
@onready var _controller_left_node := XRHelpers.get_left_controller(self)
|
||||||
|
@onready var _controller_right_node := XRHelpers.get_right_controller(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Sliding Average class
|
||||||
|
class SlidingAverage:
|
||||||
|
# Sliding window size
|
||||||
|
var _size: int
|
||||||
|
|
||||||
|
# Sum of items in the window
|
||||||
|
var _sum := 0.0
|
||||||
|
|
||||||
|
# Position
|
||||||
|
var _pos := 0
|
||||||
|
|
||||||
|
# Data window
|
||||||
|
var _data := Array()
|
||||||
|
|
||||||
|
# Constructor
|
||||||
|
func _init(size: int):
|
||||||
|
# Set the size and fill the array
|
||||||
|
_size = size
|
||||||
|
for i in size:
|
||||||
|
_data.push_back(0.0)
|
||||||
|
|
||||||
|
# Update the average
|
||||||
|
func update(entry: float) -> float:
|
||||||
|
# Add the new entry and subtract the old
|
||||||
|
_sum += entry
|
||||||
|
_sum -= _data[_pos]
|
||||||
|
|
||||||
|
# Store the new entry in the array and circularly advance the index
|
||||||
|
_data[_pos] = entry;
|
||||||
|
_pos = (_pos + 1) % _size
|
||||||
|
|
||||||
|
# Return the average
|
||||||
|
return _sum / _size
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsMovementPhysicalJump" or super(name)
|
||||||
|
|
||||||
|
|
||||||
|
# Perform jump detection
|
||||||
|
func physics_movement(delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
|
||||||
|
# Handle detecting body jump
|
||||||
|
if body_jump_enable:
|
||||||
|
_detect_body_jump(delta, player_body)
|
||||||
|
|
||||||
|
# Handle detecting arms jump
|
||||||
|
if arms_jump_enable:
|
||||||
|
_detect_arms_jump(delta, player_body)
|
||||||
|
|
||||||
|
|
||||||
|
# This method verifies the movement provider has a valid configuration.
|
||||||
|
func _get_configuration_warnings() -> PackedStringArray:
|
||||||
|
var warnings := super()
|
||||||
|
|
||||||
|
# Verify the camera
|
||||||
|
if !XRHelpers.get_xr_origin(self):
|
||||||
|
warnings.append("This node must be within a branch of an XROrigin3D node")
|
||||||
|
|
||||||
|
# Verify the camera
|
||||||
|
if !XRHelpers.get_xr_camera(self):
|
||||||
|
warnings.append("Unable to find XRCamera3D")
|
||||||
|
|
||||||
|
# Verify the left controller
|
||||||
|
if !XRHelpers.get_left_controller(self):
|
||||||
|
warnings.append("Unable to find left XRController3D node")
|
||||||
|
|
||||||
|
# Verify the right controller
|
||||||
|
if !XRHelpers.get_right_controller(self):
|
||||||
|
warnings.append("Unable to find left XRController3D node")
|
||||||
|
|
||||||
|
# Return warnings
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
|
||||||
|
# Detect the player jumping with their body (using the headset camera)
|
||||||
|
func _detect_body_jump(delta: float, player_body: XRToolsPlayerBody) -> void:
|
||||||
|
# Get the camera instantaneous velocity
|
||||||
|
var new_camera_pos := _camera_node.transform.origin.y
|
||||||
|
var camera_vel := (new_camera_pos - _camera_position) / delta
|
||||||
|
_camera_position = new_camera_pos
|
||||||
|
|
||||||
|
# Ignore zero moves (either not tracking, or no update since last physics)
|
||||||
|
if abs(camera_vel) < 0.001:
|
||||||
|
return;
|
||||||
|
|
||||||
|
# Correct for world-scale (convert to player units)
|
||||||
|
camera_vel /= XRServer.world_scale
|
||||||
|
|
||||||
|
# Clamp the camera instantaneous velocity to +/- 2x the jump threshold
|
||||||
|
camera_vel = clamp(camera_vel, -2.0 * body_jump_threshold, 2.0 * body_jump_threshold)
|
||||||
|
|
||||||
|
# Get the averaged velocity
|
||||||
|
camera_vel = _camera_velocity.update(camera_vel)
|
||||||
|
|
||||||
|
# Detect a jump
|
||||||
|
if camera_vel >= body_jump_threshold:
|
||||||
|
player_body.request_jump(body_jump_player_only)
|
||||||
|
|
||||||
|
|
||||||
|
# Detect the player jumping with their arms (using the controllers)
|
||||||
|
func _detect_arms_jump(delta: float, player_body: XRToolsPlayerBody) -> void:
|
||||||
|
# Skip if either of the controllers is disabled
|
||||||
|
if !_controller_left_node.get_is_active() or !_controller_right_node.get_is_active():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the controllers instantaneous velocity
|
||||||
|
var new_controller_left_pos := _controller_left_node.transform.origin.y
|
||||||
|
var new_controller_right_pos := _controller_right_node.transform.origin.y
|
||||||
|
var controller_left_vel := (new_controller_left_pos - _controller_left_position) / delta
|
||||||
|
var controller_right_vel := (new_controller_right_pos - _controller_right_position) / delta
|
||||||
|
_controller_left_position = new_controller_left_pos
|
||||||
|
_controller_right_position = new_controller_right_pos
|
||||||
|
|
||||||
|
# Ignore zero moves (either not tracking, or no update since last physics)
|
||||||
|
if abs(controller_left_vel) <= 0.001 and abs(controller_right_vel) <= 0.001:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Correct for world-scale (convert to player units)
|
||||||
|
controller_left_vel /= XRServer.world_scale
|
||||||
|
controller_right_vel /= XRServer.world_scale
|
||||||
|
|
||||||
|
# Clamp the controller instantaneous velocity to +/- 2x the jump threshold
|
||||||
|
controller_left_vel = clamp(
|
||||||
|
controller_left_vel,
|
||||||
|
-2.0 * arms_jump_threshold,
|
||||||
|
2.0 * arms_jump_threshold)
|
||||||
|
controller_right_vel = clamp(
|
||||||
|
controller_right_vel,
|
||||||
|
-2.0 * arms_jump_threshold,
|
||||||
|
2.0 * arms_jump_threshold)
|
||||||
|
|
||||||
|
# Get the averaged velocity
|
||||||
|
controller_left_vel = _controller_left_velocity.update(controller_left_vel)
|
||||||
|
controller_right_vel = _controller_right_velocity.update(controller_right_vel)
|
||||||
|
|
||||||
|
# Detect a jump
|
||||||
|
if controller_left_vel >= arms_jump_threshold and controller_right_vel >= arms_jump_threshold:
|
||||||
|
player_body.request_jump()
|
|
@ -0,0 +1,7 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://ckt118vcpmr6q"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_physical_jump.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="MovementPhysicalJump" type="Node" groups=["movement_providers"]]
|
||||||
|
script = ExtResource("1")
|
||||||
|
arms_jump_enable = true
|
93
addons/godot-xr-tools/functions/movement_provider.gd
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
@tool
|
||||||
|
@icon("res://addons/godot-xr-tools/editor/icons/movement_provider.svg")
|
||||||
|
class_name XRToolsMovementProvider
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Movement Provider base class
|
||||||
|
##
|
||||||
|
## This movement provider class is the base class of all movement providers.
|
||||||
|
## Movement providers are invoked by the [XRToolsPlayerBody] object in order
|
||||||
|
## to apply motion to the player.
|
||||||
|
##
|
||||||
|
## Movement provider implementations should:
|
||||||
|
## - Export an 'order' integer to control order of processing
|
||||||
|
## - Override the physics_movement method to impelment motion
|
||||||
|
|
||||||
|
|
||||||
|
## Player body scene
|
||||||
|
const PLAYER_BODY := preload("res://addons/godot-xr-tools/player/player_body.tscn")
|
||||||
|
|
||||||
|
|
||||||
|
## Enable movement provider
|
||||||
|
@export var enabled : bool = true
|
||||||
|
|
||||||
|
|
||||||
|
## If true, the movement provider is actively performing a move
|
||||||
|
var is_active := false
|
||||||
|
|
||||||
|
|
||||||
|
# If missing we need to add our [XRToolsPlayerBody]
|
||||||
|
func _create_player_body_node():
|
||||||
|
# get our origin node
|
||||||
|
var xr_origin = XRHelpers.get_xr_origin(self)
|
||||||
|
if !xr_origin:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Double check if it hasn't already been created by another movement function
|
||||||
|
var player_body := XRToolsPlayerBody.find_instance(self)
|
||||||
|
if !player_body:
|
||||||
|
# create our XRToolsPlayerBody node and add it into our tree
|
||||||
|
player_body = PLAYER_BODY.instantiate()
|
||||||
|
player_body.set_name("PlayerBody")
|
||||||
|
xr_origin.add_child(player_body)
|
||||||
|
player_body.set_owner(get_tree().get_edited_scene_root())
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsMovementProvider"
|
||||||
|
|
||||||
|
|
||||||
|
# Function run when node is added to scene
|
||||||
|
func _ready():
|
||||||
|
# If we're in the editor, help the user out by creating our XRToolsPlayerBody node
|
||||||
|
# automatically when needed.
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
var player_body = XRToolsPlayerBody.find_instance(self)
|
||||||
|
if !player_body:
|
||||||
|
# This call needs to be deferred, we can't add nodes during scene construction
|
||||||
|
call_deferred("_create_player_body_node")
|
||||||
|
|
||||||
|
|
||||||
|
## Override this method to perform pre-movement updates to the PlayerBody
|
||||||
|
func physics_pre_movement(_delta: float, _player_body: XRToolsPlayerBody):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Override this method to apply motion to the PlayerBody
|
||||||
|
func physics_movement(_delta: float, _player_body: XRToolsPlayerBody, _disabled: bool):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# This method verifies the movement provider has a valid configuration.
|
||||||
|
func _get_configuration_warnings() -> PackedStringArray:
|
||||||
|
var warnings := PackedStringArray()
|
||||||
|
|
||||||
|
# Verify we're within the tree of an XROrigin3D node
|
||||||
|
if !XRHelpers.get_xr_origin(self):
|
||||||
|
warnings.append("This node must be within a branch on an XROrigin3D node")
|
||||||
|
|
||||||
|
if !XRToolsPlayerBody.find_instance(self):
|
||||||
|
warnings.append("Missing PlayerBody node on the XROrigin3D")
|
||||||
|
|
||||||
|
# Verify movement provider is in the correct group
|
||||||
|
if !is_in_group("movement_providers"):
|
||||||
|
warnings.append("Movement provider not in 'movement_providers' group")
|
||||||
|
|
||||||
|
# Verify order property exists
|
||||||
|
if !"order" in self:
|
||||||
|
warnings.append("Movement provider does not expose an order property")
|
||||||
|
|
||||||
|
# Return warnings
|
||||||
|
return warnings
|
169
addons/godot-xr-tools/functions/movement_sprint.gd
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
@tool
|
||||||
|
class_name XRToolsMovementSprint
|
||||||
|
extends XRToolsMovementProvider
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Movement Provider for Sprinting
|
||||||
|
##
|
||||||
|
## This script provides sprinting movement for the player. It assumes there is
|
||||||
|
## a direct movement node in the scene otherwise it will not be functional.
|
||||||
|
##
|
||||||
|
## There will not be an error, there just will not be any reason for it to
|
||||||
|
## have any impact on the player. This node should be a direct child of
|
||||||
|
## the [XROrigin3D] node rather than to a specific [XRController3D].
|
||||||
|
|
||||||
|
|
||||||
|
## Signal emitted when sprinting starts
|
||||||
|
signal sprinting_started()
|
||||||
|
|
||||||
|
## Signal emitted when sprinting finishes
|
||||||
|
signal sprinting_finished()
|
||||||
|
|
||||||
|
|
||||||
|
## Enumeration of controller to use for triggering sprinting. This allows the
|
||||||
|
## developer to assign the sprint button to either controller.
|
||||||
|
enum SprintController {
|
||||||
|
LEFT, ## Use left controller
|
||||||
|
RIGHT, ## Use right controler
|
||||||
|
}
|
||||||
|
|
||||||
|
## Enumeration of sprinting modes - toggle or hold button
|
||||||
|
enum SprintType {
|
||||||
|
HOLD_TO_SPRINT, ## Hold button to sprint
|
||||||
|
TOGGLE_SPRINT, ## Toggle sprinting on button press
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Type of sprinting
|
||||||
|
@export var sprint_type : SprintType = SprintType.HOLD_TO_SPRINT
|
||||||
|
|
||||||
|
## Sprint speed multiplier (multiplier from speed set by direct movement node(s))
|
||||||
|
@export_range(1.0, 4.0) var sprint_speed_multiplier : float = 2.0
|
||||||
|
|
||||||
|
## Movement provider order
|
||||||
|
@export var order : int = 11
|
||||||
|
|
||||||
|
## Sprint controller
|
||||||
|
@export var controller : SprintController = SprintController.LEFT
|
||||||
|
|
||||||
|
## Sprint button
|
||||||
|
@export var sprint_button : String = "primary_click"
|
||||||
|
|
||||||
|
|
||||||
|
# Sprint controller
|
||||||
|
var _controller : XRController3D
|
||||||
|
|
||||||
|
# Sprint button down state
|
||||||
|
var _sprint_button_down : bool = false
|
||||||
|
|
||||||
|
# Variable to hold left controller direct movement node original max speed
|
||||||
|
var _left_controller_original_max_speed : float = 0.0
|
||||||
|
|
||||||
|
# Variable to hold right controller direct movement node original max speed
|
||||||
|
var _right_controller_original_max_speed : float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
# Variable used to cache left controller direct movement function, if any
|
||||||
|
@onready var _left_controller_direct_move := XRToolsMovementDirect.find_left(self)
|
||||||
|
|
||||||
|
# Variable used to cache right controller direct movement function, if any
|
||||||
|
@onready var _right_controller_direct_move := XRToolsMovementDirect.find_right(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsMovementSprint" or super(name)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# In Godot 4 we must now manually call our super class ready function
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Get the sprinting controller
|
||||||
|
if controller == SprintController.LEFT:
|
||||||
|
_controller = XRHelpers.get_left_controller(self)
|
||||||
|
else:
|
||||||
|
_controller = XRHelpers.get_right_controller(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Perform sprinting
|
||||||
|
func physics_movement(_delta: float, _player_body: XRToolsPlayerBody, disabled: bool):
|
||||||
|
# Skip if the controller isn't active or is not enabled
|
||||||
|
if !_controller.get_is_active() or disabled == true or !enabled:
|
||||||
|
set_sprinting(false)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Detect sprint button down and pressed states
|
||||||
|
var sprint_button_down := _controller.is_button_pressed(sprint_button)
|
||||||
|
var sprint_button_pressed := sprint_button_down and !_sprint_button_down
|
||||||
|
_sprint_button_down = sprint_button_down
|
||||||
|
|
||||||
|
# Calculate new sprinting state
|
||||||
|
var sprinting := is_active
|
||||||
|
match sprint_type:
|
||||||
|
SprintType.HOLD_TO_SPRINT:
|
||||||
|
# Sprint when button down
|
||||||
|
sprinting = sprint_button_down
|
||||||
|
|
||||||
|
SprintType.TOGGLE_SPRINT:
|
||||||
|
# Toggle when button pressed
|
||||||
|
if sprint_button_pressed:
|
||||||
|
sprinting = !sprinting
|
||||||
|
|
||||||
|
# Update sprinting state
|
||||||
|
if sprinting != is_active:
|
||||||
|
set_sprinting(sprinting)
|
||||||
|
|
||||||
|
|
||||||
|
# Public function used to set sprinting active or not active
|
||||||
|
func set_sprinting(active: bool) -> void:
|
||||||
|
# Skip if no change
|
||||||
|
if active == is_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update state
|
||||||
|
is_active = active
|
||||||
|
|
||||||
|
# Handle state change
|
||||||
|
if is_active:
|
||||||
|
# We are sprinting
|
||||||
|
emit_signal("sprinting_started")
|
||||||
|
|
||||||
|
# Since max speeds could be changed while game is running, check
|
||||||
|
# now for original max speeds of left and right nodes
|
||||||
|
if _left_controller_direct_move:
|
||||||
|
_left_controller_original_max_speed = _left_controller_direct_move.max_speed
|
||||||
|
if _right_controller_direct_move:
|
||||||
|
_right_controller_original_max_speed = _right_controller_direct_move.max_speed
|
||||||
|
|
||||||
|
# Set both controllers' direct movement functions, if appliable, to
|
||||||
|
# the sprinting speed
|
||||||
|
if _left_controller_direct_move:
|
||||||
|
_left_controller_direct_move.max_speed = \
|
||||||
|
_left_controller_original_max_speed * sprint_speed_multiplier
|
||||||
|
if _right_controller_direct_move:
|
||||||
|
_right_controller_direct_move.max_speed = \
|
||||||
|
_right_controller_original_max_speed * sprint_speed_multiplier
|
||||||
|
else:
|
||||||
|
# We are not sprinting
|
||||||
|
emit_signal("sprinting_finished")
|
||||||
|
|
||||||
|
# Set both controllers' direct movement functions, if applicable, to
|
||||||
|
# their original speeds
|
||||||
|
if _left_controller_direct_move:
|
||||||
|
_left_controller_direct_move.max_speed = _left_controller_original_max_speed
|
||||||
|
if _right_controller_direct_move:
|
||||||
|
_right_controller_direct_move.max_speed = _right_controller_original_max_speed
|
||||||
|
|
||||||
|
|
||||||
|
# This method verifies the movement provider has a valid configuration.
|
||||||
|
func _get_configuration_warnings() -> PackedStringArray:
|
||||||
|
var warnings := super()
|
||||||
|
|
||||||
|
# Make sure player has at least one direct movement node
|
||||||
|
if !XRToolsMovementDirect.find_left(self) and !XRToolsMovementDirect.find_right(self):
|
||||||
|
warnings.append("Player missing XRToolsMovementDirect nodes")
|
||||||
|
|
||||||
|
# Return warnings
|
||||||
|
return warnings
|
6
addons/godot-xr-tools/functions/movement_sprint.tscn
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://drs4eeq721ojn"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_sprint.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="MovementSprint" type="Node" groups=["movement_providers"]]
|
||||||
|
script = ExtResource("1")
|
111
addons/godot-xr-tools/functions/movement_turn.gd
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
@tool
|
||||||
|
class_name XRToolsMovementTurn
|
||||||
|
extends XRToolsMovementProvider
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Movement Provider for Turning
|
||||||
|
##
|
||||||
|
## This script provides turning support for the player. This script works
|
||||||
|
## with the PlayerBody attached to the players XROrigin3D.
|
||||||
|
|
||||||
|
|
||||||
|
## Movement mode
|
||||||
|
enum TurnMode {
|
||||||
|
DEFAULT, ## Use turn mode from project/user settings
|
||||||
|
SNAP, ## Use snap-turning
|
||||||
|
SMOOTH ## Use smooth-turning
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Movement provider order
|
||||||
|
@export var order : int = 5
|
||||||
|
|
||||||
|
## Movement mode property
|
||||||
|
@export var turn_mode : TurnMode = TurnMode.DEFAULT
|
||||||
|
|
||||||
|
## Smooth turn speed in radians per second
|
||||||
|
@export var smooth_turn_speed : float = 2.0
|
||||||
|
|
||||||
|
## Seconds per step (at maximum turn rate)
|
||||||
|
@export var step_turn_delay : float = 0.2
|
||||||
|
|
||||||
|
## Step turn angle in degrees
|
||||||
|
@export var step_turn_angle : float = 20.0
|
||||||
|
|
||||||
|
## Our directional input
|
||||||
|
@export var input_action : String = "primary"
|
||||||
|
|
||||||
|
# Turn step accumulator
|
||||||
|
var _turn_step : float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
# Controller node
|
||||||
|
@onready var _controller := XRHelpers.get_xr_controller(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsMovementTurn" or super(name)
|
||||||
|
|
||||||
|
|
||||||
|
# Perform jump movement
|
||||||
|
func physics_movement(delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
|
||||||
|
# Skip if the controller isn't active
|
||||||
|
if !_controller.get_is_active():
|
||||||
|
return
|
||||||
|
|
||||||
|
var deadzone = 0.1
|
||||||
|
if _snap_turning():
|
||||||
|
deadzone = XRTools.get_snap_turning_deadzone()
|
||||||
|
|
||||||
|
# Read the left/right joystick axis
|
||||||
|
var left_right := _controller.get_vector2(input_action).x
|
||||||
|
if abs(left_right) <= deadzone:
|
||||||
|
# Not turning
|
||||||
|
_turn_step = 0.0
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle smooth rotation
|
||||||
|
if !_snap_turning():
|
||||||
|
left_right -= deadzone * sign(left_right)
|
||||||
|
player_body.rotate_player(smooth_turn_speed * delta * left_right)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Disable repeat snap turning if delay is zero
|
||||||
|
if step_turn_delay == 0.0 and _turn_step < 0.0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update the next turn-step delay
|
||||||
|
_turn_step -= abs(left_right) * delta
|
||||||
|
if _turn_step >= 0.0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Turn one step in the requested direction
|
||||||
|
if step_turn_delay != 0.0:
|
||||||
|
_turn_step = step_turn_delay
|
||||||
|
player_body.rotate_player(deg_to_rad(step_turn_angle) * sign(left_right))
|
||||||
|
|
||||||
|
|
||||||
|
# This method verifies the movement provider has a valid configuration.
|
||||||
|
func _get_configuration_warnings() -> PackedStringArray:
|
||||||
|
var warnings := super()
|
||||||
|
|
||||||
|
# Check the controller node
|
||||||
|
if !XRHelpers.get_xr_controller(self):
|
||||||
|
warnings.append("Unable to find XRController3D node")
|
||||||
|
|
||||||
|
# Return warnings
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
|
||||||
|
# Test if snap turning should be used
|
||||||
|
func _snap_turning():
|
||||||
|
match turn_mode:
|
||||||
|
TurnMode.SNAP:
|
||||||
|
return true
|
||||||
|
|
||||||
|
TurnMode.SMOOTH:
|
||||||
|
return false
|
||||||
|
|
||||||
|
_:
|
||||||
|
return XRToolsUserSettings.snap_turning
|
6
addons/godot-xr-tools/functions/movement_turn.tscn
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://b6bk2pj8vbj28"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_turn.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="MovementTurn" type="Node" groups=["movement_providers"]]
|
||||||
|
script = ExtResource("1")
|
44
addons/godot-xr-tools/functions/movement_wall_walk.gd
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
@tool
|
||||||
|
class_name XRToolsMovementWallWalk
|
||||||
|
extends XRToolsMovementProvider
|
||||||
|
|
||||||
|
|
||||||
|
# Default wall-walk mask of 4:wall-walk
|
||||||
|
const DEFAULT_MASK := 0b0000_0000_0000_0000_0000_0000_0000_1000
|
||||||
|
|
||||||
|
|
||||||
|
## Wall walking provider order
|
||||||
|
@export var order : int = 25
|
||||||
|
|
||||||
|
## Set our follow layer mask
|
||||||
|
@export_flags_3d_physics var follow_mask : int = DEFAULT_MASK
|
||||||
|
|
||||||
|
## Wall stick distance
|
||||||
|
@export var stick_distance : float = 1.0
|
||||||
|
|
||||||
|
## Wall stick strength
|
||||||
|
@export var stick_strength : float = 9.8
|
||||||
|
|
||||||
|
|
||||||
|
func physics_pre_movement(_delta: float, player_body: XRToolsPlayerBody):
|
||||||
|
# Test for collision with wall under feet
|
||||||
|
var wall_collision := player_body.move_and_collide(
|
||||||
|
player_body.up_player * -stick_distance, true, true, true)
|
||||||
|
if !wall_collision:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the wall information
|
||||||
|
var wall_node := wall_collision.get_collider()
|
||||||
|
var wall_normal := wall_collision.get_normal()
|
||||||
|
|
||||||
|
# Skip if the wall node doesn't have a collision layer
|
||||||
|
if not "collision_layer" in wall_node:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Skip if the wall doesn't match the follow layer
|
||||||
|
var wall_layer : int = wall_node.collision_layer
|
||||||
|
if (wall_layer & follow_mask) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Modify the player gravity
|
||||||
|
player_body.gravity = -wall_normal * stick_strength
|
6
addons/godot-xr-tools/functions/movement_wall_walk.tscn
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bk6ban0hctyym"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_wall_walk.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="MovementWallWalk" type="Node" groups=["movement_providers"]]
|
||||||
|
script = ExtResource("1")
|
135
addons/godot-xr-tools/functions/movement_wind.gd
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
@tool
|
||||||
|
class_name XRToolsMovementWind
|
||||||
|
extends XRToolsMovementProvider
|
||||||
|
|
||||||
|
|
||||||
|
## XR Tools Movement Provider for Wind
|
||||||
|
##
|
||||||
|
## This script provides wind mechanics for the player. This script works
|
||||||
|
## with the [XRToolsPlayerBody] attached to the players [XROrigin3D].
|
||||||
|
##
|
||||||
|
## When the player enters an [XRToolsWindArea], the wind pushes the player
|
||||||
|
## around, and can even lift the player into the air.
|
||||||
|
|
||||||
|
|
||||||
|
## Signal invoked when changing active wind areas
|
||||||
|
signal wind_area_changed(active_wind_area)
|
||||||
|
|
||||||
|
|
||||||
|
# Default wind area collision mask of 20:player-body
|
||||||
|
const DEFAULT_MASK := 0b0000_0000_0000_1000_0000_0000_0000_0000
|
||||||
|
|
||||||
|
|
||||||
|
## Movement provider order
|
||||||
|
@export var order : int = 25
|
||||||
|
|
||||||
|
## Drag multiplier for the player
|
||||||
|
@export var drag_multiplier : float = 1.0
|
||||||
|
|
||||||
|
# Set our collision mask
|
||||||
|
@export_flags_3d_physics var collision_mask : int = DEFAULT_MASK: set = set_collision_mask
|
||||||
|
|
||||||
|
|
||||||
|
# Wind detection area
|
||||||
|
var _sense_area : Area3D
|
||||||
|
|
||||||
|
# Array of wind areas the player is in
|
||||||
|
var _in_wind_areas := Array()
|
||||||
|
|
||||||
|
# Currently active wind area
|
||||||
|
var _active_wind_area : XRToolsWindArea
|
||||||
|
|
||||||
|
|
||||||
|
# Add support for is_xr_class on XRTools classes
|
||||||
|
func is_xr_class(name : String) -> bool:
|
||||||
|
return name == "XRToolsMovementWind" or super(name)
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready():
|
||||||
|
# In Godot 4 we must now manually call our super class ready function
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Skip if running in the editor
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Skip if we don't have a camera
|
||||||
|
var camera := XRHelpers.get_xr_camera(self)
|
||||||
|
if !camera:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Construct the sphere shape
|
||||||
|
var sphere_shape := SphereShape3D.new()
|
||||||
|
sphere_shape.radius = 0.3
|
||||||
|
|
||||||
|
# Construct the collision shape
|
||||||
|
var collision_shape := CollisionShape3D.new()
|
||||||
|
collision_shape.set_name("WindSensorShape")
|
||||||
|
collision_shape.shape = sphere_shape
|
||||||
|
|
||||||
|
# Construct the sense area
|
||||||
|
_sense_area = Area3D.new()
|
||||||
|
_sense_area.set_name("WindSensorArea")
|
||||||
|
_sense_area.collision_mask = collision_mask
|
||||||
|
_sense_area.add_child(collision_shape)
|
||||||
|
|
||||||
|
# Add the sense area to the camera
|
||||||
|
camera.add_child(_sense_area)
|
||||||
|
|
||||||
|
# Subscribe to area notifications
|
||||||
|
_sense_area.area_entered.connect(_on_area_entered)
|
||||||
|
_sense_area.area_exited.connect(_on_area_exited)
|
||||||
|
|
||||||
|
|
||||||
|
func set_collision_mask(new_mask: int) -> void:
|
||||||
|
collision_mask = new_mask
|
||||||
|
if is_inside_tree() and _sense_area:
|
||||||
|
_sense_area.collision_mask = collision_mask
|
||||||
|
|
||||||
|
|
||||||
|
func _on_area_entered(area: Area3D):
|
||||||
|
# Skip if not wind area
|
||||||
|
var wind_area = area as XRToolsWindArea
|
||||||
|
if !wind_area:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Save area and set active
|
||||||
|
_in_wind_areas.push_front(wind_area)
|
||||||
|
_active_wind_area = wind_area
|
||||||
|
|
||||||
|
# Report the wind area change
|
||||||
|
emit_signal("wind_area_changed", _active_wind_area)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_area_exited(area: Area3D):
|
||||||
|
# Erase from the wind area
|
||||||
|
_in_wind_areas.erase(area)
|
||||||
|
|
||||||
|
# If we didn't leave the active wind area then we're done
|
||||||
|
if area != _active_wind_area:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Select a new active wind area
|
||||||
|
if _in_wind_areas.is_empty():
|
||||||
|
_active_wind_area = null
|
||||||
|
else:
|
||||||
|
_active_wind_area = _in_wind_areas.front()
|
||||||
|
|
||||||
|
# Report the wind area change
|
||||||
|
emit_signal("wind_area_changed", _active_wind_area)
|
||||||
|
|
||||||
|
|
||||||
|
# Perform wind movement
|
||||||
|
func physics_movement(delta: float, player_body: XRToolsPlayerBody, _disabled: bool):
|
||||||
|
# Skip if no active wind area
|
||||||
|
if !_active_wind_area:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate the global wind velocity of the wind area
|
||||||
|
var wind_velocity := _active_wind_area.global_transform.basis * _active_wind_area.wind_vector
|
||||||
|
|
||||||
|
# Drag the player into the wind
|
||||||
|
var drag_factor := _active_wind_area.drag * drag_multiplier * delta
|
||||||
|
drag_factor = clamp(drag_factor, 0.0, 1.0)
|
||||||
|
player_body.velocity = player_body.velocity.lerp(wind_velocity, drag_factor)
|
6
addons/godot-xr-tools/functions/movement_wind.tscn
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bgts3vpmjn6bb"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/godot-xr-tools/functions/movement_wind.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="MovementWind" type="Node" groups=["movement_providers"]]
|
||||||
|
script = ExtResource("1")
|
17
addons/godot-xr-tools/hands/About.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
The Hand Mesh was made using the Makehuman Community Edition
|
||||||
|
It was slightly modified inside Blender 3D and textured inside Substance Painter
|
||||||
|
|
||||||
|
If there should be concerns regarding License issues
|
||||||
|
https://github.com/makehumancommunity/makehuman/blob/master/LICENSE.md
|
||||||
|
|
||||||
|
under point D of the makehuman communtiy edition license
|
||||||
|
https://github.com/makehumancommunity/makehuman/blob/master/LICENSE.md#d-concerning-the-output-from-makehuman
|
||||||
|
As the assets have been released under CC0, there is no limitation on what you can do with this combined output.
|
||||||
|
The MakeHuman project makes no claim whatsoever over output such as:
|
||||||
|
|
||||||
|
Exports to files (FBX, OBJ, DAE, MHX2...)
|
||||||
|
Exports via direct integration (import via MPFB)
|
||||||
|
Graphical data generated via scripting or plugins
|
||||||
|
Renderings
|
||||||
|
Screenshots
|
||||||
|
Saved model files
|
126
addons/godot-xr-tools/hands/License.md
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
To the extent possible under law, DigitalN8m4r3 aka Miodrag Sejic
|
||||||
|
has waived all copyright and related or neighboring rights to Hand Models for the Godot XR Tools.
|
||||||
|
This work is published from: Austria.
|
||||||
|
Date: 7th November 2022
|
||||||
|
|
||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
CC0 1.0 Universal
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
|
Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||||
|
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
|
authorship and/or a database (each, a "Work").
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for
|
||||||
|
the purpose of contributing to a commons of creative, cultural and
|
||||||
|
scientific works ("Commons") that the public can reliably and without fear
|
||||||
|
of later claims of infringement build upon, modify, incorporate in other
|
||||||
|
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||||
|
and for any purposes, including without limitation commercial purposes.
|
||||||
|
These owners may contribute to the Commons to promote the ideal of a free
|
||||||
|
culture and the further production of creative, cultural and scientific
|
||||||
|
works, or to gain reputation or greater distribution for their Work in
|
||||||
|
part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any
|
||||||
|
expectation of additional consideration or compensation, the person
|
||||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||||
|
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||||
|
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not
|
||||||
|
limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
|
likeness depicted in a Work;
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(a), below;
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation
|
||||||
|
thereof, including any amended or successor version of such
|
||||||
|
directive); and
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
|
world based on applicable law or treaty, and any national
|
||||||
|
implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||||
|
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||||
|
of action, whether now known or unknown (including existing as well as
|
||||||
|
future claims and causes of action), in the Work (i) in all territories
|
||||||
|
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||||
|
treaty (including future time extensions), (iii) in any current or future
|
||||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||||
|
member of the public at large and to the detriment of Affirmer's heirs and
|
||||||
|
successors, fully intending that such Waiver shall not be subject to
|
||||||
|
revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||||
|
be judged legally invalid or ineffective under applicable law, then the
|
||||||
|
Waiver shall be preserved to the maximum extent permitted taking into
|
||||||
|
account Affirmer's express Statement of Purpose. In addition, to the
|
||||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||||
|
maximum duration provided by applicable law or treaty (including future
|
||||||
|
time extensions), (iii) in any current or future medium and for any number
|
||||||
|
of copies, and (iv) for any purpose whatsoever, including without
|
||||||
|
limitation commercial, advertising or promotional purposes (the
|
||||||
|
"License"). The License shall be deemed effective as of the date CC0 was
|
||||||
|
applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||||
|
of the License, and in such case Affirmer hereby affirms that he or she
|
||||||
|
will not (i) exercise any of his or her remaining Copyright and Related
|
||||||
|
Rights in the Work or (ii) assert any associated claims and causes of
|
||||||
|
action with respect to the Work, in either case contrary to Affirmer's
|
||||||
|
express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied,
|
||||||
|
statutory or otherwise, including without limitation warranties of
|
||||||
|
title, merchantability, fitness for a particular purpose, non
|
||||||
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
|
the greatest extent permissible under applicable law.
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the
|
||||||
|
Work.
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
|
@ -0,0 +1,81 @@
|
||||||
|
[gd_scene load_steps=37 format=3 uid="uid://the6y7swe6j0"]
|
||||||
|
|
||||||
|
[ext_resource type="Animation" uid="uid://dgfeikrugfewi" path="res://addons/godot-xr-tools/hands/animations/left/Cup.res" id="1_7svyl"]
|
||||||
|
[ext_resource type="Animation" uid="uid://dlxa6f6hwurka" path="res://addons/godot-xr-tools/hands/animations/left/Default pose.res" id="2_ykhfd"]
|
||||||
|
[ext_resource type="Animation" uid="uid://dqa0h82y3qn1t" path="res://addons/godot-xr-tools/hands/animations/left/Grip 1.res" id="3_y80ds"]
|
||||||
|
[ext_resource type="Animation" uid="uid://di384xtde8ydf" path="res://addons/godot-xr-tools/hands/animations/left/Grip 2.res" id="4_1nbr8"]
|
||||||
|
[ext_resource type="Animation" uid="uid://dd67rufxwj2u" path="res://addons/godot-xr-tools/hands/animations/left/Grip 3.res" id="5_28lp1"]
|
||||||
|
[ext_resource type="Animation" uid="uid://db62hs5s4n2b3" path="res://addons/godot-xr-tools/hands/animations/left/Grip 4.res" id="6_uj648"]
|
||||||
|
[ext_resource type="Animation" uid="uid://bediglpx0rj7i" path="res://addons/godot-xr-tools/hands/animations/left/Grip 5.res" id="7_yxjeh"]
|
||||||
|
[ext_resource type="Animation" uid="uid://nq3xh1olqipq" path="res://addons/godot-xr-tools/hands/animations/left/Grip Shaft.res" id="8_i0ro5"]
|
||||||
|
[ext_resource type="Animation" uid="uid://plad1r85f7ws" path="res://addons/godot-xr-tools/hands/animations/left/Grip.res" id="9_811qe"]
|
||||||
|
[ext_resource type="Animation" uid="uid://bi1l6lre2w2lp" path="res://addons/godot-xr-tools/hands/animations/left/Hold.res" id="10_o5u3q"]
|
||||||
|
[ext_resource type="Animation" uid="uid://c3e6h0rv2uw2d" path="res://addons/godot-xr-tools/hands/animations/left/Horns.res" id="11_i2b8g"]
|
||||||
|
[ext_resource type="Animation" uid="uid://dfekure1r6q13" path="res://addons/godot-xr-tools/hands/animations/left/Metal.res" id="12_yoig6"]
|
||||||
|
[ext_resource type="Animation" uid="uid://b0rhk4r0r0t32" path="res://addons/godot-xr-tools/hands/animations/left/Middle.res" id="13_kvhwh"]
|
||||||
|
[ext_resource type="Animation" uid="uid://f5k0gh4qnmv5" path="res://addons/godot-xr-tools/hands/animations/left/OK.res" id="14_ofab3"]
|
||||||
|
[ext_resource type="Animation" uid="uid://1nlkfvitq7ku" path="res://addons/godot-xr-tools/hands/animations/left/Peace.res" id="15_a4q3y"]
|
||||||
|
[ext_resource type="Animation" uid="uid://dhjb0e334tfwl" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Flat.res" id="16_ixe2d"]
|
||||||
|
[ext_resource type="Animation" uid="uid://dkjsnihi81b7p" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Large.res" id="17_oifug"]
|
||||||
|
[ext_resource type="Animation" uid="uid://bn0fdhe2jwq3h" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Middle.res" id="18_2ic2e"]
|
||||||
|
[ext_resource type="Animation" uid="uid://bo1b8w0s4ci81" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Ring.res" id="19_4a8v4"]
|
||||||
|
[ext_resource type="Animation" uid="uid://m5x2m8x3tcel" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Tight.res" id="20_ugc01"]
|
||||||
|
[ext_resource type="Animation" uid="uid://fi23m6i7orhw" path="res://addons/godot-xr-tools/hands/animations/left/Pinch Up.res" id="21_l3kgg"]
|
||||||
|
[ext_resource type="Animation" uid="uid://c8qmcuyaltdnw" path="res://addons/godot-xr-tools/hands/animations/left/PingPong.res" id="22_j83er"]
|
||||||
|
[ext_resource type="Animation" uid="uid://bqnoubqq7ogwu" path="res://addons/godot-xr-tools/hands/animations/left/Pinky.res" id="23_sm4sd"]
|
||||||
|
[ext_resource type="Animation" uid="uid://ddbo6ioa282en" path="res://addons/godot-xr-tools/hands/animations/left/Pistol.res" id="24_r4kss"]
|
||||||
|
[ext_resource type="Animation" uid="uid://brkptjihht3ae" path="res://addons/godot-xr-tools/hands/animations/left/Ring.res" id="25_2ncut"]
|
||||||
|
[ext_resource type="Animation" uid="uid://cnng6xumhw7cx" path="res://addons/godot-xr-tools/hands/animations/left/Rounded.res" id="26_02lfv"]
|
||||||
|
[ext_resource type="Animation" uid="uid://cevirj0eagdrq" path="res://addons/godot-xr-tools/hands/animations/left/Sign 1.res" id="27_o5si3"]
|
||||||
|
[ext_resource type="Animation" uid="uid://cc6phxovf1ban" path="res://addons/godot-xr-tools/hands/animations/left/Sign 2.res" id="28_ble5k"]
|
||||||
|
[ext_resource type="Animation" uid="uid://ohthjp8qbcc4" path="res://addons/godot-xr-tools/hands/animations/left/Sign 3.res" id="29_twq1x"]
|
||||||
|
[ext_resource type="Animation" uid="uid://dmx42g64577g5" path="res://addons/godot-xr-tools/hands/animations/left/Sign 4.res" id="30_eyeuv"]
|
||||||
|
[ext_resource type="Animation" uid="uid://dhsoxntrktx0p" path="res://addons/godot-xr-tools/hands/animations/left/Sign 5.res" id="31_50jrs"]
|
||||||
|
[ext_resource type="Animation" uid="uid://c0u2a3yc2vhg8" path="res://addons/godot-xr-tools/hands/animations/left/Sign_Point.res" id="32_7v122"]
|
||||||
|
[ext_resource type="Animation" uid="uid://4g211my0hoiw" path="res://addons/godot-xr-tools/hands/animations/left/Straight.res" id="33_m3lif"]
|
||||||
|
[ext_resource type="Animation" uid="uid://d06l7hygl4qt3" path="res://addons/godot-xr-tools/hands/animations/left/Surfer.res" id="34_b3p10"]
|
||||||
|
[ext_resource type="Animation" uid="uid://bxei4oebd4hu3" path="res://addons/godot-xr-tools/hands/animations/left/Thumb.res" id="35_huikq"]
|
||||||
|
|
||||||
|
[sub_resource type="AnimationLibrary" id="AnimationLibrary_kw48d"]
|
||||||
|
_data = {
|
||||||
|
"Cup": ExtResource("1_7svyl"),
|
||||||
|
"Default pose": ExtResource("2_ykhfd"),
|
||||||
|
"Grip": ExtResource("9_811qe"),
|
||||||
|
"Grip 1": ExtResource("3_y80ds"),
|
||||||
|
"Grip 2": ExtResource("4_1nbr8"),
|
||||||
|
"Grip 3": ExtResource("5_28lp1"),
|
||||||
|
"Grip 4": ExtResource("6_uj648"),
|
||||||
|
"Grip 5": ExtResource("7_yxjeh"),
|
||||||
|
"Grip Shaft": ExtResource("8_i0ro5"),
|
||||||
|
"Hold": ExtResource("10_o5u3q"),
|
||||||
|
"Horns": ExtResource("11_i2b8g"),
|
||||||
|
"Metal": ExtResource("12_yoig6"),
|
||||||
|
"Middle": ExtResource("13_kvhwh"),
|
||||||
|
"OK": ExtResource("14_ofab3"),
|
||||||
|
"Peace": ExtResource("15_a4q3y"),
|
||||||
|
"Pinch Flat": ExtResource("16_ixe2d"),
|
||||||
|
"Pinch Large": ExtResource("17_oifug"),
|
||||||
|
"Pinch Middle": ExtResource("18_2ic2e"),
|
||||||
|
"Pinch Ring": ExtResource("19_4a8v4"),
|
||||||
|
"Pinch Tight": ExtResource("20_ugc01"),
|
||||||
|
"Pinch Up": ExtResource("21_l3kgg"),
|
||||||
|
"PingPong": ExtResource("22_j83er"),
|
||||||
|
"Pinky": ExtResource("23_sm4sd"),
|
||||||
|
"Pistol": ExtResource("24_r4kss"),
|
||||||
|
"Ring": ExtResource("25_2ncut"),
|
||||||
|
"Rounded": ExtResource("26_02lfv"),
|
||||||
|
"Sign 1": ExtResource("27_o5si3"),
|
||||||
|
"Sign 2": ExtResource("28_ble5k"),
|
||||||
|
"Sign 3": ExtResource("29_twq1x"),
|
||||||
|
"Sign 4": ExtResource("30_eyeuv"),
|
||||||
|
"Sign 5": ExtResource("31_50jrs"),
|
||||||
|
"Sign_Point": ExtResource("32_7v122"),
|
||||||
|
"Straight": ExtResource("33_m3lif"),
|
||||||
|
"Surfer": ExtResource("34_b3p10"),
|
||||||
|
"Thumb": ExtResource("35_huikq")
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="AnimationPlayer" type="AnimationPlayer"]
|
||||||
|
libraries = {
|
||||||
|
"": SubResource("AnimationLibrary_kw48d")
|
||||||
|
}
|