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