Make a 3D player able to move through loop de loops

Godot Version

4.7

Question

I'm trying to make a Sonic-style fangame and this is my current roadblock. I'm not using a framework because they tend to be too complicated to edit and too bulky for lower-end systems to run, which it's my goal to have it run on those. I have tried CharacterBody3D, RigidBody3D, and Kinematic Frozen RigidBody3D to make this happen, to make the player rotate with the loop, stay stuck to it all the way around including upside-down, and rotate the controls with the character so you don't have to change from holding forward at the top of the loop to backward in order to go all the way around. I've been stuck on this for a couple of days, trying whatever I could think of and looking for any solutions and nothing has worked.

I kind of found a way to rotate the controls with .slide(), but when I do that, gravity becomes basically a glide instead of gradually increasing as you go down. I know that’s what’s causing it because if I comment out the .slide(), it works again. Here’s what I have for movement, minus the rotation itself:

extends RigidBody3D

@onready var groundHitAll = $ShapeCast3D
@export var maxRunSpeed = 100.0
@export var accelSpeed = 5.0
@export var skidSpeed = 15
@export var jumpSpeed = 10
@export var rotSpeed = 10
@export var manualGravity = 5

var velocity = Vector3.ZERO
var rotatedVel = Vector3.ZERO
var characterUp = Vector3.UP
var currentSpeed = 0
var lastDir = Vector3.ZERO
var isGrounded = false
var input
var direction

func _integrate_forces(state):
# Find ground
if groundHitAll.is_colliding():
characterUp = groundHitAll.get_collision_normal(0)
isGrounded = true
else:
characterUp = Vector3.UP
isGrounded = false

# Get directional input
input = Input.get_vector("MoveLeft","MoveRight","MoveForward","MoveBackward")
direction = (transform.basis * Vector3(input.x, 0, input.y)).normalized()

# Get speed
if direction:
	# Find input near backwards to movement direction
	var shouldSkid = false
	if currentSpeed > 0.5:
		var angle = direction.signed_angle_to(velocity, characterUp)
		if abs(rad_to_deg(angle)) > 90:
			shouldSkid = true
	
	# Skid decelerate
	if shouldSkid:
		currentSpeed = max(currentSpeed - skidSpeed * state.step, 0)
		velocity.x = lastDir.x * currentSpeed
		velocity.z = lastDir.z * currentSpeed
	# Accelerate
	else:
		currentSpeed = min(currentSpeed + accelSpeed * state.step, maxRunSpeed)
		velocity.x = direction.x * currentSpeed
		velocity.z = direction.z * currentSpeed
		lastDir = direction

# Natural decelerate or stop
else:
	currentSpeed = max(currentSpeed - accelSpeed * state.step, 0)
	
	if currentSpeed <= 0.1:
		currentSpeed = 0
		velocity.x = 0
		velocity.z = 0
	else:
		velocity.x = lastDir.x * currentSpeed
		velocity.z = lastDir.z * currentSpeed
	
	if isGrounded:
		velocity.y -= manualGravity * state.step
	velocity = velocity.slide(characterUp)

# Get gravity
velocity.y -= manualGravity * state.step

# Jump
if Input.is_action_just_pressed("Jump") and isGrounded:
	velocity.y = jumpSpeed
	isGrounded = false

# Move
state.linear_velocity = velocity