#include "engine/entity.h" #include "engine/camera.h" #include #include /* ── Distance culling ──────────────────────────────── */ /* Margin around the viewport for update culling (in pixels). * Entities within this margin still update even if off-screen, * so they are active before scrolling into view. */ #define UPDATE_MARGIN (SCREEN_WIDTH * 2.0f) /* Margin for render culling — tighter than update since only * entities actually visible (plus a small buffer) need drawing. */ #define RENDER_MARGIN 64.0f /* Check whether a body is within a rectangle expanded by margin. */ static bool in_camera_range(const Body *body, const Camera *cam, float margin) { float left = cam->pos.x - margin; float top = cam->pos.y - margin; float right = cam->pos.x + cam->viewport.x + margin; float bottom = cam->pos.y + cam->viewport.y + margin; float ex = body->pos.x + body->size.x; float ey = body->pos.y + body->size.y; return body->pos.x < right && ex > left && body->pos.y < bottom && ey > top; } void entity_manager_init(EntityManager *em) { memset(em, 0, sizeof(EntityManager)); } Entity *entity_spawn(EntityManager *em, EntityType type, Vec2 pos) { /* Find a free slot */ for (int i = 0; i < MAX_ENTITIES; i++) { if (!em->entities[i].active) { Entity *e = &em->entities[i]; memset(e, 0, sizeof(Entity)); e->type = type; e->active = true; e->body.pos = pos; e->body.gravity_scale = 1.0f; e->health = 1; e->max_health = 1; if (i >= em->count) em->count = i + 1; return e; } } fprintf(stderr, "Warning: entity limit reached (%d)\n", MAX_ENTITIES); return NULL; } void entity_destroy(EntityManager *em, Entity *e) { if (!e || !e->active) return; /* Call type-specific destructor */ if (em->destroy_fn[e->type]) { em->destroy_fn[e->type](e); } e->active = false; e->type = ENT_NONE; /* Shrink count if possible */ while (em->count > 0 && !em->entities[em->count - 1].active) { em->count--; } } void entity_update_all(EntityManager *em, float dt, const Tilemap *map, const Camera *cam) { for (int i = 0; i < em->count; i++) { Entity *e = &em->entities[i]; if (!e->active) continue; /* Check if dead */ if (e->health <= 0 && !(e->flags & ENTITY_INVINCIBLE)) { e->flags |= ENTITY_DEAD; } /* Distance culling: skip far-away entities unless they opt out. * Dead entities always get their update callback so type-specific * cleanup (destroy, particles, etc.) can run. */ if (cam && !(e->flags & (ENTITY_ALWAYS_UPDATE | ENTITY_DEAD))) { if (!in_camera_range(&e->body, cam, UPDATE_MARGIN)) continue; } /* Call type-specific update */ if (em->update_fn[e->type]) { em->update_fn[e->type](e, dt, map); } } } void entity_render_all(EntityManager *em, const Camera *cam) { for (int i = 0; i < em->count; i++) { Entity *e = &em->entities[i]; if (!e->active) continue; /* Render culling: skip entities outside the visible area. */ if (cam && !in_camera_range(&e->body, cam, RENDER_MARGIN)) continue; if (em->render_fn[e->type]) { em->render_fn[e->type](e, cam); } } } void entity_manager_clear(EntityManager *em) { for (int i = 0; i < em->count; i++) { if (em->entities[i].active) { entity_destroy(em, &em->entities[i]); } } em->count = 0; } void entity_register(EntityManager *em, EntityType type, EntityUpdateFn update, EntityRenderFn render, EntityDestroyFn destroy) { em->update_fn[type] = update; em->render_fn[type] = render; em->destroy_fn[type] = destroy; }