Camera Scripting

Headers: Core/include/Core/Components.h, Core/include/Core/CameraUtils.h

Camera Component

The Camera component defines a viewpoint for rendering:

FieldTypeDefaultDescription
fovfloat45.0Field of view in degrees
nearPlanefloat0.1Near clipping distance
farPlanefloat1000.0Far clipping distance
projectionTypePerspective / OrthographicPerspectiveProjection mode
orthoSizefloat10.0View height for orthographic
isMainboolfalseActive camera during play mode

Setting Up a Camera

  1. Create an entity in the Scene Hierarchy
  2. Add a Transform component (position and rotation)
  3. Add a Camera component
  4. Check isMain to make it the active camera

When you press Play, the engine switches from the editor camera to the entity marked as isMain.

Multiple Cameras

You can have multiple camera entities in a scene. Only one should have isMain = true at a time. Switch between them in code:

void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
  auto &input = engine.GetInput();

  if (input.IsKeyPressed(GLFW_KEY_1)) {
    auto cam1 = engine.GetWorld().lookup("MainCamera");
    engine.SetMainCamera(cam1);
  }
  if (input.IsKeyPressed(GLFW_KEY_2)) {
    auto cam2 = engine.GetWorld().lookup("CinematicCamera");
    engine.SetMainCamera(cam2);
  }
}

Camera API

MethodDescription
engine.GetMainCamera()Returns the entity with isMain == true
engine.SetMainCamera(entity)Clears isMain on all cameras, sets it on target

CameraUtils

The CameraUtils subsystem provides helper functions for building any camera behavior through scripts. Access it via engine.GetCameraUtils().

Transform Helpers

These operate on any Transform and are useful for positioning and rotating cameras:

MethodDescription
LookAt(Transform &cam, const glm::vec3 &target)Set rotation to face a target position
SmoothFollow(Transform &cam, const glm::vec3 &target, float speed, float dt)Smoothly interpolate position toward target
SmoothLookAt(Transform &cam, const glm::vec3 &target, float speed, float dt)Smoothly rotate to face target (quaternion slerp)
Orbit(Transform &cam, const glm::vec3 &center, float radius, float yaw, float pitch)Position camera on an orbit sphere and look at center

Camera Helpers

MethodDescription
Shake(float intensity, float duration)Trigger screen shake (applied to view matrix automatically)
StopShake()Immediately stop any active shake
SetZoom(Camera &cam, float targetFov, float speed, float dt)Smoothly transition FOV
ScreenToWorld(const glm::vec2 &screenPos)Convert screen coordinates to a world-space Ray
WorldToScreen(const glm::vec3 &worldPos)Project world position to screen coordinates

Direction Vectors

MethodDescription
GetForward(const Transform &t)Forward direction from a Transform's rotation
GetRight(const Transform &t)Right direction from a Transform's rotation
GetUp(const Transform &t)Up direction from a Transform's rotation

Ray Struct

ScreenToWorld returns a Ray:

struct Ray {
  glm::vec3 origin;     // Near plane intersection
  glm::vec3 direction;  // Normalized direction into the scene
};

Writing Camera Behaviors

All camera behaviors are written as scripts using the Behavior base class and CameraUtils helpers. There is no built-in follow camera — you build exactly the behavior you need.

Third-Person Follow Camera

class ThirdPersonCamera : public Behavior {
public:
  void OnStart(flecs::entity e, Engine &engine) override {
    const auto *ns = e.get<NativeScript>();
    if (ns) {
      auto it = ns->properties.find("target");
      if (it != ns->properties.end())
        m_TargetName = it->second;

      it = ns->properties.find("smoothing");
      if (it != ns->properties.end())
        m_Smoothing = std::atof(it->second.c_str());
    }
  }

  void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
    auto target = engine.GetWorld().lookup(m_TargetName.c_str());
    if (!target.is_valid() || !target.has<Transform>())
      return;

    auto *cam = e.get_mut<Transform>();
    const auto *tgt = target.get<Transform>();
    auto &utils = engine.GetCameraUtils();

    // Smoothly follow behind and above the target
    glm::vec3 desired = tgt->position + glm::vec3(0.0f, 5.0f, 8.0f);
    utils.SmoothFollow(*cam, desired, m_Smoothing, dt);

    // Smoothly look at the target
    utils.SmoothLookAt(*cam, tgt->position, m_Smoothing * 2.0f, dt);
  }

private:
  std::string m_TargetName = "Player";
  float m_Smoothing = 5.0f;
};

Orbit Camera

