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 bool selected_menu_this_frame = false; 13static enum { 14 menu_none, 15 menu_palette, 16 menu_escape 17} menu; 18 19static kf_inputbind_t 20 inputbind_move_up, 21 inputbind_move_down, 22 inputbind_move_left, 23 inputbind_move_right, 24 inputbind_ui_up, 25 inputbind_ui_down, 26 inputbind_ui_left, 27 inputbind_ui_right, 28 inputbind_select, 29 inputbind_cancel, 30 inputbind_pause, 31 inputbind_palette, 32 inputbind_zoom_reset, 33 inputbind_zoom_in, 34 inputbind_zoom_out 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 62static 63void setmenu(int m) 64{ 65 menu = m; 66 selected_menu_this_frame = true; 67} 68 69static 70void _player_tick_move(struct kf_actor *self) 71{ 72 struct kf_vec2(f32) v = {0, 0}; 73 74 /* gamepad axis movement */ 75 f32 gpx = kf_getgamepadaxis(inputbind_move_left); 76 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 if (gpx > kf_deadzone || gpx < -kf_deadzone || gpy > kf_deadzone || gpy < -kf_deadzone) 80 { 81 v.y = gpy; 82 v.x = gpx; 83 84 f32 angle = Vector2LineAngle(Vector2Zero(), (Vector2){gpx, gpy}) * RAD2DEG; 85 angle /= 90; 86 switch ((int)roundf(angle)) 87 { 88 case 0: self->pointing = kf_east; break; 89 case 1: self->pointing = kf_north; break; 90 case -2: 91 case 2: self->pointing = kf_west; break; 92 case -1: self->pointing = kf_south; break; 93 } 94 95 goto done; 96 } 97 98 /* non-axis movement */ 99 bool w = kf_checkinputdown(inputbind_move_up); 100 bool s = kf_checkinputdown(inputbind_move_down); 101 bool a = kf_checkinputdown(inputbind_move_left); 102 bool d = kf_checkinputdown(inputbind_move_right); 103 104 if (a && d) { v.x = 0; } 105 else if (a) { v.x = -1; self->pointing = kf_west; } 106 else if (d) { v.x = 1; self->pointing = kf_east; } 107 108 if (w && s) { v.y = 0; } 109 else if (w) { v.y = -1; self->pointing = kf_north; } 110 else if (s) { v.y = 1; self->pointing = kf_south; } 111 112 v = kf_normalize_vec2(f32)(v); 113 114done: 115 if (v.x || v.y) 116 kf_actor_addforce(self, v); 117} 118 119static 120void _player_tick(struct kf_world *world, struct kf_actor *self) 121{ 122 if (menu == menu_none) 123 _player_tick_move(self); 124 125 kf_actor_move(world, self, kf_dts); 126} 127 128static 129void _player_draw(struct kf_world *world, struct kf_actor *self) 130{ 131 (void)world; 132 kf_actor_draw(self); 133 134 cam.target.x = self->pos.x + (self->size.x / 2); 135 cam.target.y = self->pos.y + (self->size.y / 2); 136} 137 138 139int main(int argc, const char *argv[]) 140{ 141 (void)argc; 142 (void)argv; 143 144 SetTraceLogLevel(LOG_WARNING); 145 InitWindow(800, 600, "Keraforge"); 146 SetTargetFPS(60); 147 SetExitKey(KEY_NULL); 148 149 loadbinds(); 150 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; 159 160 struct kf_uiconfig *uiconfig = kf_ui_getconfig(); 161 uiconfig->select = inputbind_select; 162 uiconfig->cancel = inputbind_cancel; 163 uiconfig->up = inputbind_ui_up; 164 uiconfig->down = inputbind_ui_down; 165 166 if (!DirectoryExists("data")) 167 MakeDirectory("data"); 168 169 struct kf_world *world = NULL; 170 if (!kf_exists("data/map.bin")) 171 { 172 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); 176 printf("-> saving world\n"); 177 size_t len = kf_world_getsize(world); 178 printf("-> writing of %lu bytes\n", len); 179 if (!kf_writebin("data/map.bin", (u8 *)world, len)) 180 { 181 fprintf(stderr, "error creating map: failed to save map.bin\n"); 182 free(world); 183 exit(1); 184 } 185 } 186 else 187 { 188 printf("-> loading world\n"); 189 size_t len = 0; 190 world = (struct kf_world *)kf_readbin("data/map.bin", &len); 191 printf("-> world is %lu bytes\n", len); 192 } 193 if (!world) 194 { 195 fprintf(stderr, "error: failed to load world\n"); 196 exit(1); 197 } 198 199 struct kf_actor *player = kf_actor_new(kf_actor_loadspritesheet("data/res/img/char/whom.png"), 10, 10, true); 200 player->sizeoffset.y = 6; 201 player->pos.x = world->width / 4.0f * KF_TILE_SIZE_PX; 202 player->pos.y = world->height / 4.0f * KF_TILE_SIZE_PX; 203 player->tick = _player_tick; 204 player->draw = _player_draw; 205 player->controlled = true; 206 207 cam = (Camera2D){0}; 208 cam.offset.x = GetScreenWidth() / 2.0f; 209 cam.offset.y = GetScreenHeight() / 2.0f; 210 cam.zoom = 2; 211 212 int running = 1; 213 while (!WindowShouldClose() && running) 214 { 215 if (IsWindowResized()) 216 { 217 cam.offset.x = GetScreenWidth() / 2.0f; 218 cam.offset.y = GetScreenHeight() / 2.0f; 219 } 220 221 player->tick(world, player); 222 223 Vector2 v = GetScreenToWorld2D(GetMousePosition(), cam); 224 select.x = v.x / KF_TILE_SIZE_PX; 225 select.y = v.y / KF_TILE_SIZE_PX; 226 227 if (kf_checkinputpress(inputbind_palette)) 228 setmenu(menu_palette); 229 else if (kf_checkinputpress(inputbind_cancel) && menu == menu_none) 230 setmenu(menu_escape); 231 else if (kf_checkinputpress(inputbind_zoom_reset)) 232 cam.zoom = 2; 233 else if (kf_checkinputpress(inputbind_zoom_in) && cam.zoom < 3.50f) 234 cam.zoom += 0.25f; 235 else if (kf_checkinputpress(inputbind_zoom_out) && cam.zoom > 1.00f) 236 cam.zoom -= 0.25f; 237 238 BeginDrawing(); 239 ClearBackground(WHITE); 240 241 BeginMode2D(cam); 242 kf_world_draw(world, cam); 243 // kf_world_drawcolliders(world, player, cam); 244 if (select.x < world->width && select.y < world->height) 245 { 246 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 247 { 248 kf_tileid_t *t = kf_world_gettile(world, select.x, select.y); 249 *t = (kf_tileid_t)selected_tile; 250 } 251 DrawRectangleLines(select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, WHITE); 252 } 253 player->draw(world, player); 254 EndMode2D(); 255 256 if (selected_menu_this_frame) 257 { 258 selected_menu_this_frame = false; 259 } 260 else 261 { 262 switch (menu) 263 { 264 case menu_none: 265 break; 266 case menu_palette: 267 if (kf_ui_choice("Select tile", &kf_tiles.key[0], kf_tiles.count, &selected_tile)) 268 setmenu(menu_none); 269 break; 270 case menu_escape: 271 { 272 static int result = 1; 273 if (kf_ui_yesno("Exit game?", &result)) 274 { 275 menu = menu_none; 276 if (result) 277 running = 0; 278 result = 1; 279 } 280 break; 281 } 282 } 283 } 284 285 DrawFPS(0, 0); 286 DrawText(TextFormat("%f", kf_dts), 0, 20, 20, RED); 287 288 EndDrawing(); 289 290 kf_frame++; 291 kf_dts = GetFrameTime(); 292 kf_dtms = kf_dts * 1000; 293 } 294 295 if (world) 296 { 297 if (!kf_writebin("data/map.bin", (u8 *)world, kf_world_getsize(world))) 298 fprintf(stderr, "error: failed to save map.bin\n"); 299 free(world); 300 } 301 CloseWindow(); 302 303 return 0; 304}