Autoscroller Constant Speed On x Axis

Godot Version

4.3

Question

Hello!
I am trying to make an autoscroller and I want the player to always move at the same speed on the x axis but whenever it hits an incline it slows down. I’ve been stuck on this for a bit, thanks for reading!

extends CharacterBody2D


const SPEED = 700
const JUMP_VELOCITY = -400.0

func _ready() -> void:
	floor_constant_speed = true
	floor_block_on_wall = false

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	position.x += SPEED * delta
	
	move_and_slide()
	print(get_real_velocity())

Incline
Output of print(get_real_velocity())

(0, 0)
(0, 0)
(0, 0)
(-46.6333, -108.8157)
(-100.5908, -234.7034)
(-107.5781, -251.0179)
(-108.4863, -253.1323)

Could you modify the velocity based on the normal value of the floor?

extends CharacterBody2D


const SPEED = 700
const JUMP_VELOCITY = -400.0

func _ready() -> void:
	floor_constant_speed = true
	floor_block_on_wall = false

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	#Your old code:
#	position.x += SPEED * delta

	#New code for constant velocity
	#Set constant speed in x direction
	velocity.x = SPEED
	

	#scale the constant speed based on the angle of the surface

	#if you want, you could check if the floor normal is not Vector2.UP
	#and set a constant slope velocity
	if is_on_floor():
		velocity.x *= get_floor_normal().x
	elif is_on_wall():
		velocity.x *= get_wall_normal().x
	

	move_and_slide()
	print(get_real_velocity())
1 Like

Hey!
Thanks so much for the reply. This is much better than before, I can get decently close to the same speed using this idea, I’d still like to get a more exact constant speed but this was very helpful :slight_smile:

A more exact constant speed, you say… Would this help?

extends CharacterBody2D


const SPEED = 700
const JUMP_VELOCITY = -400.0

#new constants
const SLOPE_VELOCITY = 500

func _ready() -> void:
	floor_constant_speed = true
	floor_block_on_wall = false

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY


	#Set constant speed in x direction
	velocity.x = SPEED

	#Get the normal value of the current surface
	var normal: Vector2
	if is_on_floor():
		normal = get_floor_normal()
	elif is_on_wall():
		normal = get_wall_normal()

	#If not on a perfectly flat floor
	#Set the sideways velocity
	#Might need to use is_equal_approx() instead of == for precision
	if not normal == Vector2.UP:
		velocity.x = SLOPE_VELOCITY
	


	move_and_slide()
	print(get_real_velocity())

If you want, you can even compare the angles of the normal value to have a different speed for going up slopes versus going down slopes.

PS. As with the previous code, this is untested and may not work. If it does not work, let me know and I will try to help fix it.

1 Like

This does work but it’s the same as the previous code where it’s not quite constant. The velocity seems to bounce around a little when on inclines, but I understand it’s likely inconsequential outside of the one frame where it drops 30 velocity shown in the output below. Thanks for the continued help with this :slightly_smiling_face:

extends CharacterBody2D


const SPEED = 700
const JUMP_VELOCITY = -400.0

const SLOPE_VELOCITY = 762

func _ready() -> void:
	floor_constant_speed = true
	floor_block_on_wall = false

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	
	velocity.x = SPEED
	
	var normal: Vector2
	if is_on_floor():
		normal = get_floor_normal()
	elif is_on_wall():
		normal = get_wall_normal()
	
	if not normal == Vector2.UP:
		velocity.x = SLOPE_VELOCITY
	
	move_and_slide()
	print(get_real_velocity())

Output

(700.0012, 0)
(700.0012, 0)
(670.6201, -142.5327) <-- touches slope
(700.4333, -299.9572)
(700.3417, -300.3385)
(700.3454, -300.1689)
(700.4736, -299.8654)
(700.415, -300.2527)
(700.4662, -300.0904)
(700.2831, -300.5745)

I think this is because detection of the slope is a frame late (it uses the collision data from the last frame). I think you could use a well-placed raycast to check if the floor in front of the player is a slope. Maybe something similar to this?

extends CharacterBody2D

#Path to a raycast node pointed a little
#down and in the direction of movement
@onready var raycast:Raycast2D = $raycast

const SPEED = 700
const JUMP_VELOCITY = -400.0

const SLOPE_VELOCITY = 762

func _ready() -> void:
	floor_constant_speed = true
	floor_block_on_wall = false

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	
	velocity.x = SPEED
	
	var player_normal: Vector2
	if is_on_floor():
		player_normal = get_floor_normal()
	elif is_on_wall():
		player_normal = get_wall_normal()
	
	#Set default to not trigger the if statement
	var raycast_normal:Vector2 = Vector2.UP
	#Get normal value of the raycast collision
	if raycast:
		if raycast.is_colliding():
			raycast_normal = raycast.get_collision_normal()
	
	#Check floor normal and raycast normal
	if (not player_normal == Vector2.UP) or\
	(not raycast_normal == Vector2.UP):
		velocity.x = SLOPE_VELOCITY
	
	move_and_slide()
	print(get_real_velocity())

again, same warning as before

1 Like

Hm I’ve tried placing the raycast in a variety of ways but it seems to always be 662 or 719 velocity on the first frame it touches the slope, I’ve also tried having the slopes not be collision bodies and just have the player moved up when on them which does fix the speed issue but it kinda just bounces the player up along the slope.

extends CharacterBody2D

#Path to a raycast node pointed a little
#down and in the direction of movement
@onready var raycast:RayCast2D = $RayCast2D

const SPEED = 700
const JUMP_VELOCITY = -400.0

const SLOPE_VELOCITY = 762

func _ready() -> void:
	floor_constant_speed = true
	floor_block_on_wall = false

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	
	velocity.x = SPEED
	
	var player_normal: Vector2
	if is_on_floor():
		player_normal = get_floor_normal()
	elif is_on_wall():
		player_normal = get_wall_normal()
	
	#Set default to not trigger the if statement
	var raycast_normal:Vector2 = Vector2.UP
	#Get normal value of the raycast collision
	if raycast:
		if raycast.is_colliding():
			raycast_normal = raycast.get_collision_normal()
	
	#Check floor normal and raycast normal
	if ((not player_normal == Vector2.UP) or\
	(not raycast_normal == Vector2.UP)) and is_on_floor():
		velocity.x = SLOPE_VELOCITY
	
	move_and_slide()
	print(get_real_velocity())

Moving player up version

extends CharacterBody2D

#Path to a raycast node pointed a little
#down and in the direction of movement
@onready var raycast:RayCast2D = $RayCast2D

const SPEED = 700
const JUMP_VELOCITY = -400.0

const SLOPE_VELOCITY = 762

func _ready() -> void:
	floor_constant_speed = true
	floor_block_on_wall = false

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY
	
	velocity.x = SPEED
	
	if raycast.is_colliding():
		velocity.y -= 50
	
	move_and_slide()
	print(get_real_velocity())