add tilemap/tilesheet

This commit is contained in:
asrael 2025-09-24 00:39:44 -05:00
parent c18896def0
commit ff698730f1
13 changed files with 841 additions and 28 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
.ccls-cache/
**.DS_Store

39
pxl8.sh
View file

@ -83,16 +83,12 @@ print_usage() {
echo " run Build and run pxl8 [script.fnl|script.lua]"
echo " clean Remove build artifacts"
echo " update Download/update all dependencies"
echo " vendor Build specific dependencies from source"
echo " vendor Fetch source for dependencies (ex. sdl3)"
echo " help Show this help message"
echo
echo -e "${BOLD}OPTIONS:${NC}"
echo " --all Remove all artifacts including dependencies when cleaning"
echo " --release Build/run in release mode"
echo
echo -e "${BOLD}VENDOR OPTIONS:${NC}"
echo " --sdl Build SDL3 from source"
echo " --all Build all vendorable dependencies"
}
COMMAND="$1"
@ -342,8 +338,22 @@ case "$COMMAND" in
COMPILE_FLAGS="$CFLAGS $INCLUDES"
EXECUTABLE="$BINDIR/pxl8"
LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c"
SRC_SOURCE_FILES="src/pxl8.c src/pxl8_gfx.c src/pxl8_ase.c src/pxl8_font.c src/pxl8_io.c src/pxl8_lua.c src/pxl8_vfx.c src/pxl8_blit.c"
SRC_SOURCE_FILES="
src/pxl8.c
src/pxl8_ase.c
src/pxl8_blit.c
src/pxl8_font.c
src/pxl8_gfx.c
src/pxl8_io.c
src/pxl8_lua.c
src/pxl8_tilemap.c
src/pxl8_tilesheet.c
src/pxl8_vfx.c
"
LUAJIT_LIB="lib/luajit/src/libluajit.a"
OBJECT_DIR="$BUILDDIR/obj"
mkdir -p "$OBJECT_DIR"
@ -439,22 +449,7 @@ case "$COMMAND" in
;;
vendor)
for arg in "$@"; do
case $arg in
--sdl)
vendor_sdl
;;
--all)
vendor_sdl
;;
*)
;;
esac
done
if [[ -z "$1" ]]; then
vendor_sdl
fi
vendor_sdl
;;
help|--help|-h|"")

Binary file not shown.

Binary file not shown.

View file

