A game engine for top-down 2D RPG games.
rpg game-engine raylib c99

Implement basic tile rendering and a sanity checker

+4
compile_flags.txt
··· 1 1 -Wall 2 2 -Wextra 3 3 -Werror 4 + 4 5 -std=c99 5 6 -Iinclude/ 6 7 -xc-header 8 + 9 + -DKF_SANITY_CHECKS 10 + -DKF_GNU
+1
include/keraforge.h
··· 4 4 5 5 #include <keraforge/_header.h> 6 6 #include <keraforge/actor.h> 7 + #include <keraforge/error.h> 7 8 #include <keraforge/fs.h> 8 9 #include <keraforge/graphics.h> 9 10 #include <keraforge/input.h>
+41
include/keraforge/_header.h
··· 7 7 #include <stdbool.h> 8 8 9 9 10 + #ifndef __FUNCTION_NAME__ 11 + # ifdef WIN32 12 + # define __FUNCTION_NAME__ __FUNCTION__ 13 + # else 14 + # define __FUNCTION_NAME__ __func__ 15 + # endif 16 + #endif 17 + 18 + 19 + #ifdef KF_SANITY_CHECKS 20 + # include <stdio.h> /* fprintf, stderr */ 21 + # include <stdlib.h> /* exit */ 22 + # define KF_SANITY_CHECK(EXPR, ...) \ 23 + do \ 24 + { \ 25 + if (!(EXPR)) \ 26 + { \ 27 + fprintf(stderr, "\x1b[1;31msanity check failed: \x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m", __FILE__, __LINE__, __FUNCTION_NAME__); \ 28 + fprintf(stderr, __VA_ARGS__); \ 29 + fprintf(stderr, "\n"); \ 30 + kf_printbacktrace(stderr); \ 31 + exit(1); \ 32 + } \ 33 + } \ 34 + while (0) 35 + # define KF_UNREACHABLE(...) \ 36 + do \ 37 + { \ 38 + fprintf(stderr, "\x1b[1;31munreachable: \x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m", __FILE__, __LINE__, __FUNCTION_NAME__); \ 39 + fprintf(stderr, __VA_ARGS__); \ 40 + fprintf(stderr, "\n"); \ 41 + kf_printbacktrace(stderr); \ 42 + exit(1); \ 43 + } \ 44 + while (0) 45 + #else 46 + # define KF_SANITY_CHECK(EXPR, ...) do { ; } while (0) 47 + # define KF_UNREACHABLE(...) do { ; } while (0) 48 + #endif 49 + 50 + 10 51 typedef int8_t i8; 11 52 typedef int16_t i16; 12 53 typedef int32_t i32;
+11
include/keraforge/error.h
··· 1 + #ifndef __kf_error__ 2 + #define __kf_error__ 3 + 4 + 5 + #include <keraforge/_header.h> 6 + 7 + 8 + void kf_printbacktrace(FILE *file); 9 + 10 + 11 + #endif
+2
include/keraforge/sprites.h
··· 18 18 19 19 /* Load a sprite sheet with the given sprite width/height. */ 20 20 struct kf_spritesheet kf_loadspritesheet(char *filename, int spritewidth, int spriteheight); 21 + /* Draw a single sprite from the sheet with a provided width and height. */ 22 + void kf_drawsprite_wh(struct kf_spritesheet *sheet, f32 x, f32 y, f32 w, f32 h, int spritex, int spritey); 21 23 /* Draw a single sprite from the sheet at the given coordinates. */ 22 24 void kf_drawsprite(struct kf_spritesheet *sheet, f32 x, f32 y, int spritex, int spritey); 23 25
+39 -3
include/keraforge/world.h
··· 4 4 5 5 #include <keraforge/_header.h> 6 6 #include <keraforge/math.h> 7 + #include <keraforge/sprites.h> 7 8 #include <raylib.h> 8 9 10 + 11 + #define KF_TILE_SIZE_PX 16 9 12 10 13 #define KF_TILEID_MAX UINT16_MAX 11 14 typedef u16 kf_tileid_t; 12 - #define KF_TILE_SIZE_PX 16 15 + /* Used to store connectivity variant. */ 16 + typedef u16 kf_tiledatum_t; 13 17 14 18 15 19 struct kf_actor; /* Forward declaration */ 16 20 17 21 22 + #define KF_TILEMASK_NORTH (0x0001) 23 + #define KF_TILEMASK_WEST (0x0010) 24 + #define KF_TILEMASK_EAST (0x0100) 25 + #define KF_TILEMASK_SOUTH (0x1000) 26 + 27 + 28 + /* Represents a singular tile in the world. */ 29 + struct kf_tile 30 + { 31 + kf_tileid_t id; 32 + kf_tiledatum_t data; 33 + }; 34 + 18 35 /* Represents a world (or a subworld, often called "rooms"). */ 19 36 struct kf_world 20 37 { ··· 30 47 /* Height of the map. */ 31 48 u32 height; 32 49 /* Array of tiles in the map. Use kf_gettile to get a tile using an X/Y position. */ 33 - kf_tileid_t map[]; 50 + struct kf_tile map[]; 34 51 }; 35 52 36 53 /* Struct-of-arrays for tiles. See: kf_tiles */ ··· 51 68 /* Struct-of-arrays for tiles. */ 52 69 extern struct _kf_tiles kf_tiles; 53 70 71 + /* Options for creating tiles using addtile and addtiles. */ 72 + struct kf_tile_opts 73 + { 74 + char *key; 75 + Color mapcol; 76 + struct kf_spritesheet *sheet; 77 + struct kf_vec2(u32) sprite; 78 + bool collide; 79 + }; 80 + 81 + kf_tileid_t kf_addtile(struct kf_tile_opts opts); 82 + #define KF_ADDTILE(...) (kf_addtile((struct kf_tile_opts){ __VA_ARGS__ })) 83 + 54 84 /* Create a world using the given width and height. 55 85 Fills the map with the given `fill` tile. */ 56 86 struct kf_world *kf_world_new(u32 width, u32 height, kf_tileid_t fill); ··· 58 88 /* Get the size of the world in bytes. */ 59 89 size_t kf_world_getsize(struct kf_world *world); 60 90 91 + /* Get the sprite offset for a given tile datum. */ 92 + struct kf_vec2(u32) kf_getspritefortilebitmask(kf_tiledatum_t t); 93 + 94 + /* Update a tile and optionally its neighbours. */ 95 + void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours); 96 + 61 97 /* Get a pointer to the tile ID at a given position. */ 62 - kf_tileid_t *kf_world_gettile(struct kf_world *world, u32 x, u32 y); 98 + struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y); 63 99 64 100 /* Draw visible collision rectangles. */ 65 101 void kf_world_drawcolliders(struct kf_world *world, struct kf_actor *player, Camera2D camera);
+5 -2
scripts/_config.sh
··· 7 7 export CC="ccache $CC" 8 8 fi 9 9 10 - export CFLAGS="-Wall -Wextra -Werror -std=c99 -Iinclude/ -c" 11 - export LFLAGS="-lraylib -lm -lGL -lpthread -ldl -lrt -lX11" 10 + export KF_DEBUG_CFLAGS="-g -DKF_SANITY_CHECKS" 11 + export KF_DEBUG_LFLAGS="-g -rdynamic" 12 + 13 + export CFLAGS="-Wall -Wextra -Werror -std=c99 -Iinclude/ -c -DKF_GNU $KF_DEBUG_CFLAGS" 14 + export LFLAGS="-lraylib -lm -lGL -lpthread -ldl -lrt -lX11 $KF_DEBUG_LFLAGS"
+1 -1
scripts/run.sh
··· 3 3 4 4 if [ -e "./build/keraforge" ] 5 5 then 6 - ./build/keraforge 6 + gdb -q -ex "set debuginfod enabled off" -ex "file ./build/keraforge" -ex "run" 7 7 else 8 8 echo ": error: no build available" 9 9 exit 1
+2 -2
src/actor.c
··· 56 56 KF_TILE_SIZE_PX - 1, 57 57 }; /* tile rect */ 58 58 u32 x; 59 - kf_tileid_t *tile = kf_world_gettile(world, sx, sy); 59 + struct kf_tile *tile = kf_world_gettile(world, sx, sy); 60 60 61 61 for (u32 y = sy ; y <= ey ; y++) 62 62 { 63 63 for (x = sx ; x <= ex ; x++) 64 64 { 65 - if (kf_tiles.collide[*tile] && CheckCollisionRecs(r, tr)) 65 + if (kf_tiles.collide[tile->id] && CheckCollisionRecs(r, tr)) 66 66 return 0; 67 67 tile++; /* shift tile pointer to the right */ 68 68 tr.x += KF_TILE_SIZE_PX;
+31
src/error.c
··· 1 + #include <keraforge.h> 2 + 3 + #if KF_GNU 4 + #include <execinfo.h> 5 + #endif 6 + 7 + 8 + void kf_printbacktrace(FILE *file) 9 + { 10 + #if KF_GNU 11 + static const int maxtracelen = 128; 12 + void *trace[maxtracelen]; 13 + 14 + int size = backtrace(trace, maxtracelen); 15 + char **strings = backtrace_symbols(trace, size); 16 + if (strings) 17 + { 18 + fprintf(file, "backtrace: (%d frames)\n", size); 19 + for (int i = 0 ; i < size ; i++) 20 + fprintf(file, " %d: %s\n", size-i, strings[i]); 21 + } 22 + else 23 + { 24 + fprintf(file, "failed to obtain backtrace\n"); 25 + } 26 + 27 + free(strings); 28 + #else 29 + fprintf(file, "kf_printbacktrace requires GNU extensions (execinfo.h)\n"); 30 + #endif 31 + }
+36 -19
src/main.c
··· 1 - #include "keraforge/input.h" 2 1 #include <keraforge.h> 3 2 #include <raylib.h> 4 3 #include <raymath.h> ··· 74 73 /* gamepad axis movement */ 75 74 f32 gpx = kf_getgamepadaxis(inputbind_move_left); 76 75 f32 gpy = kf_getgamepadaxis(inputbind_move_up); 77 - // f32 gpx = GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X); 78 - // f32 gpy = GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y); 79 76 if (gpx > kf_deadzone || gpx < -kf_deadzone || gpy > kf_deadzone || gpy < -kf_deadzone) 80 77 { 81 78 v.y = gpy; ··· 141 138 (void)argc; 142 139 (void)argv; 143 140 144 - SetTraceLogLevel(LOG_WARNING); 141 + // SetTraceLogLevel(LOG_WARNING); 145 142 InitWindow(800, 600, "Keraforge"); 146 143 SetTargetFPS(60); 147 144 SetExitKey(KEY_NULL); 148 145 149 146 loadbinds(); 150 147 151 - kf_tiles.key[0] = "grass"; 152 - kf_tiles.mapcol[0] = GREEN; 153 - kf_tiles.key[1] = "dirt"; 154 - kf_tiles.mapcol[1] = BROWN; 155 - kf_tiles.key[2] = "stone"; 156 - kf_tiles.mapcol[2] = GRAY; 157 - kf_tiles.collide[2] = true; 158 - kf_tiles.count = 3; 148 + struct kf_spritesheet terrain = kf_loadspritesheet("data/res/img/tile/terrain.png", 16, 16); 149 + KF_ADDTILE( 150 + .key = "grass", 151 + .mapcol = GREEN, 152 + .sheet = &terrain, 153 + .sprite = {0, 0}, 154 + ); 155 + KF_ADDTILE( 156 + .key = "sand", 157 + .mapcol = YELLOW, 158 + .sheet = &terrain, 159 + .sprite = {4, 0}, 160 + ); 161 + KF_ADDTILE( 162 + .key = "stone", 163 + .mapcol = GRAY, 164 + .sheet = &terrain, 165 + .sprite = {0, 4}, 166 + .collide = true, 167 + ); 168 + KF_ADDTILE( 169 + .key = "debug", 170 + .mapcol = BLUE, 171 + .sheet = &terrain, 172 + .sprite = {4, 4}, 173 + ); 174 + printf("loaded %d tiles\n", kf_tiles.count); 159 175 160 176 struct kf_uiconfig *uiconfig = kf_ui_getconfig(); 161 177 uiconfig->select = inputbind_select; ··· 170 186 if (!kf_exists("data/map.bin")) 171 187 { 172 188 printf("-> creating world\n"); 173 - world = kf_world_new(128, 128, 0); 174 - for (size_t i = 0 ; i < world->width*world->height ; i += (size_t)((float)(rand())/RAND_MAX*10)) 175 - world->map[i] = (rand()/(float)RAND_MAX*3); 189 + world = kf_world_new(128, 128, 1); 176 190 printf("-> saving world\n"); 177 191 size_t len = kf_world_getsize(world); 178 192 printf("-> writing of %lu bytes\n", len); ··· 236 250 cam.zoom -= 0.25f; 237 251 238 252 BeginDrawing(); 239 - ClearBackground(WHITE); 253 + ClearBackground(BLACK); 240 254 241 255 BeginMode2D(cam); 242 256 kf_world_draw(world, cam); 243 257 // kf_world_drawcolliders(world, player, cam); 244 258 if (select.x < world->width && select.y < world->height) 245 259 { 260 + struct kf_tile *t = kf_world_gettile(world, select.x, select.y); 246 261 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 247 262 { 248 - kf_tileid_t *t = kf_world_gettile(world, select.x, select.y); 249 - *t = (kf_tileid_t)selected_tile; 263 + t->id = (kf_tileid_t)selected_tile; 264 + kf_world_updatetile(world, select.x, select.y, true); 250 265 } 251 266 DrawRectangleLines(select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, WHITE); 267 + struct kf_vec2(u32) s = kf_getspritefortilebitmask(t->data); 268 + DrawText(TextFormat("%d [0x%04x] (%d,%d) {%d,%d}", t->id, t->data, select.x, select.y, s.x, s.y), select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX - 10, 10, BLACK); 252 269 } 253 270 player->draw(world, player); 254 271 EndMode2D(); ··· 264 281 case menu_none: 265 282 break; 266 283 case menu_palette: 267 - if (kf_ui_choice("Select tile", &kf_tiles.key[0], kf_tiles.count, &selected_tile)) 284 + if (kf_ui_choice("Select tile", &kf_tiles.key[0], kf_tiles.count + 1, &selected_tile)) 268 285 setmenu(menu_none); 269 286 break; 270 287 case menu_escape:
+11 -7
src/sprites.c
··· 14 14 } 15 15 16 16 inline 17 - void kf_drawsprite(struct kf_spritesheet *sheet, f32 x, f32 y, int spritex, int spritey) 17 + void kf_drawsprite_wh(struct kf_spritesheet *sheet, f32 x, f32 y, f32 w, f32 h, int spritex, int spritey) 18 18 { 19 + KF_SANITY_CHECK(sheet != NULL, "spritesheet is null"); 20 + 19 21 DrawTexturePro( 20 22 sheet->texture, 21 23 (Rectangle){ ··· 23 25 spritey * sheet->spriteheight, 24 26 sheet->spritewidth, 25 27 sheet->spriteheight }, 26 - (Rectangle){ 27 - x, 28 - y, 29 - sheet->spritewidth, 30 - sheet->spriteheight }, 31 - (Vector2){0, 0}, 28 + (Rectangle){ x, y, w, h }, 29 + (Vector2){ 0, 0 }, 32 30 0, 33 31 WHITE 34 32 ); 35 33 } 34 + 35 + inline 36 + void kf_drawsprite(struct kf_spritesheet *sheet, f32 x, f32 y, int spritex, int spritey) 37 + { 38 + kf_drawsprite_wh(sheet, x, y, sheet->spritewidth, sheet->spriteheight, spritex, spritey); 39 + }
+111 -15
src/world.c
··· 1 + #include "keraforge/world.h" 1 2 #include <keraforge.h> 2 3 #include <raylib.h> 4 + #include <stdio.h> 3 5 #include <stdlib.h> 4 6 #include <string.h> 5 7 #include <math.h> ··· 8 10 struct _kf_tiles kf_tiles = {0}; 9 11 10 12 13 + static inline 14 + void _kf_updatetilebitmask(struct kf_world *world, u32 x, u32 y); 15 + 16 + 17 + kf_tileid_t kf_addtile(struct kf_tile_opts opts) 18 + { 19 + KF_SANITY_CHECK(opts.key != NULL, "tile added without key"); 20 + KF_SANITY_CHECK(opts.sheet != NULL, "tile added without sheet"); 21 + 22 + kf_tileid_t id = ++kf_tiles.count; 23 + 24 + kf_tiles.key[id] = opts.key; 25 + kf_tiles.mapcol[id] = opts.mapcol; 26 + kf_tiles.sheet[id] = opts.sheet; 27 + kf_tiles.sprite[id] = opts.sprite; 28 + kf_tiles.collide[id] = opts.collide; 29 + 30 + return id; 31 + } 32 + 11 33 struct kf_world *kf_world_new(u32 width, u32 height, kf_tileid_t fill) 12 34 { 13 - const size_t len = sizeof(kf_tileid_t) * width * height; 14 - struct kf_world *world = malloc(sizeof(struct kf_world) + len); 35 + const size_t len = sizeof(struct kf_world) + sizeof(struct kf_tile)*width*height; 36 + struct kf_world *world = malloc(len); 37 + printf("creating world: %lu bytes: %p\n", len, world); 15 38 world->revision = 0; 16 39 world->width = width; 17 40 world->height = height; 18 - memset(world->map, fill, len); 41 + memset(world->map, 0, sizeof(struct kf_tile)*width*height); 42 + for (size_t i = 0 ; i < width*height ; i++) 43 + world->map[i].id = fill; 44 + 45 + u32 x; 46 + for (u32 y = 0 ; y < height ; y++) 47 + { 48 + for (x = 0 ; x < width ; x++) 49 + { 50 + _kf_updatetilebitmask(world, x, y); 51 + } 52 + } 53 + 19 54 return world; 20 55 } 21 56 22 57 size_t kf_world_getsize(struct kf_world *world) 23 58 { 24 - return sizeof(struct kf_world) + sizeof(kf_tileid_t)*world->width*world->height; 59 + return sizeof(struct kf_world) + sizeof(struct kf_tile)*world->width*world->height; 60 + } 61 + 62 + static inline 63 + void _kf_updatetilebitmask(struct kf_world *world, u32 x, u32 y) 64 + { 65 + struct kf_tile *t = kf_world_gettile(world, x, y); 66 + kf_tileid_t 67 + n = y-1 >= world->height ? 0 : kf_world_gettile(world, x, y-1)->id, 68 + w = x-1 >= world->width ? 0 : kf_world_gettile(world, x-1, y)->id, 69 + e = x+1 >= world->width ? 0 : kf_world_gettile(world, x+1, y)->id, 70 + s = y+1 >= world->height ? 0 : kf_world_gettile(world, x, y+1)->id; 71 + t->data = 0x0000; 72 + if (t->id == n) t->data |= KF_TILEMASK_NORTH; 73 + if (t->id == w) t->data |= KF_TILEMASK_WEST; 74 + if (t->id == e) t->data |= KF_TILEMASK_EAST; 75 + if (t->id == s) t->data |= KF_TILEMASK_SOUTH; 25 76 } 26 77 27 - kf_tileid_t *kf_world_gettile(struct kf_world *world, u32 x, u32 y) 78 + void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours) 79 + { 80 + _kf_updatetilebitmask(world, x, y); 81 + if (update_neighbours) 82 + { 83 + if (y-1 < world->height) kf_world_updatetile(world, x, y-1, false); 84 + if (x-1 < world->width) kf_world_updatetile(world, x-1, y, false); 85 + if (x+1 < world->width) kf_world_updatetile(world, x+1, y, false); 86 + if (y+1 < world->height) kf_world_updatetile(world, x, y+1, false); 87 + } 88 + } 89 + 90 + inline 91 + struct kf_vec2(u32) kf_getspritefortilebitmask(kf_tiledatum_t t) 92 + { 93 + switch (t) 94 + { 95 + case 0x0000: return (struct kf_vec2(u32)){0, 3}; /* 0x0000: */ 96 + case 0x0001: return (struct kf_vec2(u32)){0, 2}; /* 0x0001: N */ 97 + case 0x0010: return (struct kf_vec2(u32)){3, 3}; /* 0x0010: W */ 98 + case 0x0011: return (struct kf_vec2(u32)){3, 2}; /* 0x0011: NW */ 99 + case 0x0100: return (struct kf_vec2(u32)){1, 3}; /* 0x0100: E */ 100 + case 0x0101: return (struct kf_vec2(u32)){1, 2}; /* 0x0101: EN */ 101 + case 0x0110: return (struct kf_vec2(u32)){2, 3}; /* 0x0110: EW */ 102 + case 0x0111: return (struct kf_vec2(u32)){2, 2}; /* 0x0111: EWN */ 103 + case 0x1000: return (struct kf_vec2(u32)){0, 0}; /* 0x1000: S */ 104 + case 0x1001: return (struct kf_vec2(u32)){0, 1}; /* 0x1001: SN */ 105 + case 0x1010: return (struct kf_vec2(u32)){3, 0}; /* 0x1010: SW */ 106 + case 0x1011: return (struct kf_vec2(u32)){3, 1}; /* 0x1011: SWN */ 107 + case 0x1100: return (struct kf_vec2(u32)){1, 0}; /* 0x1100: SE */ 108 + case 0x1101: return (struct kf_vec2(u32)){1, 1}; /* 0x1101: SEN */ 109 + case 0x1110: return (struct kf_vec2(u32)){2, 0}; /* 0x1110: SEW */ 110 + case 0x1111: return (struct kf_vec2(u32)){2, 1}; /* 0x1111: NESW */ 111 + } 112 + KF_UNREACHABLE("invalid bitmask: 0x%x", t); 113 + return (struct kf_vec2(u32)){-1, -1}; 114 + } 115 + 116 + struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y) 28 117 { 29 118 return &world->map[y*world->width + x]; 30 119 } ··· 47 136 const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); 48 137 const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); 49 138 const size_t down = world->width - ex + sx - 1; /* number of indexes to add to reach the next tile down */ 50 - kf_tileid_t *tile = kf_world_gettile(world, sx, sy); 139 + struct kf_tile *tile = kf_world_gettile(world, sx, sy); 51 140 52 141 /* check if any tiles will collide with the actor's rect */ 53 142 const f32 trx = sx * KF_TILE_SIZE_PX; ··· 63 152 { 64 153 for (x = sx ; x <= ex ; x++) 65 154 { 66 - if (kf_tiles.collide[*tile]) 155 + if (kf_tiles.collide[tile->id]) 67 156 DrawRectangleLinesEx(tr, 1, CheckCollisionRecs(r, tr) ? RED : BLACK); 68 157 69 158 tile++; /* shift tile pointer to the right */ ··· 84 173 const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); 85 174 const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); 86 175 const size_t down = world->width - ex + sx; /* number of indexes to add to reach the next tile down */ 87 - kf_tileid_t *tile = kf_world_gettile(world, sx, sy); 176 + struct kf_tile *tile = kf_world_gettile(world, sx, sy); 88 177 u32 x; 89 178 for (u32 y = sy ; y < ey ; y++) 90 179 { 91 180 for (x = sx ; x < ex ; x++) 92 181 { 93 - DrawRectangle( 94 - (int)x * KF_TILE_SIZE_PX, 95 - (int)y * KF_TILE_SIZE_PX, 96 - KF_TILE_SIZE_PX, 97 - KF_TILE_SIZE_PX, 98 - kf_tiles.mapcol[*tile] 99 - ); 182 + KF_SANITY_CHECK(tile->id <= kf_tiles.count, "erroneous tile on map at %u,%u: %u (count=%u)", x, y, tile->id, kf_tiles.count); 183 + if (tile->id) 184 + { 185 + struct kf_vec2(u32) s = kf_getspritefortilebitmask(tile->data); 186 + kf_drawsprite_wh( 187 + kf_tiles.sheet[tile->id], 188 + x*KF_TILE_SIZE_PX, 189 + y*KF_TILE_SIZE_PX, 190 + KF_TILE_SIZE_PX, 191 + KF_TILE_SIZE_PX, 192 + kf_tiles.sprite[tile->id].x + s.x, 193 + kf_tiles.sprite[tile->id].y + s.y 194 + ); 195 + } 100 196 tile++; /* shift tile pointer to the right */ 101 197 } 102 198 tile += down; /* shift tile pointer down */