aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/entity.h61
-rw-r--r--src/entity.c202
2 files changed, 263 insertions, 0 deletions
diff --git a/include/entity.h b/include/entity.h
new file mode 100644
index 0000000..60467ab
--- /dev/null
+++ b/include/entity.h
@@ -0,0 +1,61 @@
+#ifndef ENTITY_H
+#define ENTITY_H
+
+#include "raylib.h"
+#include "raytmx.h"
+
+#include "player.h"
+#include "map_manager.h"
+
+typedef struct Enemy {
+ Vector2 position;
+ Rectangle bounds;
+ uint32_t gid;
+ int health;
+ bool active;
+} Enemy;
+
+typedef struct Static {
+ Vector2 position;
+ Rectangle bounds;
+ uint32_t gid;
+ bool active;
+} Static;
+
+typedef struct Collectible {
+ Vector2 position;
+ Rectangle bounds;
+ uint32_t gid;
+ bool active;
+ int tileX;
+ int tileY;
+} Collectible;
+
+typedef struct Movable {
+ Vector2 position;
+ Rectangle bounds;
+ uint32_t gid;
+ bool active;
+} Movable;
+
+typedef struct EntityManager {
+ Enemy *enemies;
+ int enemiesCount;
+
+ Collectible *collectibles;
+ int collectiblesCount;
+
+ Static *statics;
+ int staticsCount;
+
+ Movable *movables;
+ int movablesCount;
+} EntityManager;
+
+EntityManager InitEntityManager(uint32_t capacity);
+void SpawnEntitiesFromMap(EntityManager *mgr, TmxMap *map);
+void UpdateEntities(EntityManager *mgr, Player *player, MapManager *mapMgr);
+void DrawEntities(EntityManager *mgr, TmxMap *map);
+void UnloadEntityManager(EntityManager *mgr);
+
+#endif // ENTITY_H
diff --git a/src/entity.c b/src/entity.c
new file mode 100644
index 0000000..9fc582c
--- /dev/null
+++ b/src/entity.c
@@ -0,0 +1,202 @@
+#include "entity.h"
+#include "map_manager.h"
+#include "player.h"
+#include "raymath.h"
+#include "globals.h"
+#include <stdlib.h>
+#include <string.h>
+
+EntityManager InitEntityManager(uint32_t maxPerType) {
+ EntityManager mgr;
+ mgr.enemies = (Enemy *)calloc(maxPerType, sizeof(Enemy));
+ mgr.collectibles = (Collectible *)calloc(maxPerType, sizeof(Collectible));
+ mgr.movables = (Movable *)calloc(maxPerType, sizeof(Movable));
+ mgr.statics = (Static *)calloc(maxPerType, sizeof(Static));
+
+ mgr.enemiesCount = 0;
+ mgr.collectiblesCount = 0;
+ mgr.movablesCount = 0;
+ mgr.staticsCount = 0;
+
+ return mgr;
+}
+
+void SpawnEntitiesFromMap(EntityManager *mgr, TmxMap *map) {
+ for (uint32_t i = 0; i < map->layersLength; i++) {
+ TmxLayer *layer = &map->layers[i];
+
+ // Handle Tile Layer Pickups
+ if (layer->type == LAYER_TYPE_TILE_LAYER &&
+ (strcmp(layer->name, "Pickups") == 0 || strcmp(layer->name, "Collectible") == 0)) {
+ TmxTileLayer *tileLayer = &layer->exact.tileLayer;
+ for (uint32_t y = 0; y < tileLayer->height; y++) {
+ for (uint32_t x = 0; x < tileLayer->width; x++) {
+ uint32_t gid = tileLayer->tiles[y * tileLayer->width + x];
+ if (gid != 0) {
+ float px = (float)x * map->tileWidth;
+ float py = (float)y * map->tileHeight;
+
+ // Default bounds (whole tile)
+ Rectangle customBounds = { px, py, (float)map->tileWidth, (float)map->tileHeight };
+
+ // Check if the tile has a custom collision shape from Tileset Editor
+ if (gid < map->gidsToTilesLength) {
+ TmxTile tileData = map->gidsToTiles[gid];
+
+ // Use the first animation frame's collision if it exists
+ uint32_t targetGid = gid;
+ if (tileData.hasAnimation && tileData.animation.framesLength > 0) {
+ targetGid = tileData.animation.frames[0].gid;
+ }
+
+ // Look up tile data for the frame (accounting for local vs global IDs)
+ // Note: raytmx internal gidsToTiles uses global IDs
+ if (targetGid < map->gidsToTilesLength) {
+ TmxObjectGroup colGroup = map->gidsToTiles[targetGid].objectGroup;
+ if (colGroup.objectsLength > 0) {
+ TmxObject colObj = colGroup.objects[0];
+ customBounds = (Rectangle){
+ px + (float)colObj.x,
+ py + (float)colObj.y,
+ (float)colObj.width,
+ (float)colObj.height
+ };
+ }
+ }
+ }
+
+ mgr->collectibles[mgr->collectiblesCount++] = (Collectible){
+ .position = {px, py},
+ .bounds = customBounds,
+ .active = true,
+ .gid = gid,
+ .tileX = (int)x,
+ .tileY = (int)y
+ };
+ }
+ }
+ }
+ }
+
+ if (layer->type != LAYER_TYPE_OBJECT_GROUP)
+ continue;
+
+ TmxObjectGroup group = layer->exact.objectGroup;
+
+ if (strcmp(layer->name, "Static") == 0) {
+ for (uint32_t j = 0; j < group.objectsLength; j++) {
+ TmxObject object = group.objects[j];
+ mgr->statics[mgr->staticsCount++] =
+ (Static){.position = {(float)object.x, (float)object.y},
+ .bounds = {(float)object.x, (float)object.y,
+ (float)object.width, (float)object.height},
+ .active = true,
+ .gid = object.gid};
+ }
+ }
+ if (strcmp(layer->name, "Movable") == 0) {
+ for (uint32_t j = 0; j < group.objectsLength; j++) {
+ TmxObject object = group.objects[j];
+ mgr->movables[mgr->movablesCount++] =
+ (Movable){.position = {(float)object.x, (float)object.y},
+ .bounds = {(float)object.x, (float)object.y,
+ (float)object.width, (float)object.height},
+ .active = true,
+ .gid = object.gid};
+ }
+ }
+ if (strcmp(layer->name, "Collectible") == 0) {
+ for (uint32_t j = 0; j < group.objectsLength; j++) {
+ TmxObject object = group.objects[j];
+ mgr->collectibles[mgr->collectiblesCount++] =
+ (Collectible){.position = {(float)object.x, (float)object.y},
+ .bounds = {(float)object.x, (float)object.y,
+ (float)object.width, (float)object.height},
+ .active = true,
+ .gid = object.gid};
+ }
+ }
+ if (strcmp(layer->name, "Enemy") == 0) {
+ for (uint32_t j = 0; j < group.objectsLength; j++) {
+ TmxObject object = group.objects[j];
+ mgr->enemies[mgr->enemiesCount++] =
+ (Enemy){.position = {(float)object.x, (float)object.y},
+ .bounds = {(float)object.x, (float)object.y,
+ (float)object.width, (float)object.height},
+ .active = true,
+ .gid = object.gid};
+ }
+ }
+ }
+}
+
+void UpdateEntities(EntityManager *mgr, Player *player, MapManager *mapMgr) {
+ // 1. Update Pickups (Collision only)
+ for (int i = 0; i < mgr->collectiblesCount; i++) {
+ if (mgr->collectibles[i].active &&
+ CheckCollisionRecs(player->bounds, mgr->collectibles[i].bounds)) {
+ mgr->collectibles[i].active = false;
+
+ // Visually remove from map if it's a tile pickup
+ if (mapMgr->pickupLayer && mapMgr->pickupLayer->type == LAYER_TYPE_TILE_LAYER) {
+ TmxTileLayer *tileLayer = &mapMgr->pickupLayer->exact.tileLayer;
+ int idx = mgr->collectibles[i].tileY * tileLayer->width + mgr->collectibles[i].tileX;
+ if (idx >= 0 && idx < (int)tileLayer->tilesLength) {
+ tileLayer->tiles[idx] = 0;
+ }
+ }
+ }
+ }
+
+ // 2. Update Movables (Physics/Pushing)
+ for (int i = 0; i < mgr->movablesCount; i++) {
+ Movable *m = &mgr->movables[i];
+ if (CheckCollisionRecs(player->bounds, m->bounds)) {
+ // Push logic: move the block in player's direction if no wall is there
+ Vector2 pushDir =
+ Vector2Normalize(Vector2Subtract(m->position, player->position));
+ Vector2 nextPos = Vector2Add(
+ m->position, Vector2Scale(pushDir, player->speed * GetFrameTime()));
+ Rectangle nextRect = {nextPos.x, nextPos.y, m->bounds.width,
+ m->bounds.height};
+
+ if (!IsWallCollision(mapMgr, nextRect)) {
+ m->position = nextPos;
+ m->bounds.x = nextPos.x;
+ m->bounds.y = nextPos.y;
+ }
+ }
+ }
+
+ // 3. Update Enemies (AI)
+ for (int i = 0; i < mgr->enemiesCount; i++) {
+ if (!mgr->enemies[i].active)
+ continue;
+ // Basic AI: Move toward player if within range
+ if (Vector2Distance(mgr->enemies[i].position, player->position) < 100.0f) {
+ Vector2 dir = Vector2Normalize(
+ Vector2Subtract(player->position, mgr->enemies[i].position));
+ mgr->enemies[i].position = Vector2Add(
+ mgr->enemies[i].position, Vector2Scale(dir, 50.0f * GetFrameTime()));
+ mgr->enemies[i].bounds.x = mgr->enemies[i].position.x;
+ mgr->enemies[i].bounds.y = mgr->enemies[i].position.y;
+ }
+ }
+}
+
+void UnloadEntityManager(EntityManager *mgr) {
+ if (mgr->enemies) free(mgr->enemies);
+ if (mgr->collectibles) free(mgr->collectibles);
+ if (mgr->movables) free(mgr->movables);
+ if (mgr->statics) free(mgr->statics);
+
+ mgr->enemies = NULL;
+ mgr->collectibles = NULL;
+ mgr->movables = NULL;
+ mgr->statics = NULL;
+
+ mgr->enemiesCount = 0;
+ mgr->collectiblesCount = 0;
+ mgr->movablesCount = 0;
+ mgr->staticsCount = 0;
+}