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.
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.
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!
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.
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 accelerationBabylon.js docs and maxAccelerationBabylon.js docs
Let me do some tests for the teleport. I don’t remember testing that. It might be missing
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 ?