@ -1,23 +1,18 @@
(local pxl8 (require :pxl8))
(var frame 0)
(var pxl8-sprite-id nil)
(var screen nil)
(var time 0)
(global init (fn []
(pxl8.load_palette "./res/palettes/gruvbox.ase")
(set pxl8-sprite-id (pxl8.load_sprite "./res/sprites/pxl8.ase"))
(set screen (pxl8.get_screen))
(when (not pxl8-sprite-id)
(pxl8.error "Failed to load pxl8 sprite"))))
(global update (fn [dt]))
(global update (fn [_dt]))
(global draw (fn []
(pxl8.clr 1)
(local cols 5)
(local rows 1024)
(local sprite-w 128)
(local sprite-h 64)

226
src/fnl/prydain.fnl Normal file
View file

@ -0,0 +1,226 @@
(local pxl8 (require :pxl8))
(local TILE_SIZE 16)
(local SCREEN_WIDTH 320)
(local SCREEN_HEIGHT 240)
(var game-state {
:tilesheet nil
:tilemap nil
:camera {:x 0 :y 0}
:player {:x 5 :y 5
:screen-x 80 :screen-y 80
:facing :down
:moving false
:move-timer 0}
})
(local tile-types {
:empty 0
:floor 1
:wall-top 2
:wall-front 3
:wall-left 4
:wall-right 5
:wall-corner-tl 6
:wall-corner-tr 7
:wall-corner-bl 8
:wall-corner-br 9
:door 10
:chest 11
:stairs-up 12
:stairs-down 13
})
(fn generate-room [map x y width height]
(for [ry y (+ y height -1)]
(for [rx x (+ x width -1)]
(when (and (< rx 40) (< ry 30))
(let [is-wall (or (= rx x)
(= rx (+ x width -1))
(= ry y)
(= ry (+ y height -1)))]
(if is-wall
(tset map ry rx :wall)
(tset map ry rx :floor)))))))
(fn generate-dungeon []
(local map {})
(for [y 0 29]
(tset map y {})
(for [x 0 39]
(tset map y x :empty)))
(local rooms [])
(local room-count (+ 5 (math.random 5)))
(for [i 1 room-count]
(local room-w (+ 4 (math.random 6)))
(local room-h (+ 4 (math.random 6)))
(local room-x (math.random 0 (- 39 room-w)))
(local room-y (math.random 0 (- 29 room-h)))
(generate-room map room-x room-y room-w room-h)
(table.insert rooms {:x (+ room-x (math.floor (/ room-w 2)))
:y (+ room-y (math.floor (/ room-h 2)))
:w room-w
:h room-h}))
(for [i 1 (- (length rooms) 1)]
(let [room1 (. rooms i)
room2 (. rooms (+ i 1))
start-x room1.x
start-y room1.y
end-x room2.x
end-y room2.y]
(var cx start-x)
(var cy start-y)
(while (not= cx end-x)
(set cx (+ cx (if (< cx end-x) 1 -1)))
(when (= (. map cy cx) :empty)
(tset map cy cx :floor)))
(while (not= cy end-y)
(set cy (+ cy (if (< cy end-y) 1 -1)))
(when (= (. map cy cx) :empty)
(tset map cy cx :floor)))))
(let [first-room (. rooms 1)]
(set game-state.player.x first-room.x)
(set game-state.player.y first-room.y)
(set game-state.player.screen-x (* first-room.x TILE_SIZE))
(set game-state.player.screen-y (* first-room.y TILE_SIZE)))
map)
(fn map-to-tilemap [map tilemap]
(for [y 0 29]
(for [x 0 39]
(let [tile (. map y x)
above (and (> y 0) (. map (- y 1) x))
below (and (< y 29) (. map (+ y 1) x))
left (and (> x 0) (. map y (- x 1)))
right (and (< x 39) (. map y (+ x 1)))]
(if (= tile :floor)
(pxl8.tilemap_set_tile tilemap 0 x y tile-types.floor 0)
(= tile :wall)
(let [is-top-wall (and below (= below :floor))
is-corner-tl (and (= above :empty) (= left :empty))
is-corner-tr (and (= above :empty) (= right :empty))
is-corner-bl (and (= below :floor) (= left :empty))
is-corner-br (and (= below :floor) (= right :empty))]
(if is-corner-tl (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-corner-tl pxl8.TILE_SOLID)
is-corner-tr (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-corner-tr pxl8.TILE_SOLID)
is-corner-bl (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-corner-bl pxl8.TILE_SOLID)
is-corner-br (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-corner-br pxl8.TILE_SOLID)
is-top-wall (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-top pxl8.TILE_SOLID)
(pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-front pxl8.TILE_SOLID)))
:else
(pxl8.tilemap_set_tile tilemap 0 x y 0 0))))))
(fn move-player [dx dy]
(let [p game-state.player
new-x (+ p.x dx)
new-y (+ p.y dy)]
(when (and (>= new-x 0) (< new-x 40)
(>= new-y 0) (< new-y 30)
(not (pxl8.tilemap_is_solid game-state.tilemap
(* new-x TILE_SIZE)
(* new-y TILE_SIZE))))
(set p.x new-x)
(set p.y new-y)
(set p.screen-x (* new-x TILE_SIZE))
(set p.screen-y (* new-y TILE_SIZE))
(set p.moving true)
(set p.move-timer 0.2))))
(global init (fn []
(pxl8.info "Prydain RPG initializing...")
(pxl8.load_palette "./res/palettes/gruvbox.ase")
(set game-state.tilesheet (pxl8.tilesheet_new TILE_SIZE))
(set game-state.tilemap (pxl8.tilemap_new 40 30 TILE_SIZE))
(if (and game-state.tilesheet game-state.tilemap)
(do
(let [load-result (pxl8.tilesheet_load game-state.tilesheet "./res/tiles/tilesheet-dungeon.ase")]
(if (= load-result 0)
(pxl8.info "Loaded dungeon tilesheet")
(do
(pxl8.warn "Failed to load dungeon tilesheet, using placeholder tiles")
(pxl8.info (.. "Error code: " load-result)))))
(pxl8.tilemap_set_tilesheet game-state.tilemap game-state.tilesheet)
(pxl8.info "Created tilemap 40x30 and tilesheet")
(local dungeon-map (generate-dungeon))
(map-to-tilemap dungeon-map game-state.tilemap)
(pxl8.info "Random dungeon generated"))
(pxl8.error "Failed to create tilemap or tilesheet"))))
(global update (fn [dt]
(let [p game-state.player]
(when (> p.move-timer 0)
(set p.move-timer (- p.move-timer dt))
(when (<= p.move-timer 0)
(set p.moving false)))
(when (not p.moving)
(when (pxl8.key_pressed "w")
(set p.facing :up)
(move-player 0 -1))
(when (pxl8.key_pressed "s")
(set p.facing :down)
(move-player 0 1))
(when (pxl8.key_pressed "a")
(set p.facing :left)
(move-player -1 0))
(when (pxl8.key_pressed "d")
(set p.facing :right)
(move-player 1 0))
(when (pxl8.key_pressed "r")
(pxl8.info "Regenerating dungeon...")
(local new-map (generate-dungeon))
(map-to-tilemap new-map game-state.tilemap)))
(let [target-cam-x (- p.screen-x (/ SCREEN_WIDTH 2))
target-cam-y (- p.screen-y (/ SCREEN_HEIGHT 2))]
(set game-state.camera.x (math.max 0 (math.min target-cam-x (* (- 40 20) TILE_SIZE))))
(set game-state.camera.y (math.max 0 (math.min target-cam-y (* (- 30 15) TILE_SIZE)))))
(when game-state.tilemap
(pxl8.tilemap_set_camera game-state.tilemap
game-state.camera.x
game-state.camera.y)))))
(global draw (fn []
(pxl8.clr 0)
(when game-state.tilemap
(pxl8.tilemap_render game-state.tilemap))
(let [p game-state.player
px (- p.screen-x game-state.camera.x)
py (- p.screen-y game-state.camera.y)]
(pxl8.rect_fill px py TILE_SIZE TILE_SIZE 14)
(case p.facing
:up (pxl8.rect_fill (+ px 6) py 4 4 15)
:down (pxl8.rect_fill (+ px 6) (+ py 12) 4 4 15)
:left (pxl8.rect_fill px (+ py 6) 4 4 15)
:right (pxl8.rect_fill (+ px 12) (+ py 6) 4 4 15)))
(pxl8.text "WASD: Move | R: New Dungeon" 5 5 15)
(pxl8.text (.. "Pos: " game-state.player.x "," game-state.player.y) 5 15 15)))

View file

@ -189,4 +189,64 @@ function pxl8.gfx_fade_palette(start, count, amount, target_color)
C.pxl8_gfx_fade_palette(gfx, start, count, amount, target_color)
end
function pxl8.tilesheet_new(tile_size)
return C.pxl8_tilesheet_new(tile_size or 16)
end
function pxl8.tilesheet_destroy(tilesheet)
C.pxl8_tilesheet_destroy(tilesheet)
end
function pxl8.tilesheet_load(tilesheet, filepath)
return C.pxl8_tilesheet_load(tilesheet, filepath, gfx)
end
function pxl8.tilemap_new(width, height, tile_size)
return C.pxl8_tilemap_new(width, height, tile_size or 16)
end
function pxl8.tilemap_destroy(tilemap)
C.pxl8_tilemap_destroy(tilemap)
end
function pxl8.tilemap_set_tilesheet(tilemap, tilesheet)
return C.pxl8_tilemap_set_tilesheet(tilemap, tilesheet)
end
function pxl8.tilemap_set_tile(tilemap, layer, x, y, tile_id, flags)
C.pxl8_tilemap_set_tile(tilemap, layer or 0, x, y, tile_id or 0, flags or 0)
end
function pxl8.tilemap_get_tile_id(tilemap, layer, x, y)
return C.pxl8_tilemap_get_tile_id(tilemap, layer or 0, x, y)
end
function pxl8.tilemap_set_camera(tilemap, x, y)
C.pxl8_tilemap_set_camera(tilemap, x, y)
end
function pxl8.tilemap_render(tilemap)
C.pxl8_tilemap_render(tilemap, gfx)
end
function pxl8.tilemap_render_layer(tilemap, layer)
C.pxl8_tilemap_render_layer(tilemap, gfx, layer)
end
function pxl8.tilemap_is_solid(tilemap, x, y)
return C.pxl8_tilemap_is_solid(tilemap, x, y)
end
function pxl8.tilemap_check_collision(tilemap, x, y, w, h)
return C.pxl8_tilemap_check_collision(tilemap, x, y, w, h)
end
pxl8.TILE_FLIP_X = 1
pxl8.TILE_FLIP_Y = 2
pxl8.TILE_SOLID = 4
pxl8.TILE_TRIGGER = 8
pxl8.gfx = gfx
pxl8.input = input
return pxl8

View file

@ -46,6 +46,22 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_key_down(const pxl8_input_state* input, i32 key);\n"
"bool pxl8_key_pressed(const pxl8_input_state* input, i32 key);\n"
"\n"
"typedef struct pxl8_tilesheet pxl8_tilesheet;\n"
"typedef struct pxl8_tilemap pxl8_tilemap;\n"
"pxl8_tilesheet* pxl8_tilesheet_new(u32 tile_size);\n"
"void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);\n"
"i32 pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx);\n"
"pxl8_tilemap* pxl8_tilemap_new(u32 width, u32 height, u32 tile_size);\n"
"void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);\n"
"i32 pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);\n"
"void pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);\n"
"u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n"
"void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);\n"
"void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx);\n"
"void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u32 layer);\n"
"bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);\n"
"bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);\n"
"\n"
"typedef struct {\n"
" float x, y, z;\n"
" float vx, vy, vz;\n"

View file

@ -81,3 +81,11 @@ static inline void pxl8_log_timestamp(char* buffer, size_t size) {
fprintf(stdout, __VA_ARGS__); \
fprintf(stdout, "\n"); \
} while(0)
#ifndef pxl8_min
#define pxl8_min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef pxl8_max
#define pxl8_max(a, b) ((a) > (b) ? (a) : (b))
#endif

215
src/pxl8_tilemap.c Normal file
View file

@ -0,0 +1,215 @@
#include "pxl8_tilemap.h"
#include "pxl8_tilesheet.h"
#include "pxl8_macros.h"
#include <stdlib.h>
#include <string.h>
pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32 tile_size) {
if (!tilemap) return PXL8_ERROR_NULL_POINTER;
if (width > PXL8_MAX_TILEMAP_WIDTH || height > PXL8_MAX_TILEMAP_HEIGHT) {
return PXL8_ERROR_INVALID_SIZE;
}
memset(tilemap, 0, sizeof(pxl8_tilemap));
tilemap->width = width;
tilemap->height = height;
tilemap->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
tilemap->active_layers = 1;
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
tilemap->layers[i].width = width;
tilemap->layers[i].height = height;
tilemap->layers[i].visible = (i == 0);
tilemap->layers[i].opacity = 255;
size_t tiles_size = width * height * sizeof(pxl8_tile);
tilemap->layers[i].tiles = calloc(1, tiles_size);
if (!tilemap->layers[i].tiles) {
for (u32 j = 0; j < i; j++) {
free(tilemap->layers[j].tiles);
}
return PXL8_ERROR_OUT_OF_MEMORY;
}
}
return PXL8_OK;
}
void pxl8_tilemap_free(pxl8_tilemap* tilemap) {
if (!tilemap) return;
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
if (tilemap->layers[i].tiles) {
free(tilemap->layers[i].tiles);
tilemap->layers[i].tiles = NULL;
}
}
}
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet) {
if (!tilemap || !tilesheet) return PXL8_ERROR_NULL_POINTER;
tilemap->tilesheet = tilesheet;
if (tilesheet->tile_size != tilemap->tile_size) {
pxl8_warn("Tilesheet tile size (%d) differs from tilemap tile size (%d)",
tilesheet->tile_size, tilemap->tile_size);
tilemap->tile_size = tilesheet->tile_size;
}
return PXL8_OK;
}
pxl8_result pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags) {
if (!tilemap) return PXL8_ERROR_NULL_POINTER;
if (layer >= PXL8_MAX_TILE_LAYERS) return PXL8_ERROR_INVALID_ARGUMENT;
if (x >= tilemap->width || y >= tilemap->height) return PXL8_ERROR_INVALID_COORDINATE;
pxl8_tilemap_layer* l = &tilemap->layers[layer];
u32 idx = y * tilemap->width + x;
l->tiles[idx].id = tile_id;
l->tiles[idx].flags = flags;
if (layer >= tilemap->active_layers) {
tilemap->active_layers = layer + 1;
l->visible = true;
}
return PXL8_OK;
}
pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
pxl8_tile empty_tile = {0, 0, 0};
if (!tilemap || layer >= PXL8_MAX_TILE_LAYERS) return empty_tile;
if (x >= tilemap->width || y >= tilemap->height) return empty_tile;
return tilemap->layers[layer].tiles[y * tilemap->width + x];
}
void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y) {
if (!tilemap) return;
tilemap->camera_x = x;
tilemap->camera_y = y;
}
void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx_ctx* gfx, pxl8_tilemap_view* view) {
if (!tilemap || !gfx || !view) return;
view->x = -tilemap->camera_x;
view->y = -tilemap->camera_y;
view->width = gfx->framebuffer_width;
view->height = gfx->framebuffer_height;
view->tile_start_x = pxl8_max(0, tilemap->camera_x / (i32)tilemap->tile_size);
view->tile_start_y = pxl8_max(0, tilemap->camera_y / (i32)tilemap->tile_size);
view->tile_end_x = pxl8_min((i32)tilemap->width,
(tilemap->camera_x + view->width) / (i32)tilemap->tile_size + 1);
view->tile_end_y = pxl8_min((i32)tilemap->height,
(tilemap->camera_y + view->height) / (i32)tilemap->tile_size + 1);
}
void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u16 tile_id, i32 x, i32 y, u8 flags) {
if (!tilemap || !gfx || !tilemap->tilesheet) return;
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, x, y, flags);
}
void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u32 layer) {
if (!tilemap || !gfx || layer >= tilemap->active_layers) return;
if (!tilemap->tilesheet) {
pxl8_warn("No tilesheet set for tilemap");
return;
}
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
if (!l->visible) return;
pxl8_tilemap_view view;
pxl8_tilemap_get_view(tilemap, gfx, &view);
for (i32 ty = view.tile_start_y; ty < view.tile_end_y; ty++) {
for (i32 tx = view.tile_start_x; tx < view.tile_end_x; tx++) {
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
if (tile.id == 0) continue;
i32 screen_x = tx * tilemap->tile_size - tilemap->camera_x;
i32 screen_y = ty * tilemap->tile_size - tilemap->camera_y;
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile.id, screen_x, screen_y, tile.flags);
}
}
}
void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx) {
if (!tilemap || !gfx) return;
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
pxl8_tilemap_render_layer(tilemap, gfx, layer);
}
}
bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y) {
if (!tilemap) return true;
u32 tile_x = x / tilemap->tile_size;
u32 tile_y = y / tilemap->tile_size;
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tile_x, tile_y);
if (tile.flags & PXL8_TILE_SOLID) return true;
}
return false;
}
bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h) {
if (!tilemap) return true;
i32 left = x / tilemap->tile_size;
i32 top = y / tilemap->tile_size;
i32 right = (x + w - 1) / tilemap->tile_size;
i32 bottom = (y + h - 1) / tilemap->tile_size;
for (i32 ty = top; ty <= bottom; ty++) {
for (i32 tx = left; tx <= right; tx++) {
if (tx < 0 || tx >= (i32)tilemap->width ||
ty < 0 || ty >= (i32)tilemap->height) return true;
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
if (tile.flags & PXL8_TILE_SOLID) return true;
}
}
}
return false;
}
pxl8_tilemap* pxl8_tilemap_new(u32 width, u32 height, u32 tile_size) {
pxl8_tilemap* tilemap = calloc(1, sizeof(pxl8_tilemap));
if (!tilemap) {
pxl8_error("Failed to allocate tilemap");
return NULL;
}
pxl8_result result = pxl8_tilemap_init(tilemap, width, height, tile_size);
if (result != PXL8_OK) {
pxl8_error("Failed to initialize tilemap");
free(tilemap);
return NULL;
}
return tilemap;
}
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap) {
if (!tilemap) return;
pxl8_tilemap_free(tilemap);
free(tilemap);
}
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
if (!tilemap) return 0;
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, x, y);
return tile.id;
}

