immersive-home/app/addons/godot-xr-tools/functions/movement_glide.gd

237 lines
7.6 KiB
GDScript3
Raw Normal View History

2023-10-16 20:10:20 +03:00
@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