From b6676b443963184644cf553b7f4815c69dcaebab Mon Sep 17 00:00:00 2001 From: asrael Date: Wed, 24 Sep 2025 00:39:44 -0500 Subject: [PATCH] add tilemap/tilesheet --- res/tiles/tilesheet-dungeon.ase | Bin 0 -> 1715 bytes res/tiles/tilesheet-world.ase | Bin 0 -> 6336 bytes src/fnl/demo.fnl | 7 +- src/fnl/prydain.fnl | 226 ++++++++++++++++++++++++++++++++ src/lua/pxl8.lua | 60 +++++++++ src/pxl8_lua.c | 16 +++ src/pxl8_macros.h | 8 ++ src/pxl8_tilemap.c | 215 ++++++++++++++++++++++++++++++ src/pxl8_tilemap.h | 80 +++++++++++ src/pxl8_tilesheet.c | 182 +++++++++++++++++++++++++ src/pxl8_tilesheet.h | 35 +++++ 11 files changed, 823 insertions(+), 6 deletions(-) create mode 100644 res/tiles/tilesheet-dungeon.ase create mode 100644 res/tiles/tilesheet-world.ase create mode 100644 src/fnl/prydain.fnl create mode 100644 src/pxl8_tilemap.c create mode 100644 src/pxl8_tilemap.h create mode 100644 src/pxl8_tilesheet.c create mode 100644 src/pxl8_tilesheet.h diff --git a/res/tiles/tilesheet-dungeon.ase b/res/tiles/tilesheet-dungeon.ase new file mode 100644 index 0000000000000000000000000000000000000000..573598af117483b83e2edb8f145798690ff71a5d GIT binary patch literal 1715 zcmcJLk2}+O9KgTJ&su)WG`Y;ylZ);o%#S0?lDp#+JExEu(J@yOrTmx)SG2JFIB`d= zAC!xP;8ayf1da2c|Y&>`+2{f=hLJO0OM5% zARGt)bO8ha08#4vH;@2gwFwxi-~SN=wSPsX`KMJ=Ex@Q#2mhz?Jzbc(HpL_V?yP%n z@?Hr8fKLdZW-@7&ocL8TnX81auF{sAsV1qAqh`dANX@IT9LB0oXyP^gdkD}1{34Q% zBou1_fENC*8NBA7oz>4uDsK+CP*VGeZrB?BJs8e%4)8QjneXpvDx)6ov;JIWTkVtI zu(r+~XC}7x<5SiVS(NEWnsb@_?u(~VsO>9{S3_VBW|_^)-W}OJq6)pf?{>3lT;)^D zt_2r`wZsSh$H1x*v5eq0BN{24wjo%!+k!Rg>d;AML@Tr)t|Vlsi1%5)K=N6~N1zDO zw{gW$V`b$0ID4E+q!kk)6wPEdnFaFRX3j%;?QSeeR3~ITamA-xc0lPI`n&X$ z)fTJ^qhJW<&a*^;@h8xu*r4x{7%zJ2kEeIVJ+I*!!cIF7D$?~Hxg6{mXO(3vH*_0B zYqW>5rcyS-xx%k*CG)#9-gu@3$ghOS^bSF(Zh0LOoLS8NIj_y;?kcQT^PQEZhu@?- zUQDES)+v*s&NCcxgPx!ZuQ;B6Dn(6(7SG7+Z2f1FE!xl7Uc;xxBb8UbBgypHzvNP+ zE@1oz4m;)&cJhr;{=w`GU-H-EA7oE@c!cYC|HxgdaXD}66waUz&x{RU<7}m!M*ns0 zc@=aXzE(Xv5iOZT*p9PXB22i~&IuZ9aG6oZZW@WZ7=LK zJ`_2Al5PqX+!emOYoCL{QYJ2(aNvZbADoIXe=NCiv&DBX;$0Nv81wO9S6Vg0c>A5+ zV2O+*D}S9I!L&D*BRovooMumbbb?;Q#26}Wl%=CmVvoPv=uTXJc{2l}tTp)@b3@o_ zEkO-(dPH*5zmd!@V9;^e0%z&=RiiOu_J9Xn};aF z8~3*`L>0BEeZp=Av#WA;zw&Mb?Uvc=VN1n*Tv2f@(T=`jx_qC{XgxQ(1yE_cb}2#<^~_LGkm5Qeiep9M~?AmRLEW`JZsf2AS4av?3J|FtrTt&}g9 ze=Dxso}=&DBHeKL_JB(#3lto<=&qDIloGZ|A;u1lYHaS3o)S%nS!a_4U9K}dHFOQ+ z7)9z3%?5dKf#7UIygbOemwyma_;|;vwCNp)~p_ z#e!ZUM}@9NACw-Kcb=`WYLm(H;h{lC&k7^)&E=(U(~sFv=RHzY-nuoAE?kfEXW}oWzR>O6#aq{C^$oqK!U3xBU8N-D7%ul3|kS>h*=qELFs5fpZlhfN}L{T zp3l>2>xrguWhIa*!DMkukmB}*9?LH%05qn$V^(i#ebcFd;TLMwG{!!ASdRg6yz@v~K*bps28PI*`V-H8>4hsYC;rJ_W vN^P+3_8X1k>ZN@tqg7fF4MFGc2Mous#)Xq&r0c@zrH*E}Q(;1x2tfP+o-h+4 literal 0 HcmV?d00001 diff --git a/res/tiles/tilesheet-world.ase b/res/tiles/tilesheet-world.ase new file mode 100644 index 0000000000000000000000000000000000000000..e28fab20c230359a2ca544b871a75a9f63774381 GIT binary patch literal 6336 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 lv 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))) diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index c17a09c..30e422b 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -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 diff --git a/src/pxl8_lua.c b/src/pxl8_lua.c index 1ab9814..1c9a3fd 100644 --- a/src/pxl8_lua.c +++ b/src/pxl8_lua.c @@ -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" diff --git a/src/pxl8_macros.h b/src/pxl8_macros.h index cf70710..55b180f 100644 --- a/src/pxl8_macros.h +++ b/src/pxl8_macros.h @@ -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 diff --git a/src/pxl8_tilemap.c b/src/pxl8_tilemap.c new file mode 100644 index 0000000..2db24a3 --- /dev/null +++ b/src/pxl8_tilemap.c @@ -0,0 +1,215 @@ +#include "pxl8_tilemap.h" +#include "pxl8_tilesheet.h" +#include "pxl8_macros.h" +#include +#include + +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; +} \ No newline at end of file diff --git a/src/pxl8_tilemap.h b/src/pxl8_tilemap.h new file mode 100644 index 0000000..764a62a --- /dev/null +++ b/src/pxl8_tilemap.h @@ -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 \ No newline at end of file diff --git a/src/pxl8_tilesheet.c b/src/pxl8_tilesheet.c new file mode 100644 index 0000000..f7865a4 --- /dev/null +++ b/src/pxl8_tilesheet.c @@ -0,0 +1,182 @@ +#include "pxl8_tilesheet.h" +#include "pxl8_tilemap.h" +#include "pxl8_ase.h" +#include "pxl8_macros.h" +#include +#include + +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); +} \ No newline at end of file diff --git a/src/pxl8_tilesheet.h b/src/pxl8_tilesheet.h new file mode 100644 index 0000000..e40fdbf --- /dev/null +++ b/src/pxl8_tilesheet.h @@ -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 \ No newline at end of file