Physics character controller

I’m happy to annouce the availability of the Physics Character Controller.
It provides a simple api to control and extent a character in a physicalized world.

A documented PG is available here : https://playground.babylonjs.com/#FMQX86#1

Documentation page is here : Babylon.js docs

As usual, this feature is driven by Babylon.js community. Feel free to discuss, comment and contribute on this new feature.

Have fun !

41 Likes

Wow! Thank you so much, I was waiting this for a while, good job!

2 Likes

It’s a great addition. THANKS

2 Likes

@Cedric you are the best !!!

1 Like

Amazing! :heart_eyes_cat: Thank you!

This is awesome! Thank you, Cedric :grin:

Niiiiice :clap: Thanks a lot !

Great work! Just looking at the controller part: does this want to have it’s own class/separate it because I’m understanding this code actually as a “PhysicsCharacter”. As it could be controlled by a Player or AI right? Plus I couldn’t see any controller related code (I’m using my phone). The Controller part I usually define as the interface for the Player or AI.

This new class controls and constraints a physics body. Input can be human or AI. Human inputs are handled in the PG.

1 Like

That’s great

Wish I could +1 this a dozen times, this is awesome!

1 Like

Looks amazing! Can’t wait to play around with it

Hi, could you please modify the example to include rotation functionality? Specifically, I’d like the player to rotate when pressing A/D keys, always face the forward direction, and also rotate while jumping. Thank you!

Great addition, really appreciate it. Will use this as the base for my own character controller!

1 Like

Hey @Cedric … Can you please explain a bit about how you use the character controller. It seems a bit different than how you would use btKinematicCharacterController for example.

getDesiredVelocity … That seems to be doing alot of stuff. Where as before you might just do a inputVector.normalized*speed and the that was your movement velocity you used. I think moveWithCollisions worked that way as well.

