A game engine for top-down 2D RPG games.
rpg game-engine raylib c99
at main 9.5 kB view raw
1#include "keraforge/bini.h" 2#include <keraforge.h> 3#include <raylib.h> 4#include <stdio.h> 5#include <stdlib.h> 6#include <string.h> 7#include <math.h> 8 9 10struct _kf_tiles kf_tiles = {0}; 11 12 13static inline 14void _kf_updatetilebitmask(struct kf_world *world, u32 x, u32 y); 15 16 17kf_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 kf_tiles.transparent[id] = opts.transparent; 30 31 return id; 32} 33 34struct kf_world *kf_world_new(u32 width, u32 height, kf_tileid_t fill) 35{ 36 const size_t len = sizeof(struct kf_world) + sizeof(struct kf_tile)*width*height; 37 struct kf_world *world = malloc(len); 38 kf_loginfo("creating world: %lu bytes: %p", len, world); 39 world->revision = 0; 40 world->width = width; 41 world->height = height; 42 memset(world->map, 0, sizeof(struct kf_tile)*width*height); 43 for (size_t i = 0 ; i < width*height ; i++) 44 { 45 world->map[i].subid = fill; 46 world->map[i].id = fill; 47 } 48 49 u32 x; 50 for (u32 y = 0 ; y < height ; y++) 51 { 52 for (x = 0 ; x < width ; x++) 53 { 54 _kf_updatetilebitmask(world, x, y); 55 } 56 } 57 58 return world; 59} 60 61size_t kf_world_getsize(struct kf_world *world) 62{ 63 return sizeof(struct kf_world) + sizeof(struct kf_tile)*world->width*world->height; 64} 65 66static inline 67void _kf_updatetilebitmask(struct kf_world *world, u32 x, u32 y) 68{ 69 struct kf_tile *t = kf_world_gettile(world, x, y); 70 kf_tileid_t 71 n = y-1 >= world->height ? 0 : kf_world_gettile(world, x, y-1)->id, 72 w = x-1 >= world->width ? 0 : kf_world_gettile(world, x-1, y)->id, 73 e = x+1 >= world->width ? 0 : kf_world_gettile(world, x+1, y)->id, 74 s = y+1 >= world->height ? 0 : kf_world_gettile(world, x, y+1)->id; 75 t->data = 0b0000; 76 if (t->id == n) t->data |= KF_TILEMASK_NORTH; 77 if (t->id == w) t->data |= KF_TILEMASK_WEST; 78 if (t->id == e) t->data |= KF_TILEMASK_EAST; 79 if (t->id == s) t->data |= KF_TILEMASK_SOUTH; 80} 81 82void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours) 83{ 84 _kf_updatetilebitmask(world, x, y); 85 if (update_neighbours) 86 { 87 if (y-1 < world->height) kf_world_updatetile(world, x, y-1, false); 88 if (x-1 < world->width) kf_world_updatetile(world, x-1, y, false); 89 if (x+1 < world->width) kf_world_updatetile(world, x+1, y, false); 90 if (y+1 < world->height) kf_world_updatetile(world, x, y+1, false); 91 } 92} 93 94inline 95struct kf_vec2(u32) kf_getspritefordatum(kf_tiledatum_t t) 96{ 97 static const 98 struct kf_vec2(u32) v[] = { 99 {0, 3}, /* 0b0000: */ 100 {0, 2}, /* 0b0001: N */ 101 {3, 3}, /* 0b0010: W */ 102 {3, 2}, /* 0b0011: NW */ 103 {1, 3}, /* 0b0100: E */ 104 {1, 2}, /* 0b0101: EN */ 105 {2, 3}, /* 0b0110: EW */ 106 {2, 2}, /* 0b0111: EWN */ 107 {0, 0}, /* 0b1000: S */ 108 {0, 1}, /* 0b1001: SN */ 109 {3, 0}, /* 0b1010: SW */ 110 {3, 1}, /* 0b1011: SWN */ 111 {1, 0}, /* 0b1100: SE */ 112 {1, 1}, /* 0b1101: SEN */ 113 {2, 0}, /* 0b1110: SEW */ 114 {2, 1}, /* 0b1111: NESW */ 115 }; 116 return v[t]; 117} 118 119struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y) 120{ 121 return &world->map[y*world->width + x]; 122} 123 124void kf_world_drawcolliders(struct kf_world *world, struct kf_actor *player, Camera2D camera) 125{ 126 Rectangle r = { 127 player->pos.x + (player->size.x / 2) + player->sizeoffset.x, 128 player->pos.y + (player->size.y / 2) + player->sizeoffset.y, 129 player->size.x, 130 player->size.y 131 }; 132 133 DrawRectangleLinesEx(r, 1, BLUE); 134 135 const Vector2 start = GetScreenToWorld2D((Vector2){0, 0}, camera); 136 const Vector2 end = GetScreenToWorld2D((Vector2){GetScreenWidth(), GetScreenHeight()}, camera); 137 const u32 sx = fmax(0, floorf(start.x / KF_TILE_SIZE_PX)); 138 const u32 sy = fmax(0, floorf(start.y / KF_TILE_SIZE_PX)); 139 const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); 140 const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); 141 const size_t down = world->width - ex + sx - 1; /* number of indexes to add to reach the next tile down */ 142 struct kf_tile *tile = kf_world_gettile(world, sx, sy); 143 144 /* check if any tiles will collide with the actor's rect */ 145 const f32 trx = sx * KF_TILE_SIZE_PX; 146 Rectangle tr = { 147 trx, 148 sy * KF_TILE_SIZE_PX, 149 KF_TILE_SIZE_PX, 150 KF_TILE_SIZE_PX, 151 }; /* tile rect */ 152 u32 x; 153 154 for (u32 y = sy ; y <= ey ; y++) 155 { 156 for (x = sx ; x <= ex ; x++) 157 { 158 if (kf_tiles.collide[tile->id]) 159 DrawRectangleLinesEx(tr, 1, CheckCollisionRecs(r, tr) ? RED : BLACK); 160 161 tile++; /* shift tile pointer to the right */ 162 tr.x += KF_TILE_SIZE_PX; 163 } 164 tile += down; /* shift tile pointer down */ 165 tr.x = trx; 166 tr.y += KF_TILE_SIZE_PX; 167 } 168} 169 170void kf_world_draw(struct kf_world *world, Camera2D camera) 171{ 172 const Vector2 start = GetScreenToWorld2D((Vector2){0, 0}, camera); 173 const Vector2 end = GetScreenToWorld2D((Vector2){GetScreenWidth(), GetScreenHeight()}, camera); 174 const u32 sx = fmax(0, floorf(start.x / KF_TILE_SIZE_PX)); 175 const u32 sy = fmax(0, floorf(start.y / KF_TILE_SIZE_PX)); 176 const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); 177 const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); 178 const size_t down = world->width - ex + sx; /* number of indexes to add to reach the next tile down */ 179 struct kf_tile *tile = kf_world_gettile(world, sx, sy); 180 u32 x; 181 for (u32 y = sy ; y < ey ; y++) 182 { 183 for (x = sx ; x < ex ; x++) 184 { 185 KF_SANITY_CHECK(tile->subid <= kf_tiles.count, "erroneous subtile on map at %u,%u: %u (count=%u)", x, y, tile->subid, kf_tiles.count); 186 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); 187 188 /* 15: full tile, no subtile rendering needed (unless transparent) */ 189 if ((tile->data != 15 || kf_tiles.transparent[tile->id]) && tile->subid && tile->id) 190 { 191 kf_drawsprite_wh( 192 kf_tiles.sheet[tile->subid], 193 x*KF_TILE_SIZE_PX, 194 y*KF_TILE_SIZE_PX, 195 KF_TILE_SIZE_PX, 196 KF_TILE_SIZE_PX, 197 kf_tiles.sprite[tile->subid].x + 2, /* 2,1 are offsets for the "middle" tile */ 198 kf_tiles.sprite[tile->subid].y + 1 199 ); 200 } 201 202 if (tile->id) 203 { 204 struct kf_vec2(u32) s = kf_getspritefordatum(tile->data); 205 kf_drawsprite_wh( 206 kf_tiles.sheet[tile->id], 207 x*KF_TILE_SIZE_PX, 208 y*KF_TILE_SIZE_PX, 209 KF_TILE_SIZE_PX, 210 KF_TILE_SIZE_PX, 211 kf_tiles.sprite[tile->id].x + s.x, 212 kf_tiles.sprite[tile->id].y + s.y 213 ); 214 } 215 216 tile++; /* shift tile pointer to the right */ 217 } 218 219 tile += down; /* shift tile pointer down */ 220 } 221} 222 223#define _KF_MAPFILE_TMP "data/tmp/map.bin" 224#define _KF_MAPFILE_XZ "data/map.bin.xz" 225#define _KF_MAPFILE "data/map.bin" 226 227static 228void _kf_world_save_bs(struct kf_world *world, struct bini_stream *bs) 229{ 230 bini_wiu(bs, world->revision); 231 bini_wiu(bs, world->width); 232 bini_wiu(bs, world->height); 233 struct kf_tile *t = &world->map[0]; 234 for (size_t i = 0 ; i < world->width*world->height ; i++) 235 { 236 bini_wsu(bs, t->subid); 237 bini_wsu(bs, t->id); 238 bini_wcu(bs, t->data); 239 t++; 240 } 241} 242 243int kf_world_save(struct kf_world *world, bool compress, char *outfile) 244{ 245 outfile = outfile ? outfile : (compress ? _KF_MAPFILE_TMP : _KF_MAPFILE); 246 struct bini_stream *bs = bini_new(); 247 _kf_world_save_bs(world, bs); 248 if (!kf_writebin(outfile, bs->buffer, bs->len)) 249 KF_THROW("failed to open %s", outfile); 250 bini_close(bs); 251 252 if (compress) 253 { 254 if (!kf_compress(_KF_MAPFILE_TMP, _KF_MAPFILE_XZ)) 255 { 256 KF_THROW("failed to compress %s", _KF_MAPFILE_XZ); 257 return 0; /* unreachable */ 258 } 259 /* we don't need this anymore, might as well toss it to save file storage. */ 260 remove(_KF_MAPFILE_TMP); 261 } 262 263 return 1; 264} 265 266static 267void _kf_world_load_bs(struct kf_world **pworld, struct bini_stream *bs) 268{ 269 u32 r = bini_riu(bs); 270 u32 w = bini_riu(bs); 271 u32 h = bini_riu(bs); 272 struct kf_world *world = malloc(sizeof(*world) + sizeof(struct kf_tile)*w*h); 273 world->revision = r; 274 world->width = w; 275 world->height = h; 276 struct kf_tile *t = &world->map[0]; 277 for (size_t i = 0 ; i < world->width*world->height ; i++) 278 { 279 t->subid = bini_rsu(bs); 280 t->id = bini_rsu(bs); 281 t->data = bini_rcu(bs); 282 t++; 283 } 284 *pworld = world; 285} 286 287int kf_world_load(struct kf_world **pworld, bool compressed, char *infile) 288{ 289 if (compressed) /* decompress before loading */ 290 { 291 if (!kf_exists(_KF_MAPFILE_XZ)) 292 { 293 KF_THROW("no such file: %s", _KF_MAPFILE_XZ); 294 return 0; /* unreachable */ 295 } 296 297 kf_logdbg("decompressing %s to %s", _KF_MAPFILE_XZ, _KF_MAPFILE_TMP); 298 if (!kf_decompress(_KF_MAPFILE_XZ, _KF_MAPFILE_TMP)) 299 { 300 KF_THROW("failed to decompress %s", _KF_MAPFILE_XZ); 301 return 0; /* unreachable */ 302 } 303 } 304 305 infile = infile ? infile : (compressed ? _KF_MAPFILE_TMP : _KF_MAPFILE); 306 kf_logdbg("loading world: %s", infile); 307 308 size_t len = 0; 309 struct bini_stream bs = { 310 .mode = BINI_STREAM, 311 .buffer = kf_readbin(infile, &len), 312 }; 313 if (!bs.buffer) 314 KF_THROW("failed to read/open %s", infile); 315 bs.cap = len; 316 bs.len = len; 317 kf_logdbg("loaded world into binary stream: len=%lu", len); 318 _kf_world_load_bs(pworld, &bs); 319 free(bs.buffer); 320 kf_logdbg("loaded world (%p): r=%d, wh=%dx%d, len=%lu", *pworld, (*pworld)->revision, (*pworld)->width, (*pworld)->height, len); 321 322 if (compressed) 323 { 324 /* we don't need this anymore, might as well toss it to save file storage. */ 325 remove(_KF_MAPFILE_TMP); 326 } 327 328 if (!*pworld) 329 { 330 KF_THROW("failed to load world"); 331 return 0; /* unreachable */ 332 } 333 334 return 1; 335} 336