class OrbitCamera : public Behavior {
public:
  void OnStart(flecs::entity e, Engine &engine) override {
    const auto *ns = e.get<NativeScript>();
    if (ns) {
      auto it = ns->properties.find("target");
      if (it != ns->properties.end())
        m_TargetName = it->second;

      it = ns->properties.find("radius");
      if (it != ns->properties.end())
        m_Radius = std::atof(it->second.c_str());
    }
  }

  void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
    auto target = engine.GetWorld().lookup(m_TargetName.c_str());
    if (!target.is_valid() || !target.has<Transform>())
      return;

    auto *cam = e.get_mut<Transform>();
    const auto *tgt = target.get<Transform>();
    auto &input = engine.GetInput();
    auto &utils = engine.GetCameraUtils();

    // Mouse controls orbit
    glm::vec2 delta = input.GetMouseDelta();
    m_Yaw += delta.x * 0.3f;
    m_Pitch = glm::clamp(m_Pitch - delta.y * 0.3f, -80.0f, 80.0f);

    // Scroll controls distance
    m_Radius = glm::clamp(m_Radius - input.GetScrollDelta() * 2.0f,
                           2.0f, 50.0f);

    utils.Orbit(*cam, tgt->position, m_Radius, m_Yaw, m_Pitch);
  }

private:
  std::string m_TargetName = "Player";
  float m_Radius = 10.0f;
  float m_Yaw = 0.0f;
  float m_Pitch = 20.0f;
};

First-Person Camera

class FirstPersonCamera : public Behavior {
public:
  void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
    auto *cam = e.get_mut<Transform>();
    auto &input = engine.GetInput();
    auto &utils = engine.GetCameraUtils();

    // Mouse look
    glm::vec2 delta = input.GetMouseDelta();
    cam->rotation.y += delta.x * m_Sensitivity;
    cam->rotation.x = glm::clamp(cam->rotation.x - delta.y * m_Sensitivity,
                                  -89.0f, 89.0f);

    // WASD movement relative to camera direction
    glm::vec3 forward = utils.GetForward(*cam);
    glm::vec3 right = utils.GetRight(*cam);

    glm::vec3 move(0.0f);
    if (input.IsKeyDown(GLFW_KEY_W)) move += forward;
    if (input.IsKeyDown(GLFW_KEY_S)) move -= forward;
    if (input.IsKeyDown(GLFW_KEY_D)) move += right;
    if (input.IsKeyDown(GLFW_KEY_A)) move -= right;

    if (glm::length(move) > 0.001f)
      cam->position += glm::normalize(move) * m_Speed * dt;
  }

private:
  float m_Speed = 5.0f;
  float m_Sensitivity = 0.15f;
};

Camera Shake on Impact

class PlayerWithShake : public Behavior {
public:
  void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
    auto &utils = engine.GetCameraUtils();

    // Trigger shake on landing or collision
    if (m_JustLanded) {
      utils.Shake(0.3f, 0.4f);  // intensity 0.3, duration 0.4s
      m_JustLanded = false;
    }
  }

private:
  bool m_JustLanded = false;
};

Zoom / Scope Effect

void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
  auto *cam = e.get_mut<Camera>();
  auto &input = engine.GetInput();
  auto &utils = engine.GetCameraUtils();

  float targetFov = input.IsKeyDown(GLFW_KEY_Z) ? 20.0f : 45.0f;
  utils.SetZoom(*cam, targetFov, 8.0f, dt);
}

Cinematic Camera Switcher

Switch between cameras on a timer for cutscenes:

class CinematicDirector : public Behavior {
public:
  void OnStart(flecs::entity e, Engine &engine) override {
    m_Timer = 0.0f;
    auto cam1 = engine.GetWorld().lookup("CineCam1");
    engine.SetMainCamera(cam1);
  }

  void OnUpdate(flecs::entity e, Engine &engine, float dt) override {
    m_Timer += dt;

    if (m_Timer > 5.0f && m_CurrentCam == 0) {
      auto cam2 = engine.GetWorld().lookup("CineCam2");
      engine.SetMainCamera(cam2);
      m_CurrentCam = 1;
    }
    if (m_Timer > 10.0f && m_CurrentCam == 1) {
      auto cam3 = engine.GetWorld().lookup("CineCam3");
      engine.SetMainCamera(cam3);
      m_CurrentCam = 2;
    }
  }

private:
  float m_Timer = 0.0f;
  int m_CurrentCam = 0;
};

Editor Features

Frustum Gizmo

In edit mode, every camera entity displays a wireframe frustum showing its field of view, near/far planes, and forward direction. This helps you position cameras in the scene.

Inspector Preview

When you select a camera entity, the Inspector shows a live preview thumbnail of what that camera sees. This updates in real-time as you adjust the camera's Transform or properties.