logo

Babylon.js Market

By Lawrence

3 minutes

Camera Component System

Let's walk through building a camera component system step by step. We'll break down the code into digestible pieces and explain what each part does.

Setting Up the Camera Imports

shell
touch src/Components/Camera.ts

First, we need to import everything we'll use from Babylon.js and our ECS framework:

src/components/Camera.ts
import { Vector3, UniversalCamera, FollowCamera } from "@babylonjs/core";
import { Component, Entity, World, System } from "~/lib/ECS";

We're pulling in Vector3 for 3D math, UniversalCamera for our main camera type, and all our ECS building blocks.

Camera Options

Next, we define what data our camera component needs when it's created:

src/components/Camera.ts
export interface CameraComponentInput {
  offset: number[];
}

The offset array will define how far the camera sits from its target. Simple but effective.

Creating the Camera Component

Now we build the actual component class:

src/components/Camera.ts
export class CameraComponent extends Component {
  constructor(data: CameraComponentInput) {
    super(data);
    this.offset = Vector3.FromArray(data.offset);
  }
}

This converts our offset array into a proper Babylon.js Vector3. The component inherits all the base functionality from our ECS Component class.

Setting Up the Camera System

The system handles all the camera logic. Let's start with the constructor:

src/components/Camera.ts
export class CameraSystem extends System {
  constructor(world: World, componentClasses = [CameraComponent]) {
    super(world, componentClasses);
  }

This tells the system it should process any entity that has a CameraComponent.

Loading the Camera Entity

When an entity with a camera component gets added to the world, this method runs:

src/components/Camera.ts
loadEntity(entity: Entity) {
  const cameraComponent = entity.getComponent(CameraComponent);
  let { offset, type, target, camera } = cameraComponent;
  const canvas = this.scene.getEngine().getRenderingCanvas();
  camera = new UniversalCamera(
    "UniversalCamera",
    entity.position,
    this.scene,
  );

  cameraComponent.camera = camera;
  camera.parent = entity;
  cameraComponent.loaded = true;
  console.log("Camera component loaded");
}

We grab the camera component, create a new UniversalCamera at the entity's position, then link everything together. The camera becomes a child of the entity so they move together.

Processing Camera Movement Each Frame

Smooth camera following can be acheived with a Lerp:

src/components/Camera.ts
processEntity(entity: Entity, deltaTime: number) {
  const cameraComponent = entity.getComponent(CameraComponent);
  const { camera, target } = cameraComponent;
  const t =
    this.scene.getNodeByName(target) || this.scene.getNodeByName("World");

First we get our camera and find what it should be looking at. If no specific target exists, we default to looking at "World".

Smooth Target Following

Next, we smoothly move the camera's focus:

src/components/Camera.ts
camera.setTarget(Vector3.Lerp(camera.getTarget(), t.position, 0.01));

Vector3.Lerp smoothly interpolates between the current target and the new target position. The 0.01 factor makes it gradual - no jarring camera movements.

Dynamic Distance Adjustment

Now we handle automatic distance adjustment based on how far we are from the target:

src/components/Camera.ts
var dist = Vector3.Distance(camera.position, t.position);
const amount = (Math.min(dist - 2, 0) + Math.max(dist - 8, 0)) * 0.01;
var cameraDirection = camera.getDirection(new Vector3(0, 0, 1));
cameraDirection.y = 0;
cameraDirection.normalize();

This calculates how much to move the camera. If we're closer than 2 units, we back away. If we're further than 8 units, we move closer. The direction calculation gives us which way to move.

Applying the Movement

Finally, we apply the movement and add a ground constraint:

src/components/Camera.ts
cameraDirection.scaleAndAddToRef(amount, camera.position);
if (camera.position.y < 0) camera.position.y = 0;

We move the camera in the calculated direction by the calculated amount. The Y-constraint keeps the camera above ground level.

In Sum

This camera system gives you smooth following behavior with automatic distance adjustment. The camera will naturally maintain a good viewing distance from its target while smoothly tracking movement. Perfect for third-person games or any situation where you need intelligent camera behavior.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search