Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add automatic smooth stairs step-up and step-down for KinematicBody using move_and_slide() #2751

Open
elvisish opened this issue May 20, 2021 · 61 comments

Comments

@elvisish
Copy link

Describe the project you are working on

Character controller for Godot

Describe the problem or limitation you are having in your project

For any type of movement that requires a kinematic rigidbody, a stair test should be built-in to the move_and_slide function, allowing the user to:

  • Specify a step height that determines if a step is low enough to smoothly climb or is a wall that cannot be climbed.
  • Up and down stairs without losing contact, similar to how move_and_slide_with_snap works.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Step up distance: Any stable surface below this height will be walkable by the character.

image

Step down distance: When losing contact with the ground, if the distance between the character and the ground is less or equal to this value, then the character will be automatically grounded (sticking to the ground). If there is no ground at all, then the character will be not grounded.

image

image

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

The user would specify the if smooth stepping up and down stairs is enabled, and the step height:
Vector3 move_and_slide ( Vector3 linear_velocity, Vector3 up_direction=Vector3( 0, 0, 0 ), bool stop_on_slope=false, int max_slides=4, float floor_max_angle=0.785398, bool infinite_inertia=true, smooth_step=true, step_height=4 )

It would also work with move_and_slide_with_snap:
Vector3 move_and_slide_with_snap ( Vector3 linear_velocity, Vector3 snap, Vector3 up_direction=Vector3( 0, 0, 0 ), bool stop_on_slope=false, int max_slides=4, float floor_max_angle=0.785398, bool infinite_inertia=true, smooth_step=true, step_height=4 )

If this enhancement will not be used often, can it be worked around with a few lines of script?

No, it would need to be integrated to move_and_slide.

Is there a reason why this should be core and not an add-on in the asset library?

It would need to be implemented in the kinematic body engine code.

@Calinou
Copy link
Member

Calinou commented May 20, 2021

See also #2184 and Shifty's Godot Character Movement Manifesto, which recommends using RigidBodies for characters instead of KinematicBodies.

@Calinou Calinou changed the title Smooth stairs step-up and step-down for kinemetic rigidbody using move_and_slide Add automatic smooth stairs step-up and step-down for KinematicBody using move_and_slide() May 20, 2021
@wareya
Copy link

wareya commented May 21, 2021

+1

Logic for stepping up stairs is basically mandatory to a lot of styles of first person shooter, and no, using capsules doesn't cut it as a universal solution (they only work for relatively short steps and cause other gameplay changes).

I once tried writing my own FPS-style movement controller on top of move_and_collide before, but the way that move_and_collide handles marginal collisions (in ways that move_and_slide's internal traces don't) caused a lot of problems. Stuff like this should be built in.

Getting move_and_slide_with_snap to handle stepping down stairs for you is already possible to a certain limited degree.

@SaracenOne
Copy link
Member

Very much in favour of exploring this further. Trying to retrofit this behaviour with existing physics engine issues has consistenly been a massive pain point, breaking in so many instances. An official high quality representation of a character controller with a reliable smooth climb would be a huge benefit to anyone doing any kind of action game.

@atlrvrse
Copy link

Very very much in favor as well, would make things so much easier. I tried implementing it via script and I got pretty close but results didn't satisfy

@Zireael07
Copy link

Bump. I hacked around with some local translations, but it's jittery. I need something smooth.

@timshannon
Copy link

FWIW @Zireael07 I get around the jittery issues in my stair-stepping code by lerping the camera's offset from the character controller.

@immccc
Copy link

immccc commented Nov 5, 2022

This would be a really nice feature for next version.

In the meantime I'm trying to implement this on my own. The idea I have in mind, in short, is:

  1. Get the next collision according to the direction the kinematic body has.
  2. If collision position is NOT below certain threshold of the kinematic body, do not climb the step.
  3. If collision normal is NOT horizontal and with opposite direction than the kinematic body movement, do not climb the step.
  4. Otherwise, climb the step.

stairs_example

But I have some problems with this approach:

  • I need to have a particular collision layer for the stairs, in order to avoid the body to climb walls. Unfortunately, a particular tilemap can only have one collision layer.

climbing wall

I could solve this to have two separate tilemaps, one for the floor and another for the wall, but trust me when I say I have not enough collision layers to waste, as I implemented a "multilayered" system of tilemaps (as you can see from the first gif on this message).

  • The collision position is not in the edge of the next step, as I expected. Instead, it remains in the floor, and given that the shape of the kinematic body touches the collision area of the tilemap in multiple points, Godot only returns the one in the floor. The result of this is that the kinematic body would be able to climb / jump pits.

@Calinou
Copy link
Member

Calinou commented Nov 5, 2022

I need to have a particular collision layer for the stairs, in order to avoid the body to climb walls. Unfortunately, a particular tilemap can only have one collision layer.