I am trying to replace my RigidBody based character controller i wrote that worked like the Ammo btKinematicCharacterController with the new BABYLON.PhysicsCharacterController underneath my ThirdPersonCharacterController, but I am having trouble understanding all this, and how i can use this in place of my Unity Style Character controller I was using with Ammo.js

 // State handling
        // depending on character state and support, set the new state
        var getNextState = function(supportInfo) {
            if (state == "IN_AIR") {
                if (supportInfo.supportedState == BABYLON.CharacterSupportedState.SUPPORTED) {
                    return "ON_GROUND";
                }
                return "IN_AIR";
            } else if (state == "ON_GROUND") {
                if (supportInfo.supportedState != BABYLON.CharacterSupportedState.SUPPORTED) {
                    return "IN_AIR";
                }

                if (wantJump) {
                    return "START_JUMP";
                }
                return "ON_GROUND";
            } else if (state == "START_JUMP") {
                return "IN_AIR";
            }
        }

        // From aiming direction and state, compute a desired velocity
        // That velocity depends on current state (in air, on ground, jumping, ...) and surface properties
        var getDesiredVelocity = function(deltaTime, supportInfo, characterOrientation, currentVelocity) {
            let nextState = getNextState(supportInfo);
            if (nextState != state) {
                state = nextState;
            }

            let upWorld = characterGravity.normalizeToNew();
            upWorld.scaleInPlace(-1.0);
            let forwardWorld = forwardLocalSpace.applyRotationQuaternion(characterOrientation);
            if (state == "IN_AIR") {
                let desiredVelocity = inputDirection.scale(inAirSpeed).applyRotationQuaternion(characterOrientation);
                let outputVelocity = characterController.calculateMovement(deltaTime, forwardWorld, upWorld, currentVelocity, BABYLON.Vector3.ZeroReadOnly, desiredVelocity, upWorld);
                // Restore to original vertical component
                outputVelocity.addInPlace(upWorld.scale(-outputVelocity.dot(upWorld)));
                outputVelocity.addInPlace(upWorld.scale(currentVelocity.dot(upWorld)));
                // Add gravity
                outputVelocity.addInPlace(characterGravity.scale(deltaTime));
                return outputVelocity;
            } else if (state == "ON_GROUND") {
                // Move character relative to the surface we're standing on
                // Correct input velocity to apply instantly any changes in the velocity of the standing surface and this way
                // avoid artifacts caused by filtering of the output velocity when standing on moving objects.
                let desiredVelocity = inputDirection.scale(onGroundSpeed).applyRotationQuaternion(characterOrientation);

                let outputVelocity = characterController.calculateMovement(deltaTime, forwardWorld, supportInfo.averageSurfaceNormal, currentVelocity, supportInfo.averageSurfaceVelocity, desiredVelocity, upWorld);
                // Horizontal projection
                {
                    outputVelocity.subtractInPlace(supportInfo.averageSurfaceVelocity);
                    let inv1k = 1e-3;
                    if (outputVelocity.dot(upWorld) > inv1k) {
                        let velLen = outputVelocity.length();
                        outputVelocity.normalizeFromLength(velLen);

                        // Get the desired length in the horizontal direction
                        let horizLen = velLen / supportInfo.averageSurfaceNormal.dot(upWorld);

                        // Re project the velocity onto the horizontal plane
                        let c = supportInfo.averageSurfaceNormal.cross(outputVelocity);
                        outputVelocity = c.cross(upWorld);
                        outputVelocity.scaleInPlace(horizLen);
                    }
                    outputVelocity.addInPlace(supportInfo.averageSurfaceVelocity);
                    return outputVelocity;
                }
            } else if (state == "START_JUMP") {
                let u = Math.sqrt(2 * characterGravity.length() * jumpHeight);
                let curRelVel = currentVelocity.dot(upWorld);
                return currentVelocity.add(upWorld.scale(u - curRelVel));
            }
            return Vector3.Zero();
        }

        // Display tick update: compute new camera position/target, update the capsule for the character display
        scene.onBeforeRenderObservable.add((scene) => {
            displayCapsule.position.copyFrom(characterController.getPosition());

            // camera following
            var cameraDirection = camera.getDirection(new BABYLON.Vector3(0,0,1));
            cameraDirection.y = 0;
            cameraDirection.normalize();
            camera.setTarget(BABYLON.Vector3.Lerp(camera.getTarget(), displayCapsule.position, 0.1));
            var dist = BABYLON.Vector3.Distance(camera.position, displayCapsule.position);
            const amount = (Math.min(dist - 6, 0) + Math.max(dist - 9, 0)) * 0.04;
            cameraDirection.scaleAndAddToRef(amount, camera.position);
            camera.position.y += (displayCapsule.position.y + 2 - camera.position.y) * 0.04;
        });

The new character controller gives a lot of control but user has to take care of state machine.
To replace the previous Ammo, I would not care about the state machine and just call calculateMovement with appropriate parameters.

Yo @Cedric

How do we set or change the current position of the physics based character.

In other character controller system, we had the teleport or someway of overriding and setting the position of the physics based character. I dont see an easy way to set the position beside the initial character position in the constructor.

Also… There is alot of easing and the character still keeps moving after you stop. Can we control how much easing so it does not keep moving so much? Its hard to sync the character animations with all that extra easing in the movement.

For the easing, you can use acceleration Babylon.js docs and maxAccelerationBabylon.js docs
Let me do some tests for the teleport. I don’t remember testing that. It might be missing :confused:

Some more accessors might be missing :slight_smile: Let me know if you’re thinking of one.

2 Likes

Hey @Cedric In the following playground: https://playground.babylonjs.com/#FMQX86#1

the character position is set to ground position with 0.3 height. That is the floor height. when you do anything that makes that character rise higher, like traversing stairs or even just jumping, it never goes back (gravity) to the floor height of 0.3. like the contact or internal raycast or what ever is not fulling grounding the player character. Sometimes you jump and land and the character is at position.y = 0.33 or 0.32 or 0.28… but almost never back to the ground position of 0.3.

The big problem for me, is when I stick a player skeletal mesh in the player character and his feet are perfectly aligned on the ground at 0.3 floor height. After jumping it may seem like the character is floating when above 0.3 floor. height or even sunk into the floor when less than floor height.

Is there any anything we can set to make the collision detection with the ground more precise ?

1 Like