-
-
Notifications
You must be signed in to change notification settings - Fork 96
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
Comments
See also #2184 and Shifty's Godot Character Movement Manifesto, which recommends using RigidBodies for characters instead of KinematicBodies. |
move_and_slide()
+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. |
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. |
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 |
Bump. I hacked around with some local translations, but it's jittery. I need something smooth. |
FWIW @Zireael07 I get around the jittery issues in my stair-stepping code by lerping the camera's offset from the character controller. |
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:
But I have some problems with this approach:
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).
|
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). |
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 😂 |
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.) |
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 🙄 |
This comment was marked as off-topic.
This comment was marked as off-topic.
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:
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:
|
Here's the setup in Godot: Unfortunately, it looks like I ran into my first "character getting stuck" bug: Full video: |
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:
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. |
godotengine/godot-demo-projects#849 also features stairstepping using a ShapeCast3D, although the camera height isn't smoothed yet. |
@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. |
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. |
Best stairs implementation so far is mrezai's: https://github.com/mrezai/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: A stress test of 250 enemies shows just how slow it is to run the stair-step function on every frame. Comparatively on fps: 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. |
There clearly is a pretty bad documentation issue if nobody here realized you can use SeparationRayShape for stair stepping. |
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) |
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. |
To be fair, this was opened long before SeparationRayShape was added to the engine.
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. |
That's really an error of the physics, slopes should never be considered stairs unless the slope angle is too high. |
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: 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. |
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 # 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() |
@elvisish In case I wasn't clear: visually it's a slope but physically it's stairs |
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: stairs.mp4The 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. |
Colliders with ramp-like bottoms have their own problems and aren't a solution either:
|
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.
|
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:
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 (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 |
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 getting stuck on 2 cubes with same xz and different y or in an stair with close ceiling next a character where the separation ray have 2m of height, and the collision shape 1m 2023-10-05.01-34-05.mp4yet 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. |
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". |
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. |
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/ 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). |
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: 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 ported my stair stepping implementation to Godot 4: https://github.com/mrezai/GodotStairs |
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:
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 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 |
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. |
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 |
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 |
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). 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. 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. |
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 |
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. |
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 |
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. |
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 solutionsOne 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 Ray-based solutionsSo ray-based solutions, including 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 solutionsThe idea is that instead of using rays, we can simulate the whole movement of the player collision shape using Naive front-based pokingThe most common method probe for stairs is to have a front-based poking: There is a question of how much in front this should be tried. Either constant (will cause unexpected velocity change) or correlated to Up-forward-downSo instead of just going down, we need to also try going forward. But then you also have an issue with the ceiling: 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.
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: 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 distanceThis may look decent, but the maths tell us that this is going to make the player climb the steps too fast: 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:
We could explore this further, but in practice we have a way more important dormant issue... The capsule geometryWhat 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: 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 AccelerationSo 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 Step downWhile 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 if jumping or (not _step_up(delta) and not _step_down()):
move_and_slide() ConclusionI'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. |
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.mp4Here 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. |
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:
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.
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.
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.
The text was updated successfully, but these errors were encountered: