Files
project-vivi/Assets/Classes/PlayerCharacter/player_movement_handler.gd
T
ActualHorse ddc86b6554 - Project updated to support the new CharacterBody3D node structure.
- PlayerMovementHandler updated to properly use physics process root motion.
2026-10-04 21:42:59 -04:00

210 lines
8.7 KiB
GDScript

class_name RootMovementHandler
extends Node
@export_category("Nodes")
@export var character : Character3D
@export var body : CharacterBody3D
@export var player_camera : PlayerCamera3D
@export var slice_ring_spawner : SliceRingSpawner
@export_category("Input Settings")
@export var _walk_reaction_speed : float = 1.0
@export var _walk_turn_speed : float = 10.0
@export_category("Input Resources")
@export var _player_movement_standard : InputResource
@export var _player_movement_slice_time : InputResource
@export_category("Physics Constants")
@export_range(0.0, 10.0, 0.01) var _gravity_multiplier : float = 1.0
@export_range(0.0, 100.0, 0.1) var _terminal_velocity : float = 35.0
@export_range(0.0, 10.0, 0.05) var _air_acceleration : float = 6.0
@export_range(0.0, 20.0, 0.05) var _max_air_velocity : float = 6.0
@export_range(0.0, 10.0, 0.05) var _drag : float = 1.0
@export_range(0.0, 50.0, 0.05) var _slice_time_aim_blend_speed = 30.0
@onready var _state_chart : StateChart = $StandardMovementChart
var _target_movement_vector : Vector2 = Vector2.ZERO
var _current_movement_speed : float = 0.0
var _physics_motion_accumulator : Vector3 = Vector3.ZERO
var _gravity : float = 0.0
var _slice_time_aim_blend : float = 0.0
var _slice_time_tilt : float = 0.5
var _slice_time_angle : float = 0.0
signal state_changed(StringName)
func _ready() -> void:
InputHandler.add_input_resource(self, _player_movement_standard)
character.character_skeleton.animation_tree.advance_expression_base_node = self.get_path()
func _on_movement_2d(_delta:float, _vector:Vector2) -> void:
_target_movement_vector = _vector
func _on_slice_time_rotate_2d(_delta:float, _vector:Vector2) -> void:
_target_movement_vector = _vector
func _on_jump_pressed() -> void:
pass
func _on_slice_time_pressed() -> void:
_state_chart.send_event("slice_time")
func _on_slice_time_released() -> void:
_state_chart.send_event("no_more_slice_time")
func _on_walking_state_entered() -> void:
_physics_motion_accumulator = Vector3.ZERO
_gravity = 0.0
emit_signal("state_changed", "walk")
func _on_coyote_time_state_entered() -> void:
_on_walking_state_entered()
func _on_walking_state_processing(_delta:float) -> void:
_camera_relative_heading_process(_delta)
func _on_coyote_time_state_processing(_delta: float) -> void:
_on_walking_state_processing(_delta)
func _on_walking_state_physics_processing(_delta:float) -> void:
_root_motion_walk_process(_delta)
_lateral_movement_physics_process(_delta)
#_gravity_physics_process(_delta)
_move_character()
if !body.is_on_floor():
_state_chart.send_event("fall")
_gravity = 0.0
func _on_coyote_time_state_physics_processing(_delta: float) -> void:
_on_walking_state_physics_processing(_delta)
func _on_falling_state_entered() -> void:
_gravity = 0.0
_physics_motion_accumulator = body.velocity
func _on_falling_state_processing(_delta:float) -> void:
_camera_relative_heading_process(_delta)
_physics_movement_accumulation_process(_delta)
func _on_falling_state_physics_processing(_delta:float) -> void:
_physics_movement_physics_process(_delta)
_gravity_physics_process(_delta)
_move_character()
if body.is_on_floor():
_state_chart.send_event("land")
func _on_landed_state_entered() -> void:
emit_signal("state_changed", "landed")
func _on_landed_state_physics_processing(_delta:float) -> void:
pass
func _on_slice_time_state_entered() -> void:
InputHandler.remove_input_resource(self, _player_movement_standard)
InputHandler.add_input_resource(self, _player_movement_slice_time)
_reset_all_movement()
character.character_skeleton.animation_tree["parameters/playback"].travel("slice_time")
_slice_time_tilt = 0.5
player_camera.state_chart.send_event("slice_time")
slice_ring_spawner.spawn(self, true);
func _on_slice_time_state_exited() -> void:
_reset_all_movement()
character.character_skeleton.animation_tree["parameters/playback"].travel("walk")
slice_ring_spawner.despawn()
InputHandler.remove_input_resource(self, _player_movement_slice_time)
InputHandler.add_input_resource(self, _player_movement_standard)
player_camera.state_chart.send_event("no_more_slice_time")
func _on_slice_time_state_processing(_delta:float) -> void:
character.character_skeleton.animation_tree["parameters/slice_time/slice_time_idle/slice_time_pitch/seek_request"] = _slice_time_tilt
#character.character_skeleton.animation_tree["parameters/slice_time_active/aim_angle/seek_request"] = 0.0
func _on_slice_time_state_physics_processing(_delta:float) -> void:
#_lateral_movement_physics_process(_delta)
#_move_character()
pass
func _on_slice_time_aim_state_entered() -> void:
var _slice_time_input : InputAction = _player_movement_standard.find_input_action("slice_time")
if not _slice_time_input or not _slice_time_input.is_active():
_state_chart.send_event("no_more_slice_time")
func _on_slice_time_aim_state_processing(_delta:float) -> void:
_slice_time_tilt = clampf(_slice_time_tilt + (_target_movement_vector.y * _delta), 0.0, 1.0)
var old_blend_time : float = character.character_skeleton.animation_tree["parameters/slice_time/slice_time_idle/slice_time_angle_blend/blend_amount"] as float
character.character_skeleton.animation_tree["parameters/slice_time/slice_time_idle/slice_time_angle_blend/blend_amount"] = lerpf(old_blend_time, _slice_time_aim_blend, _slice_time_aim_blend_speed * _delta)
character.character_skeleton.animation_tree["parameters/slice_time/slice_time_idle/slice_time_angle/seek_request"] = _slice_time_angle
var old_slice_tilt : float = character.character_skeleton.animation_tree["parameters/slice_time/slice_time_idle/slice_time_pitch/seek_request"]
character.character_skeleton.animation_tree["parameters/slice_time/slice_time_idle/slice_time_pitch/seek_request"] = lerpf(old_slice_tilt, _slice_time_tilt, _slice_time_aim_blend_speed * _delta)
func _on_slice_time_aim_state_physics_processing(_delta:float) -> void:
_slice_time_rotation_physics_process(_delta)
func _on_slice_time_slice_state_entered() -> void:
pass
func _on_slice_time_slice_state_processing(_delta:float) -> void:
_on_slice_time_aim_state_processing(_delta)
func _on_slice_time_slice_state_physics_processing(_delta:float) -> void:
_on_slice_time_aim_state_physics_processing(_delta)
func _move_character():
body.move_and_slide()
func _root_motion_walk_process(_delta:float) -> void:
_current_movement_speed = lerpf(_current_movement_speed, _target_movement_vector.length_squared(), clampf(_delta * _walk_reaction_speed, 0.0, 1.0))
character.character_skeleton.animation_tree["parameters/walk/blend_position"] = _current_movement_speed
func _camera_relative_heading_process(_delta:float):
var _target_movement_length_squared : float = _target_movement_vector.length_squared()
if _target_movement_length_squared >= 0.0001:
var _camera_relative_rotation : Quaternion = \
Quaternion(Vector3(_target_movement_vector.x, 0.0, _target_movement_vector.y).normalized(), \
Vector3(-player_camera.camera.global_basis.z.x, 0.0, -player_camera.camera.global_basis.z.z).normalized())
character.quaternion = character.quaternion.slerp(_camera_relative_rotation, clampf(_delta * _walk_turn_speed, 0.0, 1.0))
func _lateral_movement_physics_process(_delta:float) -> void:
var root_motion_speed : Vector3 = character.quaternion * character.character_skeleton.animation_tree.get_root_motion_position()
if root_motion_speed.length_squared() >= 0.00000000:
body.velocity = root_motion_speed / _delta
func _physics_movement_accumulation_process(_delta:float) -> void:
_physics_motion_accumulator = _physics_motion_accumulator.lerp(character.basis.z * _max_air_velocity, _target_movement_vector.length_squared() * _delta * _air_acceleration)
func _physics_movement_physics_process(_delta:float) -> void:
_physics_motion_accumulator = _physics_motion_accumulator.lerp(Vector3.ZERO, _drag * _delta * (1.0 - _target_movement_vector.length_squared()))
body.velocity = _physics_motion_accumulator
func _slice_time_rotation_physics_process(_delta:float) -> void:
character.rotate_y(-_target_movement_vector.x * _delta * 1.5)
func _on_slice_aim_update(_is_aiming:bool, _angle:float) -> void:
_slice_time_angle = 1.0 - (absf(_angle - PI) / TAU)
_slice_time_aim_blend = 1.0 if _is_aiming else 0.0
func _on_slice_executed() -> void:
_state_chart.send_event("slice")
func _gravity_physics_process(_delta:float) -> void:
_gravity = clamp(_gravity + (ProjectSettings.get_setting("physics/3d/default_gravity") * _gravity_multiplier * _delta), 0.0, _terminal_velocity)
body.velocity -= character.global_basis.y * _gravity
func _reset_all_movement() -> void:
_target_movement_vector = Vector2.ZERO
_current_movement_speed = 0.0
_physics_motion_accumulator = Vector3.ZERO
_gravity = 0.0
character.character_skeleton.animation_tree["parameters/walk/blend_position"] = _current_movement_speed