2D Slope For Loop Error

Godot Version

3.5.3.stable

Question

I’m creating a platformer with slope physics where you can run up a curved surface. After implementing a run button, I found that moving too fast compared to a curve can make it so that you miss the slope. So, I decided that if the player is moving too fast, their velocity will be cut up and then applied bit by bit in a single frame, inside a for loop. It doesn’t seem to be working though, and I can’t find where my error is.

Code

	var last_rotation = null
	var last_pivotpos = null
	var rotation_difference = null
	var pivot_difference = null
	speed = abs(velocity.length())
	var xSpeed = velocity.x
	print("speed = " + str(speed))
	if (velocity.length() * delta) > maxStep and is_on_ground:
		#print("BEEP")
		var z = ceil(velocity.length() * delta / maxStep)
		var return_velocity = velocity / z
		print("velocity = " + str(velocity))
		print("z = " + str(z))
		print("return velocity = " + str(return_velocity))
		for n in z:
			return_velocity = move_and_slide(return_velocity.rotated(rotation), Vector2.UP.rotated(rotation)).rotated(-rotation)
			print("return velocity = " + str(return_velocity))
			
			if is_on_ground:
				if return_velocity.length() != (speed / z) and return_velocity.x != 0:
					return_velocity = return_velocity.normalized() * (xSpeed / z)
					print("WAP")
					print("return velocity = " + str(return_velocity))
			
			raycast1.force_raycast_update()
			raycast2.force_raycast_update()
			raycast3.force_raycast_update()
			
			_updateRayVariables()
			
			was_on_ground = is_on_ground
			is_on_ground = (rayCollisionID != 0 and not rayGroundDistance > rayHalfLength)
			
			print("rotation before = " + str(rotation))
			
			if is_on_ground:
				if return_velocity.y > 0:
					return_velocity.y = 0
				
				var floorAdjust = Vector2(0, -rayHalfLength + raycastSlopeCase("groundDistance")).rotated(rotation)
				position += floorAdjust
				
				last_rotation = rotation
				if not rayCollisionID == 0:
					if (not rayCollisionID == 5) and (not rayCollisionID == 1) and (not rayCollisionID == 2):
						rotation = raycastCollisionCase("normal2").angle() + (PI / 2)
					else:
						rotation = raycastCollisionCase("normal").angle() + (PI / 2)
				if not was_on_ground:
					rotation_difference = rotation - last_rotation
					return_velocity = return_velocity.rotated(-rotation_difference)
				if last_rotation != rotation:
					raycast1.force_raycast_update()
					raycast2.force_raycast_update()
					raycast3.force_raycast_update()
					
					_updateRayVariables()
					
					if raycastSlopeCase("groundDistance") > rayHalfLength:
						floorAdjust = Vector2(0, -rayHalfLength + raycastSlopeCase("groundDistance")).rotated(rotation)
						position += floorAdjust
			
			if not is_on_ground:
				if rotation_degrees != 0:
					last_rotation = rotation_degrees
					last_pivotpos = pivotCounter.global_position
					rotation_degrees = 0
					rotation_difference = rotation_degrees - last_rotation
					pivot_difference = last_pivotpos - pivotCounter.global_position
					return_velocity = return_velocity.rotated(deg2rad(-rotation_difference))
					position = position + pivot_difference / 2
			
			print("rotation after = " + str(rotation))
		print("return velocity before = " + str(return_velocity))
		return_velocity *= z
		print("return velocity after = " + str(return_velocity))
		print("return velocity speed = " + str(abs(return_velocity.length())))
		velocity = return_velocity

If I could get any help on this, it’d be greatly appreciated. Here’s an output log for one of the loops, as well.

speed = 93.94442
velocity = (74.150085, 57.682919)
z = 2
return velocity = (37.075043, 28.841459)
return velocity = (37.075043, 28.841459)
rotation before = -0.785398
rotation after = -0.785398
return velocity = (37.075043, 0)
WAP
return velocity = (37.075043, 0)
rotation before = -0.785398
rotation after = 0
return velocity before = (26.216013, -26.216013)
return velocity after = (52.432026, -52.432026)
return velocity speed = 74.150085

What exactly is happening if you try it out?

It seems to reset rotation when it should be aligned with the slope. You fall off of any slope you try to move on. For a complicated curved slope made with a collision polygon, you sometimes shoot to the bottom of the slope. Walking straight left and right on an upright surface seems to work fine, besides a little bug where you can’t move in the negative direction (Probably something small, shouldn’t take long to find.)

Edit: After further testing, the problem only seems to occur on diagonal surfaces. I can run along the walls and ceiling just fine, but on a 45 degree angle I fall right off.

Updated code. Doesn’t fix the issue, just works a little better in some other circumstances. Doesn’t cap the jump, etc.

	var last_rotation = null
	var last_pivotpos = null
	var rotation_difference = null
	var pivot_difference = null
	speed = abs(velocity.x)
	if (speed * delta) > maxStep and is_on_ground:
		print("BEEP")
		var z = ceil(speed * delta / maxStep)
		var return_velocity = velocity / z
		for n in range(z):
			return_velocity = move_and_slide(return_velocity.rotated(rotation), Vector2.UP.rotated(rotation)).rotated(-rotation)
			
			raycast1.force_raycast_update()
			raycast2.force_raycast_update()
			raycast3.force_raycast_update()
			
			_updateRayVariables()
			
			was_on_ground = is_on_ground
			is_on_ground = (rayCollisionID != 0 and not rayGroundDistance > rayHalfLength)
			
			if is_on_ground:
				if abs(return_velocity.x) != (speed / z) and not (abs(return_velocity.x) * z) < 1e-4:
					return_velocity = return_velocity.normalized() * (speed / z)
			
			if is_on_ground:
				if return_velocity.y > 0:
					return_velocity.y = 0
				
				var floorAdjust = Vector2(0, -rayHalfLength + raycastSlopeCase("groundDistance")).rotated(rotation)
				position += floorAdjust
				
				last_rotation = rotation
				if not rayCollisionID == 0:
					if (not rayCollisionID == 5) and (not rayCollisionID == 1) and (not rayCollisionID == 2):
						rotation = raycastCollisionCase("normal2").angle() + (PI / 2)
					else:
						rotation = raycastCollisionCase("normal").angle() + (PI / 2)
				if not was_on_ground:
					rotation_difference = rotation - last_rotation
					return_velocity = return_velocity.rotated(-rotation_difference)
				if last_rotation != rotation:
					raycast1.force_raycast_update()
					raycast2.force_raycast_update()
					raycast3.force_raycast_update()
					
					_updateRayVariables()
					
					if raycastSlopeCase("groundDistance") > rayHalfLength:
						floorAdjust = Vector2(0, -rayHalfLength + raycastSlopeCase("groundDistance")).rotated(rotation)
						position += floorAdjust
			
			if not is_on_ground:
				if rotation_degrees != 0:
					last_rotation = rotation_degrees
					last_pivotpos = pivotCounter.global_position
					rotation_degrees = 0
					rotation_difference = rotation_degrees - last_rotation
					pivot_difference = last_pivotpos - pivotCounter.global_position
					return_velocity = return_velocity.rotated(deg2rad(-rotation_difference))
					position = position + pivot_difference / 2
			
		
		return_velocity *= z
		velocity = return_velocity

Edit: If anyone wanted to know how my player character works, here: It’s a square collider that floats off the ground, using 3 raycasts at the bottom to do so. It uses the normals of the surfaces the raycasts hit to rotate the character to stand on them. Originally it was going to use all 3 raycasts to smooth in and out, but it was a bit too buggy, so I kept it simple and just used the middle one.