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}