# How to Implement Custom Camera Behaviors Within the Camera_System in Equilibrium Engine

> Learn to implement custom camera behaviors in Equilibrium Engine's camera_system. Define new ECS components and systems to control camera position and view matrices post update for unique gameplay perspectives.

- Repository: [Alexander/equilibriumengine](https://github.com/clibequilibrium/equilibriumengine)
- Tags: how-to-guide
- Published: 2026-02-27

---

**You implement custom camera behaviors within the camera_system by defining new ECS components to store behavior parameters and creating systems that run in the PostUpdate phase to modify the Camera component's position and view matrices after the default input handling completes.**

The Equilibrium Engine leverages a data-oriented **Entity-Component-System (ECS)** architecture via **flecs**, allowing you to implement custom camera behaviors within the camera_system without modifying core engine code. Because the default logic lives entirely inside [`equilibrium/systems/scene/camera_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/scene/camera_system.c) and operates on raw data components, you can layer orbital controls, smooth follow mechanics, or cinematic cuts by introducing new components and scheduling additional systems.

## Understanding the Camera System Architecture

Before extending the system, you need to understand how the default implementation processes camera data each frame.

### Component Registration and Data Layout

The **Camera** component is defined as plain data in [`equilibrium/components/scene/scene_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/components/scene/scene_components.h) (lines 9–12). It stores position, rotation, FOV, and view/projection matrices. The `SceneComponentsImport` function registers this component with the ECS world using `ECS_COMPONENT_DECLARE(Camera)`, making it queryable by any system.

### System Phases and Execution Order

In [`equilibrium/systems/scene/camera_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/scene/camera_system.c) (lines 77–90), the `CameraSystemImport` function registers two critical elements:
1. An **observer** called `InitializeCamera` that triggers on `EcsOnSet` when an entity has both `AppWindow` and `Camera` components (lines 14–60)
2. A **system** called `UpdateCamera` that runs on the `OnInput` phase and processes keyboard/mouse input to update rotation and position (lines 62–174)

The ECS resolves execution order by **phase tags**. Because `UpdateCamera` runs on `OnInput`, you can implement custom camera behaviors within the camera_system by creating systems that run on `PostUpdate` (or use `after = UpdateCamera` dependencies), ensuring your logic executes after the default fly-around controller.

### The Default Update Loop

The built-in `UpdateCamera` system queries `[in] gui.components.AppWindow, scene.components.Camera` alongside `Input` components. It writes to `camera->position` and `camera->rotation`, then relies on downstream renderer logic (or a dedicated matrix update system) to recalculate `camera->view` and `camera->proj`. This separation means any custom system simply needs to overwrite these fields before the renderer consumes them.

## Step-by-Step Implementation Guide

To implement custom camera behaviors within the camera_system, follow this pattern of component definition and system registration.

### Step 1: Define the Behavior Component

Create a struct that stores parameters specific to your behavior. For example, a third-person follow camera needs a target entity and offset distances.

```c
/* file: equilibrium/components/scene/scene_components.h */

typedef struct FollowTarget {
    ecs_entity_t target;   /* Entity whose Transform to follow */
    float        distance; /* Distance behind the target */
    float        height;   /* Vertical offset */
} FollowTarget;

/* Declare the component handle */
EQUILIBRIUM_API extern ECS_COMPONENT_DECLARE(FollowTarget);

```

Place this definition near the existing `Camera` struct (around line 9) to maintain consistency with the engine's component organization.

### Step 2: Register the Component

Inside the `SceneComponentsImport` function in the same file, register the new component so the ECS recognizes it:

```c
void SceneComponentsImport(ecs_world_t *world) {
    ECS_MODULE(world, SceneComponents);
    
    /* Existing registrations */
    ECS_COMPONENT_DECLARE(Camera);
    
    /* New registration */
    ECS_COMPONENT_DECLARE(FollowTarget);
    
    /* ... rest of imports ... */
}

```

### Step 3: Create the Custom System

Create a new file (e.g., [`equilibrium/systems/scene/follow_camera_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/scene/follow_camera_system.c)) that implements your logic. The system queries the `Camera` component alongside your custom component and any required dependencies like `Transform`.

```c
/* file: equilibrium/systems/scene/follow_camera_system.c */
#include "components/scene/scene_components.h"
#include "components/transform.h"
#include <cglm/cglm.h>

static void FollowCamera(ecs_iter_t *it) {
    Camera       *cam = ecs_field(it, Camera, 2);
    FollowTarget *ft  = ecs_field(it, FollowTarget, 3);
    Transform    *tr  = ecs_field(it, Transform, 4); /* Target's transform */
    
    for (int i = 0; i < it->count; i++) {
        /* Calculate position behind target using target's rotation */
        vec3 forward;
        glm_quat_rotatev(tr[i].rotation, GLM_ZUP, forward);
        
        vec3 offset;
        glm_vec3_scale(forward, -ft[i].distance, offset);
        offset[1] = ft[i].height;
        
        glm_vec3_add(tr[i].position, offset, cam[i].position);
        
        /* Orient camera to look at target */
        glm_lookat(cam[i].position, tr[i].position, GLM_YUP, cam[i].view);
    }
}

```

