How to complete a loop

Godot Version

4.4.1

Question

I’m attempting to make a loop in the vein of Sonic the Hedgehog, but I’m running into an issue where having X momentum opposite of the wall the player is currently on immediately throws you off of the loop (For example, in the video, I am holding Right). As a result, the player can’t yet run down walls.

The code for how I handle the states of a loop looks like this.

if is_on_floor():
	GashModelRotate.rotation = Vector3(GashRotation,(-90 * PI/180),0)
	normal_collision.rotation = Vector3(GashRotation,(-90 * PI/180),0)
	GroundFinder.rotation = Vector3(GashRotation,(-90 * PI/180),0)
	var GashRotationOutput = wrapf(GashRotation, 0, TAU)
	var AngleFinderText = str(GashRotationOutput)
	AngleFinder.text=AngleFinderText
	
	
	if Input.is_action_just_pressed("gash_sword"):
		sword_release = 2
		State=GROUNDATTACK
	
	if floor_mode == 0:
		up_direction = Vector3(0,1,0)
		if (GashRotationOutput > 0.84 and GashRotationOutput < 1.9 and
		floor_mode == 0 and direction.x > 0):
			loop_switch_timer = 1
			up_direction = Vector3(1,0,0)
			floor_mode = 1
		if (GashRotationOutput > 3.99 and GashRotationOutput < 5.4 and
		floor_mode == 0 and direction.x < 0):
			loop_switch_timer = 1
			floor_mode = 3

	if floor_mode == 1:
		print('right wall')
		loop_switch_timer -= 0.05
		up_direction = Vector3(-1,0,0)
		gash_velocity.y = final_speed * sign(direction.x)
		gash_velocity.x = gash_velocity.x * sign(direction.x)
		if GashRotationOutput >= 2.35 and floor_mode == 1:
			loop_switch_timer = 1
			up_direction = Vector3(0,1,0)
			floor_mode = 2
		
	if floor_mode == 2:
		loop_switch_timer -= 0.05
		up_direction = Vector3(0,-1,0)
		print("ceiling")
		gravity = 0
		direction.x = direction.x * -1
		if State==AIR:
			print("air")
		if (GashRotationOutput <= 2.35 and floor_mode == 2
		and loop_switch_timer <= 0):
			loop_switch_timer = 1
			floor_mode = 1
		if (GashRotationOutput >= 3.90 and floor_mode == 2
		and loop_switch_timer <= 0):
			loop_switch_timer = 1
			floor_mode = 3
		
	if floor_mode == 3:
		print('left wall')
		gash_velocity.y = final_speed * sign(direction.x)
		gash_velocity.x = gash_velocity.x * sign(direction.x)
		loop_switch_timer -= 0.05
		up_direction = Vector3(1,0,0)
		if GashRotationOutput < 3.90 and floor_mode == 3 and loop_switch_timer <= 0:
			loop_switch_timer = 1
			up_direction = Vector3(0,-1,0)
			floor_mode = 2

else:
	GashModelRotate.rotation_degrees = Vector3(0,-90,0)
	normal_collision.rotation = Vector3(0,0,0)
	GroundFinder.rotation = Vector3(0,0,0)
	up_direction = Vector3(0,1,0)
	floor_mode = 0

final_speed = abs(gash_velocity.x)

Is there anything I can do to make this work, or a better way to go about this?

Set the up direction to collider’s normal each frame, and velocity to collider’s tangent.
In general you want to adapt the movement basis to the current orientation of the “ground” if the velocity magnitude is large enough.

How do I get the collider’s tangent?

You can cross the current velocity with the normal to get the “right” vector and then cross the normal with that vector.

I’ve currently set it up like so:

Blockquote
var right_vector = GroundFinder.get_collision_normal().cross(gash_velocity)
var vector_tangent = GroundFinder.get_collision_normal().cross(right_vector)

With this in floor_mode 1:

Blockquote
up_direction = GroundFinder.get_collision_normal()
gash_velocity = vector_tangent

Is that how it’s supposed to be done? As it stands, holding right starts pushing me backwards in this setup.

You need to normalize the tangent and multiply with speed (or current velocity magnitude). Cross products are just to create orthogonal direction vectors. They don’t preserve magnitudes.

I have vector_tangent_normal = vector_tangent.normalized() in my variables and

Blockquote
gash_velocity = (vector_tangent_normal * gash_velocity.length())

in the floor_mode 1 velocity segment, but it still seems to be not working. Thank you for your patience with me thus far, by the way. I’ve not really worked with vectors much before.

As I said, you should re-work your system to explicitly maintain the movement basis deduced from the current terrain normal and then map the controls to that movement basis. I never played a sonic game so I don’t really know what happens with the controls when you’re upside down, but if you always have your movement basis defined then you know in which direction the character is supposed to go depending on keys that are currently held down.