Thank you so much for this video, the concept of "stairs" is such a basic fundamental thing for... _any_ 3D game, and requires a bunch of function calls that newcomers would never figure out on their own. It is absolutely _INSANE_ to me that so few 3D movement tutorials bother to touch on this.
Glad I could help! Yeah I hear ya, I was actually thinking about doing a more comprehensive character controller tutorial, with more specific features like this included. Are there any other features you (or anyone else who happens to read this comment) might need?
Nice video! Small tip that will help your if statement hygiene.. instead of having a super long "if a and b and c then do thing", check for the opposite of each desired condition at the top of your func and return if that negative condition is met. So it'll look like: If !a: return If !b: return If !c: return This helps a lot to keep your if statements and indents from becoming spaghetti and let's you more easily see the order your checks are done in and debug them if needed.
Okay now hear me out on this one, What beginners need is a tutorial on getting from a building a character creator screen to a fully locomotion and gear (weapons for FPS/Hack and slash, and tools for adventure/farm sim) enabled character, wherein the basic locomotion piece can be easily recycled into NPC characters. Then having the game save once you create your character and get in game.
The way I did it is like this: - if you run into a wall, check forward and up a bit to see if the wall is just a step. - From the forward + up position, check downwards until we hit a floor. - Find step height by comparing the check position with a reference, (I made a Marker3D named Bottom, and put it a bit under the collider) - If we have step height, move_and_collide upwards Not on the computer right now but it's roughly like thus
This is a solution and frankly you're the only one who has covered this in as much detail and with a beginner-friendly approach. Godot should implement some more simple ways to properly climb stairs. Separation Ray Shapes create a lot of issues. For example, they do not account for roofs. For example, climbing up a set of stairs leading to a roof will make the player clip into it. Does anybody have any ideas on how to achieve what this tutorial does but addresses this issue?
Thanks appreciate the comment! Yeah I was surprised to not be able to find any tutorials on it when I first tried to solve it. About the separation rays, I agree they create some issues... I have been thinking about some solution, I think I will do another video on it when I implement the character controller for my own game. Currently what I'm planning is something similar to what this guy did: ruclips.net/video/pN4rmTVu_f4/видео.html I'm not sure exactly how he implemented it, but it looks like he is pushing the player up by some velocity to create a sort of softer separation ray thing, same for going down stairs.
This was super helpful! I just found out that my solution for stairs resulted in some wierd behavior for NPCs and this tutorial gave me a much better way to do it. Funny enough, I had a working solution for going up but not for going down. You taught me how to go down the stairs and then showed me a node that makes my code for going up obsolete :)
Glad I could help! I didn't find any super clear ways of doing stairs so I gave it my best shot at doing it cleanly and put it up in hopes it might be useful to others in the same spot.
@@MajikayoGames The SeparationRay edge cases don't bother me at this point but my first method might come in handy for anyone who wants to avoid them: I raycast down (from in front and based on the movement direction) and check the height difference between you and the collision. If the height diff is below max_step_height, I add the height to the player body y position. The new height combined with moving forward makes you go up the step. I also get the normal back from the collision and can calculate the angle of the surface. With a max_step_angle you can avoid going up steep ramps and only consider flat surfaces.
Very interesting tutorial, helped me to improve my code a bit. My current conundrum is how to disable the character from mantling the ledges during jumps.
My next video probably coming tomorrow is a redo of this method actually lol. A lot of people asked for camera smoothing and better walking up stairs handling so I added that. Maybe you will find something in there to help.
@@MajikayoGames such a fortunate timing for me. I'll give it a watch when it's out. For now I'm going back to failing, for as the saying goes - trying is the first step to failure.
For rotating the separation rays around the character: If you implement a wish_dir variable that saves the player's directional input (updated in physics_process) you can use that to get the direction of movement you need to rotate the separation rays. This has the added benefit of "disabling" the separation rays when there's no input because the wish_dir will be a zero vector. I also found that for down step snap, if you add to the velocity instead of the position you can make going down a lot smoother, though you'll need to multiply the translate_y value (I multiplied by 12) to make sure you go all the way down. Not sure if there's a way to smooth out the up step snap in a similar way. Thanks for making this video though, really helped me out. EDIT: for anyone using jolt physics, separation rays seem to be very glitchy so be aware.
Yeah I haven't implemented smoothing for mine yet. The idea I had was just to smooth the camera. Hadn't thought of manipulating the Y velocity. Are you still able to jump when going down the staircase with that approach? I guess if you are using coyote time/a jump grace period it might not matter.
@@MajikayoGames You should still be able to jump as long as you're setting the velocity.y = jump_velocity with coyote time since the smoothing adds downward velocity instead of setting it. Its kinda like adding extra gravity.
Why re-invent the wheel? Just use self.test_move. Example: if self.test_move(self.transform, direction * speed * delta): ----> var motion : vector3 ---->motion.x = direction.x * speed * delta ---->motion.y = 0.5 #Whatever stair step height you want. ---->motion.z = direction.z * speed * delta ---->if not self.test_move(self.transform, motion) ----> ---->self.position += motion ----> ----> apply_floor_snap() else: ---->velocity.x = direction.x * speed ----velocity.z = direction.z * speed And up the stairs you go. self.test_move will take a vector and test to see if you can move to that spot. It returns true if something is in the way, so add some height to the current vector and test again. If it's now clear (false) then we encountered a step (motion + 0.5 added on y axis check) and so we just teleport to that spot. I did this in C# originally so apologies if there are mistakes in the GDScript code. Also not, it works for the most part but you'll need to fine tune it a bit as I found myself getting stuck occasionally and had to reverse and try again. No clue why but I'll play with it some more in future. This was just a rough first draft as these things go. Okay, using smaller speed values seems to fix the getting stuck issue. A simple direction.z * speed /2* delta fixed it. It makes sense as smaller stairs will need smaller movement.
Yeah I may end up going with something similar to this approach. Separation rays have some problems, can be glitchy, and the solution ends up even more convoluted needing 3 and to rotate them. I think eventually what I want to do is body_test_motion for up the stairs as well, and see if I can tune it to apply just the right amount of velocity before a move so you smoothly glide up the stairs. That's currently what I want to try. And I'll need to test it a good bit for edge cases. Have tried a few approaches and each have their problems... I also tried a 'soft' separation ray that pushed the character up by some velocity but that was pretty clunky feeling going up the stairs. Who knew stairs were so hard to solve. lol
Neat implementation, I'm doing something very similar to this, although like you said it doesn't cover some of the edge cases or it can be wonky at times.
Yep, I think even in a lot of AAA games we run into edge cases. It's hard to make a 100% perfect character controller. I think this will work fine for most, I'm sure it could be polished and improved a bit more if you want to hand tune it to your levels/use case.
if this is in godot 4.x is there any reason as to why you wouldn't just use a SeparationRayShape as an additional shape for the CharacterBody's CollisionShape? I think this might be a better solution than attempting to make a custom one for the stairs. it works going up and down as long as there is a value for floor_snap_length.
I do use the SeparationRayShape for walking up the stairs here, I use 3 of them which rotate towards the movement direction to ensure walking up stairs at odd angles works as it only worked running head on before. I found you also need to disable the SeparationRayShapes when running up surfaces of a great angle than floor_max_angle as the rays make the CharacterBody3D no longer respect that. Just using apply_floor_snap may work to replace the PhysicsServer3D.body_test_motion code in _snap_down_to_stairs_check, that's a good point. I set floor_snap_length to 0.5 and it appears to work. Good find!
After testing this a bit, one caveat I realized of using the increased floor_snap_length method is that it makes the vertical movement down the stairs noticeably less smooth. My current implementation calls apply_floor_snap after moving the body to the travel y given by the body_test_motion call, but apply_floor_snap only succeeds and moves the body about half the time. This is because much of the time the player is snapped down to the corner of the stairs which is not considered a floor as the stair corner is the first collision the CharacterBody3D makes in body_test_motion. When setting the floor_snap_length to 0.5, that only succeeds about half the time as well, because it won't move the body down when it would be clipping into the corner of the stairs. This results in a larger jump in y value when it is called, which from my testing looks to be about every other frame.
Thank you so so much for making godot tutorials like this, I managed to make a player controller prototype work only because I found this video. If anyone is interested, I am using a different method to go upstairs, similar to the source engine, I measure a step's height using a small shapecast by my feet, and the step's depth with a taller, but shallow box shapecast that checks if there's enough space to walk up. my issue was figuring out how to tell the player where to head upstairs, I didn't want to make 8 raycasts in different directions hoping one would hit a stair and then figure out how to move them towards that direction, so I employed the "feet" shapecast and made it move towards the input direction by modifying this code slightly, I removed the velocity check and swapped it out for an input check using direction.normalized() which I get from the player's inputs. @onready var _stairsup_cast_dist = abs(shape_shoes.position.z) func _rotate_stairsup_cast(): var input_dir = direction.normalized() var xz_castpos = input_dir * _stairsup_cast_dist shape_shoes.global_position.x = self.global_position.x + xz_castpos.x shape_shoes.global_position.z = self.global_position.z + xz_castpos.z I only changed the code because I had a specific issue: when I was walking upstairs, I would eventually hit vel 0 from the steps slowing me down gradually, and when my vel was 0, then the shapecast would stop reacting to input, controling it using input only solved my problem and works fine for my small game! Thank you again Majikayo!!!
Going down the stairs isn't working for me. I did your code the same way as you. My speed is 5.0. I think you said in a comment that yours was also 5.0. I'm using stairs that were created from the Cyclops World Builder addon. I haven't implemented the code for going UP but so far the code for going DOWN doesn't do anything. I still just fly off whatever stair I'm on. Edit: Okay so the raycast wasn't registering because it was too high. It wasn't even coming out the bottom of the character. But I fixed it and now it works. Thanks!
In the Hammer editor, we used to make a slope and then have no clip stair triangles along the slope. You glide up and don't fly down. No code was necessary.
Congrats on your first video! It was informative and useful! You really gotta get that lip smacking under control (lol) but otherwise I thought it was well done! My current CharacterBody3D won't walk up ANY steps, no matter the height. Thug life, I guess.
thanks for the comment glad you liked it. still figuring out the audio quality. i tried to make this implementation pretty simple, condensed into 2 pretty (i hope) clear functions and some and some raycasts/separation rays on the character body.
i an trying to make the source character controller in unity, using a box instead of a capsule for collision, its both easier and harder, rn im stuck on making the player walk up steps not teleport up them right at the edge. for like 2-3 weeks cuz its a sideproject
im working on a video for a source like controller in godot now. trying to release it in the next couple weeks. for walking up stairs, i recommend teleporting + camera/animation smoothing. it is difficult enough to get simple teleporting to work fine without glitchiness. making them actually glide up is too much i think.
@@MajikayoGames im using the tf2 source code leak as a guide to things, source is weird af, i still have to test some things in game to figure out when you go up the step,
@@nocoreal My new solution is to run a single body test motion directly down starting slightly in front and above (calculate new character XZ/horizontal position based on velocity, add max step height + some small clearance amount). If it hits at the feet, and less than step height, I teleport. This is the best I've found so far.
@@MajikayoGames i am finding the step height by boxcasting top to bottom then moving ypu up it (still got to figure this out), but in source you bounding box gets projected forwards and up 16 pixels and do which one returns true
Great tutorial, I only need the going up steps and it works well, as well as diagonals! My problem is with the floor angle adherence part. I followed your tutorial 1:1 minus var names. I tried a bunch of "print tests" and it says the raycasts never collide when I'm on a slope (sometimes even when I'm not), and collision normal dot products never stop being 1 (which is higher than my "max_slope_ang_dot") Maybe my Godot or project is broken or something, regardless, very frustrating, and I'm not sure you'd have a fix but ah well
A few thoughts that could maybe help: -The code for the dot product is maybe a little confusing. The way a dot product works, as long as the two vectors are .normalized(), it acts as kind of a 'percent' of how much they are facing in the same direction. 1 is exact same direction. 0 is right angle/perpendicular, -1 is exactly opposite. We get the 'percent' in same direction of max floor angle to up, and compare that with the 'percent' of the collision normal. If it's a lower, number, we know the collision surface is steeper (further away from up) than the max floor angle allows. -Is it on all floor types? I noticed some glitchiness with my terrain but not my angled boxes test. It's possible you found some bug. -Are your ray casts pointing down not up? Your code might be correct but if there's some slight oddity in the editor it could be hard to see. I recall the direction the rays were points was not super clear in the editor view. -As a last ditch effort, you could download my project, run it, and incrementally keep making changes till it looks like your scene/bringing in things from your own project to debug the problem. -If you downloaded and used my controller from the github and still found a case where it's broken I could take a look later today when I have time. You can fork my repo and make an issue
@@MajikayoGames First of all, your explanation of dot() here was really good and I feel I have a good grasp on it now. Although it's confusing why it's less than < when the dot product of the normal is closer to 1 (steeper) and the max angle is closer to 0.7 and that determines it's too steep, programming is weird but whatever. I downloaded your project, changed code until it was like mine. It worked. I imported my player scene, and IT WORKED. It seems the world I use is messed up in a way I wouldn't know. My guess is that something broke when I updated the world to godot4 from 3 (I was too lazy to make a new project). Thank you for helping and for the good guide!
@@Jx_- glad i could help. regarding why less than the dot product is steeper, it's because a normal vector is a vector pointing directly out of a surface. so for a flat ground we are walking on, all the normal vectors would be pointing up directly towards the sky. you can imagine a steep hill the normals would be pointing out kind of at a 45 degree angle, not straight up. and a cliff would have a normal pointing totally horizontal at that point. the ceiling's normals are arrows/vectors pointing towards the ground. we .dot the collision surface normal (hill, cliff, etc) with the 'directly up' normal, which is essentially going to be the normal of the totally ideal case of the flat ground. so we have the 'percent closeness of collision surface to up direction.' if it's too far away (closer to -1) then it's too steep. we can find the exact number which is too steep by doing a dot product with a normal pointed toward 'floor_max_angle' with the ideal flat ground up vector. so now we have both of the 'percents' away from up. compare the collision surface normal 'percent away from up' with the floor max angle as a normal 'percent away from up.' if it's less, then it's further away from up, a.k.a. too steep. hopefully that makes sense. if you run into more issues you could upload the zip somewhere and make an issue on the github page. if you happened to find cases where my controller is broken id be interested to see, so i can fix it for my game.
What is your player speed? I think mine are too fast causing the ray cast to not detect ground in time and the player flys off the stairs with too much momentum.
Yes this is possible. Mine is 5*delta per _phys_process. The way the snap down works is if they were on the floor last frame but not this frame, it will cast a ray down to look for the stairs. However, if they moved multiple steps across in 1 frame they might not snap down to stairs.
There must be something wrong with my code or maybe a setting on my Raycasts or something. I'm still able to go up steep slopes like they're floors. Here is my code for that. Is there something wrong? Also I named my nodes a bit different. # Slope Restriction var max_slope_ang_dot = Vector3(0,1,0).rotated(Vector3(1.0,0,0), self.floor_max_angle).dot(Vector3(0,1,0)) var any_too_steep = false if $StairUpCheck_F/RayCast3D.is_colliding() and $StairUpCheck_F/RayCast3D.get_collision_normal().dot(Vector3(0,1,0)) < max_slope_ang_dot: any_too_steep = true if $StairUpCheck_L/RayCast3D.is_colliding() and $StairUpCheck_L/RayCast3D.get_collision_normal().dot(Vector3(0,1,0)) < max_slope_ang_dot: any_too_steep = true if $StairUpCheck_R/RayCast3D.is_colliding() and $StairUpCheck_R/RayCast3D.get_collision_normal().dot(Vector3(0,1,0)) < max_slope_ang_dot: any_too_steep = true
It looks like you are disabling the check rays themselves, rather than the separation ray shapes. github.com/majikayogames/godot-character-controller-stairs/blob/e067dd13fa37072cf4836639505db170c49733da/entities/Player/Player.gd#L105 The idea is to have a ray at exactly the same length/direction as the collision shape, or maybe slightly longer just incase, and then disable the separation ray (collision) shape before it lets us walk up walls. Also, are you sure about your raycast length and direction? It is easy to accidentally set them the wrong direction, like pointing up rather than down, it looks nearly the same in editor. If all else, fails, you can copy the controller from my project. Or copy portions of the character controller nodes/code till it works.
@@PastaMaster115 I see. Well then the code looks fine from what I can tell. Here are a few more ideas you can try: -ensure calling force_raycast_update -ensure self.floor_max_angle is set on the CharacterBody3D -ensure node structure and properties are exactly correct. many things could go wrong here. -like I said, you could just move my character controller from the github into your project, and copy my nodes from the character controller my project, and rename it to your node names. -add some debug statements to try to sort out what is going on. maybe print out max_slope_ang_dot, as well as the collision normal dot results somewhere Good luck. Hope this helps.
In the utils folder of the github there is a utility class for creating stairs which you can use in your project: github.com/majikayogames/godot-character-controller-stairs
Approximately 0.4 high in the example scene but the same idea should work if you increase the maximum step size in the code as well as the rays/separation ray colliders on the controller
Because layer/mask for what to collide with can't be set individually on the ray shapes, you'd have to do it programmatically. I would add some additional checks in the _rotate_step_up_separation_ray function. Similar to how we disable the step-up ray colliders when the raycasts hit on a collision normal that is too steep, you could also disable them if it hits any non static body. Something like: var any_too_steep_or_rigidbody = false if $StepUpSeparationRay_F/RayCast3D.is_colliding() and ($StepUpSeparationRay_F/RayCast3D.get_collision_normal().dot(Vector3(0,1,0)) < max_slope_ang_dot or $StepUpSeparationRay_F/RayCast3D.get_collider() is RigidBody3D): any_too_steep_or_rigidbody = true .... all the rest similarly. haven't tested but this is probably what I would try.
i actually know a better solution, well, more sourse-games-like. you use body_test_motion to ckeck if in the next frame there will be a collision, and you test if this colission is a flat, 90 degrees plane. if so, and if this plane isn't higher then, say, 0.25 hight, you move the player up by that amount. it makes a solid yet too sharp stairs collision.
yeah i was thinking about this solution for walking up stairs as well. might test it eventually. i was thinking it would be a bit complicated, probably a few body_test_motion calls per stair step, there might also be issues as to whether the body is actually on the ground or not the frame after you move them if they're positioned on the corner/lip of the stair. i think you would still need to shoot rays directly down in front to check if the surface is flat too, because pretty sure the collision normal could be horizontal in some cases, like when it collides with the corner or front of a stair. in the end i decided to go with this with some plan to implement camera smoothing. another solution i thought of, is just have a raycast in front, detect when it hits a stair, and apply a tiny jump/up velocity just enough to get the player up the next stair and still consider them on the floor. i think another commenter mentioned a similar approach to walking down the stairs and said it worked well for smooth motion, apply a down velocity rather than snapping them fully.
@@MajikayoGames i wouldn't really check if there is a ground after the step, why would you need to check this? Even if player is staying at the edge somewhere and to go forward, they sure can. Ofc youll need to do about 2 body checks(if there is a step next frame and if ceiling isnt too low when you're on a step. Otherwise i can't say anything more is necessary.
@@the-guy-beyond-the-socket without checking if there is ground, it will indeed work, but i believe it will have the same problem as the separation rays in the video. since the player is teleported there, we do not check if the surface is steeper than max_floor_angle, allowing us to walk up very steep cliffs. you can see the problem at 20:00 in the video.
@@the-guy-beyond-the-socket oh i misread that comment. rather, the reason you need to check if there is ground with a ray, is that body_test_motion may hit the side of a stair, where the collision normal would be 90 degrees sideways. this would then cause not being up to step up that stair. whereas with a ray cast directly down a little in front of the player, it will not hit the front/side of the stair step.
@@MajikayoGames makes sence but i would allow to go side stairs, if player is walking into a stair even by some side, they want to go uo that stair. and a situation where player is absolutelly parralel to a stair would still give sane results. I maybe need to do the demo to show what i mean.
Make sure you increase the length of both the separation ray and raycast3Ds, and their height off the ground. They should both be pointing at the ground, with their tips exactly aligned with the character's hitbox. The character will only be able to step up a step equal to or maybe slightly smaller than each ray's length.
@@MajikayoGames Hello :) Thank you for your reply. I have changed the height of the seperationray and raycast, I'm wondering whether the issue may be caused by the fact that I'm scaling the CharacterBody3D that they're held in.
@@theshiftybloke4672 Ah that could definitely be it. I think Godot has some undefined behavior with non uniform scaling on collision objects (if you scale x/y/z by different values). Not sure about uniform scaling. Also not sure how the rays get scaled/if it all. Would definitely try to see if it works as expected if not scaled to confirm that's not the problem.
@@MajikayoGames I've just scaled my stairs down to 0.25 rather than my character to 4 and can confirm that it works. Will have to see what I can do with my character model :) Thanks
assuming you're talking about the buggy is_on_floor() when walking on terrain, I think it's just the ConcavePolygon shape is very error prone, I've had issues with it in a couple projects. the normals look fine in blender, maybe it's possible on import to godot some may have broke. but it also happens with backface collisions on. i did give it a try with the godot jolt physics engine instead of the default and it doesn't happen at all, even with backface collisions off. so i think it may just be a bug in godot.
@@MajikayoGames or 3 raycast that return position then cross product, that's what some game did pre generalized third party physics engine, for example Mario 64
New and improved tutorial is out (Better up stairs handling & smoothed camera)
ruclips.net/video/Tb-R3l0SQdc/видео.html
your content is super helpful 💖
Thank you so much for this video, the concept of "stairs" is such a basic fundamental thing for... _any_ 3D game, and requires a bunch of function calls that newcomers would never figure out on their own. It is absolutely _INSANE_ to me that so few 3D movement tutorials bother to touch on this.
Glad I could help! Yeah I hear ya, I was actually thinking about doing a more comprehensive character controller tutorial, with more specific features like this included. Are there any other features you (or anyone else who happens to read this comment) might need?
This is definitely going in the "Saved videos" hall of fame
Nice video!
Small tip that will help your if statement hygiene.. instead of having a super long "if a and b and c then do thing", check for the opposite of each desired condition at the top of your func and return if that negative condition is met.
So it'll look like:
If !a: return
If !b: return
If !c: return
This helps a lot to keep your if statements and indents from becoming spaghetti and let's you more easily see the order your checks are done in and debug them if needed.
thank you for not playing music in the background
Hey everyone. What other tutorials would you want to see on this channel?
would it be possible to show how to make it smoother . If you managed to find out how that is .
Okay now hear me out on this one, What beginners need is a tutorial on getting from a building a character creator screen to a fully locomotion and gear (weapons for FPS/Hack and slash, and tools for adventure/farm sim) enabled character, wherein the basic locomotion piece can be easily recycled into NPC characters. Then having the game save once you create your character and get in game.
@@Xaneph thank you Xaneph great suggestions! I have a list of ideas for future tutorials I'm planning and I'll add these.
@@MajikayoGames Thank you for adding quality tutorials to the community.
The way I did it is like this:
- if you run into a wall, check forward and up a bit to see if the wall is just a step.
- From the forward + up position, check downwards until we hit a floor.
- Find step height by comparing the check position with a reference, (I made a Marker3D named Bottom, and put it a bit under the collider)
- If we have step height, move_and_collide upwards
Not on the computer right now but it's roughly like thus
This is a solution and frankly you're the only one who has covered this in as much detail and with a beginner-friendly approach. Godot should implement some more simple ways to properly climb stairs.
Separation Ray Shapes create a lot of issues. For example, they do not account for roofs. For example, climbing up a set of stairs leading to a roof will make the player clip into it.
Does anybody have any ideas on how to achieve what this tutorial does but addresses this issue?
Thanks appreciate the comment! Yeah I was surprised to not be able to find any tutorials on it when I first tried to solve it. About the separation rays, I agree they create some issues... I have been thinking about some solution, I think I will do another video on it when I implement the character controller for my own game. Currently what I'm planning is something similar to what this guy did: ruclips.net/video/pN4rmTVu_f4/видео.html
I'm not sure exactly how he implemented it, but it looks like he is pushing the player up by some velocity to create a sort of softer separation ray thing, same for going down stairs.
super easy to follow, and integrate in existing code, good job!
This was super helpful!
I just found out that my solution for stairs resulted in some wierd behavior for NPCs and this tutorial gave me a much better way to do it.
Funny enough, I had a working solution for going up but not for going down. You taught me how to go down the stairs and then showed me a node that makes my code for going up obsolete :)
Glad I could help! I didn't find any super clear ways of doing stairs so I gave it my best shot at doing it cleanly and put it up in hopes it might be useful to others in the same spot.
@@MajikayoGames The SeparationRay edge cases don't bother me at this point but my first method might come in handy for anyone who wants to avoid them:
I raycast down (from in front and based on the movement direction) and check the height difference between you and the collision. If the height diff is below max_step_height, I add the height to the player body y position. The new height combined with moving forward makes you go up the step.
I also get the normal back from the collision and can calculate the angle of the surface. With a max_step_angle you can avoid going up steep ramps and only consider flat surfaces.
Very interesting tutorial, helped me to improve my code a bit. My current conundrum is how to disable the character from mantling the ledges during jumps.
My next video probably coming tomorrow is a redo of this method actually lol. A lot of people asked for camera smoothing and better walking up stairs handling so I added that. Maybe you will find something in there to help.
@@MajikayoGames such a fortunate timing for me. I'll give it a watch when it's out. For now I'm going back to failing, for as the saying goes - trying is the first step to failure.
For rotating the separation rays around the character: If you implement a wish_dir variable that saves the player's directional input (updated in physics_process) you can use that to get the direction of movement you need to rotate the separation rays. This has the added benefit of "disabling" the separation rays when there's no input because the wish_dir will be a zero vector.
I also found that for down step snap, if you add to the velocity instead of the position you can make going down a lot smoother, though you'll need to multiply the translate_y value (I multiplied by 12) to make sure you go all the way down. Not sure if there's a way to smooth out the up step snap in a similar way. Thanks for making this video though, really helped me out.
EDIT: for anyone using jolt physics, separation rays seem to be very glitchy so be aware.
Yeah I haven't implemented smoothing for mine yet. The idea I had was just to smooth the camera. Hadn't thought of manipulating the Y velocity. Are you still able to jump when going down the staircase with that approach? I guess if you are using coyote time/a jump grace period it might not matter.
@@MajikayoGames You should still be able to jump as long as you're setting the velocity.y = jump_velocity with coyote time since the smoothing adds downward velocity instead of setting it. Its kinda like adding extra gravity.
Why re-invent the wheel? Just use self.test_move. Example:
if self.test_move(self.transform, direction * speed * delta):
----> var motion : vector3
---->motion.x = direction.x * speed * delta
---->motion.y = 0.5 #Whatever stair step height you want.
---->motion.z = direction.z * speed * delta
---->if not self.test_move(self.transform, motion)
----> ---->self.position += motion
----> ----> apply_floor_snap()
else:
---->velocity.x = direction.x * speed
----velocity.z = direction.z * speed
And up the stairs you go. self.test_move will take a vector and test to see if you can move to that spot. It returns true if something is in the way, so add some height to the current vector and test again. If it's now clear (false) then we encountered a step (motion + 0.5 added on y axis check) and so we just teleport to that spot.
I did this in C# originally so apologies if there are mistakes in the GDScript code. Also not, it works for the most part but you'll need to fine tune it a bit as I found myself getting stuck occasionally and had to reverse and try again. No clue why but I'll play with it some more in future. This was just a rough first draft as these things go.
Okay, using smaller speed values seems to fix the getting stuck issue. A simple direction.z * speed /2* delta fixed it. It makes sense as smaller stairs will need smaller movement.
Yeah I may end up going with something similar to this approach. Separation rays have some problems, can be glitchy, and the solution ends up even more convoluted needing 3 and to rotate them. I think eventually what I want to do is body_test_motion for up the stairs as well, and see if I can tune it to apply just the right amount of velocity before a move so you smoothly glide up the stairs. That's currently what I want to try. And I'll need to test it a good bit for edge cases. Have tried a few approaches and each have their problems... I also tried a 'soft' separation ray that pushed the character up by some velocity but that was pretty clunky feeling going up the stairs. Who knew stairs were so hard to solve. lol
Works like a charm, thanks
Holy crap this is exactly what i need!
Neat implementation, I'm doing something very similar to this, although like you said it doesn't cover some of the edge cases or it can be wonky at times.
Yep, I think even in a lot of AAA games we run into edge cases. It's hard to make a 100% perfect character controller. I think this will work fine for most, I'm sure it could be polished and improved a bit more if you want to hand tune it to your levels/use case.
if this is in godot 4.x is there any reason as to why you wouldn't just use a SeparationRayShape as an additional shape for the CharacterBody's CollisionShape? I think this might be a better solution than attempting to make a custom one for the stairs. it works going up and down as long as there is a value for floor_snap_length.
I do use the SeparationRayShape for walking up the stairs here, I use 3 of them which rotate towards the movement direction to ensure walking up stairs at odd angles works as it only worked running head on before. I found you also need to disable the SeparationRayShapes when running up surfaces of a great angle than floor_max_angle as the rays make the CharacterBody3D no longer respect that. Just using apply_floor_snap may work to replace the PhysicsServer3D.body_test_motion code in _snap_down_to_stairs_check, that's a good point. I set floor_snap_length to 0.5 and it appears to work. Good find!
After testing this a bit, one caveat I realized of using the increased floor_snap_length method is that it makes the vertical movement down the stairs noticeably less smooth. My current implementation calls apply_floor_snap after moving the body to the travel y given by the body_test_motion call, but apply_floor_snap only succeeds and moves the body about half the time. This is because much of the time the player is snapped down to the corner of the stairs which is not considered a floor as the stair corner is the first collision the CharacterBody3D makes in body_test_motion. When setting the floor_snap_length to 0.5, that only succeeds about half the time as well, because it won't move the body down when it would be clipping into the corner of the stairs. This results in a larger jump in y value when it is called, which from my testing looks to be about every other frame.
oh interesting I'll try some digging as well to see if there is a way around this case
Thank you so so much for making godot tutorials like this, I managed to make a player controller prototype work only because I found this video.
If anyone is interested, I am using a different method to go upstairs, similar to the source engine, I measure a step's height using a small shapecast by my feet, and the step's depth with a taller, but shallow box shapecast that checks if there's enough space to walk up.
my issue was figuring out how to tell the player where to head upstairs, I didn't want to make 8 raycasts in different directions hoping one would hit a stair and then figure out how to move them towards that direction, so I employed the "feet" shapecast and made it move towards the input direction by modifying this code slightly, I removed the velocity check and swapped it out for an input check using direction.normalized() which I get from the player's inputs.
@onready var _stairsup_cast_dist = abs(shape_shoes.position.z)
func _rotate_stairsup_cast():
var input_dir = direction.normalized()
var xz_castpos = input_dir * _stairsup_cast_dist
shape_shoes.global_position.x = self.global_position.x + xz_castpos.x
shape_shoes.global_position.z = self.global_position.z + xz_castpos.z
I only changed the code because I had a specific issue: when I was walking upstairs, I would eventually hit vel 0 from the steps slowing me down gradually, and when my vel was 0, then the shapecast would stop reacting to input, controling it using input only solved my problem and works fine for my small game!
Thank you again Majikayo!!!
Going down the stairs isn't working for me. I did your code the same way as you. My speed is 5.0. I think you said in a comment that yours was also 5.0. I'm using stairs that were created from the Cyclops World Builder addon. I haven't implemented the code for going UP but so far the code for going DOWN doesn't do anything. I still just fly off whatever stair I'm on.
Edit: Okay so the raycast wasn't registering because it was too high. It wasn't even coming out the bottom of the character. But I fixed it and now it works. Thanks!
Very nice and simple. The only challenge now is to do vertical camera smoothing.
Yess, trying to find a solution to this rn
one of the best tutorials ive ever used 😋
In the Hammer editor, we used to make a slope and then have no clip stair triangles along the slope. You glide up and don't fly down. No code was necessary.
kinda annoying to make when you have a large map
@@mr_sauce_cooks unless you use a stair scene with slope-collision attached to the piece that is reused throughout the map as a module.
How do you create the stairs mesh? Do I have to make it in blender and then export?
Congrats on your first video! It was informative and useful!
You really gotta get that lip smacking under control (lol) but otherwise I thought it was well done!
My current CharacterBody3D won't walk up ANY steps, no matter the height. Thug life, I guess.
thanks for the comment glad you liked it. still figuring out the audio quality. i tried to make this implementation pretty simple, condensed into 2 pretty (i hope) clear functions and some and some raycasts/separation rays on the character body.
i an trying to make the source character controller in unity, using a box instead of a capsule for collision, its both easier and harder, rn im stuck on making the player walk up steps not teleport up them right at the edge. for like 2-3 weeks cuz its a sideproject
im working on a video for a source like controller in godot now. trying to release it in the next couple weeks. for walking up stairs, i recommend teleporting + camera/animation smoothing. it is difficult enough to get simple teleporting to work fine without glitchiness. making them actually glide up is too much i think.
@@MajikayoGames im using the tf2 source code leak as a guide to things, source is weird af, i still have to test some things in game to figure out when you go up the step,
@@nocoreal My new solution is to run a single body test motion directly down starting slightly in front and above (calculate new character XZ/horizontal position based on velocity, add max step height + some small clearance amount). If it hits at the feet, and less than step height, I teleport. This is the best I've found so far.
@@MajikayoGames i am finding the step height by boxcasting top to bottom then moving ypu up it (still got to figure this out), but in source you bounding box gets projected forwards and up 16 pixels and do which one returns true
Great tutorial, I only need the going up steps and it works well, as well as diagonals! My problem is with the floor angle adherence part. I followed your tutorial 1:1 minus var names. I tried a bunch of "print tests" and it says the raycasts never collide when I'm on a slope (sometimes even when I'm not), and collision normal dot products never stop being 1 (which is higher than my "max_slope_ang_dot")
Maybe my Godot or project is broken or something, regardless, very frustrating, and I'm not sure you'd have a fix but ah well
A few thoughts that could maybe help:
-The code for the dot product is maybe a little confusing. The way a dot product works, as long as the two vectors are .normalized(), it acts as kind of a 'percent' of how much they are facing in the same direction. 1 is exact same direction. 0 is right angle/perpendicular, -1 is exactly opposite. We get the 'percent' in same direction of max floor angle to up, and compare that with the 'percent' of the collision normal. If it's a lower, number, we know the collision surface is steeper (further away from up) than the max floor angle allows.
-Is it on all floor types? I noticed some glitchiness with my terrain but not my angled boxes test. It's possible you found some bug.
-Are your ray casts pointing down not up? Your code might be correct but if there's some slight oddity in the editor it could be hard to see. I recall the direction the rays were points was not super clear in the editor view.
-As a last ditch effort, you could download my project, run it, and incrementally keep making changes till it looks like your scene/bringing in things from your own project to debug the problem.
-If you downloaded and used my controller from the github and still found a case where it's broken I could take a look later today when I have time. You can fork my repo and make an issue
@@MajikayoGames First of all, your explanation of dot() here was really good and I feel I have a good grasp on it now. Although it's confusing why it's less than < when the dot product of the normal is closer to 1 (steeper) and the max angle is closer to 0.7 and that determines it's too steep, programming is weird but whatever.
I downloaded your project, changed code until it was like mine. It worked. I imported my player scene, and IT WORKED. It seems the world I use is messed up in a way I wouldn't know. My guess is that something broke when I updated the world to godot4 from 3 (I was too lazy to make a new project). Thank you for helping and for the good guide!
@@Jx_- glad i could help. regarding why less than the dot product is steeper, it's because a normal vector is a vector pointing directly out of a surface. so for a flat ground we are walking on, all the normal vectors would be pointing up directly towards the sky. you can imagine a steep hill the normals would be pointing out kind of at a 45 degree angle, not straight up. and a cliff would have a normal pointing totally horizontal at that point. the ceiling's normals are arrows/vectors pointing towards the ground.
we .dot the collision surface normal (hill, cliff, etc) with the 'directly up' normal, which is essentially going to be the normal of the totally ideal case of the flat ground. so we have the 'percent closeness of collision surface to up direction.' if it's too far away (closer to -1) then it's too steep.
we can find the exact number which is too steep by doing a dot product with a normal pointed toward 'floor_max_angle' with the ideal flat ground up vector. so now we have both of the 'percents' away from up. compare the collision surface normal 'percent away from up' with the floor max angle as a normal 'percent away from up.' if it's less, then it's further away from up, a.k.a. too steep. hopefully that makes sense.
if you run into more issues you could upload the zip somewhere and make an issue on the github page. if you happened to find cases where my controller is broken id be interested to see, so i can fix it for my game.
What is your player speed? I think mine are too fast causing the ray cast to not detect ground in time and the player flys off the stairs with too much momentum.
Yes this is possible. Mine is 5*delta per _phys_process. The way the snap down works is if they were on the floor last frame but not this frame, it will cast a ray down to look for the stairs. However, if they moved multiple steps across in 1 frame they might not snap down to stairs.
@@MajikayoGames Thanks, I'll test it out using your speed.
There must be something wrong with my code or maybe a setting on my Raycasts or something. I'm still able to go up steep slopes like they're floors. Here is my code for that. Is there something wrong? Also I named my nodes a bit different.
# Slope Restriction
var max_slope_ang_dot = Vector3(0,1,0).rotated(Vector3(1.0,0,0), self.floor_max_angle).dot(Vector3(0,1,0))
var any_too_steep = false
if $StairUpCheck_F/RayCast3D.is_colliding() and $StairUpCheck_F/RayCast3D.get_collision_normal().dot(Vector3(0,1,0)) < max_slope_ang_dot:
any_too_steep = true
if $StairUpCheck_L/RayCast3D.is_colliding() and $StairUpCheck_L/RayCast3D.get_collision_normal().dot(Vector3(0,1,0)) < max_slope_ang_dot:
any_too_steep = true
if $StairUpCheck_R/RayCast3D.is_colliding() and $StairUpCheck_R/RayCast3D.get_collision_normal().dot(Vector3(0,1,0)) < max_slope_ang_dot:
any_too_steep = true
$StairUpCheck_F.disabled = any_too_steep
$StairUpCheck_L.disabled = any_too_steep
$StairUpCheck_R.disabled = any_too_steep
It looks like you are disabling the check rays themselves, rather than the separation ray shapes. github.com/majikayogames/godot-character-controller-stairs/blob/e067dd13fa37072cf4836639505db170c49733da/entities/Player/Player.gd#L105
The idea is to have a ray at exactly the same length/direction as the collision shape, or maybe slightly longer just incase, and then disable the separation ray (collision) shape before it lets us walk up walls. Also, are you sure about your raycast length and direction? It is easy to accidentally set them the wrong direction, like pointing up rather than down, it looks nearly the same in editor.
If all else, fails, you can copy the controller from my project. Or copy portions of the character controller nodes/code till it works.
@@MajikayoGames The nodes I've named StairUpCheck is the same as your StairUpSeparationRay. I just named them different.
@@PastaMaster115 I see. Well then the code looks fine from what I can tell. Here are a few more ideas you can try:
-ensure calling force_raycast_update
-ensure self.floor_max_angle is set on the CharacterBody3D
-ensure node structure and properties are exactly correct. many things could go wrong here.
-like I said, you could just move my character controller from the github into your project, and copy my nodes from the character controller my project, and rename it to your node names.
-add some debug statements to try to sort out what is going on. maybe print out max_slope_ang_dot, as well as the collision normal dot results somewhere
Good luck. Hope this helps.
There are no default stairs mesh in Godot 4.
In the utils folder of the github there is a utility class for creating stairs which you can use in your project:
github.com/majikayogames/godot-character-controller-stairs
@@MajikayoGames No copyright public domain license and free right?
@@boom_fish_blocky yep the stairs mesh maker is. but the FPS character controller uses LegionGames so that might have some MIT license on it
What is your single stair's step size? I think my steps are too big so the code could not work.
Approximately 0.4 high in the example scene but the same idea should work if you increase the maximum step size in the code as well as the rays/separation ray colliders on the controller
Hey, any way to prevent the step up from walking up certain collision layers? I am trying to make it so it wont step up RigidBody 3Ds
Because layer/mask for what to collide with can't be set individually on the ray shapes, you'd have to do it programmatically. I would add some additional checks in the _rotate_step_up_separation_ray function. Similar to how we disable the step-up ray colliders when the raycasts hit on a collision normal that is too steep, you could also disable them if it hits any non static body. Something like:
var any_too_steep_or_rigidbody = false
if $StepUpSeparationRay_F/RayCast3D.is_colliding() and ($StepUpSeparationRay_F/RayCast3D.get_collision_normal().dot(Vector3(0,1,0)) < max_slope_ang_dot or $StepUpSeparationRay_F/RayCast3D.get_collider() is RigidBody3D):
any_too_steep_or_rigidbody = true
.... all the rest similarly. haven't tested but this is probably what I would try.
i actually know a better solution, well, more sourse-games-like. you use body_test_motion to ckeck if in the next frame there will be a collision, and you test if this colission is a flat, 90 degrees plane. if so, and if this plane isn't higher then, say, 0.25 hight, you move the player up by that amount. it makes a solid yet too sharp stairs collision.
yeah i was thinking about this solution for walking up stairs as well. might test it eventually. i was thinking it would be a bit complicated, probably a few body_test_motion calls per stair step, there might also be issues as to whether the body is actually on the ground or not the frame after you move them if they're positioned on the corner/lip of the stair. i think you would still need to shoot rays directly down in front to check if the surface is flat too, because pretty sure the collision normal could be horizontal in some cases, like when it collides with the corner or front of a stair. in the end i decided to go with this with some plan to implement camera smoothing. another solution i thought of, is just have a raycast in front, detect when it hits a stair, and apply a tiny jump/up velocity just enough to get the player up the next stair and still consider them on the floor. i think another commenter mentioned a similar approach to walking down the stairs and said it worked well for smooth motion, apply a down velocity rather than snapping them fully.
@@MajikayoGames i wouldn't really check if there is a ground after the step, why would you need to check this? Even if player is staying at the edge somewhere and to go forward, they sure can. Ofc youll need to do about 2 body checks(if there is a step next frame and if ceiling isnt too low when you're on a step. Otherwise i can't say anything more is necessary.
@@the-guy-beyond-the-socket without checking if there is ground, it will indeed work, but i believe it will have the same problem as the separation rays in the video. since the player is teleported there, we do not check if the surface is steeper than max_floor_angle, allowing us to walk up very steep cliffs. you can see the problem at 20:00 in the video.
@@the-guy-beyond-the-socket oh i misread that comment. rather, the reason you need to check if there is ground with a ray, is that body_test_motion may hit the side of a stair, where the collision normal would be 90 degrees sideways. this would then cause not being up to step up that stair. whereas with a ray cast directly down a little in front of the player, it will not hit the front/side of the stair step.
@@MajikayoGames makes sence but i would allow to go side stairs, if player is walking into a stair even by some side, they want to go uo that stair. and a situation where player is absolutelly parralel to a stair would still give sane results. I maybe need to do the demo to show what i mean.
tube-ception :O
Ty so much
For some reason, no matter what I can only get up 0.3 height steps >:(
Make sure you increase the length of both the separation ray and raycast3Ds, and their height off the ground. They should both be pointing at the ground, with their tips exactly aligned with the character's hitbox. The character will only be able to step up a step equal to or maybe slightly smaller than each ray's length.
@@MajikayoGames Hello :) Thank you for your reply. I have changed the height of the seperationray and raycast, I'm wondering whether the issue may be caused by the fact that I'm scaling the CharacterBody3D that they're held in.
@@theshiftybloke4672 Ah that could definitely be it. I think Godot has some undefined behavior with non uniform scaling on collision objects (if you scale x/y/z by different values). Not sure about uniform scaling. Also not sure how the rays get scaled/if it all. Would definitely try to see if it works as expected if not scaled to confirm that's not the problem.
@@MajikayoGames I've just scaled my stairs down to 0.25 rather than my character to 4 and can confirm that it works. Will have to see what I can do with my character model :) Thanks
👍🏽👍🏽👍🏽
🙏
Video is great but the thumbnail is realllyyy weird dude, the ai art just looks weird
lol yea it does look pretty weird. i just put this up as a test and made that in a few seconds. didn't expect it to get many views.
Check the ground normal
assuming you're talking about the buggy is_on_floor() when walking on terrain, I think it's just the ConcavePolygon shape is very error prone, I've had issues with it in a couple projects. the normals look fine in blender, maybe it's possible on import to godot some may have broke. but it also happens with backface collisions on. i did give it a try with the godot jolt physics engine instead of the default and it doesn't happen at all, even with backface collisions off. so i think it may just be a bug in godot.
@@MajikayoGames or 3 raycast that return position then cross product, that's what some game did pre generalized third party physics engine, for example Mario 64
this is good. I changed the floor angle when controller hits stairs
But in future it will create poblem for me