80
src/pxl8_tilemap.h Normal file
View file

@ -0,0 +1,80 @@
#pragma once
#include "pxl8_types.h"
#include "pxl8_gfx.h"
#include "pxl8_tilesheet.h"
#define PXL8_TILE_SIZE 16
#define PXL8_MAX_TILEMAP_WIDTH 256
#define PXL8_MAX_TILEMAP_HEIGHT 256
#define PXL8_MAX_TILE_LAYERS 4
typedef enum pxl8_tile_flags {
PXL8_TILE_FLIP_X = 1 << 0,
PXL8_TILE_FLIP_Y = 1 << 1,
PXL8_TILE_SOLID = 1 << 2,
PXL8_TILE_TRIGGER = 1 << 3,
} pxl8_tile_flags;
typedef struct pxl8_tile {
u16 id;
u8 flags;
u8 palette_offset;
} pxl8_tile;
typedef struct pxl8_tilemap_layer {
pxl8_tile* tiles;
u32 width;
u32 height;
bool visible;
u8 opacity;
} pxl8_tilemap_layer;
typedef struct pxl8_tilemap {
pxl8_tilemap_layer layers[PXL8_MAX_TILE_LAYERS];
u32 width;
u32 height;
u32 tile_size;
u32 active_layers;
pxl8_tilesheet* tilesheet;
i32 camera_x;
i32 camera_y;
} pxl8_tilemap;
typedef struct pxl8_tilemap_view {
i32 x, y;
i32 width, height;
i32 tile_start_x, tile_start_y;
i32 tile_end_x, tile_end_y;
} pxl8_tilemap_view;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32 tile_size);
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);
void pxl8_tilemap_free(pxl8_tilemap* tilemap);
pxl8_result pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);
pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);
void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx_ctx* gfx, pxl8_tilemap_view* view);
void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx);
void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u32 layer);
void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u16 tile_id, i32 x, i32 y, u8 flags);
bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);
bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);
pxl8_tilemap* pxl8_tilemap_new(u32 width, u32 height, u32 tile_size);
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
#ifdef __cplusplus
}
#endif

