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