You can probably use a second raycast to check whether the step can actually be climbed (i.e. it's not obstructed by a wall).

@immccc
Copy link

immccc commented Nov 5, 2022

I need to have a particular collision layer for the stairs, in order to avoid the body to climb walls. Unfortunately, a particular tilemap can only have one collision layer.

That's a good idea. I was thinking on two areas, a top and a bottom. If only bottom one is colliding, then a step is detected and the body climbs it.

Problem is that my kinematic body is getting more areas than population in eastern Asia 😂

@wareya
Copy link

wareya commented Nov 5, 2022

Stairstepping algorithms usually work by performing several "real" collision traces in a row rather than by comparing the collision point. For example: trace up, trace sideways, trace down, check if downwards trace is a "floor". If it's not a floor, there's no stairs to climb here, reset and use normal slide-based movement; and if it instead is a floor, you're done.

(Sometimes "floor" for stair stepping is defined in a more complicated way than just using the collision normal; for example, if you have small steep edges that you don't want the player to snag on, you might want to flag their geometry as "always a floor", or you might want to do a raycast (as in, actual raycast) to find the floor instead of a collision trace, depending on the game.)

@elvisish
Copy link
Author

elvisish commented Nov 5, 2022

This should be at engine core level for best integration with the physics instead of glued on top after with gdscript. And this should probably be changed to include move() and CharacterBody since we’re nearly into GD4 now 🙄

@elvisish

This comment was marked as off-topic.

@KeyboardDanni
Copy link

So the way early games approached this was to sample tiles at points around the character. For example, to check for walls, a single point is tested on the left and right sides. This point is typically vertically centered around the character, so if the player walks into a low enough step, the side detectors will miss it, and then the floor detectors will eventually come into contact with the step and move the character upward.

Here is how M.C. Kids did it: https://games.greggman.com/game/programming_m_c__kids/

And here's a demo of the movement and sensors in action in Sonic 1:

SPGCollisionDemoLite
(taken from https://info.sonicretro.org/SPG:Slope_Collision )

Notice how right after the loop here, there's a sudden "bump" in the terrain, but it's not an issue as the wall sensor is high enough to pass over it.

I played around with this and it looks like yes, you can in fact do this today in Godot, even with the standard CharacterBody2D! You just need to use several SeparationRayShape2D colliders in place of a box or capsule collider. So far it seems to work pretty well with a few caveats:

  • You need to enable "slide on slope" in the shape's settings or it won't work right.
  • You'll want to temporarily enable the ray shapes only for the move_and_slide() call, then disable them and reenable your box/capsule collider, so that you get sensible collisions with coins and bullets and such.
  • One-way tiles don't work (they become fully solid). Unsure what part of the engine is responsible for making these work. Workarounds probably exist.
  • The character will lower a bit at the crest of hills. This was a bug in Sonic as well:

SPGSensorsOnTiles

@KeyboardDanni
Copy link

Here's the setup in Godot:

2023-02-20 16_36_08-Godot

Unfortunately, it looks like I ran into my first "character getting stuck" bug:

2023-02-20 16_47_25-2D Test (DEBUG)

Full video:
https://user-images.githubusercontent.com/34800072/220202852-00e441ca-1149-462f-870d-77db7e09650b.mp4

@baznzab
Copy link

baznzab commented Apr 10, 2023

There is an implementation of stair movement in godot 3.5. It also refers to an article which describes the idea behind it. I tried to implement such algorithm myself and it worked pretty well, I probably could polish it to some extent, but the thing is, in order to do that I would need to reimplement the whole logic behind existing CharacterBody3D. I think it would make much more sense if it was built-in into engine's default node, since it already has pretty much everything in order to achieve this.

Here is my code:

class_name ThirdPersonController
extends CharacterBody3D


@export var camera: ThirdPersonCamera
@export var walk_speed: float = 4
@export var sprint_speed: float = 6
@export var jump_height: float = 1
@export var gravity: float = -9.8
@export var step_height: float = 0.25


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

	# Handle Jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = sqrt(-2 * gravity * jump_height)

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
	var direction := (camera.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	var speed = sprint_speed if Input.is_action_pressed("sprint") else walk_speed
	if direction:
		velocity.x = direction.x * speed
		velocity.z = direction.z * speed

		var yaw = atan2(-direction.x, -direction.z)
		quaternion = Quaternion(Vector3.UP, yaw).normalized()
	else:
		velocity.x = move_toward(velocity.x, 0, speed)
		velocity.z = move_toward(velocity.z, 0, speed)

#	_step_up(delta)
#	move_and_slide()
	if not _step_up(delta):
		move_and_slide()


func _slide(
	body: RID,
	from: Transform3D,
	motion: Vector3,
	margin: float = 0.001,
	max_slides: int = 6,
	max_collisions: int = 16
	) -> Vector3:

	for i in range(max_slides):
		var params := PhysicsTestMotionParameters3D.new()
		params.from = from
		params.motion = motion
		params.margin = margin
		params.max_collisions = max_collisions

		var result := PhysicsTestMotionResult3D.new()
		if not PhysicsServer3D.body_test_motion(body, params, result):
			break

		var normal: Vector3 = (
			range(result.get_collision_count())
			.map(func(collision_index): return result.get_collision_normal(collision_index))
			.reduce(func(sum, normal): return sum + normal, Vector3.ZERO)
			.normalized()
		)
		motion = result.get_remainder().slide(normal)
		from = from.translated(result.get_travel())

	return motion


func _step_up(delta: float) -> bool:
	# do step only if grounded
	if not is_on_floor():
		return false

	# cast body upword by step_height
	var up_test_params := PhysicsTestMotionParameters3D.new()
	up_test_params.from = global_transform
	up_test_params.motion = step_height * up_direction
	up_test_params.margin = safe_margin
	if PhysicsServer3D.body_test_motion(get_rid(), up_test_params):
		print("up block")
		return false

	var up_transform = global_transform.translated(step_height * up_direction)
	var slide_motion = (
		_slide(get_rid(), up_transform, velocity * delta, safe_margin, max_slides)
		.slide(up_direction)
	)

	# cast body by slide motion
	var forward_test_params := PhysicsTestMotionParameters3D.new()
	forward_test_params.from = up_transform
	forward_test_params.motion = slide_motion
	forward_test_params.margin = safe_margin
	if PhysicsServer3D.body_test_motion(get_rid(), forward_test_params):
		print("fwd block")
		return false

	# cast body downward by step_height
	var down_test_from := up_transform.translated(slide_motion)
	var down_test_params := PhysicsTestMotionParameters3D.new()
	down_test_params.from = down_test_from
	down_test_params.motion = -step_height * up_direction
	down_test_params.margin = safe_margin
	var down_test_result := PhysicsTestMotionResult3D.new()
	if PhysicsServer3D.body_test_motion(get_rid(), down_test_params, down_test_result):
		#global_transform.translated(-down_test_result.get_remainder())
		global_transform = down_test_from.translated(down_test_result.get_travel())
		return true

	return false

I needed to reimplement sliding logic here to virtually move character body to test if it's possible to step up, which might have some collisions with existing sliding logic inside the CharacterBody3D. It also is very likely to conflict with floor snapping inside move_and_slide(). Of course my code lacking slope detection, moving platforms and so on... and this is the reason why I think it should be built-in (cause it's already done). Otherwise if u just need proper stair movement (which u usually do), u simply need to reimplement the whole CharacterBody3D and add stair movement on top.

@Calinou
Copy link
Member

Calinou commented Apr 10, 2023

godotengine/godot-demo-projects#849 also features stairstepping using a ShapeCast3D, although the camera height isn't smoothed yet.

@baznzab
Copy link

baznzab commented Apr 11, 2023

@Calinou thank you for your reply. I checked your solutions out. I think it might work as fast and easy implementation. But it isn't that reliable. It doesn't take into account how the controller moves under the hood, which makes it pretty easy to break. For example it took me just a few minutes to get my character jitter up and down when trying to move forward while facing more or less complex corner. The reason why ShapeCast3D is not reliable in this case is because it just checks how character would move in one direction while it is necessary to trace the whole path (first upwords to check if character can elevate, second along velocity direction to check if body can advance and lastly downward to check where to step exactly). Furthermore event doing those checks isn't sufficient because down the whole path I just described the body should follow same rules CharacterBody3D does. Simple example why it is needed is when character is trying to step up while facing wall at some angle. If u just stop him from moving as soon as "forward" ShapeCast hits something body would just stuck instead of both stepping up and sliding down the wall. I'm not even mentioning more advanced cases like detecting moving platforms and so on... That is why I'm telling that it would make a lot of sense if it was implemented inside CharacterBody3D itself since this functionality already exists in there. I did some investigation on how the node works and sure enough it isn't quite ready to easily do it atm, and it would require some refactoring, but it is totally doable. If it was implemented it would really make CharacterBody3D much more usefull. It already has great advantages over for example Unity's built-in PhysX-based CharacterController (like the ability to set up_direction or moving platform awareness out of the box), the only thing it really lacks is built-in reliable step handling unfortunately.

@wareya
Copy link

wareya commented Apr 12, 2023

Having implemented movement solvers from scratch many times before, both in 2d and 3d, I want to add my two cents and say that juju8e's solution, referencing the The Low Rooms article, is The Way. It is very common and resilient. It's the approach Quake uses, for example, and consequently also Source Engine games.

@elvisish
Copy link
Author

elvisish commented May 5, 2023

Best stairs implementation so far is mrezai's: https://github.com/mrezai/GodotStairs
I ported it to 4.0: https://github.com/elvisish/GodotStairs

This built-in to KinemeticBody3D/CharacterBody3D would be more than adequate, honestly.

@elvisish
Copy link
Author

elvisish commented Jul 23, 2023

Best stairs implementation so far is mrezai's: https://github.com/mrezai/GodotStairs I ported it to 4.0: https://github.com/elvisish/GodotStairs

This built-in to KinemeticBody3D/CharacterBody3D would be more than adequate, honestly.

This is the best there is for stairs, but a good example of why this should be built-in:
image

A stress test of 250 enemies shows just how slow it is to run the stair-step function on every frame. Comparatively on fps:

With stairs:
image

Without stairs:
image

This really, really should be built-in by now. At the very least if Godot is planning on switching over to Jolt physics eventually, consider building stair-stepping into that.

@reduz
Copy link
Member

reduz commented Jul 24, 2023

There clearly is a pretty bad documentation issue if nobody here realized you can use SeparationRayShape for stair stepping.

@Zireael07
Copy link

I think I'm inclined to agree - I use raycasts for "stepping" up sloped roads (which are effectively stairs as far as the physics are concerned)

@reduz
Copy link
Member

reduz commented Jul 24, 2023

You can use SeparationRayShapes in both 2D and 3D with kinematic controllers and the snap functions. This makes it work fine with stairs and has been well tested. It was designed for this very use case.

@elvisish
Copy link
Author

There clearly is a pretty bad documentation issue if nobody here realized you can use SeparationRayShape for stair stepping.

To be fair, this was opened long before SeparationRayShape was added to the engine.

You can use SeparationRayShapes in both 2D and 3D with kinematic controllers and the snap functions. This makes it work fine with stairs and has been well tested. It was designed for this very use case.

It would be interesting to use this in conjunction with @mrezai 's stair-stepping method, since proper stair-stepping requires the motion to be checked in a multiple of ways to ensure the stepping works correctly. Is there a working example of SeparationRayShape being used for stairstepping? I still believe it would be more performant to have it built-in to the engine at low-level though.

@elvisish
Copy link
Author

I think I'm inclined to agree - I use raycasts for "stepping" up sloped roads (which are effectively stairs as far as the physics are concerned)

That's really an error of the physics, slopes should never be considered stairs unless the slope angle is too high.

@wareya
Copy link

wareya commented Jul 24, 2023

The reason for using several collision traces isn't to overcome a lack of CCD (CCD is assumed for all techniques); it's to avoid probing from inaccessible locations, without at the same time missing any gaps that the character Can actually travel through. For example, in this image:

image

Even if you know that there is a stair at the target location, with a safe place to stand on top of it... You must perform an extra trace, from the original character position, upwards, to know that there is a ceiling above the player and that the player cannot fit through the gap because it is too small.

In general, to avoid strange clipping behavior at high movement speeds in this kind of geometry, you have to outline the shape of the stair step with collision traces/sweeps, which boils down to at least three collision sweeps: one upwards, one forwards, and one down. Importantly, each of these traces starts from the actual end location of the previous trace (with the upwards one starting from the original position), to accommodate any differences in the actual locations of nearby ceilings and walls.

@wareya
Copy link

wareya commented Jul 25, 2023

I just made a small test project for testing stair-stepping. It has CSG geometry set up to make it easy to test common stair-stepping bugs. (Edit: It's licensed as CC0, feel free to look without worry.)

It also has an FPS character controller, one that tries to use a basic three-trace stair-stepping algorithm, but it falls back to plain old move_and_slide if it doesn't find a stair. The implementation is not ideal, since it doesn't check every possible slide like move_and_slide does, so it's not perfect. But any recommended approach should work at least as well as this, especially since it's not perfect.

https://github.com/wareya/GodotStairTester/tree/main

Godot_v4 1-stable_win64_2023-07-24_19-54-26

    # check for simple stairs; three steps
    var start_position = global_position
    var found_stairs = false
    var wall_test_travel = null
    var wall_collision = null
    if do_stairs:
        # step 1: upwards trace
        var ceiling_collision = move_and_collide(step_height * Vector3.UP)
        var ceiling_travel_distance = step_height if not ceiling_collision else abs(ceiling_collision.get_travel().y)
        # step 2: "check if there's a wall" trace
        wall_test_travel = velocity * delta
        wall_collision = move_and_collide(wall_test_travel)
        # step 3: downwards trace
        var floor_collision = move_and_collide(Vector3.DOWN * (ceiling_travel_distance + (step_height if is_on_floor() else 0.0)))
        if floor_collision and floor_collision.get_collision_count() > 0 and acos(floor_collision.get_normal(0).y) < floor_max_angle:
            found_stairs = true
    
    # (this section is more complex than it needs to be, because of move_and_slide taking velocity and delta for granted)
    if found_stairs:
        # if we found stairs, climb up them
        var old_velocity = velocity
        if wall_collision and wall_test_travel.length_squared() > 0.0:
            # try to apply the remaining travel distance if we hit a wall
            var remaining_factor = wall_collision.get_remainder().length() / wall_test_travel.length()
            velocity *= remaining_factor
            move_and_slide()
            velocity /= remaining_factor
        else:
            # even if we didn't hit a wall, we still need to use move_and_slide to make is_on_floor() work properly
            var old_vel = velocity
            velocity = Vector3()
            move_and_slide()
            velocity = old_vel
    else:
        # no stairs, do "normal" non-stairs movement
        global_position = start_position
        move_and_slide()

@Zireael07
Copy link

@elvisish In case I wasn't clear: visually it's a slope but physically it's stairs

@operatios
Copy link

I think this thread #333 is relevant to the discussion of SeparationRayShapes and why they're not robust or all that useful for stairstepping.

Even if you don't care about the fact that the bottom part of the character is being approximated by a single raycast, here's an illustration of the issue @wareya is talking about:
scene

stairs.mp4

The obvious thing is that you end up getting stuck inside the second cube, but even if you didn't, the path you take is not reversible.

Below are some code references (I'm not sure if Godot maintainers are allowed to look at them based on licensing or whatever), but basically, you have to do multiple traces to achieve proper stairstepping in your character controller, meaning SeparationRayShapes are not the solution for this problem.

https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/game/shared/gamemovement.cpp#L1517

https://github.com/NVIDIA-Omniverse/PhysX/blob/8f6cf9cfe89f2bedafbf7788a72cb04f11c31e1f/physx/source/physxcharacterkinematic/src/CctCharacterController.cpp#L1762

https://github.com/jrouwe/JoltPhysics/blob/b1cd84951022a565a23f33ff77ffee9e71487bbc/Jolt/Physics/Character/CharacterVirtual.cpp#L1149

https://github.com/dimforge/rapier/blob/958fba8ed43328e7e33bb9585a735577fa1061e1/src/control/character_controller.rs#L526

@wareya
Copy link

wareya commented Aug 9, 2023

Colliders with ramp-like bottoms have their own problems and aren't a solution either:

  1. The maximum step height is limited by your maximum ramp angle and the width of your collider. Most stair stepping heights in games are about half a meter. To replicate that your collision hull would have to be a full meter wide in diameter.
  2. You can't control whether stair-stepping only works when you're on the ground or not, so you can jump up larger ledges than you would otherwise be able to.
  3. You always start to decline on the edge of any object even if that's not part of how your game is supposed to work (e.g. with arena FPSs and voxel games, you're not supposed to decline on edges, and people will make fun of your game or complain about it feeling "off" if you do).
  4. The best collider with a ramp-like-bottom is a cylinder with cones on the top and bottom, which is a rare and unsupported shape. Capsules are inappropriate because they waste potential stair height, making point 1 even worse. So you have to use a polyhedral approximation of a cylinder-with-cone-ends, which is either slow (at large vertex counts) or feels bad to use because the corners are so chunky (at small vertex counts).
  5. They "vibrate" when climbing stair cases that aren't exactly the same rise-over-run as the bottom of the collider.

@timshannon
Copy link

I've been using the separation ray 3d shape for a while, and it definitely simplifies things, but there are several issues as well that I thought would be worth laying out here.

  1. It doesn't seem to respect floor / wall min / max angles. It'll always just push you away from the collision point. So in my case where a separation ray is my "feet", it'll just push the player vertically up any ramp or angled wall, regardless of the max floor angle. You can enable "slide on slope" which prevents this, but that also doesn't seem to respect min / max slope angles, and of course if you don't want to slide on slopes it's not an option.

  2. I started with a single ray, and it bounced like crazy. I think it's fighting back and forth with separation vs de-penetration. It stabilizes considerably with more rays, but if you have a collision that connects between the rays, and collides with a traditional collision shape like a cylinder or capsule, then stop abruptly. I'm up to 8 separation rays as my "feet" currently for smooth stepping, and it still stops abruptly at certain angles.

@joeymanderley
Copy link

joeymanderley commented Sep 19, 2023

Hello, I spent a month this summer implementing a 3D rigidbody-based character controller for Unity, which I have recently switched away from for reasons you can probably guess. I agree with wareya et al that the only way to do this robustly is with (at least) three shapecasts. Here's the pseudocode for the core movement of my CC:

Assuming the character is on the ground:

Try to do a step up.
	Simulate (with a shapecast) moving up by step height
	Then simulate a slide move forward in the amount the character would've moved on the ground this physics tick
	Then simulate a step down by the step height
	Check that the final position is valid (on the ground, not in a solid, vertical delta > 0, etc.)

If the final position is valid, teleport the character to the simulated end point and exit.

If the final position isn't valid, do a forward slide move, then simulate a step down if the character isn't already moving up 
(just do a shapecast down, then move the character to the shapecast unless it doesn't hit anything)

I found this to be fairly smooth (1) and solid for most kinds of arbitrary geometry (2), but there are still some quirks, such as slope movement being treated the same way as stair movement (3), inconsistent behavior depending on the angle of approach for certain step heights (4), and the usual capsule problems (5). Many of these issues are tied to choice of collision shape + algorithm and are therefore likely to be present in some form regardless of the game engine used (see footnotes).

That said, 90% of my time was spent fixing edge cases, and if there's anything I learned, it's that physics programming is a PITA and full of gotchas, even when you're using multiple full shape casts instead of raycasts. Which is a good argument for why if Godot were to have stair stepping and/or rigidbody support, it should be built-in (or a C++ module) rather than left to scripters. No shade toward GDScript, but it doesn't seem like it's meant for lowish-level programming work (correct me if I'm wrong) but rather there should be a try_move_with_step_up API function that returns a bool or detailed information if the attempted move fails. Or it could work like the now-defunct move_and_slide_with_snap and just be part of move_and_slide, although having a separate function would allow certain obscure use cases (6)

(1) Smooth for capsules! This is because capsules have a round bottom and can therefore "slide up" the lip of a stair step whereas with AABBs, you'd probably have to do some interpolation
(2) Including on top of other rigidbodies. While you can design around and add invisible ramps to static geometry, I've found stair stepping to be most useful for allowing the CC to interact well with dynamic geometry
(3) Possibly desirable for shallow slopes but bad on steep slopes
(4) Unlike with AABBs used by classic shooters (which can also do an arbitrary number of box casts against BSP for almost no cost btw), capsules are round on the bottom, which means they can't "catch" straight stair steps as easily when moving toward them non-orthogonally
(5) The step down code and/or gravity will cause the round bottom of the capsule to "grip" edges when partially over them, which actually helps make walking down steps feel smooth but is undesired when over the edge of a tall cliff or something
(6) One of the Quake games runs stair stepping code to allow the player to get up over ledges when using a grappling hook

@vensauro
Copy link

vensauro commented Oct 5, 2023

Edit: the affirmation bellow is false, i have misconfigured my setup here, is just more evidences that the separation ray have more problems than solutions for the "stairs problem"

the bug that @operatios have seen here #2751 (comment) can be solved if the separation ray have the size of the character.

On the next image, the "wrong" way, having the capsule of 1m and the separation ray of 1m for a char of 2m
image

getting stuck on 2 cubes with same xz and different y
image

or in an stair with close ceiling
image

next a character where the separation ray have 2m of height, and the collision shape 1m

2023-10-05.01-34-05.mp4

yet this solution is not good at all (but can be useful), on the video we can see some bugs, and we have others problems (on slopers angles) like cited by @timshannon.

@wareya
Copy link

wareya commented Oct 5, 2023

This doesn't actually solve the bug, it just turns it into a different bug; "head stuck in object" turns into "teleporting around or through things when the other version would get stuck".

@vensauro
Copy link

vensauro commented Oct 5, 2023

Yeah, i have said this, that is not the ideal, but forget all that i have said, i have misconfigured my things here, and see that this behaviour is not the normal, i was using the Jolt physics and not the GodotPhysics3D.
On the default physics system, we are stucked on the things again, even with the separation ray having the height to teleport over.

@timshannon
Copy link

timshannon commented Oct 12, 2023

More anecdata on the usefulness of adding stair stepping to the engine character body: https://www.reddit.com/r/godot/comments/175au28/coming_from_unity_i_was_very_surprised_what_godot/

image

The flood of developers coming over from Unity will likely expect stair stepping out of the box and separation rays are not a valid solution (at least in their current form).

@JheKWall
Copy link

JheKWall commented Jan 6, 2024

I ended up running into issues implementing stair-stepping as well. Although SeparationRays are recommended, they are very inconsistent and seem to be actually broken when using Jolt Physics (it will randomly pick and choose which objects to separate from). So I hacked together a stair-stepping system that uses a 2-node collision hitbox, 5 raycasts, and body_test_moves with some camera smoothing to round it all out.

2024-01-05.Stair-stepping.Demo.mp4

(Full vid here: https://youtu.be/FjD-Ndx8mBk)

While its kinda (really) messy, it seems to work pretty well. I tried as many edge-cases as possible including some of the ones discussed above and it seems to hold up. Here's a link to the demo project with all of the code if you want to pick it apart and test it some more:
https://github.com/JheKWall/Godot-Stair-Step-Demo
Edit: also on the asset library now if you just want to quickly grab it https://godotengine.org/asset-library/asset/2481

I hope we can get a standardized stair-stepping implementation for Godot soon, though. Especially since this topic is very poorly documented due to how most major engines already include it by default, turning it into a really big hurdle for beginner devs starting with Godot.

@mrezai
Copy link

mrezai commented Jan 12, 2024

I ported my stair stepping implementation to Godot 4: https://github.com/mrezai/GodotStairs
This implementation only uses body_test_motion for stair stepping, no raycast and no separation ray used and because of that it is more reliable IMO.
Some explanation about this project and its limitations: https://www.reddit.com/r/godot/comments/194vqe2/godotstairs_a_poc_implementation_of_stair/

@iiMidknightii
Copy link

I can add to this by saying that I have also experienced some inconsistencies with separation rays as well. I initially tried moving my capsule collision shape up by 0.5m, and putting a 0.5m ray pointing down to handle the floor for me. Like it was stated earlier:

1. It doesn't seem to respect floor / wall min / max angles.  It'll always just push you away from the collision point.  So in my case where a separation ray is my "feet", it'll just push the player vertically up any ramp or angled wall, regardless of the max floor angle.  You can enable "slide on slope" which prevents this, but that also doesn't seem to respect min / max slope angles, and of course if you don't want to slide on slopes it's not an option.

Using this setup, and a CharacterBody3D.floor_max_angle of 45 degrees, I was able to walk up and down 60 degree slopes at full horizontal velocity. Obviously I shouldn't have been able to walk up them at all, and it wasn't even respecting the fact that floor_constant_speed was false. If I selected slide on slope, it would at least prevent me from going up the 60 degree slopes, but then it would also make my character slide down a 30 degree slope.

I also tried keeping the capsule shape on the ground and moving the separation ray to about 0.2 meters along the character's +Z. It also worked in allowing me to climb up stairs, but exhibited the same problem of not respecting slopes. Also, if you move the separation ray too far forward, it has the problem of automatically teleporting you to the top of the stair, no matter how tall it was (it was teleporting me to the top of 20m walls). I suspect it's because, while the ray was only from 0.5m to 0.0m on the y axis, that technically fell inside the steps and it tried to push itself to the top no matter what distance that took to do so.

In response to this, I've had to implement a similar technique as @mrezai where I cast the motion up->toward velocity->down in order to make sure I can climb the steps/obstacles.

I'm not super experienced in physics engines, so take my word with a grain of salt, but I the best option would be to add a property to CharacterBody3D (and maybe 2D) that to signify move_and_slide should handle steps. I think floor_snap_length is a good property to use for the stair stepping height, or maybe a separate one could be added if you want it separated. When that property is true, move_and_slide can automatically do the full tests and move up steps lower than the threshold.

@Lamoot
Copy link

Lamoot commented Jan 31, 2024

I hope we can get a standardized stair-stepping implementation for Godot soon, though. Especially since this topic is very poorly documented due to how most major engines already include it by default, turning it into a really big hurdle for beginner devs starting with Godot.

I'm one of those beginner devs and it's indeed a hurdle setting it up. I've been researching this in the past days and only yesterday thought to check Godot proposals if there's an existing issue. I didn't expect the issue to actually be the best source of information on the topic and I'll be using the system you provided at https://github.com/JheKWall/Godot-Stair-Step-Demo Many thanks for setting this up and sharing the code.

I'm definitely not the only one faced with this problem and having stair handling built into CharacterBody3D would be very welcome. I hope this gets some attention sooner rather than later.

@elvisish
Copy link
Author

Version 4.3 and 3.6 of Godot should just add a C++ engine-core port of any one of the excellent stair-step examples in this proposal and be done with it. Unity/Unreal converts would be a lot less confused and long-time users would have lot better efficiency without having GDScript-based solutions pulling performance down.

@Lamoot
Copy link

Lamoot commented Feb 2, 2024

Version 4.3 and 3.6 of Godot should just add a C++ engine-core port of any one of the excellent stair-step examples in this proposal and be done with it. Unity/Unreal converts would be a lot less confused and long-time users would have lot better efficiency without having GDScript-based solutions pulling performance down.

We'll also need a test level however, so the implementation can be evaluated to cover as many situations as possible. A combination of level geometry found in https://github.com/mrezai/GodotStairs and https://github.com/JheKWall/Godot-Stair-Step-Demo would be a good testing playground. Both share quite a few of the test obstacles, but they each have some of their own, valid ones, as well.

I'll try to find time in the next few days to combine them so a potential implementer has a reference to test against. While I'm limited in coding skills, I hope such a contribution will help with solving the issue. I'll share the combined testing level here and of course accept feedback if anyone sees there's something missing from it.

@elvisish
Copy link
Author

elvisish commented Feb 2, 2024

Version 4.3 and 3.6 of Godot should just add a C++ engine-core port of any one of the excellent stair-step examples in this proposal and be done with it. Unity/Unreal converts would be a lot less confused and long-time users would have lot better efficiency without having GDScript-based solutions pulling performance down.

We'll also need a test level however, so the implementation can be evaluated to cover as many situations as possible. A combination of level geometry found in https://github.com/mrezai/GodotStairs and https://github.com/JheKWall/Godot-Stair-Step-Demo would be a good testing playground. Both share quite a few of the test obstacles, but they each have some of their own, valid ones, as well.

I'll try to find time in the next few days to combine them so a potential implementer has a reference to test against. While I'm limited in coding skills, I hope such a contribution will help with solving the issue. I'll share the combined testing level here and of course accept feedback if anyone sees there's something missing from it.

Here's another great one with sloped stair handling: https://github.com/wareya/GodotStairTester

@Menacing
Copy link

One nice to have feature with this would be the ability to not only specify a max step height but also a minimum step size so that the character doesn't try to step up onto extremely narrow ledges

@JheKWall
Copy link

JheKWall commented Feb 25, 2024

I've been working on getting stair stepping to work exclusively with the PhysicsServer3D's body_test_motion, and I've gotten it to almost work perfectly with some help (https://github.com/JheKWall/Godot-Stair-Step-Demo).
However, there is an issue when attempting to step up onto walkable slopes from the side. My code checks if the object we're stepping onto is a walkable surface to ensure we aren't getting onto a slope that exceeds the floor_max_angle. Ideally, this would return the exact normal of the slope as if we were walking onto it from the front. This isn't the case though as the player will collide with the corner of the slope first, causing body_test_motion to return the normal of the corner (See image below).

Capture

The normal of the slope tested above is 45 degrees or ~0.785 radians, the max floor angle being the same. The corner collision returns a value of ~53.8 degrees, which is much higher than 45 degrees. I'm unsure if this is the expected behavior of body_test_motion as it returns the correct angle when walking up square objects.
My current (hacky) solution is to just check the slope against a higher max_floor_angle to account for this corner-weirdness, although I'd prefer if there was a better way to do it. This is bad as it allows you to walk up steep slopes from head-on. My new approach is to just use a non-rotating BoxShape3D as the player's collision shape. Although, I will note that this doesn't work if the slope you're trying to walk onto is not axis-aligned as you run into the corner clipping issue.

Feedback would be appreciated. Again, other than this issue my current implementation is working well.

Note that this occurs with both default Godot Physics and Jolt Physics.

@wareya
Copy link

wareya commented Feb 26, 2024

This is normal and can't be avoided when the test for walkability is a collision normal check. AFAIK, the main fix in AAA games is to assign walkability to individual triangles instead of determining it based on collision normals, but a lot of games (like source engine games) just ship with the issue and design level geometry that doesn't run into it (e.g. using AABBs for character collision instead of capsules/cylinders, only ever using 90 and 45 degree aligned ramps, etc). As a workaround, you can have a pass of the stair step solver that "steps over" small unwalkable regions if they're small. This is what I do in https://github.com/wareya/GodotStairTester

@ratkingsminion
Copy link

ratkingsminion commented Feb 26, 2024

The normal of the slope tested above is 45 degrees or ~0.785 radians, the max floor angle being the same. The corner collision returns a value of ~53.8 degrees, which is much higher than 45 degrees. I'm unsure if this is the expected behavior of body_test_motion as it returns the correct angle when walking up square objects. My current (hacky) solution is to just check the slope against a higher max_floor_angle to account for this corner-weirdness, although I'd prefer if there was a better way to do it.

For my character controller in Unity I usually shoot a raycast (from the character's head or so) with the collision point as target, but a tiny bit higher on the Y axis, and use the resulting normal of this raycast as basis for slope calculations.

@sinewavey
Copy link

A long time ago, I was with the opinion it should be left to the developer. A lot, lot, lot has changed (from my perspective) since then and I firmly believe this is something that needs to be integrated within CharacterBody3D's move_and_slide as well. Given that it's already a bit of a black box of functionality, and as many have stated, SeparationRays simply do not solve the problem!

I've also written a stair stepping algorithm, and while it's simple enough, interacting with move_and_slide for something this fundamental tends to be a complete hassle now. So I fall back to having to reimplement move_and_slide myself.

Personally I found the approach using PhysicsDirectSpaceState3D.cast_motion and get_rest_info() to create a custom collision trace was the most straightforward and rather similar to what m_and_s already does.

@wareya
Copy link

wareya commented Mar 11, 2024

This is normal and can't be avoided when the test for walkability is a collision normal check. AFAIK, the main fix in AAA games is to assign walkability to individual triangles instead of determining it based on collision normals, but a lot of games (like source engine games) just ship with the issue and design level geometry that doesn't run into it (e.g. using AABBs for character collision instead of capsules/cylinders, only ever using 90 and 45 degree aligned ramps, etc). As a workaround, you can have a pass of the stair step solver that "steps over" small unwalkable regions if they're small. This is what I do in https://github.com/wareya/GodotStairTester

I realized another possible workaround for this, but it requires the physics engine to expose information that it doesn't necessarily have for all shapes. Basically, this only happens when the collision plane is derived from two edges, rather than from a vertex and a triangle or plane. What you could do, in theory, if the physics engine supported it, is check the planes associated with the two meeting edges, and see if they have walkable normals. But it's rare for physics engines to support this for arbitrary shapes, and it would be slightly more expensive.

@ubitux
Copy link

ubitux commented Jun 1, 2024

Like many people here, I had my take at implementing staircase climbing. I read several times the thread and would like to make a summary of the different solutions, their drawbacks and the solution I decided to implement. There is not a lot of new things but there are a few implementation details that may interest some people.

I probably have forgotten a lot of things and made mistakes, please correct me and I'll adjust as needed. Suggestions to the implementation snippets are also very welcome.

Note: I will only mention the 3D case

The ramp solutions

One popular method that hasn't been mentioned much is the use of invisible slope geometry collision on top of models. It's kind of off-topic to this issue, but I feel like having a "bake ramp geometry" mechanism similar to what we have with NavigationRegion3D might be an interesting idea to explore.

Ray-based solutions

So ray-based solutions, including SeparationRayShape3D, seem to suffer from unreliability when it comes to cracks in the geometry. For instance if the stairs look like this:

2024-05-30-185721-Uzoono8o

This will, to my understanding, glitch the player into an deadlock if the ray passes through. I believe it doesn't even need to be something so deep and a simple concave shape might be fatal.

More rays is often what's used as a workaround, but then what about more holes?

Physics-based solutions

The idea is that instead of using rays, we can simulate the whole movement of the player collision shape using PhysicsServer3D.body_test_motion(). Using physics addresses the issues related to rays so everything that follow will assume physics based poking and not rays (even if simple arrows are used to depict the physics for simplicity).

Naive front-based poking

The most common method probe for stairs is to have a front-based poking:

2024-05-30-185137-HohTo8Ap

There is a question of how much in front this should be tried. Either constant (will cause unexpected velocity change) or correlated to velocity and delta. In any case, this creates an issue because it may allow the player to glitch through thin walls (and maybe not so thin if the player is going fast):

2024-05-30-185428-Ohwee9xo

Up-forward-down

So instead of just going down, we need to also try going forward. But then you also have an issue with the ceiling:

2024-05-30-190209-cePh5Go9

Indeed, the player is not supposed to be able to pass through here. So what we do is go up, forward, then down. This method seems to be the most popular, which seems to have been introduced by the low rooms article. It's what has been implemented by most people in this thread.

  1. go up by the amount of the maximum step height the player is able to climb
  2. go forward by an amount correlated to velocity and delta (more on that later)
  3. go down by the maximum step height again to reach the top of the step

One thing to note here is that in any of these 3 directions, we may have a collision (ceiling or wall), but this doesn't mean we should cancel the move. Instead, the occurring collision should simply cap the movement: we may not be able to extend fully in either direction, but it might still be enough to climb the step:

2024-05-30-190750-Zie7quae
2024-05-30-191053-pah3duGu

Here is what the code may look like, roughly (I'll describe in more details later on):

	var from := global_transform
	var motions := [
		up_direction * STEPPING_MAX_HEIGHT,  # up
		velocity * delta * STEPPING_ADVANCE_RATIO,  # forward
		up_direction * -STEPPING_MAX_HEIGHT,  # down
	]
	var collided := false
	var test_motion_result := PhysicsTestMotionResult3D.new()
	for motion in motions:
		var test_motion_params := PhysicsTestMotionParameters3D.new()
		test_motion_params.from = from
		test_motion_params.motion = motion
		collided = PhysicsServer3D.body_test_motion(get_rid(), test_motion_params, test_motion_result)
		from.origin += test_motion_result.get_travel()

	if collided:
		var normal := test_motion_result.get_collision_normal()
		var collision_angle := normal.angle_to(up_direction)
		if collision_angle <= floor_max_angle
			global_position = from.origin
                        # ...

Note how we also check here if we landed on something flat enough.

The forward distance

This may look decent, but the maths tell us that this is going to make the player climb the steps too fast:

2024-05-30-191414-quoh5Zie

If we move to the expected destination the player would have traveled a too long distance. We don't know the height of the step in advance, so we can't triangulate in advance in order to know the forward vector. We still have a few heuristics and potential solutions to explore:

  1. We don't have the step height, but we could simply use an average step height. For example we could assume h=0.18 (average/recommended step height in meters) and do something like forward=sqrt(delta^2 - h^2), but that's probably expensive and overkill
  2. Use an arbitrary rough slowdown threshold; here we could have STEPPING_ADVANCE_RATIO=2.0/3.0, probably good enough and it doesn't need to be exact as the effect we want to simulate is "climb slower since it's harder than walking forward"; that's what I chose to do
  3. Smooth/slow down the camera movement, but this can't be within CharacterBody3D, and is limited to a first person camera
  4. Maybe some shenanigans with the intersection of a circle (?)
  5. Maybe readjusting with a backward movement (?)

We could explore this further, but in practice we have a way more important dormant issue...

The capsule geometry

What happens in practice when we implement all that with the classic capsule, is that the bottom of the capsule is going to hit the stairs at an unfortunate angle:

2024-05-30-231725-iete3She

This is typically one of the last issue mentioned in this thread. To address the issue, one solution might to brute force over a few frames using a "climbing" state boolean until we reach a flat area. We can test that the collision angle reduces over time to make sure we are actually making progress toward the safe zone, but I noticed it's not necessary if we clamp the first up motion to the maximum height: as soon as we enter the climbing state, we need to save the player position and use that as a reference point to never climb too high/forever.

The logic starts to be a bit more wonky at this point, but it's possible to get something reliable.

The code is not perfect but this how the stepping up function could be implemented:

const STEPPING_ADVANCE_RATIO: float = 2.0 / 3.0 # How much progress made horizontally
const STEPPING_MAX_HEIGHT: float = 0.5 # Average real world stair height should be around 0.185
var is_stepping := false
var climbing_from := Vector3.ZERO

func _step_up(delta: float) -> bool:
	if is_on_floor():
		climbing_from = global_position
	elif not is_stepping:
		return false

	# Compute the remaining height to climb to prevent climbing forever
	var total_height := (global_position - climbing_from).dot(up_direction)
	var remaining_max_height = STEPPING_MAX_HEIGHT - total_height

	# Run a up-forward-down collision check to detect steps
	# We might hit a ceiling or a wall in the process; if that happens, we stick
	# to that collision and continue, because it might not be a blocker for
	# climbing the step
	var from := global_transform
	var motions := [
		up_direction * remaining_max_height,  # up
		velocity * delta * STEPPING_ADVANCE_RATIO,  # forward
		up_direction * -STEPPING_MAX_HEIGHT,  # down
	]
	var collided := false
	var test_motion_result := PhysicsTestMotionResult3D.new()
	for motion in motions:
		var test_motion_params := PhysicsTestMotionParameters3D.new()
		test_motion_params.from = from
		test_motion_params.motion = motion
		collided = PhysicsServer3D.body_test_motion(get_rid(), test_motion_params, test_motion_result)
		from.origin += test_motion_result.get_travel()

	# No ground collision, we go back to the normal codepath
	if not collided:
		is_stepping = false
		return false

	# If we didn't make any progress stepping we bail out to prevent staying
	# in the air if the player keep pushing in that direction
	var climb_motion := from.origin - global_position
	if climb_motion.is_zero_approx():
		is_stepping = false
		return false

	# Check if we reached a walkable floor. We might have hit a corner with an
	# unfortunate angle, or it might simply be a non-climbable obstacle
	# If the angle is too steep, we assume that we are currently stepping up
	var normal := test_motion_result.get_collision_normal()
	var collision_angle := normal.angle_to(up_direction)
	is_stepping = collision_angle > floor_max_angle

	# Move to target position and make sure is_on_floor state is consistent
	global_position = from.origin
	velocity = Vector3.ZERO
	move_and_slide()

	return true

This has the effect of making the player jump in front of too high stairs, but it doesn't sound like an unreasonable behaviour to have.

An alternative would be to use the cylinder or a box, but then you probably loose all sort of sliding properties.

One very important consequence of this brute force logic is that the STEPPING_ADVANCE_RATIO doesn't make much sense anymore since the climbing is actually split across several frames, so it might actually make sense to go above 1 for this scale.

Acceleration

So far, this assumed an immediate velocity. Something like what the default template proposes (since the proposed deceleration is currently broken):

	var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	velocity.x = direction.x * SPEED
	velocity.z = direction.z * SPEED

But if you have proper acceleration/deceleration, let's say:

	var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	velocity.x = lerp(velocity.x, direction.x * SPEED, 1.0 - exp(-delta * MOVE_STEEPNESS))
	velocity.z = lerp(velocity.z, direction.z * SPEED, 1.0 - exp(-delta * MOVE_STEEPNESS))

This will cause the computed advance to be slow initially, and so when trying to climb stairs from an immobile position, the climbing process will hit a ton of collisions before reaching the first step, making the step climbing super slow.

An easy way out is to use direction * SPEED and not velocity when probing for stairs, but then this means instant acceleration when climbing stairs.

Step down

While following a similar logic, the step down should be easier, but in practice is also fairly tricky as well. I came up with a method where I only check below, meaning I try to snap down when falling a small height. It also requires special handling when free falling or jumping.

var is_falling := false

func _step_down() -> bool:
	if is_on_floor():
		is_falling = false
		return false

	if is_falling or is_stepping:
		return false

	# Test if we hit something below us
	var test_result := PhysicsTestMotionResult3D.new()
	var test_params := PhysicsTestMotionParameters3D.new()
	test_params.from = global_transform
	test_params.motion = up_direction * -STEPPING_MAX_HEIGHT
	if not PhysicsServer3D.body_test_motion(get_rid(), test_params, test_result):
		is_falling = false
		return false

	# Make sure it's flat
	var normal_collision_angle := test_result.get_collision_normal().angle_to(up_direction)
	if normal_collision_angle > floor_max_angle:
		is_falling = false
		return false

	# Move to target position and make sure is_on_floor state is consistent
	global_position += test_result.get_travel()
	velocity = Vector3.ZERO
	move_and_slide()
	is_falling = false

	return true

The main driving code looks like this in _process_physics:

	if jumping or (not _step_up(delta) and not _step_down()):
		move_and_slide()

Conclusion

I'm aware I'm bringing more problems than solutions here, but given how long this took me, I feel like a summary of the situation might have been welcome. I must say the current situation is pretty frustrating and quite a blocker when considering level design.

@ubitux
Copy link

ubitux commented Jun 3, 2024

This might be starting to be off-topic, but I thought about the problem from a different perspective and wanted to share. This may not be a solution for many cases, but how about jumping from stairs to stairs instead of teleporting?

jump-stairs-cut.mp4

Here for example, when the a step up is detected, the velocity is set to generate a mini-jump to the next stair. It needs some analytic math to do it properly, but if someone wants to explore the idea further, I think it might be one of the useful alternatives.

@Calinou
Copy link
Member

Calinou commented Jun 3, 2024

Here for example, when the a step up is detected, the velocity is set to generate a mini-jump to the next stair. It needs some analytic math to do it properly, but if someone wants to explore the idea further, I think it might be one of the useful alternatives.

This is a good idea for Minecraft-style autojump, but it's too slow for general stair stepping. For camera smoothing, you want to smooth out the visual representation of the player (and its camera height) as opposed to actually jumping for every step. This is what most games do in practice.

@elvisish
Copy link
Author

elvisish commented Jul 17, 2024

Maybe we could just let Jolt do the work for us:
image
image
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests