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}