A game engine for top-down 2D RPG games.
rpg game-engine raylib c99
1#include <keraforge.h> 2#include <raylib.h> 3#include <raymath.h> 4#include <stdio.h> 5#include <stdlib.h> 6 7 8static Camera2D cam; 9static struct kf_vec2(u32) select = { 0, 0 }; 10static int selected_tile = 0; 11static bool selected_menu_this_frame = false; 12static enum { 13 menu_none, 14 menu_palette, 15 menu_escape 16} menu; 17 18static kf_inputbind_t 19 inputbind_move_up, 20 inputbind_move_down, 21 inputbind_move_left, 22 inputbind_move_right, 23 inputbind_ui_up, 24 inputbind_ui_down, 25 inputbind_ui_left, 26 inputbind_ui_right, 27 inputbind_select, 28 inputbind_cancel, 29 inputbind_pause, 30 inputbind_palette, 31 inputbind_zoom_reset, 32 inputbind_zoom_in, 33 inputbind_zoom_out 34; 35 36 37static 38void loadbinds() 39{ 40 inputbind_move_up = kf_addinput("move_up", KEY_W, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y); 41 inputbind_move_down = kf_addinput("move_down", KEY_S, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y); 42 inputbind_move_left = kf_addinput("move_left", KEY_A, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X); 43 inputbind_move_right = kf_addinput("move_right", KEY_D, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X); 44 45 inputbind_ui_up = kf_addinput("ui_up", KEY_W, KEY_UP, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_UP, GAMEPAD_AXIS_UNKNOWN); 46 inputbind_ui_down = kf_addinput("ui_down", KEY_S, KEY_DOWN, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN); 47 inputbind_ui_left = kf_addinput("ui_left", KEY_A, KEY_LEFT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_LEFT, GAMEPAD_AXIS_UNKNOWN); 48 inputbind_ui_right = kf_addinput("ui_right", KEY_D, KEY_RIGHT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 49 50 inputbind_select = kf_addinput("select", KEY_E, KEY_ENTER, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN); 51 inputbind_cancel = kf_addinput("cancel", KEY_Q, KEY_ESCAPE, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 52 53 inputbind_pause = kf_addinput("pause", KEY_ESCAPE, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_MIDDLE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 54 inputbind_palette = kf_addinput("palette", KEY_TAB, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_UP, GAMEPAD_AXIS_UNKNOWN); 55 56 inputbind_zoom_reset = kf_addinput("zoom_reset", KEY_ZERO, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 57 inputbind_zoom_in = kf_addinput("zoom_in", KEY_EQUAL, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 58 inputbind_zoom_out = kf_addinput("zoom_out", KEY_MINUS, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 59} 60 61static 62void setmenu(int m) 63{ 64 menu = m; 65 selected_menu_this_frame = true; 66} 67 68static 69void _player_tick_move(struct kf_actor *self) 70{ 71 struct kf_vec2(f32) v = {0, 0}; 72 73 /* gamepad axis movement */ 74 f32 gpx = kf_getgamepadaxis(inputbind_move_left); 75 f32 gpy = kf_getgamepadaxis(inputbind_move_up); 76 if (gpx > kf_deadzone || gpx < -kf_deadzone || gpy > kf_deadzone || gpy < -kf_deadzone) 77 { 78 v.y = gpy; 79 v.x = gpx; 80 81 f32 angle = Vector2LineAngle(Vector2Zero(), (Vector2){gpx, gpy}) * RAD2DEG; 82 angle /= 90; 83 switch ((int)roundf(angle)) 84 { 85 case 0: self->pointing = kf_east; break; 86 case 1: self->pointing = kf_north; break; 87 case -2: 88 case 2: self->pointing = kf_west; break; 89 case -1: self->pointing = kf_south; break; 90 } 91 92 goto done; 93 } 94 95 /* non-axis movement */ 96 bool w = kf_checkinputdown(inputbind_move_up); 97 bool s = kf_checkinputdown(inputbind_move_down); 98 bool a = kf_checkinputdown(inputbind_move_left); 99 bool d = kf_checkinputdown(inputbind_move_right); 100 101 if (a && d) { v.x = 0; } 102 else if (a) { v.x = -1; self->pointing = kf_west; } 103 else if (d) { v.x = 1; self->pointing = kf_east; } 104 105 if (w && s) { v.y = 0; } 106 else if (w) { v.y = -1; self->pointing = kf_north; } 107 else if (s) { v.y = 1; self->pointing = kf_south; } 108 109 v = kf_normalize_vec2(f32)(v); 110 111done: 112 if (v.x || v.y) 113 kf_actor_addforce(self, v); 114} 115 116static 117void _player_tick(struct kf_world *world, struct kf_actor *self) 118{ 119 if (menu == menu_none) 120 _player_tick_move(self); 121 122 kf_actor_move(world, self, kf_dts); 123} 124 125static 126void _player_draw(struct kf_world *world, struct kf_actor *self) 127{ 128 (void)world; 129 kf_actor_draw(self); 130 131 cam.target.x = self->pos.x + (self->size.x / 2); 132 cam.target.y = self->pos.y + (self->size.y / 2); 133} 134 135 136int main(int argc, const char *argv[]) 137{ 138 (void)argc; 139 (void)argv; 140 141 // SetTraceLogLevel(LOG_WARNING); 142 InitWindow(800, 600, "Keraforge"); 143 SetTargetFPS(60); 144 SetExitKey(KEY_NULL); 145 146 loadbinds(); 147 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); 175 176 struct kf_uiconfig *uiconfig = kf_ui_getconfig(); 177 uiconfig->select = inputbind_select; 178 uiconfig->cancel = inputbind_cancel; 179 uiconfig->up = inputbind_ui_up; 180 uiconfig->down = inputbind_ui_down; 181 182 if (!DirectoryExists("data")) 183 MakeDirectory("data"); 184 185 struct kf_world *world = NULL; 186 if (!kf_exists("data/map.bin")) 187 { 188 printf("-> creating world\n"); 189 world = kf_world_new(128, 128, 1); 190 printf("-> saving world\n"); 191 size_t len = kf_world_getsize(world); 192 printf("-> writing of %lu bytes\n", len); 193 if (!kf_writebin("data/map.bin", (u8 *)world, len)) 194 { 195 fprintf(stderr, "error creating map: failed to save map.bin\n"); 196 free(world); 197 exit(1); 198 } 199 } 200 else 201 { 202 printf("-> loading world\n"); 203 size_t len = 0; 204 world = (struct kf_world *)kf_readbin("data/map.bin", &len); 205 printf("-> world is %lu bytes\n", len); 206 } 207 if (!world) 208 { 209 fprintf(stderr, "error: failed to load world\n"); 210 exit(1); 211 } 212 213 struct kf_actor *player = kf_actor_new(kf_actor_loadspritesheet("data/res/img/char/whom.png"), 10, 10, true); 214 player->sizeoffset.y = 6; 215 player->pos.x = world->width / 4.0f * KF_TILE_SIZE_PX; 216 player->pos.y = world->height / 4.0f * KF_TILE_SIZE_PX; 217 player->tick = _player_tick; 218 player->draw = _player_draw; 219 player->controlled = true; 220 221 cam = (Camera2D){0}; 222 cam.offset.x = GetScreenWidth() / 2.0f; 223 cam.offset.y = GetScreenHeight() / 2.0f; 224 cam.zoom = 2; 225 226 int running = 1; 227 while (!WindowShouldClose() && running) 228 { 229 if (IsWindowResized()) 230 { 231 cam.offset.x = GetScreenWidth() / 2.0f; 232 cam.offset.y = GetScreenHeight() / 2.0f; 233 } 234 235 player->tick(world, player); 236 237 Vector2 v = GetScreenToWorld2D(GetMousePosition(), cam); 238 select.x = v.x / KF_TILE_SIZE_PX; 239 select.y = v.y / KF_TILE_SIZE_PX; 240 241 if (kf_checkinputpress(inputbind_palette)) 242 setmenu(menu_palette); 243 else if (kf_checkinputpress(inputbind_cancel) && menu == menu_none) 244 setmenu(menu_escape); 245 else if (kf_checkinputpress(inputbind_zoom_reset)) 246 cam.zoom = 2; 247 else if (kf_checkinputpress(inputbind_zoom_in) && cam.zoom < 3.50f) 248 cam.zoom += 0.25f; 249 else if (kf_checkinputpress(inputbind_zoom_out) && cam.zoom > 1.00f) 250 cam.zoom -= 0.25f; 251 252 BeginDrawing(); 253 ClearBackground(BLACK); 254 255 BeginMode2D(cam); 256 kf_world_draw(world, cam); 257 // kf_world_drawcolliders(world, player, cam); 258 if (select.x < world->width && select.y < world->height) 259 { 260 struct kf_tile *t = kf_world_gettile(world, select.x, select.y); 261 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 262 { 263 t->id = (kf_tileid_t)selected_tile; 264 kf_world_updatetile(world, select.x, select.y, true); 265 } 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); 269 } 270 player->draw(world, player); 271 EndMode2D(); 272 273 if (selected_menu_this_frame) 274 { 275 selected_menu_this_frame = false; 276 } 277 else 278 { 279 switch (menu) 280 { 281 case menu_none: 282 break; 283 case menu_palette: 284 if (kf_ui_choice("Select tile", &kf_tiles.key[0], kf_tiles.count + 1, &selected_tile)) 285 setmenu(menu_none); 286 break; 287 case menu_escape: 288 { 289 static int result = 1; 290 if (kf_ui_yesno("Exit game?", &result)) 291 { 292 menu = menu_none; 293 if (result) 294 running = 0; 295 result = 1; 296 } 297 break; 298 } 299 } 300 } 301 302 DrawFPS(0, 0); 303 DrawText(TextFormat("%f", kf_dts), 0, 20, 20, RED); 304 305 EndDrawing(); 306 307 kf_frame++; 308 kf_dts = GetFrameTime(); 309 kf_dtms = kf_dts * 1000; 310 } 311 312 if (world) 313 { 314 if (!kf_writebin("data/map.bin", (u8 *)world, kf_world_getsize(world))) 315 fprintf(stderr, "error: failed to save map.bin\n"); 316 free(world); 317 } 318 CloseWindow(); 319 320 return 0; 321}