make the camera follow the player + add limited vision default tip

Sun, 16 Nov 2025 23:02:11 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 16 Nov 2025 23:02:11 +0100
changeset 288
8796f03aac26
parent 287
359eaf2a8bd2

make the camera follow the player + add limited vision

demo/snake/snake.c file | annotate | diff | comparison | revisions
src/ascension/camera.h file | annotate | diff | comparison | revisions
src/ascension/scene_node.h file | annotate | diff | comparison | revisions
src/camera.c file | annotate | diff | comparison | revisions
src/window.c file | annotate | diff | comparison | revisions
--- a/demo/snake/snake.c	Sun Nov 16 22:00:13 2025 +0100
+++ b/demo/snake/snake.c	Sun Nov 16 23:02:11 2025 +0100
@@ -58,10 +58,15 @@
 static asc_vec2i directions[4];
 
 typedef struct {
+    AscSceneNode *node;
     asc_color color;
     enum MoveDirection direction;
     enum MoveDirection target_direction;
     /**
+     * The number of tiles this player can look.
+     */
+    float view_distance;
+    /**
      * The speed in tiles per second.
      */
     float speed;
@@ -473,6 +478,8 @@
     );
     asc_scene_add_node(MAIN_SCENE, node);
     Player *player = asc_scene_node_allocate_data(node, sizeof(Player));
+    player->node = node;
+    player->view_distance = 7.f; // start with 7 tiles
     player->speed = 3.f; // start with 3 tiles/sec
     player->number = 1;
     player->health = 100;
@@ -491,8 +498,7 @@
     return player;
 }
 
-static asc_rect main_scene_viewport_update(asc_vec2u window_size) {
-
+static void main_scene_viewport_update(AscCamera *camera, asc_vec2u window_size) {
     // margins
     const unsigned margin = 16;
 
@@ -505,7 +511,8 @@
 
     // check if there is still a viewport left and chicken out when not
     if (window_size.width < rw || window_size.height < rh) {
-        return ASC_RECT(0, 0, 0, 0);
+        camera->viewport = ASC_RECT(0, 0, 0, 0);
+        return;
     }
     window_size.width -= rw;
     window_size.height -= rh;
@@ -525,7 +532,30 @@
     offset_y += margin;
 
     // Set the viewport to the scaled and centered region
-    return ASC_RECT(offset_x, offset_y, viewport_size, viewport_size);
+    camera->viewport = ASC_RECT(offset_x, offset_y, viewport_size, viewport_size);
+}
+
+static void main_scene_camera_update(AscCamera *camera) {
+    // follow the player
+    // TODO: for hot seat / split screen updates we need to attach custom data to the camera
+    //       s.t. the camera knows which player to follow
+    const Player *player = game.players[0];
+
+    const AscSceneNode *player_node = player->node;
+    const float view_distance = player->view_distance;
+    const float cam_view_distance = (1+view_distance) * GAME_FIELD_TILE_SIZE;
+    const float proj_size = 2 * cam_view_distance;
+
+    // TODO: create camera API to avoid direct access to the matrices
+
+    // update the projection matrix
+    asc_mat4f_ortho_update_size(camera->projection, proj_size, proj_size);
+
+    // update the view matrix
+    asc_vec2f pos = asc_scene_node_get_position2f(player_node);
+    pos.x = -pos.x + cam_view_distance;
+    pos.y = -pos.y + cam_view_distance;
+    asc_transform_translate2f(camera->view, pos);
 }
 
 int main(void) {
@@ -553,23 +583,19 @@
     AscCamera backdrop_camera;
     asc_camera_init(&backdrop_camera,
         .type = ASC_CAMERA_ORTHO,
-        .projection_update_func = asc_camera_ortho_update_size
+        .viewport_update_func = asc_camera_ortho_update_size
     );
     asc_scene_init(BACKDROP_SCENE, "backdrop", &backdrop_camera);
     backdrop_create();
 
     // Initialize the main scene
-    const unsigned initial_view_distance = 10;
     AscCamera main_camera;
     asc_camera_init(&main_camera,
         .type = ASC_CAMERA_ORTHO,
-        .ortho.rect = ASC_RECT(0, 0,
-            initial_view_distance*2*GAME_FIELD_TILE_SIZE,
-            initial_view_distance*2*GAME_FIELD_TILE_SIZE
-        ),
         .viewport_clear = true,
         .clear_color = ASC_RGBi(0, 32, 16),
-        .viewport_update_func = main_scene_viewport_update
+        .viewport_update_func = main_scene_viewport_update,
+        .update_func = main_scene_camera_update
     );
     asc_scene_init(MAIN_SCENE, "main", &main_camera);
 
--- a/src/ascension/camera.h	Sun Nov 16 22:00:13 2025 +0100
+++ b/src/ascension/camera.h	Sun Nov 16 23:02:11 2025 +0100
@@ -32,8 +32,8 @@
 
 typedef struct asc_camera_s AscCamera;
 
-typedef asc_rect(*asc_camera_viewport_update_func)(asc_vec2u window_size);
-typedef void(*asc_camera_projection_update_func)(AscCamera*, asc_vec2u window_size);
+typedef void(*asc_camera_viewport_update_func)(AscCamera*, asc_vec2u window_size);
+typedef void(*asc_camera_update_func)(AscCamera*);
 
 struct asc_camera_s {
     asc_mat4f projection;
@@ -43,13 +43,8 @@
      * Will be updated every frame.
      */
     asc_rect viewport;
-    /**
-     * Function that gets invoked whenever the window sized changed.
-     * Calculates the new drawing viewport.
-     * If @c NULL, the entire window will be used.
-     */
     asc_camera_viewport_update_func viewport_update_func;
-    asc_camera_projection_update_func projection_update_func;
+    asc_camera_update_func update_func;
     asc_color clear_color;
     bool viewport_clear;
 };
@@ -70,8 +65,17 @@
             TODO: implement
         } perspective;*/
     };
