Hello,
I'm trying to recreate a climbing mechanic similar to the game "The Legends of Zelda : Breath of the Wild" but encounter some difficulty :
Sometime the character teleport to the origin of the map
When facing a certain direction and initializing a transition (ex : turning around a corner) the character do a 360 turn
On some occasion the character won't turn around a corner (i'm not certain but i think it's because the angle is superior to 90°)
When transitioning from a wall to another if they aren't straight the rotation doesn't go well
And that's only the problem that i found, here is how i proceed so far when the character is climbing if it encounter a corner i initialize a transition with some parameters and then apply the transition during the physics function has follow :
func physics_process(delta: float) -> void:
if transition:
_make_transition(delta)
return
var input_direction: = get_input_direction()
var up: Vector3 = player.global_transform.basis.y * input_direction.y
var right: Vector3 = player.global_transform.basis.x * input_direction.x
var move_direction: = up + right
if move_direction.length() > 1.0:
move_direction = move_direction.normalized()
#movement
if _can_move(move_direction):
velocity = move_direction * climb_speed
velocity = player.move_and_slide(velocity, Vector3.UP,true)
if player.is_on_floor() or _is_floor(player.knee_cast.get_collision_normal()):
_state_machine.transition_to("Movement/Idle")
if player.is_on_floor.is_colliding():
_state_machine.transition_to("Movement/Idle")
if player.climb_cast.is_colliding():
#rotation
player.transform = _look_at(player.translation, player.climb_cast.get_collision_normal())
#distance from wall
var collision: Vector3 = player.climb_cast.get_collision_point()
var length_from_wall = (player.translation - collision).length()
if length_from_wall > offset_from_wall:
var vz = -player.global_transform.basis.z
# warning-ignore:return_value_discarded
player.move_and_collide(vz)
player.translation -= vz.normalized() * offset_from_wall
if input_direction.y > 0 and !player.head_cast.is_colliding():
var speed: float = 4.0
if _is_floor(player.ledge_cast.get_collision_normal()):
speed = 2.0
start_climb = true
_initialise_transition(speed, player.ledge_cast, player.translation)
else:
_initialise_transition(speed, player.ledge_cast, player.ledge_cast.get_collision_point())
return
if input_direction.x < 0:
if player.left_corner_cast.is_colliding():
_initialise_transition(4.0, player.left_corner_cast, player.translation)
return
if !player.climb_cast.is_colliding():
_initialise_transition(4.0, player.left_corner_cast_2, player.left_corner_cast_2.get_collision_point(),true)
abs_normal = false
return
if input_direction.x > 0:
if player.right_corner_cast.is_colliding():
_initialise_transition(4.0, player.right_corner_cast, player.translation)
return
if !player.climb_cast.is_colliding():
_initialise_transition(4.0, player.right_corner_cast_2, player.right_corner_cast_2.get_collision_point())
abs_normal = false
return
func _initialise_transition(speed : float, cast, rot_center : Vector3, invert_angle : bool = false) -> void:
transition_percentage = 0.0
var col = cast.get_collision_point()
var normal: Vector3 = cast.get_collision_normal()
transition_speed = speed
start_position = player.translation
target_position = col
target_position += normal * offset_from_wall
start_rotation = player.rotation
helper.transform = _look_at(target_position, normal)
target_rotation = helper.rotation
center_of_rotation = rot_center
if start_climb:
start_climb = false
else:
target_position -= helper.transform.basis.y * 0.5
if center_of_rotation != player.translation:
if !start_climb:
center_of_rotation -= helper.transform.basis.y * 0.5
var vect : Vector3 = player.translation - rot_center
rotation_angle = vect.angle_to(normal)
if rad2deg(rotation_angle) > 90 or rad2deg(rotation_angle) < -90:
supl_rot_angle = 2 * rotation_angle
else:
supl_rot_angle = 0.0
if invert_angle:
rotation_angle = -rotation_angle
previous_rotation_angle = 0.0
previous_supl_rot_angle = 0.0
rotation_normal = helper.transform.basis.y
helper.translation = target_position
helper.look_at(helper.translation - player.translation, rotation_normal)
transition = true
func _make_transition(delta : float):
transition_percentage += transition_speed * delta
if transition_percentage > 1:
transition_percentage = 1
transition = false
if center_of_rotation == player.translation:
var tp: Vector3 = lerp(start_position, target_position, transition_percentage)
player.translation = tp
var rp: Vector3 = lerp(start_rotation, target_rotation, transition_percentage)
player.rotation = rp
center_of_rotation = player.translation
else:
var angle: float = lerp(0.0, rotation_angle, transition_percentage)
var current_angle = angle
angle -= previous_rotation_angle
previous_rotation_angle = current_angle
var supl_angle: float
var current_supl_rot: float
if supl_rot_angle != 0.0:
supl_angle = lerp(0.0, supl_rot_angle, transition_percentage)
current_supl_rot = supl_angle
supl_angle -= previous_supl_rot_angle
previous_supl_rot_angle = current_supl_rot
helper.rotate(rotation_normal, supl_angle)
else:
helper.rotate(rotation_normal, angle)
player.translation = center_of_rotation + helper.transform.basis.z - helper.transform.basis.z * offset_from_wall
player.rotate(rotation_normal,angle)
Note that the detection of a corner is made with raycast 
I'm probably forgetting something but i have join a minimal project here
If you need some precision don't hesitate to ask
PS : You can find the climbing logic under the folder "src/player/states/climbing" or in the player scene "StateMachine/Climbing"
PS : I place some position3d on the main map to place the player in the same starting position as the gif