2D Platformer Movement (on slopes)

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By BrightC
:warning: Old Version Published before Godot 3 was released.

Hello everyone,

I started using Godot recently and am currently trying to get a simple platformer to work. I followed the tutorial on 2DKinematicBodys (http://docs.godotengine.org/en/latest/tutorials/2d/kinematic_character_2d.html ) and so far everything is well and good. However, upon trying to create my own platformer based on that tutorial, I ran into a number of issues.

First of all: Downwards slope movement. While using the collision normal vector - like the tutorial suggests - works fine for upwards slopes, I can not use this technique for downward slopes. This means everytime my character moves on a downwards slope and is in the air for a very brief moment due to the gravity not being strong enough to pull him back on the ground immediately, it will look like he’s “hopping” down the slope, which is terribly awkward.

I’ve tried a variety of things in order to prevent this from happening. At the end of each _fixed_process I would check if test_move(0, 16) (16 being the maximum amount of pixels that the character could step down without jumping) returned true. If it did, I would move(0, 16), which should make the character move back onto the ground. While this did work, it also made the movement extremely jittery, for reasons unknown to me. As if everytime the character moved, the following frame the character moved a few pixels upwards before making its next move.

For my next attempt I increased the gravity to levels where it would drag my character immediately back onto the ground, unless the slope was very steep. Again, while it worked, it caused the same jittery movement I had before, ontop of having the annoying side effect of accelerating the downwards movement speed to unpleasantly high levels (Which is another thing I don’t fully understand, since any gravity that is being applied should only be able to drag the character down vertically, with the horizontal movement being unaffected).

Which leads me to my second problem. While of course it seems realistic for characters to move quicker when moving down a slope and slower when moving up, it is undesireable in my case. I understand this behaviour must be linked to the Vector2.slide function the tutorial is using, but I am not sure how to avoid it, especially since I don’t fully understand the slide function, and the documentation on this engine in general seems to be somewhat sparse. (I don’t quite understand why it takes a Vector2 as parameter. Does it simply take the length of that vector and converts it to a different angle? Also, why does the character not run up walls with the code from the tutorial? If it runs into a wall, the collision normal vector would be pointing to the left/right, which means sliding alongside that would result in moving up.)

When I was using Godot for the past few days I was quite happy with it, seeing how it strikes a nice balance between complexity and efficiency, after using a large number of other game engines. However, the issues listed above turned out to be rather frustrating. If anyone has any solutions or suggestions to either of the problems (or even an entirely different approach from the tutorial), they would be very much appreciated, since I would really like to keep using this engine. Thanks in advance!

the link is broken

tiernich | 2016-04-17 17:45

Thank you for the hint, the parantheses broke it. It’s fixed now.

BrightC | 2016-04-17 18:16

since any gravity that is being applied should only be able to drag the character down vertically, with the horizontal movement being unaffected.

You can help the character to stay on the downward slope by adding an Area2D and inserting a force field against the slope perpendicular to it. This way you don’t need to play with the gravity and it could help the character to not jitter.

Just one idea.

genete | 2016-04-18 12:02

I am pretty much on the same boat as you, friend.
I actually went towards a different approach, using this whole series of videos as a base for my own controller. The guy uses unity, and I am kinda like “porting” it to godot. I am a fairly new programmer myself and I believe it is just because I haven’t the skill to code that I wasn’t able to progress as fast as I would like.

Nevertheless, it is a really good approach to platforming with slopes. Have a look.

I wanna follow your and this Question developtment to see what more seasoned programmers can do.

brunosxs | 2016-04-18 12:29

Thank you for your suggestions so far. I did some tests on the KinematicBody and might’ve found where some of the problems originate from. While a KinematicBody doesn’t move on its own, the movement is still not perfectly precise when you move it. That means, contrary to what my intuition was, the move method will not be pixel-perfect and may in fact cause the KinematicBody to move horizontally even though the move vector points straight down. That is what was happening when I applied the gravity on a slope. I am quite certain that the very same behaviour causes the jittery movement, simply because the KinematicBody will end up one or two pixels away from its destination. While I guess this is acceptable for most games, in this particular scenario even the movement of a KinematicBody is too unprecise. The same limitations apply to the test_move command, making it equally useless to check for collisions here. However, I might have found a way to implement objects with pixel-perfect movement. Every Node2D comes with a set-local-x/set-local-y/set-pos method, which can be used to move it around. The collision checking is a lot more tricky and I am still trying to get it to work myself. Obviously you can access the world in your game and its so called “direct_space_set” ( World2D — Godot Engine (latest) documentation in English ), which, from my understanding, seems to be the object managing your world’s collisions (and physics in general). It provides the function intersect_shape amongst others, which you can use to check which shapes are colliding with the shape you passed to the function (for instance the collisionbox of your character). If the number of returned shapes is larger or equal to 1 and that 1 shape is not the character itself, it means there is a collision at that position. The problem I ran into now is that for some reason calling get_world_2d().get_direct_space_state().intersect_shape(get_node("CollisionShape2D").get_shape()) will crash the engine. (The console will literally just print “shit?: No error” ) My friend and me are currently trying to figure if this is a bug in the engine or if we’re just using that function wrongly. If anyone knows anything on that matter I would really appreciate the help.

As for the Unity tutorial: I don’t have time to look into it in-depth right now, but to me it seems it makes excessive use of raycasts, which I personally consider a huge mistake ( although even the Godot documentation encourages using them, annoyingly). While they’re useful in some situations, they will reduce the collision box to one or two rays, between those two rays, ANYTHING can happen, and in my experience it is very rarely the case that this little data is enough to react to any reaction accordingly.

EDIT: I managed to make it not crash, I’ll post the whole solution once I’m completely done with everything. If it works, that is.

BrightC | 2016-04-18 14:47

:bust_in_silhouette: Reply From: Gokudomatic2

maybe you want to try this demo I wrote 2 years ago:

The code is very old and is not maintained, so it will need some adjustment. However I documented it as much as I could.

:bust_in_silhouette: Reply From: BrightC

After finding the solution, let me just answer this for everyone. The gravity affecting the x-axis can be avoided by storing the x-position before applying the movement. If there was no velocity on the x-axis to begin with, you can simply reset the x-position to what it was before. I noticed that doing this also seems to reset the scale of the node for some reason, which appears to be a bug, although it can be easily worked around.

Knowing that the gravity will now only drag the character straight down, you can set it to a high enough value for the character to stick to the ground at all times. Of course, always having such a high gravity is still undesireable, as it makes it impossible for the character to jump. For this reason, you will have to check with test_move(0,1) (Or test_move(0,3), in order to account for some imprecision) if the character is in the air. If it is, you will actually want to set the gravity to a lower value, since the gravity holding him on the ground was unnaturally high.

The character slowing down on slopes can be avoided by normalizing the movement-vector and multiplying it with the velocity on the x-axis, so that the character always moves the entire distance on the x-axis.

I modified the code from the tutorial for those who are interested. My version of it looks like this: (I can not get the code to show correctly in this for some reason, maybe a mod could fix that)

extends KinematicBody2D

var GRAVITY = 1000.0
const WALK_SPEED = 200

var velocity = Vector2()

func _fixed_process(delta):

if (test_move(Vector2(0,1)) && velocity.y > 0):
	GRAVITY = 10000
	velocity.y = 0;
else:
	GRAVITY = 1000

velocity.y += delta * GRAVITY
if (Input.is_action_pressed("ui_left")):
	velocity.x = - WALK_SPEED
elif (Input.is_action_pressed("ui_right")):
	velocity.x =   WALK_SPEED
else:
	velocity.x = 0

var motion = velocity * delta
var old_x = get_global_pos().x;
var old_velocity_x = velocity.x;
motion = move(motion)

if (is_colliding()):
	var n = get_collision_normal()
	#velocity = n.slide(motion) # I don't understand why 
	#the tutorial suggests to change the velocity here. 
	#It makes little sense to me and in fact glitches out 
	#the rest of the code I added
	motion = n.slide(motion).normalized() * abs(motion.x)
	move(motion)
	if (old_velocity_x == 0):
		set_global_pos(Vector2(old_x,get_global_pos().y))

func _ready():
set_fixed_process(true)

This will result in smooth movement on most slopes and make the player stick to the ground on those. Be aware that a much higher gravity will still create unwanted effects, like the character sliding down slopes or not being able to walk onto really steep ones. You can increase the Gravity from 10000 to a higher value in order to limit how steep slopes may be before the character is unable to walk on them. Perhaps someone can think of a way to work around that if it is not wanted. I suppose you’d somehow need to check the angle of the normalized vector and apply gravity or not depending on whether it points upwards or downwards. Anyways, hope this helps the people who had similiar problems.

:bust_in_silhouette: Reply From: lovegd

(with godot 3)
I faced hopping issues and slow down on climbing slopes, fixed with :-

  1. velo=move_and_slide( …)
    do
    newvelo= velo+change
    not (eg: assign (0,0) on floor)
    newvelo=Vector2(…)
    this will get the necessary gravity changes for slopes in velocity (log velo and see)
    2.hopping occurs bec player goes in air, that occurs bec of weak or zero gravity, increasing gravity fixed hopping.

Can you show your exact code? Because I could not really understand your steps.

Lodugh | 2018-03-05 16:43