Reorient player to any surface and treat as floor

Godot Version

4.3

Question

I am struggling with what feels like it should be a pretty straightforward thing, but it has really been causing me some grief. I am working on a third-person 3D platformer and want my CharacterBody3D player to be able to rotate to and treat any surface as the floor as long as it achieves that angle from a slope within the player object’s Max Angle. I am rotating the player object to the floor normal via the basis through a RayCast collision and using set_up_direction to make the up direction whatever the colliding normal is. When the up direction.y isn’t 1, it also causes the camera to toggle from top layer to being parented to the player object again in order to rotate the inputs relative to the current orientation. Unfortunately while this allows the player object to exceed the Max Angle as it should, it will still come to a stop at angles approaching 90 degrees (I’m a new user so I can’t upload an example video yet). My current approach also comes with the side effect of breaking floor_snap and the player will bounce on slopes well within its Max Angle limit.

I’ve been stuck iterating different solutions for this problem for a while and any help would be super appreciated.

extends CharacterBody3D

@onready var camControl : Node3D = %CamControl
@onready var grounding : RayCast3D = %Grounding
@onready var front : RayCast3D = %Front
@onready var back : RayCast3D = %Back
@onready var character : Node3D = %Character


const SPEED = 25
const JUMP_VELOCITY = 12

#gravity
var gravity = 25

var xform : Transform3D


func _process(delta):
	if camControl.top_level == true:
		camControl.position = position
	else:
		pass

func _physics_process(delta):
	
	# Inputs and direction
	var input_dir = Input.get_vector("Left", "Right", "Up", "Down")
	var direction = (camControl.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	
	if input_dir != Vector2(0, 0):
		character.rotation_degrees.y = camControl.rotation_degrees.y - rad_to_deg(input_dir.angle()) - 90

			
	#New Normal
	if is_on_floor():
		set_up_direction(grounding.get_collision_normal())
		align_with_y(grounding.get_collision_normal())
		global_transform = global_transform.interpolate_with(xform, 0.8)

		
	elif not is_on_floor():
		set_up_direction(Vector3(-0, 1, 0))
		align_with_y(Vector3.UP)
		global_transform = global_transform.interpolate_with(xform, 0.1)
		global_rotation.y = direction.y
		
	# Gravity
	if not is_on_floor():
		velocity.y -= gravity * delta
		camControl.transform.basis.x.y = 0.0
		camControl.global_rotation.z = 0.0

	# Jump
	if grounding.is_colliding:
		if Input.is_action_just_pressed("Jump"):
			velocity.y = JUMP_VELOCITY
		else:
			velocity.y -= 1.5 * delta


	#Latch camera to player for up direction transition
	if get_up_direction().y != 1:
		camControl.top_level = false
	else:
		camControl.top_level = true

	#Apply velocity
	if direction:
		velocity.x = direction.x * SPEED
		velocity.z = direction.z * SPEED

			
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)
		velocity.z = move_toward(velocity.z, 0, SPEED)


	move_and_slide()

	print_debug(is_on_floor(), grounding.is_colliding(), up_direction)

func align_with_y(newNormal):
	xform = global_transform
	xform.basis.y = newNormal
	xform.basis.x = -xform.basis.z.cross(newNormal)
	xform.basis = xform.basis.orthonormalized()