Jump to content
  • Advertisement
Sign in to follow this  
wcassella

Simplest 3D physical character controller solution

This topic is 521 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

 

I'm working on a game built on my own engine for my senior thesis in college. Right now I'm having some trouble with physics integration. Currently I'm using Bullet, which I've had great success with in the past for things like rigid body simulation and collision detection, but now I'm trying to use the built in character controller (btKinematicCharacterController), and it's... not great.

 

The problems:

 

I'm getting strange behavior where the movement will skip slightly every few frames. It looks reeaally bad in first-person, and I'd like to get rid of it as soon as possible. I've done some tests, and I'm sure at this point that it's nothing to do with my rendering code, or anything that isn't controlled by Bullet.

 

I've also noticed that the way it detects if you're on the ground doesn't seem to be very intelligent, so if you hit your head on something mid-jump, it allows you to jump again.

 

Also, it says in the documentation that users of the API are supposed to implement interaction with rigid bodies themselves, but when I tested it it seems that interaction already works (you can push objects around, and they can push you). Problem is, I can't figure out how to control it.

 

I'm not sure about the rest of Bullet, but the brief time I spent poking through the character controller source kinda killed the confidence I had in the quality of the code. There's tons of stuff that's just commented or ifdef'd out, and the parameter to btKinematicCharacterController::jump has a default value that leaves it uninitialized, which causes you to fly off into space unless you supply the argument yourself.

 

What I need:

 

