Platformer - Climb ledge

Godot Version

V4.2.1.stable.official

Background

In a classic 2D tilemapped platformer I want to be able to grab and climb ledges. I detect the ledges by using an area2D and tileset where ledges have a specific collision layer. When hanging on a ledge, the character should be able to do a climb up and end up on top of the tile.

My arhcitecture

I have some custom state nodes handling character state, like Ground, Air and now Ledge, and expecting a lot more in the future. These states act on _physics_process() have a reference to an custom animator node which in turn is feeding the animation_tree with parameters like velocity and whatever conditional flags.
It works really well for the basic run/idle/jum/float/fall movement.

For example LedgeState will call animator.set_state('Ledge') when it enters that state and the animator will call the tree like playback.travel(state) or for conditions tree.set('parameters/StateMachine/' + state + '/conditions/' + condition, value)

Climb up

Climb up is triggered when hanging on a ledge and pressing up. I start the climb up animation, start a timer that is updated with delta and ignore further input. Both the character.velocity and .position is Vector2.Zero during the climb up. Hence, the animation will animate climb up from the hanging position.

The problem

Now the problem, when my timer is done, about 1 second (climb up animation is also 1s) the character is animated on top of the ledge-tile, but the body is still positioned next to it, as it was when hanging on the ledge.

So I position the character on top of the tile and swap animation to Ground for an ā€˜idle’-pose.

The position happen before the animation has changed (guessing this is due to position is set in _physics_process and animation is updated in _process) so for about a frame I will have the climb-up animation but the new position. It’s an ugly flicker-glitch before the ā€˜idle’-animation is showing. I have tested to set the new position on defer but couldn’t see any improvement.

So what happes in code is:


character.position = ledgeTile.position
animation_tree.travel('idle')

It is positioned corectly on the tile but the glitch is due to the climb-up animation being offset as it runs from a position next to the tile. I need both animation change and position change to happen in the same visual frame.

Solution?

I’ve been fiddling with the timer of climb up and the climb-up animation and made some improvement but it feels really fragile and will not scale well if I want to change timings later to tune things, or have it variable of speed or whatever.

Is there a better way to solve climb-up animation + actual movement? Im thinking of have it the other way, starting with positioning the character on top of the tile and have the animation offset downwards instead. But I could end up having a animation-glitch in the beginning of climb up instead of the end of it…

Thanks. This was long, but I hope it was detailed enough to give you enough info to help me with some pointers, ideas, or how-tos. Thank you in advance.

1 Like

I’m creating a 2D platformer as well, and I have a very similar climbing mechanic. It’s not perfect, but the way I do it is when a character touches a wall, I spawn an object with a collisionshape on top of whatever the player touched if the player is falling or jumping. If the player also touches that object, I can safely assume that they are able to climb up that ledge. The way I solved the sudden snapping problem is two fold. First, as I play the climb-up animation, I slowly move the camera to the new position where the player will be (which is the same position as the spawned instance I use to check if the player can climb up or not), and on top of that, my animation is drawn in a way that makes the player character actually ā€œclimb upā€ a ledge, so the animation includes the character’s sprite moving up slightly and stand on top of the ledge before it switches to the idle state.

Furthermore, I don’t use a timer, I simply connect the AnimationFinished signal (or in my case with C#, AnimationFinished event) of the AnimationPlayer Node to know when the animation has finished, so I can set the player’s new position and change their animation to idle.

Hi thanks for sharing inisights!

Sounds like the actual climbing thechnique is the same. Animation is moving but body is still in ā€˜hang-position’ then it is teleported up on ledge together with a new animation.

My problem is the syncing of the teleport and the animation. I use a timer and switch animation on timeout - you fire event when animation is finished.

Hmm. I can see how that would solve my problem… at a glance - I rather not have my animations to control anything but only act on events - I do however control the length of the animation so that could replace the need of a timer…
Thanks, I’ll think about it

1 Like

So coming back after fixing my issues to anyone experiencing the same…

After some code-smelly hacks I didn’t want in the first place, like last frame for the climb-up made the the character invisible and teleporting the character to place in the _process rather than _physics_process using get_tree().process_frame.connect(setPosition, CONNECT_ONE_SHOT) I realized during a step-by-step debugging that my position was a few pixels above ground, hence the ā€œanimation glitchā€ I experienced was actually the character not finding ground and entering ā€œairā€-animation for just a few pixels.

Ugh. so - this was yet another ā€œIt’s not the engine, it’s your codeā€-issue hehe - sorry for that.