+    /**
+     * Function that only gets invoked when the window size changed.
+     * Implementations should update the @c viewport rectangle
+     * and may update the projection matrix as well.
+     */
     asc_camera_viewport_update_func viewport_update_func;
-    asc_camera_projection_update_func projection_update_func;
+    /**
+     * Function that is invoked every frame.
+     * Example use: update the position of the camera to follow an object.
+     */
+    asc_camera_update_func update_func;
     /**
      * Indicates whether the viewport for this camera shall be cleared before rendering.
      * The active drawing color will be used as the clear color (can be changed in the camera struct, afterward).
@@ -88,26 +92,16 @@
 
 
 /**
- * Updates the camera with the new window viewport.
- *
- * This is automatically called when a window resizes.
- * It should not be necessary to call this function manually.
- *
- * @param camera the camera
- * @param window_size the new window size
- */
-void asc_camera_update_viewport(AscCamera *camera, asc_vec2u window_size);
-
-/**
  * Shorter version of updating an orthographic camera which assumes the top right corner at (0,0).
  *
  * @attention The camera MUST have been initialized with asc_camera_ortho() at position (0,0).
  *
- * @note This function can be used as an asc_camera_projection_update_func to keep the orthographic projection
+ * @note This function can be used as an asc_camera_viewport_update_func to keep the orthographic projection
  * aligned with the window size (useful for UIs or backdrops).
  *
  * @param camera the camera
  * @param size the new size
+ * @see asc_camera_ortho()
  */
 void asc_camera_ortho_update_size(AscCamera *camera, asc_vec2u size);
 
--- a/src/ascension/scene_node.h	Sun Nov 16 22:00:13 2025 +0100
+++ b/src/ascension/scene_node.h	Sun Nov 16 23:02:11 2025 +0100
@@ -246,6 +246,12 @@
     asc_scene_node_update_transform(node);
 }
 
+static inline void asc_scene_node_set_position2f(AscSceneNode *node, asc_vec2f position) {
+    node->position.x = position.x;
+    node->position.y = position.y;
+    asc_scene_node_update_transform(node);
+}
+
 static inline void asc_scene_node_move(AscSceneNode *node, asc_vec3f offset) {
     node->position.x += offset.x;
     node->position.y += offset.y;
@@ -253,6 +259,20 @@
     asc_scene_node_update_transform(node);
 }
 
+static inline void asc_scene_node_move2f(AscSceneNode *node, asc_vec2f offset) {
+    node->position.x += offset.x;
+    node->position.y += offset.y;
+    asc_scene_node_update_transform(node);
+}
+
+static inline asc_vec3f asc_scene_node_get_position(const AscSceneNode *node) {
+    return node->position;
+}
+
+static inline asc_vec2f asc_scene_node_get_position2f(const AscSceneNode *node) {
+    return ASC_VEC2F(node->position.x, node->position.y);
+}
+
 static inline void asc_scene_node_set_scale(AscSceneNode *node, asc_vec3f scale) {
     node->scale = scale;
     asc_scene_node_update_transform(node);
@@ -263,12 +283,20 @@
     asc_scene_node_update_transform(node);
 }
 