The game I'm trying to make is a fairly simple first-person puzzle game. All I really need from the physics engine is solid collision detection, and controlled interaction with certain objects (basically just pushing blocks around). I don't have a lot of time to focus on physics (I'm already worried about finishing what I have left to do in the time I have, even if this were already fixed). I've considered switching to PhysX, but I'd rather keep the game source code as easy to redistribute as possible, and integrating PhysX is just more time I spend not working on other things. Does anyone know any good tricks to beat the bullet kinematic character controller into shape, or a good implementation I can use in it's place? Is it worth switching to a different physics engine?

 

Thanks

Edited by Easy Rider

Share this post


Link to post
Share on other sites
Advertisement

Yeah, the btKinematicCharacterController is a little flaky. I have had moderate success using this (http://dev-room.blogspot.fi/2015/03/some-example-works-like.html) model as a base, however I have never used it for a first person game so I don't know how well it works in that setting. The "3 step" controller detailed in the linked article is used (although heavily modified, actually mostly rewritten) in this prototype: https://www.gamedev.net/topic/684831-jailbreak-prototype-released/

Share this post


Link to post
Share on other sites

Not sure how much use it will be to you but here is my version:

 

http://www.sourcedrive.net/BasisCharacterController3Step.h

http://www.sourcedrive.net/BasisCharacterController3Step.cpp

 

There is a bit of engine specific code in there but you should be able to figure out how it works pretty easily. One thing you might want to know is that I don't actually rotate the physical part of the characters at all, only the visuals. That's why I set the angular factor to zero. I am not sure if that is what you want or not, but the model should work equally well if you want to rotate the capsule of the character as the player rotates around the up-axis. My character controller is controlled through the setMovementXZ() and jump() methods. That's all I have needed for now :)

 

I am quite interested in making better Bullet-driven characters in the future, let me know how it works out!

Share this post


Link to post
Share on other sites
I'm going to have to think about releasing mine. Trouble is it is all implemented in my high level wrapper of Bullet so would need to break it out.

Let me have a think about when I might have time. Mine works well for a particular subset of requirements. My journal has some details in the mists of time passed.

Share this post


Link to post
Share on other sites

After a few uses of the built-in character controllers in both Bullet and PhysX, I've always defaulted to rolling my own.

 

A capsule collider with downwards ray/sphere cast and a virtual spring works pretty well for basic movement and ground detection. Cast another ray/sphere upwards to catch ceiling collisions force crouching appropriately.

 

Extend as necessary for wall runs, grinds, and other parkour staples. It's not as hard as it might appear on first blush...

Share this post


Link to post
Share on other sites
I agree with swiftcoder, it wasn't quite as hard as I expected although there were a few details that took a lot of messing to get right.

The basic idea I use is to use Bullet's GJK on its own, with a btCapsule shape tested against the shapes and transforms of the level geometry. It is quite easy to get a floating capsule without any gravity working and sliding against walls and floors like this by just applying the minimum separation vector to the capsule position in the event of a collision. I found I needed to loop a few times over this process to get the capsule to settle in the correct position when in corners and so on.

Vec3 separatingVector(Physics &physics, const Shape &shape, const Vec3 &at)
{
    Vec3 result = at;
    Matrix to = translationMatrix(result);

    BroadphaseResult br = physics.broadphaseAabb(shape.aabb(to).expanded(2));

    if(br.valid())
    {
        int it = 0;
        bool loop = true;

        while(it < 5 && loop)
        {
            loop = false;

            for(auto &b: br.bodies)
            {
                ConvexResult r = physics.convexIntersection(shape, to, b->shape(), b->transform());

                if(r.valid())
                {
                    result += r.separatingVector();
                    to = translationMatrix(result);

                    loop = true;
                }
            }

            ++it;
        }
    }

    return result - at;
}
For ground walking, I fire a ray down from the base of the capsule to measure the distance to the floor and then, if within a certain tolerance of the floor, lock the capsule to a fixed height above the floor and stop applying gravity until the ray test reports we are in the air again.

For extra credit, you can work out what the minimum distance to the floor based on the normal of the floor is (the more sloped the floor, the larger this distance based on the spherical shape of the base of a capsule).

float sphericalDistanceToNormal(float r, const Vec3 &n)
{
    float d = dotVectors(Vec3(0, 1, 0), n);

    return d ? r / d : r;
}

float minFloorDistance(float radius, const Vec3 &normal, float margin)
{
    return sphericalDistanceToNormal(radius, normal) + (margin * 2);
}
Call minFloorDistance with the radius of the base of the capsule and the normal of the floor the ray has found and it returns the correct distance to "lock" the capsule to above that slope.

The KCC's main step method looks like this (all wrapped in my own wrapper for Bullet, sorry):

void Kcc::implementMove(Vec3 &position, bool &grounded, Physics &physics, MoveFlags flags, const Vec3 &step) const
{
    bool flying = step.y > 0;
    float hh = shape.height() / 2;

    RayResult floor = findFloor(physics, shape.radius(), position + step + Vec3(0, -(hh - shape.radius()), 0));

    Vec3 dv(step.x, 0, step.z);
    Vec3 mv = floor.valid() && !flying ? alignToFloor(dv, floor.normal()) : dv;

    mv.y = step.y;

    Vec3 sep = separatingVector(physics, shape, position + mv);

    if(!flying)
    {
        mv += slopeCorrection(sep);
    }

    if(flags & StayOnEdges)
    {
        sep += edgeQuery(floor);
    }

    position += mv + sep;

    grounded = false;
    if(step.y <= 0)
    {
        grounded = lockToFloor(physics, position, shape.radius(), shape.height());
    }
}
Hope this might provide a little inspiration. An obvious improvement I'll get round to one day would be to use swept collision tests to get continuous collision, but as long as the character doesn't move too fast, using discrete checking works fine for my purposes.

Share this post


Link to post
Share on other sites

These are all really helpful, thanks! I've been busy with rendering code recently, but now I've got a chance to try these out.

Share this post


Link to post
Share on other sites
Use raycasting with slope limiting and recursive pushback. Shouldn't need to use physics for the player unless it's a requirement for your project.

Share this post


Link to post
Share on other sites

Hi there, I've seen some professional character controllers that work like this writeup I did here a while ago: https://forums.tigsource.com/index.php?topic=51779.msg1203653#msg1203653. This is to resolve collisions on the position level without the use of forces or accelerations. The rest of the character controller would include custom logic for things like sliding on walls, jumping, is_touching_ground, etc. Just note that the best shape for general purpose character controllers is likely to be a 3D capsule that cannot freely rotate (only translates). Capsules yield efficient test routines and the rounded edges help to avoid catching hard edges like a box-based character controller would suffer from.

 

Using a GJK-based approach can work pretty well, but I would also recommend sinking some time into fallback routines that handle the case of penetrating shapes as sometimes this is unavoidable; for example something spawns on the player, player squeezing into a narrow corridor making a "ping-pong" effect that the Seidel solver has trouble with, two players colliding into each other, and the list goes on.

Edited by Randy Gaul

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!