182
src/pxl8_tilesheet.c Normal file
View file

@ -0,0 +1,182 @@
#include "pxl8_tilesheet.h"
#include "pxl8_tilemap.h"
#include "pxl8_ase.h"
#include "pxl8_macros.h"
#include <stdlib.h>
#include <string.h>
pxl8_result pxl8_tilesheet_init(pxl8_tilesheet* tilesheet, u32 tile_size) {
if (!tilesheet) return PXL8_ERROR_NULL_POINTER;
memset(tilesheet, 0, sizeof(pxl8_tilesheet));
tilesheet->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
return PXL8_OK;
}
void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet) {
if (!tilesheet) return;
if (tilesheet->data) {
free(tilesheet->data);
tilesheet->data = NULL;
}
if (tilesheet->tile_valid) {
free(tilesheet->tile_valid);
tilesheet->tile_valid = NULL;
}
}
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx) {
if (!tilesheet || !filepath || !gfx) return PXL8_ERROR_NULL_POINTER;
pxl8_ase_file ase_file;
pxl8_result result = pxl8_ase_load(filepath, &ase_file);
if (result != PXL8_OK) {
pxl8_error("Failed to load tilesheet: %s", filepath);
return result;
}
if (tilesheet->data) {
free(tilesheet->data);
}
u32 width = ase_file.header.width;
u32 height = ase_file.header.height;
if (ase_file.header.grid_width > 0 && ase_file.header.grid_height > 0) {
tilesheet->tile_size = ase_file.header.grid_width;
pxl8_info("Using Aseprite grid size: %dx%d",
ase_file.header.grid_width, ase_file.header.grid_height);
}
tilesheet->width = width;
tilesheet->height = height;
tilesheet->tiles_per_row = width / tilesheet->tile_size;
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
tilesheet->color_mode = gfx->color_mode;
size_t data_size = width * height;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
data_size *= 4;
}
tilesheet->data = malloc(data_size);
if (!tilesheet->data) {
pxl8_ase_free(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) {
memcpy(tilesheet->data, ase_file.frames[0].pixels, data_size);
}
tilesheet->tile_valid = calloc(tilesheet->total_tiles + 1, sizeof(bool));
if (!tilesheet->tile_valid) {
free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_free(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
u32 valid_tiles = 0;
bool is_hicolor = (tilesheet->color_mode == PXL8_COLOR_MODE_HICOLOR);
for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) {
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
u32 tile_y = ((tile_id - 1) / tilesheet->tiles_per_row) * tilesheet->tile_size;
bool has_content = false;
for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) {
for (u32 px = 0; px < tilesheet->tile_size; px++) {
if (is_hicolor) {
u32 idx = ((tile_y + py) * width + (tile_x + px)) * 4;
u8 alpha = tilesheet->data[idx + 3];
if (alpha > 0) {
has_content = true;
break;
}
} else {
u32 idx = (tile_y + py) * width + (tile_x + px);
if (tilesheet->data[idx] != 0) {
has_content = true;
break;
}
}
}
}
if (has_content) {
tilesheet->tile_valid[tile_id] = true;
valid_tiles++;
}
}
pxl8_info("Loaded tilesheet %s: %dx%d, %d valid tiles out of %d slots (%dx%d each)",
filepath, width, height, valid_tiles, tilesheet->total_tiles,
tilesheet->tile_size, tilesheet->tile_size);
pxl8_ase_free(&ase_file);
return PXL8_OK;
}
void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx_ctx* gfx,
u16 tile_id, i32 x, i32 y, u8 flags) {
if (!tilesheet || !gfx || !tilesheet->data) return;
if (tile_id == 0 || tile_id > tilesheet->total_tiles) return;
if (tilesheet->tile_valid && !tilesheet->tile_valid[tile_id]) return;
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
u32 tile_y = ((tile_id - 1) / tilesheet->tiles_per_row) * tilesheet->tile_size;
for (u32 py = 0; py < tilesheet->tile_size; py++) {
for (u32 px = 0; px < tilesheet->tile_size; px++) {
u32 src_x = tile_x + px;
u32 src_y = tile_y + py;
if (flags & PXL8_TILE_FLIP_X) src_x = tile_x + (tilesheet->tile_size - 1 - px);
if (flags & PXL8_TILE_FLIP_Y) src_y = tile_y + (tilesheet->tile_size - 1 - py);
u32 src_idx = src_y * tilesheet->width + src_x;
u8 color_idx = tilesheet->data[src_idx];
if (color_idx != 0) {
i32 screen_x = x + px;
i32 screen_y = y + py;
if (screen_x >= 0 && screen_x < gfx->framebuffer_width &&
screen_y >= 0 && screen_y < gfx->framebuffer_height) {
pxl8_pixel(gfx, screen_x, screen_y, color_idx);
}
}
}
}
}
bool pxl8_tilesheet_is_tile_valid(const pxl8_tilesheet* tilesheet, u16 tile_id) {
if (!tilesheet || tile_id == 0 || tile_id > tilesheet->total_tiles) return false;
return tilesheet->tile_valid ? tilesheet->tile_valid[tile_id] : true;
}
pxl8_tilesheet* pxl8_tilesheet_new(u32 tile_size) {
pxl8_tilesheet* tilesheet = calloc(1, sizeof(pxl8_tilesheet));
if (!tilesheet) {
pxl8_error("Failed to allocate tilesheet");
return NULL;
}
pxl8_result result = pxl8_tilesheet_init(tilesheet, tile_size);
if (result != PXL8_OK) {
pxl8_error("Failed to initialize tilesheet");
free(tilesheet);
return NULL;
}
return tilesheet;
}
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) {
if (!tilesheet) return;
pxl8_tilesheet_free(tilesheet);
free(tilesheet);
}

35
src/pxl8_tilesheet.h Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include "pxl8_types.h"
#include "pxl8_gfx.h"
typedef struct pxl8_tilesheet {
u8* data;
bool* tile_valid;
u32 width;
u32 height;
u32 tile_size;
u32 tiles_per_row;
u32 total_tiles;
pxl8_color_mode color_mode;
} pxl8_tilesheet;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_result pxl8_tilesheet_init(pxl8_tilesheet* tilesheet, u32 tile_size);
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx);
void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet);
void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx_ctx* gfx,
u16 tile_id, i32 x, i32 y, u8 flags);
bool pxl8_tilesheet_is_tile_valid(const pxl8_tilesheet* tilesheet, u16 tile_id);
pxl8_tilesheet* pxl8_tilesheet_new(u32 tile_size);
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);
#ifdef __cplusplus
}
#endif