Page 1 of 1

[SOLVED] Dynamic Character Controller doubt

Posted: Sat Jun 27, 2020 8:14 pm
by rfunes
Hello,

I am trying to use a dynamic (non-kinematic) character controller for my game, using the capsule shape as the collision shape.

It is working great, except for one case that I am not able to find a good solution so far.

When standing a tiny bit over an edge, the contact point between the ground and the bottom of the capsule shape is no longer perfectly centered in the middle of the capsule shape. In this scenario, if I induce a rotation (either by adding torque or adding angular speed), the capsule not only rotates, but also translates laterally.

I understand that this is the correct physics behavior, but I need to override it, because as much as it is dynamically correct (given the capsule shape and how friction/center of mass works), it is terrible for the character control.

Right now I am casting rays to check if the character is on an edge, and then reposition the rigid body on the desired position, instead of letting the physics control the rotation. This way I can force the capsule shape to "stay put" on the same place and avoid the unwanted translation.

This still feels wrong, as it induces a different behavior when rotating while walking a little bit past an edge, as the repositioning of the rigid body cancels any other movement components that were also applying forces to the body.

For example, when the character is walking on a flat surface, and I induce a rotation, the character naturally keeps moving forward while at the same time rotating. This is the correct behavior that feels very natural when playing. But when walking close (or a tiny bit past) an edge, due to this "solution" I implemented, for a tiny moment the character stops moving forward while it turns on the same spot, due to the fact it is being reinstantiated.

I wonder if there is any other solution for this problem, other than using a kinematic character controller? I tried reducing the friction, but it induces other major problems caused by too low friction.

I am sure this should be a common problem for dynamic character controllers, so I hope that someone already faced this before and a better solution exists already... I really want to try keeping using a rigid body, as for everything else it is working great.

In the hope of being a little more clear, here follows a poorly drawn MS Paint image to explain my problem.
Problem.png
Problem.png (42.93 KiB) Viewed 4513 times
Thanks !

Re: Dynamic Character Controller doubt

Posted: Sat Jun 27, 2020 9:04 pm
by drleviathan
A solution to this problem I have used successfully is to not allow non-zero angular velocity of the character and just slam its rotation. In other words, in CharacterController::updateAction() do something like this:

Code: Select all

btTransform transform = body->getWorldTransform();
transform.setRotation(desiredRotation);
body->setWorldTransform(transform);
body->setAngularVelocity(btVector3(0.0));
Note, that will prevent the character from being rotated (or being prevented from rotating) by collisions with other objects and will also prevent the it from rolling/shifting other dynamic objects from its turning motion. If your gameplay doesn't require such interactions then the solution might work for you.

More advice (perhaps you know this but just in case): if you use the current rotation to compute it the new desiredRotation then you must normalize desiredRotation before you apply it. Else floating point error will accumulate, the transform will go unstable, and you'll introduce NaN into the physics simulation.

Re: Dynamic Character Controller doubt

Posted: Sat Jun 27, 2020 11:20 pm
by rfunes
So if I understood how your solution works, it gets the object's transform (the current position), then apply the desired rotation, then move the object back to the original transform, therefore nullifying any unwanted translations that would have been caused by the physics simulation?

Quick doubt, given that this is how the solution works, why do we need to zero the angular velocity after setting the world transform?

I implemented your proposed solution and it looked like this (I am using LibGDX):

Code: Select all

if (obj.isOnEdge()) {
    // New code as suggested by drleviathan
    Matrix4 transform = obj.getInstance().body.getWorldTransform();  // LibGDX uses Matrix4 instead of btTransform, but it is the same thing
    transform.rotate(new Vector3(Vector3.Y), turnDirection * turnSpeed); // This is the equivalent LibGDX method
    obj.getInstance().body.setWorldTransform(transform);
    obj.getInstance().body.setAngularVelocity(new Vector3(0f, 0f, 0f));
} else {
    // On a flat surface when the bottom of the capsule shape makes contact with the ground, I can simply set the angular velocity and it works great
    obj.getInstance().body.setAngularVelocity(new Vector3(0f, turnDirection * turnSpeed, 0f));
}
I would like to test a little bit more before I mark this as "solved", but the character seems to be handling in a very natural way !
Thanks !

Re: Dynamic Character Controller doubt

Posted: Sun Jun 28, 2020 9:59 pm
by drleviathan
The solution gets the current transform (position + rotation), overwrites the rotation part, and slams the body's transform to be that of the modified value. The end result is: the body's position doesn't change, but its rotation does.

You set the angular velocity to be zero because... it shouldn't be spinning because... it is rotated exactly how you want it. To spin it would be to rotate it away from the target. Put another way: the angular velocity of the character is meaningless since it is always pointing exactly where you want it, and when a value is meaningless you might as well set it to zero.

Finally, a non-zero angular velocity might introduce artifacts when the character stands on dynamic objects and spins around: it might "push/roll" such dynamic platforms, causing them to move when they shouldn't.

Re: Dynamic Character Controller doubt

Posted: Mon Jun 29, 2020 2:26 am
by rfunes
Thanks, makes complete sense. Your solution works perfectly and I am using it to handle all rotations, even when on a flat ground surface, since there is no need to use the other one (setAngularVelocity) in my case.

In the end what I have now is that for forward/backward/jump movement, I am applying forces, but for rotation I am using your solution.

So it became a hybrid solution, where the rotation is implemented in a similar way to what a kinematic controller would use, and all other movements are using the dynamic rigid body physics.

Reviewing my old solution, my error was that I was applying the current position to the translation (setToTranslation) and only then applying the rotation. Of course when I set the current position to the translation, the character would stop where it was when I induced a rotation, which was not good to control when walking forward while trying to rotate to make a turn, for example..