Initial commit
This commit is contained in:
219
src/engine/tilemap.c
Normal file
219
src/engine/tilemap.c
Normal file
@@ -0,0 +1,219 @@
|
||||
#include "engine/tilemap.h"
|
||||
#include "engine/camera.h"
|
||||
#include "engine/assets.h"
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) {
|
||||
fprintf(stderr, "Failed to open level: %s\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(map, 0, sizeof(Tilemap));
|
||||
|
||||
char line[1024];
|
||||
char tileset_path[256] = {0};
|
||||
int current_layer = -1; /* 0=collision, 1=bg, 2=fg */
|
||||
int row = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
/* Skip comments and empty lines */
|
||||
if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;
|
||||
|
||||
/* Parse directives */
|
||||
if (strncmp(line, "TILESET ", 8) == 0) {
|
||||
sscanf(line + 8, "%255s", tileset_path);
|
||||
} else if (strncmp(line, "SIZE ", 5) == 0) {
|
||||
sscanf(line + 5, "%d %d", &map->width, &map->height);
|
||||
if (map->width <= 0 || map->height <= 0 ||
|
||||
map->width > 4096 || map->height > 4096) {
|
||||
fprintf(stderr, "Invalid map size: %dx%d\n", map->width, map->height);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
int total = map->width * map->height;
|
||||
map->collision_layer = calloc(total, sizeof(uint16_t));
|
||||
map->bg_layer = calloc(total, sizeof(uint16_t));
|
||||
map->fg_layer = calloc(total, sizeof(uint16_t));
|
||||
} else if (strncmp(line, "SPAWN ", 6) == 0) {
|
||||
float sx, sy;
|
||||
sscanf(line + 6, "%f %f", &sx, &sy);
|
||||
map->player_spawn = vec2(sx * TILE_SIZE, sy * TILE_SIZE);
|
||||
} else if (strncmp(line, "GRAVITY ", 8) == 0) {
|
||||
sscanf(line + 8, "%f", &map->gravity);
|
||||
} else if (strncmp(line, "TILEDEF ", 8) == 0) {
|
||||
int id, tx, ty;
|
||||
uint32_t flags;
|
||||
sscanf(line + 8, "%d %d %d %u", &id, &tx, &ty, &flags);
|
||||
if (id < MAX_TILE_DEFS) {
|
||||
map->tile_defs[id].tex_x = (uint16_t)tx;
|
||||
map->tile_defs[id].tex_y = (uint16_t)ty;
|
||||
map->tile_defs[id].flags = flags;
|
||||
if (id >= map->tile_def_count) {
|
||||
map->tile_def_count = id + 1;
|
||||
}
|
||||
}
|
||||
} else if (strncmp(line, "BG_COLOR ", 9) == 0) {
|
||||
int r, g, b;
|
||||
if (sscanf(line + 9, "%d %d %d", &r, &g, &b) == 3) {
|
||||
map->bg_color = (SDL_Color){
|
||||
(uint8_t)r, (uint8_t)g, (uint8_t)b, 255
|
||||
};
|
||||
map->has_bg_color = true;
|
||||
}
|
||||
} else if (strncmp(line, "PARALLAX_FAR ", 13) == 0) {
|
||||
sscanf(line + 13, "%255s", map->parallax_far_path);
|
||||
} else if (strncmp(line, "PARALLAX_NEAR ", 14) == 0) {
|
||||
sscanf(line + 14, "%255s", map->parallax_near_path);
|
||||
} else if (strncmp(line, "MUSIC ", 6) == 0) {
|
||||
sscanf(line + 6, "%255s", map->music_path);
|
||||
} else if (strncmp(line, "ENTITY ", 7) == 0) {
|
||||
if (map->entity_spawn_count < MAX_ENTITY_SPAWNS) {
|
||||
EntitySpawn *es = &map->entity_spawns[map->entity_spawn_count];
|
||||
float tx, ty;
|
||||
if (sscanf(line + 7, "%31s %f %f", es->type_name, &tx, &ty) == 3) {
|
||||
es->x = tx * TILE_SIZE;
|
||||
es->y = ty * TILE_SIZE;
|
||||
map->entity_spawn_count++;
|
||||
}
|
||||
}
|
||||
} else if (strncmp(line, "LAYER ", 6) == 0) {
|
||||
row = 0;
|
||||
if (strstr(line, "collision")) current_layer = 0;
|
||||
else if (strstr(line, "bg")) current_layer = 1;
|
||||
else if (strstr(line, "fg")) current_layer = 2;
|
||||
} else if (current_layer >= 0 && map->width > 0) {
|
||||
/* Parse tile row */
|
||||
uint16_t *layer = NULL;
|
||||
switch (current_layer) {
|
||||
case 0: layer = map->collision_layer; break;
|
||||
case 1: layer = map->bg_layer; break;
|
||||
case 2: layer = map->fg_layer; break;
|
||||
}
|
||||
if (layer && row < map->height) {
|
||||
char *tok = strtok(line, " \t\n\r");
|
||||
for (int col = 0; col < map->width && tok; col++) {
|
||||
layer[row * map->width + col] = (uint16_t)atoi(tok);
|
||||
tok = strtok(NULL, " \t\n\r");
|
||||
}
|
||||
row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
/* Load tileset texture */
|
||||
if (tileset_path[0] && renderer) {
|
||||
map->tileset = assets_get_texture(tileset_path);
|
||||
if (map->tileset) {
|
||||
int tex_w;
|
||||
SDL_QueryTexture(map->tileset, NULL, NULL, &tex_w, NULL);
|
||||
map->tileset_cols = tex_w / TILE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Loaded level: %s (%dx%d tiles)\n", path, map->width, map->height);
|
||||
return true;
|
||||
}
|
||||
|
||||
void tilemap_render_layer(const Tilemap *map, const uint16_t *layer,
|
||||
const Camera *cam, SDL_Renderer *renderer) {
|
||||
if (!map || !layer || !map->tileset) return;
|
||||
|
||||
/* Only render visible tiles */
|
||||
int start_x = 0, start_y = 0;
|
||||
int end_x = map->width, end_y = map->height;
|
||||
|
||||
if (cam) {
|
||||
start_x = (int)(cam->pos.x / TILE_SIZE) - 1;
|
||||
start_y = (int)(cam->pos.y / TILE_SIZE) - 1;
|
||||
end_x = start_x + (int)(cam->viewport.x / TILE_SIZE) + 3;
|
||||
end_y = start_y + (int)(cam->viewport.y / TILE_SIZE) + 3;
|
||||
|
||||
if (start_x < 0) start_x = 0;
|
||||
if (start_y < 0) start_y = 0;
|
||||
if (end_x > map->width) end_x = map->width;
|
||||
if (end_y > map->height) end_y = map->height;
|
||||
}
|
||||
|
||||
for (int y = start_y; y < end_y; y++) {
|
||||
for (int x = start_x; x < end_x; x++) {
|
||||
uint16_t tile_id = layer[y * map->width + x];
|
||||
if (tile_id == 0) continue; /* 0 = empty */
|
||||
|
||||
/* Look up tile definition for source rect */
|
||||
SDL_Rect src;
|
||||
if (tile_id < map->tile_def_count &&
|
||||
(map->tile_defs[tile_id].tex_x || map->tile_defs[tile_id].tex_y)) {
|
||||
src.x = map->tile_defs[tile_id].tex_x * TILE_SIZE;
|
||||
src.y = map->tile_defs[tile_id].tex_y * TILE_SIZE;
|
||||
} else {
|
||||
/* Fallback: tile_id maps directly to tileset grid position */
|
||||
int cols = map->tileset_cols > 0 ? map->tileset_cols : 16;
|
||||
src.x = ((tile_id - 1) % cols) * TILE_SIZE;
|
||||
src.y = ((tile_id - 1) / cols) * TILE_SIZE;
|
||||
}
|
||||
src.w = TILE_SIZE;
|
||||
src.h = TILE_SIZE;
|
||||
|
||||
Vec2 world_pos = vec2(tile_to_world(x), tile_to_world(y));
|
||||
Vec2 screen_pos = cam ? camera_world_to_screen(cam, world_pos) : world_pos;
|
||||
|
||||
SDL_Rect dst = {
|
||||
(int)screen_pos.x,
|
||||
(int)screen_pos.y,
|
||||
TILE_SIZE,
|
||||
TILE_SIZE
|
||||
};
|
||||
|
||||
SDL_RenderCopy(renderer, map->tileset, &src, &dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool tilemap_is_solid(const Tilemap *map, int tile_x, int tile_y) {
|
||||
if (!map) return false;
|
||||
if (tile_x < 0 || tile_x >= map->width) return true; /* out of bounds = solid */
|
||||
if (tile_y < 0) return false; /* above map = open */
|
||||
if (tile_y >= map->height) return true; /* below map = solid */
|
||||
|
||||
uint16_t tile_id = map->collision_layer[tile_y * map->width + tile_x];
|
||||
if (tile_id == 0) return false;
|
||||
|
||||
/* Check tile def flags */
|
||||
if (tile_id < map->tile_def_count) {
|
||||
return (map->tile_defs[tile_id].flags & TILE_SOLID) != 0;
|
||||
}
|
||||
|
||||
/* Default: non-zero collision tile is solid */
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t tilemap_flags_at(const Tilemap *map, int tile_x, int tile_y) {
|
||||
if (!map) return 0;
|
||||
if (tile_x < 0 || tile_x >= map->width) return TILE_SOLID;
|
||||
if (tile_y < 0) return 0;
|
||||
if (tile_y >= map->height) return TILE_SOLID;
|
||||
|
||||
uint16_t tile_id = map->collision_layer[tile_y * map->width + tile_x];
|
||||
if (tile_id == 0) return 0;
|
||||
|
||||
if (tile_id < map->tile_def_count) {
|
||||
return map->tile_defs[tile_id].flags;
|
||||
}
|
||||
|
||||
return TILE_SOLID; /* default non-zero = solid */
|
||||
}
|
||||
|
||||
void tilemap_free(Tilemap *map) {
|
||||
if (!map) return;
|
||||
free(map->collision_layer);
|
||||
free(map->bg_layer);
|
||||
free(map->fg_layer);
|
||||
/* Don't free tileset - asset manager owns it */
|
||||
memset(map, 0, sizeof(Tilemap));
|
||||
}
|
||||
Reference in New Issue
Block a user