### Step 4: Schedule System Execution

Register the system in a phase that runs after the default camera update. Using `PostUpdate` ensures the follow behavior overrides the default fly-around input handling from [`camera_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/camera_system.c).

```c
void FollowCameraSystemImport(ecs_world_t *world) {
    ECS_MODULE(world, FollowCameraSystem);
    ECS_IMPORT(world, SceneComponents);
    ECS_IMPORT(world, TransformComponents);
    
    /* Run in PostUpdate to override UpdateCamera's results */
    ECS_SYSTEM(world, FollowCamera, PostUpdate,
               [in] gui.components.AppWindow,
               scene.components.Camera,
               scene.components.FollowTarget,
               [in] scene.components.Transform);
}

```

### Step 5: Import the System

In your main entry point (e.g., [`equilibrium/main.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/main.c)), import your custom system after `CameraSystemImport` to ensure all component types are registered before the system queries are built.

```c
/* file: equilibrium/main.c */
int main(int argc, char *argv[]) {
    ecs_world_t *world = ecs_init_w_args(argc, argv);
    
    CameraSystemImport(world);          /* Default system (lines 77-90) */
    FollowCameraSystemImport(world);    /* Custom behavior */
    
    while (ecs_progress(world, 0)) { }
    
    return ecs_fini(world);
}

```

## Complete Working Example: Third-Person Follow Camera

Here is the complete integration showing entity setup. This example attaches a follow camera to a player entity:

```c
/* Spawn player with Transform */
ecs_entity_t player = ecs_new(world);
ecs_set(world, player, Transform, {
    .position = {0.0f, 0.0f, 0.0f},
    .rotation = {0.0f, 0.0f, 0.0f, 1.0f}
});

/* Create camera entity with both Camera and FollowTarget */
ecs_entity_t cam = ecs_new(world);
ecs_set(world, cam, Camera, {
    .fov = 60.0f,
    .near = 0.1f,
    .far = 1000.0f
});
ecs_set(world, cam, FollowTarget, {
    .target = player,
    .distance = 5.0f,
    .height = 2.0f
});

/* Optional: Establish hierarchy */
ecs_add_pair(world, cam, EcsChildOf, player);

```

Now the camera automatically stays 5 units behind the player at a height of 2 units. Because the `FollowCamera` system runs in `PostUpdate`, it completely overrides the default input-driven movement from `UpdateCamera` (lines 62–174) while preserving the ability to react to window events via the `AppWindow` component.

## Summary

- **Component-based architecture**: The Equilibrium Engine stores camera data in plain C structs (`Camera` in [`scene_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/scene_components.h)) separate from behavior logic, enabling easy extension.
- **Phase scheduling**: Implement custom camera behaviors within the camera_system by registering systems in the `PostUpdate` phase (or using `after = UpdateCamera`) to ensure they execute after the default input handling in [`camera_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/camera_system.c) (lines 77–90).
- **No core modifications required**: You never need to edit [`camera_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/camera_system.c); simply define new components, query them alongside `Camera`, and write to `camera->position` and `camera->view` directly.
- **Matrix recalculation**: Always recompute the view matrix (using `glm_lookat` or similar) after modifying position/rotation, as the renderer reads `camera->view` and `camera->proj` during the render phase.

## Frequently Asked Questions

### How do I disable the default fly-around controls for specific cameras?

Add your custom system with an `after = UpdateCamera` dependency in the `OnInput` phase, or simply run in `PostUpdate` and overwrite the position/rotation values. Because the default `UpdateCamera` (lines 62–174) only writes data without side effects, your subsequent system can completely replace the results without "disabling" the original system globally.

### Can I combine multiple custom behaviors on the same camera entity?

Yes. You can attach multiple custom components (e.g., `FollowTarget`, `CameraShake`, `ZoomConstraint`) to a single camera entity and create separate systems for each. Use explicit `before`/`after` dependencies in the `ECS_SYSTEM` macro to define the order of operations (e.g., apply follow, then shake, then clamp).

### Where should I place my custom system files?

Follow the existing directory convention: place component definitions in `equilibrium/components/scene/` and system implementations in `equilibrium/systems/scene/`. Mirror the pattern seen in [`camera_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/camera_system.c) and [`camera_system.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/camera_system.h) to maintain consistency with the engine's modular architecture.

### Do I need to recompute the projection matrix in my custom system?

No, unless you are modifying `fov`, `near`, or `far` planes. The projection matrix (`camera->proj`) typically remains static and is calculated during initialization (or by a dedicated matrix-update system). Your custom behavior only needs to update `camera->position`, `camera->rotation`, and `camera->view` using functions like `glm_lookat` from cglm.