Character Controller with Physics V2

Babylon.js
4 min readMar 21, 2024

--

A Character Controller is an essential part of a game and resides in the junction of many technologies:

  • Physics collision detection and response (in water, in space, …)
  • Inputs and interaction (how to map character to a pad and keyboard)
  • Sound (what sound, pitch modulation,…)
  • Animation (blending, procedural, motion matching,…)
  • State management (running, jumping, swimming, fighting, falling, …)
  • Locomotion (animation control movement or the inverse)

It’s a very broad piece of technology, that is that is essentialy the first one you put in your game or interactive app. The second one is obviously the camera.

Character can be a cute little bunny, a space ship, a ball, a fish, …

It’s impossible to have one size fits all. Nature of environments, constraints, velocity curves, type of obstacle,… everything is different from one experience to another. You can work on a component for years and years and still find new ways to play, interact or have feedback.

Toolkits (like https://playground.babylonjs.com/?UnityToolkit#5ZZIX ) and libraries ( https://github.com/armomu/ergoudan ) exist and can help you craft rapidly your app.

Lifts and platforms are sometimes difficult to handle properly. It gets easier with Physics engine V2. https://playground.babylonjs.com/#GZYGLJ#21

In this blog post, I’ll focus on 2 properties of the physics engine, very useful for coding your own character controller.

Character shape and collision

Obviously, the character must fit in a shape that is used for collision detection and response. It can be a cylinder, a capsule or even a box, depending on your experience. Here, I want the character to get in a lift so a capsule is more appropriate for the little gap.

Important part is its creation:

const characterAggregate = new BABYLON.PhysicsAggregate(characterMesh,
BABYLON.PhysicsShapeType.CAPSULE,
{ mass: 1, friction: 0.5, restitution: 0 },
this.scene);
const characterBody = characterAggregate.body;
characterBody.disablePreStep = false;
characterBody.setMassProperties({ inertia: BABYLON.Vector3.ZeroReadOnly });

First line is the aggregate creation. It’s documented here with many examples.

Now, the important line is setting the inertia to 0. It’s Havok specific and, basically, it tells the physics solver to not allow rotation. Our capsule will stay up but can be moved just like any other dynamic physics body.

Next line to disablePreStep has also some documentation. In the case of this test PG, it’s needed to restore the characterMesh position to its spawn point when falling away from the ground.

It’s possible to move the character by applying forces or, for more control, set its linear velocity.

Shape casting

Raycasting a determining the distance a point starting from a position can travel in the world before reaching its destination or colliding with a physic shape. Shape casting is the same but with a shape instead of a point.

The little red sphere shows the casting result of the capsule to the ground. A raycast would detect the ground below but the casting show the capsule is still on the platform

Queries are also very similar to raycasts. Start/end position and the orientation.

const shapeLocalResult = new BABYLON.ShapeCastResult();
const hitWorldResult = new BABYLON.ShapeCastResult();
hk.shapeCast({shape: characterAggregate.shape,
rotation: characterMesh.rotationQuaternion,
startPosition: characterMesh.position,
endPosition: new BABYLON.Vector3(characterMesh.position.x, characterMesh.position.y-10, characterMesh.position.z),
shouldHitTriggers: false,
}, shapeLocalResult, hitWorldResult);

Collision results are provided in local space and world space and provide similar interface (hasHit method for example).

Casting the capsule shape will determine the possible distance before colliding.

Character state

I don’t want to go into details about character state. It would deserve its own post (or series of posts). There are only two state variables here: isFalling and the platform it’s on.

If isFalling is true, then linearVelocity computation is a bit different with inputs not taken into account. if the platform is not null, then add the platform velocity to the character to make its movements relative to the plaform.

Quick words on the Camera controller.

It’s only 4 lines:

camera.setTarget(BABYLON.Vector3.Lerp(camera.getTarget(), characterMesh.position, 0.1));
var dist = BABYLON.Vector3.Distance(camera.position, characterMesh.position);
const amount = (Math.min(dist-10, 0) + Math.max(dist-15, 0)) * 0.02;
cameraDirection.scaleAndAddToRef(amount, camera.position);

And has these simple properties:

  • do not move if not necessary (stay in the [10..15] distance range)
  • try to follow a target but not locked on it (Use Vector3.Lerp)
  • can only go forward or backward (cameraDirection vector added to position)

Simple yet really good at framing as a 3rd person camera.

Takeways

Character controller is a (very, very) wide topic. There are some libraries and toolkits that can help. Or, for more specific controller, you can use software bricks provided by the physics engine. Here, I detail two of them: setting inertia to 0 so shape doesn’t rotate and shape casting. Shape casting is like raycasting with thickness. Casting can be use full to detect if character is falling, if it’s on a platform or, less specificaly, if it can advance in one direction without a collision.

Have fun hacking and doing experiment with this Playground.

Then, on top of that physics part, you can plug more complexity with states, sound, skinned and animated character,…

https://twitter.com/skaven_

--

--

Babylon.js
Babylon.js

Written by Babylon.js

Babylon.js: Powerful, Beautiful, Simple, Open — Web-Based 3D At Its Best. https://www.babylonjs.com/

No responses yet