-static inline void asc_scene_node_set_position2f(AscSceneNode *node, asc_vec2f position) {
-    node->position.x = position.x;
-    node->position.y = position.y;
+static inline void asc_scene_node_set_origin2f(AscSceneNode *node, asc_vec2f origin) {
+    node->origin.x = origin.x;
+    node->origin.y = origin.y;
     asc_scene_node_update_transform(node);
 }
 
+static inline asc_vec3f asc_scene_node_get_origin(const AscSceneNode *node) {
+    return node->origin;
+}
+
+static inline asc_vec2f asc_scene_node_get_origin2f(const AscSceneNode *node) {
+    return ASC_VEC2F(node->origin.x, node->origin.y);
+}
+
 /**
  * Sets the z-index of a 2D scene node.
  *
@@ -283,24 +311,12 @@
     asc_scene_node_update_transform(node);
 }
 
-static inline void asc_scene_node_move2f(AscSceneNode *node, asc_vec2f offset) {
-    node->position.x += offset.x;
-    node->position.y += offset.y;
-    asc_scene_node_update_transform(node);
-}
-
 static inline void asc_scene_node_set_scale2f(AscSceneNode *node, asc_vec2f scale) {
     node->scale.width = scale.width;
     node->scale.height = scale.height;
     asc_scene_node_update_transform(node);
 }
 
-static inline void asc_scene_node_set_origin2f(AscSceneNode *node, asc_vec2f origin) {
-    node->origin.x = origin.x;
-    node->origin.y = origin.y;
-    asc_scene_node_update_transform(node);
-}
-
 static inline void asc_scene_node_set_rotation(AscSceneNode *node, asc_transform rotation) {
     memcpy(node->rotation, rotation, ASC_TRANSFORM_SIZE);
     asc_scene_node_update_transform(node);
--- a/src/camera.c	Sun Nov 16 22:00:13 2025 +0100
+++ b/src/camera.c	Sun Nov 16 23:02:11 2025 +0100
@@ -45,7 +45,7 @@
         asc_error("Illegal argument for asc_camera_init(): type = %d", args.type);
     }
     camera->viewport_update_func = args.viewport_update_func;
-    camera->projection_update_func = args.projection_update_func;
+    camera->update_func = args.update_func;
     camera->viewport_clear = args.viewport_clear;
     camera->clear_color = args.clear_color;
 }
@@ -60,17 +60,7 @@
 }
 
 void asc_camera_ortho_update_size(AscCamera *camera, asc_vec2u size) {
+    camera->viewport.size = size;
     asc_mat4f_ortho_update_size(camera->projection, (float)size.width, (float)size.height);
 }
 
-void asc_camera_update_viewport(AscCamera *camera, asc_vec2u window_size) {
-    if (camera->viewport_update_func == NULL) {
-        // this assumes the viewport was initialized with zeros!
-        camera->viewport.size = window_size;
-    } else {
-        camera->viewport = camera->viewport_update_func(window_size);
-    }
-    if (camera->projection_update_func != NULL) {
-        camera->projection_update_func(camera, window_size);
-    }
-}
--- a/src/window.c	Sun Nov 16 22:00:13 2025 +0100
+++ b/src/window.c	Sun Nov 16 23:02:11 2025 +0100
@@ -69,7 +69,7 @@
         snprintf(ui_scene_name, sizeof(ui_scene_name), "Window %u UI", index);
         asc_camera_init(&window->ui_camera, .type = ASC_CAMERA_ORTHO,
             .ortho.rect = ASC_RECT(0, 0, window->dimensions.width, window->dimensions.height),
-            .projection_update_func = asc_camera_ortho_update_size);
+            .viewport_update_func = asc_camera_ortho_update_size);
         asc_scene_init(&window->ui, ui_scene_name, &window->ui_camera);
         asc_dprintf("Window %u initialized at index %u", window->id, index);
         asc_context.active_window = index;
@@ -121,6 +121,21 @@
     memset(window, 0, sizeof(AscWindow));
 }
 
+static void asc_window_update_viewport(AscCamera *camera, asc_vec2u window_size) {
+    if (camera->viewport_update_func == NULL) {
+        // this assumes the viewport was initialized with zeros!
+        camera->viewport.size = window_size;
+    } else {
+        camera->viewport_update_func(camera, window_size);
+    }
+}
+
+static void asc_window_update_camera(AscCamera *camera) {
+    if (camera->update_func != NULL) {
+        camera->update_func(camera);
+    }
+}
+
 void asc_window_sync(unsigned int index) {
     if (index > ASC_MAX_WINDOWS) {
         asc_error("Index for syncing window out of bounds (%u/%u).", index, ASC_MAX_WINDOWS);
@@ -146,12 +161,20 @@
     if (window->resized) {
         for (unsigned int i = 0; i < ASC_MAX_SCENES; i++) {
             if (asc_scene_active(&window->scenes[i])) {
-                asc_camera_update_viewport(window->scenes[i].camera, window->dimensions);
+                asc_window_update_viewport(window->scenes[i].camera, window->dimensions);
             }
         }
-        asc_camera_update_viewport(window->ui.camera, window->dimensions);
+        asc_window_update_viewport(window->ui.camera, window->dimensions);
     }
 
+    // Execute camera updates
+    for (unsigned int i = 0; i < ASC_MAX_SCENES; i++) {
+        if (asc_scene_active(&window->scenes[i])) {
+            asc_window_update_camera(window->scenes[i].camera);
+        }
+    }
+    asc_window_update_camera(window->ui.camera);
+
     // Execute all behaviors
     // TODO: this can eventually be parallelized
     for (unsigned int i = 0; i < ASC_MAX_SCENES; i++) {

mercurial