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}