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

Add state for persistent data beyond just the world

Changed files
+162 -67
include
keraforge
src
+1
include/keraforge.h
··· 13 13 #include <keraforge/input.h> 14 14 #include <keraforge/math.h> 15 15 #include <keraforge/sprites.h> 16 + #include <keraforge/state.h> 16 17 #include <keraforge/ui.h> 17 18 #include <keraforge/world.h> 18 19
+31
include/keraforge/state.h
··· 1 + #ifndef __kf_state__ 2 + #define __kf_state__ 3 + 4 + 5 + #include <keraforge/actor.h> 6 + 7 + 8 + #define KF_NPCPOOL_SIZE 1024 9 + 10 + 11 + /* Stores global variables to be saved alongside the world. Use this for save data. 12 + Do not use pointers here! This gets transmuted into a u8 array for serialization. */ 13 + struct kf_state 14 + { 15 + /* Do not modify this and do not relocate it. See kf_world for an explanation. */ 16 + u16 revision; 17 + /* Player data */ 18 + struct { 19 + struct kf_vec2(f32) pos; 20 + } player; 21 + // struct kf_actor npc[KF_NPCPOOL_SIZE]; 22 + }; 23 + 24 + 25 + /* Save a state. */ 26 + int kf_state_save(struct kf_state *state); 27 + /* Load a state. Returns 1 if the state was freshly created and 0 if an existing state was loaded. */ 28 + int kf_state_load(struct kf_state **pstate); 29 + 30 + 31 + #endif
+82 -63
src/main.c
··· 1 + #include "keraforge/world.h" 1 2 #include <keraforge.h> 2 3 #include <raylib.h> 3 4 #include <raymath.h> ··· 19 20 } modal; 20 21 static char *modals[] = { "play", "edit" }; 21 22 static bool dirty = false; 22 - static bool preserve_mapdotbin = false; 23 23 24 24 static kf_inputbind_t 25 25 inputbind_move_up, ··· 44 44 45 45 46 46 static 47 - void loadbinds() 47 + void loadbinds(void) 48 48 { 49 49 inputbind_move_up = kf_addinput("move_up", KEY_W, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y); 50 50 inputbind_move_down = kf_addinput("move_down", KEY_S, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y); ··· 69 69 70 70 inputbind_toggle_fps_limit = kf_addinput("toggle_fps_limit", KEY_NINE, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 71 71 inputbind_toggle_editor = kf_addinput("toggle_editor", KEY_EIGHT, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 72 + } 73 + 74 + static 75 + void loadtiles(struct kf_spritesheet *terrain) 76 + { 77 + KF_ADDTILE( 78 + .key = "grass", 79 + .mapcol = GREEN, 80 + .sheet = terrain, 81 + .sprite = {0, 0}, 82 + ); 83 + KF_ADDTILE( 84 + .key = "sand", 85 + .mapcol = YELLOW, 86 + .sheet = terrain, 87 + .sprite = {4, 0}, 88 + ); 89 + KF_ADDTILE( 90 + .key = "stone", 91 + .mapcol = GRAY, 92 + .sheet = terrain, 93 + .sprite = {0, 4}, 94 + ); 95 + KF_ADDTILE( 96 + .key = "debug", 97 + .mapcol = BLUE, 98 + .sheet = terrain, 99 + .sprite = {4, 4}, 100 + ); 101 + KF_ADDTILE( 102 + .key = "brick", 103 + .mapcol = RED, 104 + .sheet = terrain, 105 + .sprite = {8, 0}, 106 + .collide = true, 107 + ); 108 + KF_ADDTILE( 109 + .key = "ice", 110 + .mapcol = BLUE, 111 + .sheet = terrain, 112 + .sprite = {8, 4}, 113 + .transparent = true, 114 + ); 115 + KF_ADDTILE( 116 + .key = "dirt", 117 + .mapcol = BROWN, 118 + .sheet = terrain, 119 + .sprite = {12, 0}, 120 + ); 121 + KF_ADDTILE( 122 + .key = "torch", 123 + .mapcol = ORANGE, 124 + .sheet = terrain, 125 + .sprite = {12, 4}, 126 + .transparent = true, 127 + ); 128 + kf_logdbg("loaded %d tiles", kf_tiles.count); 72 129 } 73 130 74 131 static ··· 207 264 loadbinds(); 208 265 209 266 struct kf_spritesheet terrain = kf_loadspritesheet("data/res/img/tile/terrain.png", 16, 16); 210 - KF_ADDTILE( 211 - .key = "grass", 212 - .mapcol = GREEN, 213 - .sheet = &terrain, 214 - .sprite = {0, 0}, 215 - ); 216 - KF_ADDTILE( 217 - .key = "sand", 218 - .mapcol = YELLOW, 219 - .sheet = &terrain, 220 - .sprite = {4, 0}, 221 - ); 222 - KF_ADDTILE( 223 - .key = "stone", 224 - .mapcol = GRAY, 225 - .sheet = &terrain, 226 - .sprite = {0, 4}, 227 - ); 228 - KF_ADDTILE( 229 - .key = "debug", 230 - .mapcol = BLUE, 231 - .sheet = &terrain, 232 - .sprite = {4, 4}, 233 - ); 234 - KF_ADDTILE( 235 - .key = "brick", 236 - .mapcol = RED, 237 - .sheet = &terrain, 238 - .sprite = {8, 0}, 239 - .collide = true, 240 - ); 241 - KF_ADDTILE( 242 - .key = "ice", 243 - .mapcol = BLUE, 244 - .sheet = &terrain, 245 - .sprite = {8, 4}, 246 - .transparent = true, 247 - ); 248 - KF_ADDTILE( 249 - .key = "dirt", 250 - .mapcol = BROWN, 251 - .sheet = &terrain, 252 - .sprite = {12, 0}, 253 - ); 254 - kf_logdbg("loaded %d tiles", kf_tiles.count); 267 + loadtiles(&terrain); 255 268 256 269 struct kf_uiconfig *uiconfig = kf_ui_getconfig(); 257 270 uiconfig->select = inputbind_select; ··· 262 275 if (!DirectoryExists("data")) 263 276 MakeDirectory("data"); 264 277 278 + struct kf_state *state = NULL; 279 + int is_new_state = kf_state_load(&state); 280 + 265 281 struct kf_world *world = NULL; 266 282 kf_world_load(&world, true); 267 283 if (!world) 268 - KF_THROW("failed to load world: %p", world); 284 + KF_THROW("failed to load world"); 269 285 270 286 struct kf_actor *player = kf_actor_new(kf_actor_loadspritesheet("data/res/img/char/template.png"), 10, 10, true); 271 287 player->sizeoffset.y = 6; 272 - player->pos.x = world->width / 4.0f * KF_TILE_SIZE_PX; 273 - player->pos.y = world->height / 4.0f * KF_TILE_SIZE_PX; 274 288 player->tick = _player_tick; 275 289 player->draw = _player_draw; 276 290 player->controlled = true; 291 + if (is_new_state) 292 + { 293 + state->player.pos.x = world->width * KF_TILE_SIZE_PX / 2.0f; 294 + state->player.pos.y = world->width * KF_TILE_SIZE_PX / 2.0f; 295 + } 296 + player->pos.x = state->player.pos.x; 297 + player->pos.y = state->player.pos.y; 298 + kf_loginfo("pos: %f,%f", player->pos.x, player->pos.y); 277 299 278 300 cam = (Camera2D){0}; 279 301 cam.offset.x = GetScreenWidth() / 2.0f; 280 302 cam.offset.y = GetScreenHeight() / 2.0f; 303 + cam.target.x = player->pos.x + (player->size.x / 2); 304 + cam.target.y = player->pos.y + (player->size.y / 2); 281 305 cam.zoom = 2; 282 306 283 307 int running = 1; ··· 368 392 kf_s += kf_dts; 369 393 } 370 394 371 - if (world) 372 - { 373 - if (dirty) 374 - { 375 - if (!kf_writebin("data/tmp/map.bin", (u8 *)world, kf_world_getsize(world))) 376 - KF_THROW("failed to save map.bin"); 377 - if (!kf_compress("data/tmp/map.bin", "data/map.bin.xz")) 378 - KF_THROW("failed to compress map.bin into map.bin.xz"); 379 - if (!preserve_mapdotbin) 380 - remove("data/tmp/map.bin"); 381 - } 382 - free(world); 383 - } 395 + state->player.pos = player->pos; 396 + kf_state_save(state); 397 + free(state); 398 + 399 + if (dirty) 400 + kf_world_save(world, true); 401 + free(world); 402 + 384 403 CloseWindow(); 385 404 386 405 return 0;
+43
src/state.c
··· 1 + #include <keraforge.h> 2 + 3 + 4 + #define _KF_STATEFILE "data/state.bin" 5 + 6 + int kf_state_save(struct kf_state *state) 7 + { 8 + if (!kf_writebin(_KF_STATEFILE, (u8 *)state, sizeof(*state))) 9 + { 10 + KF_THROW("failed to write to %s", _KF_STATEFILE); 11 + return 0; /* unreachable */ 12 + } 13 + 14 + return 1; 15 + } 16 + 17 + int kf_state_load(struct kf_state **pstate) 18 + { 19 + char *infile = _KF_STATEFILE; 20 + kf_logdbg("loading state: %s", infile); 21 + 22 + int res = 0; 23 + 24 + if (!kf_exists(infile)) 25 + { 26 + kf_logdbg("creating state..."); 27 + struct kf_state s = {0}; 28 + kf_state_save(&s); 29 + res = 1; 30 + } 31 + 32 + size_t len = 0; 33 + *pstate = (struct kf_state *)kf_readbin(infile, &len); 34 + kf_logdbg("loaded state (%p): len=%lu (res=%d)", *pstate, len, res); 35 + 36 + if (!*pstate) 37 + { 38 + KF_THROW("failed to load state"); 39 + return -1; /* unreachable */ 40 + } 41 + 42 + return res; 43 + }
+4 -3
src/world.c
··· 35 35 { 36 36 const size_t len = sizeof(struct kf_world) + sizeof(struct kf_tile)*width*height; 37 37 struct kf_world *world = malloc(len); 38 - kf_loginfo("creating world: %lu bytes: %p\n", len, world); 38 + kf_loginfo("creating world: %lu bytes: %p", len, world); 39 39 world->revision = 0; 40 40 world->width = width; 41 41 world->height = height; ··· 215 215 216 216 tile++; /* shift tile pointer to the right */ 217 217 } 218 + 218 219 tile += down; /* shift tile pointer down */ 219 220 } 220 221 } ··· 269 270 270 271 size_t len = 0; 271 272 *pworld = (struct kf_world *)kf_readbin(infile, &len); 272 - kf_logdbg("loaded world (%p): r=%d, wh=%dx%d, len=%lu", pworld, (*pworld)->revision, (*pworld)->width, (*pworld)->height, len); 273 + kf_logdbg("loaded world (%p): r=%d, wh=%dx%d, len=%lu", *pworld, (*pworld)->revision, (*pworld)->width, (*pworld)->height, len); 273 274 274 275 if (compressed) 275 276 { ··· 277 278 remove(_KF_MAPFILE_TMP); 278 279 } 279 280 280 - if (!pworld) 281 + if (!*pworld) 281 282 { 282 283 KF_THROW("failed to load world"); 283 284 return 0; /* unreachable */
+1 -1
todo
··· 9 9 10 10 . Core 11 11 . World 12 - / Tiles 12 + x Tiles 13 13 . Actors 14 14 x Compression 15 15 . Compress without saving the world binary as an intermediate step.