Rotate the Character in the Direction of Movement
We've animated our characters, but they're still moving like robots, rigidly facing the same direction regardless of their path. Let's fix that by implementing a system that smoothly rotates our characters in the direction of their movement.
The CharacterComponent
: Defining Orientation
First, we need a way to store information about our character's orientation. Let's create a CharacterComponent
:
import { Component } from "../lib";
import { Vector3 } from "@babylonjs/core";
export class CharacterComponent extends Component {
forward: Vector3;
constructor(data) {
super(data);
this.forward = Vector3.FromArray(data.forward);
}
}
Key Property:
forward
: AVector3
representing the direction the character is facing.
The CharacterSystem
: Steering the Rotation
Now, let's build the CharacterSystem
to handle character rotation:
import { Entity, EntityManager, System } from "../lib";
import { MovementComponent } from "../components/MovementComponent";
import { AssetComponent } from "../components/AssetComponent";
import { PlayerInputComponent } from "../components/PlayerInputComponent";
import { CharacterComponent } from "../components/CharacterComponent";
import { Scene, Quaternion, Vector3, Mesh } from "@babylonjs/core";
export class CharacterSystem extends System {
constructor(
entityManager: EntityManager,
componentClasses = [
AssetComponent,
MovementComponent,
PlayerInputComponent,
CharacterComponent,
],
) {
super(entityManager, componentClasses);
this.componentClasses = componentClasses;
console.log("CharacterSystem initialized");
}
processEntity(entity: Entity, deltaTime: number) {
const movementComponent = entity.getComponent(MovementComponent);
// const playerInputComponent = entity.getComponent(PlayerInputComponent);
const characterComponent = entity.getComponent(CharacterComponent);
const assetComponent = entity.getComponent(AssetComponent);
const { rotationSpeed, velocity } = movementComponent;
// const { roll } = playerInputComponent;
const mesh = assetComponent.mainMesh;
if (movementComponent && characterComponent && mesh) {
this.rotateTowards(
mesh,
velocity,
rotationSpeed,
deltaTime,
characterComponent,
);
}
}
rotateTowards(
mesh: Mesh,
targetVector: Vector3,
speed: number,
deltaTime: number,
characterComponent: CharacterComponent,
) {
if (targetVector.length() === 0) return; // No rotation needed if no input vector
const currentForward = mesh.forward.normalize();
const targetDirection = targetVector.normalize();
const sign = Math.sign(Vector3.Cross(currentForward, targetDirection).y);
let angle = Math.atan2(
Vector3.Cross(currentForward, targetDirection).length(),
Vector3.Dot(currentForward, targetDirection),
);
angle *= sign;
if (Math.abs(angle) > 0.001) {
const axis = Vector3.Up();
const amountToRotate = speed * deltaTime;
const clampedRotation =
Math.sign(angle) * Math.min(Math.abs(angle), amountToRotate);
const q = Quaternion.RotationAxis(axis, clampedRotation);
mesh.rotationQuaternion = Quaternion.Slerp(
mesh.rotationQuaternion,
mesh.rotationQuaternion.multiply(q),
1 - deltaTime,
);
characterComponent.forward = mesh.forward.normalize();
}
}
}
Explanation:
- The
CharacterSystem
processes entities withMovementComponent
,PlayerInputComponent
,CharacterComponent
, andAssetComponent
components. - It retrieves the entity's mesh from the
AssetComponent
and its velocity from theMovementComponent
. - The
rotateTowards
function calculates the angle between the character's current forward direction and the target direction (based on velocity). - It uses quaternions (
Quaternion.Slerp
) to smoothly rotate the mesh towards the target direction, ensuring a gradual and visually appealing rotation.
Updating scene.json
We'd add the CharacterComponent
to our player entity in scene.json
:
{
"entities": {
// ... other entities
"Player": {
"components": {
// ... other components
"Character": {
"forward": [0, 0, 1] // Initial forward direction
}
}
}
}
}
With this system in place, our character will now smoothly rotate to face the direction of its movement. This adds a layer of realism and responsiveness to our game, making the character's actions feel more natural and engaging.
In Sum
Now our character can rotate smoothly in the direction of its movement. What will we do next? Well in order to have a game we probably need collision detection and a way to interact with the environment. Let's add that next.