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: A Vector3 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 with MovementComponent, PlayerInputComponent, CharacterComponent, and AssetComponent components.
  • It retrieves the entity's mesh from the AssetComponent and its velocity from the MovementComponent.
  • 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.