add tilemap/tilesheet
This commit is contained in:
parent
c18896def0
commit
ff698730f1
13 changed files with 841 additions and 28 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
.ccls-cache/
|
||||
**.DS_Store
|
||||
|
|
|
|||
39
pxl8.sh
39
pxl8.sh
|
|
@ -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|"")
|
||||
|
|
|
|||
BIN
res/tiles/tilesheet-dungeon.ase
Normal file
BIN
res/tiles/tilesheet-dungeon.ase
Normal file
Binary file not shown.
BIN
res/tiles/tilesheet-world.ase
Normal file
BIN
res/tiles/tilesheet-world.ase
Normal file
Binary file not shown.
|
|
@ -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
226
src/fnl/prydain.fnl
Normal 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)))
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
215
src/pxl8_tilemap.c
Normal 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
80
src/pxl8_tilemap.h
Normal 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
182
src/pxl8_tilesheet.c
Normal 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
35
src/pxl8_tilesheet.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue