Topic was automatically imported from the old Question2Answer platform.

Asked By

Diet Estus

I am trying to implement slopes into my 2D platformer using Godot 3.0.

I currently use move_and_slide() to update my position since I didn’t have any luck designing custom collision response for slopes with move_and_collide().

Using move_and_slide(), I’ve noticed that my player moves slower proportional to the steepness of the slope. I don’t like this.

So, my question: Assuming move_and_slide(), is there a way to adjust my Player’s velocity while on the slope so that his velocity up the slope diagonal is an upwards projection of his would-be horizontal movement, or, alternatively, scaled to this would-be horizontal movement?

I had planned to do this using move_and_collide(), projecting the velocity remainder onto the collision surface’s plane using a normal and Vector2.slide(), but, like I said, I didn’t have much luck. (Ascending slopes worked fine, but I got jittery behavior when descending them, as well as slid down them during idle.) Ideally, I’d like a way using move_and_slide().

I know that I can get the collision data (collision normal, remainder, etc.) used in the move_and_slide() by calling get_slide_collision() after it. Maybe there is some way to use this data to achieve my desired result?

For now to achieve control of velocity in slopes a use a combination of nodes. First I use horizontal and vertical ray cast to get collision normal (and not only the normal obtained with KinematicBody2D). Base on the horizontal normal and the vertical normal, I set tiles type (you can use enum o variables to do that). Then I control the way (and linear velocity) of the actor (character) when up slope and down slope.To control the linear velocity I use lineal equation of y = mxb and tan(normal.angle()). For example, if the slope is 45° it means that the actor have to up slope in y axis the same velocity as it advance on x axis. That is because tan(45°) = 1 so for every unit (pixel) advanced on x it have to advanced on y. Something like this:

if is_on_slope and is_up_slope:
var x = speed
var y = tan(normal.angle()) * speed
linear_velocity = Vector2(x, y)
# y = mx OR y = tan(angle)x
# if the speed = 100 and normal.angle() = 45°, then tan(angle) = 1, so y = 1x (https://ggbm.at/QHavT7Ay)
# if the speed = 100 and normal.angle() = 25°, then tan(angle) = 0.47, so y = 0.47x. Means that x = 100 and y = 0.47 * 100 (https://ggbm.at/fEU94NHz)
# if the speed = 100 and normal.angle() = 65°, then tan(angle) = 2.14, so y = 2.14x. Means that x = 100 and y = 2.14 * 100 (https://ggbm.at/b5SsMZC5)

For down slope a resolve for x and y = speed.

Then you want to take some control for consecutive slopes (one beside the other) and when the character is running on slope and the next tile is a slope with different tangent.

Then if you want to go more fast or more slow, you can play with y = mx adding or subtracting speed.

If you want, you can use trigonometry to, where x = cos(normal angle) * velocity.x and y = sin(normal angle) * velocity.y and so on…

Forgive my english because I am not a native english writer. Have nice time coding on your game!

I managed to get my desired movement on slopes using move_and_collide() rather than move_and_slide().

Here is the code, roughly:

var collision = move_and_slide(vel * delta)
if collision:
# get collision info
var normal = collision.normal
var remainder = collision.remainder
var horizontal_movement = abs(remainder.x)
# reset y vel so it doesn't keep accumulating
vel.y = Vector2(0, vel.y).slide(normal).y
# get movement vector along slope
var remaining_movement = remainder.slide(normal) # get projection of remainder on slope
remaining_movement = remaining_movement.normalized() # normalize projection
remaining_movement *= horizontal_movement # scale projection to horizontal distance
move_and_collide(remaining_movement) # move the sprite along the slope

To prevent my sprite’s animation from changing to its descend animation in the event of losing contact during the descent of a slope, I use a 16-pixel long downward raycast vector to set a grounded boolean, rather than rely on Godot’s in-built is_on_floor()