Introduction to how physic engines work
The way most physics engines out there work including GodotPhysics and Bullet is like this※:
- Move the objects first.
- Then check if they are breaking some constraint (including collisions and joints).
- Then move the objects so they are not breaking the constraints.
Since moving the objects so they are not breaking the constraints they were breaking might result in breaking other constraints, in practice the second and third steps are done multiple times. With a hard cap in how many times it will repeat them, as to avoid and infinite loop. If the hard cap is exceded, it might result in objects stuck inside of each other. In Godot you can configure the hard cap, search for "solver iterations" in Project Setting.
You, of course, do not see the intermediary steps of this process.
You can also imagine that if the character were moving fast enough, it would move to the other side of the terrain in a single step and never collide with it. That phenomenon is known tunneling.
And yes, this is virtually every physics engine, not just Godot's one.
※: Barring continuous collision detection (which is only available for
2D) in Godot, btw).
How does the physic engine end up pushing the character down the terrain
As you know, we want the player character to be moving downwards according to gravity, so it collides with the ground. Now think about how that happens...
- First the player character moves down, so it is clipping the terrain.
- Then Godot sees that it is colliding.
- So Godot pushes the player character outside of the terrain.
And now you can imagine how this goes wrong: It pushes the player character out the wrong direction.
Why would it do that?
While I can argue that modern physics engines have all they need to figure out from where the objects were moving before they collided and push them out in that direction... In practice many physics engine push the objects the way that it is closer. Because this requires less computations, and thus it means they can handle more collisions in the same time.
I don't know if in Godot having multiple colliders in the character would make it more likely to get pushed the wrong direction.
You might try switching between GodotPhysics and Bullet to see which handles this situation better.
Having thin terrain makes this more likely. Because your character would be partially on both sides of the terrain when it is colliding with it.
Regardless how unlikely it might be, it only has to go wrong once for the character to clip through the terrain, so we want the chance to be zero.
Solution with extra colliders
Duplicating the collider gives you a second chance that the player character will be pushed the correct way. And, assuming you place the duplicated collider slightly lower, it has even better chances.
If you want to fix this with with extra colliders, consider using a
WorldBoundaryShape3D which is often used for infinite impassable walls for the boundary of your game world. The
WorldBoundaryShape3D acts as if it had infinite thickness, it will only ever push in the same direction.
Solution with terrain thickness
Another solution is to give thickness to the terrain. And this is also the solution for clipping through other things where you can't afford a
WorldBoundaryShape3D (e.g. walls).
Any thickness would minimize the problem simply because when clipping the amount of the characters would sticks out the other side would be less than the same situation with a thin collider... But tunneling and almost tunneling (moving mostly to the other side and getting pushed that way) might still be possible at high enough speeds.
So, to completely fix this using thickness, you want:
- Set a terminal speed (not just for falling, for everything). So you limit the velocity magnitud at which your character moves to that terminal speed.
- Compute which distance the player character would move in a single physics step at that terminal speed.
- Make the thickness of any physical obstacle greater than that distance. My advice is to use the double of the distance as minimum thickness. Although it should not be necessary assuming the character size is significantly greater than the computed distance. I rather not have to think about it.
So, how much time is a physics step? First of all, you have how many physics frame per second happen (which is in Project Settings under "physics/common/physics_ticks_per_second") and second you have how many physics steps it will do per frame (which is in Project Settings under "physics/common/max_physics_steps_per_frame").
By default, we would have that each physics frame is
1/60 of a second. And each frame has 8 steps, so each step is
1/8 of that. So each step is a
1/(60*8) of a second that is
1/480 of a second or
And I'm advising to double it, following that advice we can assume it is the double of the time, so
0.004166666666 s... And let us round that up to
0.005 s (which is fine since we are defining a minimum threshold).
So you take your terminal speed multiply it by
0.005 s and you have your minimum thickness.
If you changed the defaults so physics steps are longer, then you need to recalculate because this thickness might no longer be safe. But if you made the physics steps shorter, then the minimum thickness would be lower, meaning that whatever thickness you were using is till fine.
For example, for a terminal velocity of
14 m/s the minimum thickness is
0.07 m, and for a more realistic terminal velocity of
60 m/s it is
I also want to note that there is another reason to have a minimum thickness to things - not just terrain - and that is light leaking. And that might be larger than what you need for physics.
About the seam between colliders
By the way, in the seam where colliders meet, you might have the situation where pushing the character out of one collider pushes it into the other... And that ultimately might result in pushing the character the wrong way.
You would solve that by having the colliders overlap... Which you can accomplish by making the colliders larger, or by adding extra colliders in the seams.
However, this problem is less common in Godot and other engines where collision shapes have a non-zero extra margin, because that makes it so that aligning the meshes result in colliders slightly overlap in practice.