+1
include/keraforge.h
+1
include/keraforge.h
+2
-2
include/keraforge/input.h
+2
-2
include/keraforge/input.h
···
9
10
#define KF_INPUTBIND_MAX UINT8_MAX
11
typedef u8 kf_inputbind_t;
12
+
#define KF_INPUTBIND_NONE ((kf_inputbind_t)0)
13
14
struct _kf_inputbinds
15
{
16
+
kf_inputbind_t count; /* must start at 1. 0 is the `none` keybind. */
17
char *id[KF_INPUTBIND_MAX];
18
KeyboardKey key[KF_INPUTBIND_MAX];
19
MouseButton mouse[KF_INPUTBIND_MAX];
+24
include/keraforge/ui.h
+24
include/keraforge/ui.h
···
···
1
+
#ifndef __kf_ui__
2
+
#define __kf_ui__
3
+
4
+
#include <keraforge/input.h>
5
+
6
+
struct kf_uiconfig
7
+
{
8
+
kf_inputbind_t select, cancel, up, down;
9
+
char *fmt, *selectfmt;
10
+
int fontsize;
11
+
Color bg, fg;
12
+
int panelheight;
13
+
int xpadding, ypadding;
14
+
};
15
+
16
+
struct kf_uiconfig *kf_ui_getconfig(void);
17
+
18
+
int kf_ui_panel(char *title);
19
+
20
+
int kf_ui_choice(char *title, char **choices, int nchoices, int *choice);
21
+
int kf_ui_yesno(char *title, int *choice);
22
+
int kf_ui_textinput(char *title, char *text);
23
+
24
+
#endif
+47
license
+47
license
···
···
1
+
=== Keraforge - BSD 3-Clause ===
2
+
3
+
Copyright 2025 Emmeline Coats
4
+
5
+
Redistribution and use in source and binary forms, with or without
6
+
modification, are permitted provided that the following conditions are met:
7
+
8
+
1. Redistributions of source code must retain the above copyright notice, this
9
+
list of conditions and the following disclaimer.
10
+
11
+
2. Redistributions in binary form must reproduce the above copyright notice,
12
+
this list of conditions and the following disclaimer in the documentation
13
+
and/or other materials provided with the distribution.
14
+
15
+
3. Neither the name of the copyright holder nor the names of its contributors
16
+
may be used to endorse or promote products derived from this software
17
+
without specific prior written permission.
18
+
19
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
20
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+
=== Raylib - zlib ===
31
+
32
+
Copyright (c) 2013-2025 Ramon Santamaria (@raysan5)
33
+
34
+
This software is provided "as-is", without any express or implied warranty. In no event
35
+
will the authors be held liable for any damages arising from the use of this software.
36
+
37
+
Permission is granted to anyone to use this software for any purpose, including commercial
38
+
applications, and to alter it and redistribute it freely, subject to the following restrictions:
39
+
40
+
1. The origin of this software must not be misrepresented; you must not claim that you
41
+
wrote the original software. If you use this software in a product, an acknowledgment
42
+
in the product documentation would be appreciated but is not required.
43
+
44
+
2. Altered source versions must be plainly marked as such, and must not be misrepresented
45
+
as being the original software.
46
+
47
+
3. This notice may not be removed or altered from any source distribution.
+66
readme
+66
readme
···
···
1
+
2
+
Keraforge
3
+
=========
4
+
5
+
A game engine for top-down 2D RPG games.
6
+
7
+
[Warning]
8
+
Keraforge is still a work-in-progress. Expect breaking
9
+
changes!
10
+
11
+
Motive
12
+
------
13
+
14
+
There's already a large number of quality game engines
15
+
and frameworks in the wild, so what does Keraforge do
16
+
differently?
17
+
18
+
Design:
19
+
Keraforge is designed with a specific kind of game in
20
+
mind: top-down, story-driven, handcrafted RPGs. If your
21
+
dream game fits in this category, then Keraforge aims to
22
+
help make it a reality.
23
+
24
+
Simplicity:
25
+
Game engines and frameworks always have a learning
26
+
curve. Keraforge is no exception. What I can aim for,
27
+
though, is keep the learning curve from being exponential
28
+
and overwhelming users. I want Keraforge to allow anyone
29
+
to share their story with an engine that gives them the
30
+
ability to pour love into their work.
31
+
32
+
Cost:
33
+
Keraforge is 100% free (BSD 3-Clause), zero royalties,
34
+
no up-front costs, and no paywalls. I want to give people
35
+
the chance to create something beautiful, not to take
36
+
their money.
37
+
38
+
It's also important to discuss the cons of Keraforge.
39
+
It's going to be fundamentally different from any other
40
+
engine since it's made for a very specific style of game.
41
+
This means that if your game does not fit this style, you
42
+
might have more trouble.
43
+
44
+
Usage
45
+
-----
46
+
47
+
Pre-built binaries are not *yet* distributed. I'll start
48
+
publishing binaries once the engine reaches a stable state.
49
+
For now, you can compile it yourself. See the section on
50
+
development below.
51
+
52
+
If you want to see my development progress, see <todo>.
53
+
54
+
Develop
55
+
-------
56
+
57
+
Build "system" is contained in a folder of shell scripts,
58
+
`scripts`. You can run these with the `run.sh` script to
59
+
run multiple tasks in order. Generally:
60
+
`sh run.sh build run`
61
+
is all you'll need to run while developing.
62
+
63
+
License
64
+
-------
65
+
66
+
BSD 3-Clause, see <license>.
+6
scripts/_config.sh
+6
scripts/_config.sh
+5
-4
src/input.c
+5
-4
src/input.c
···
4
#include <stdio.h>
5
#include <string.h>
6
7
-
const kf_inputbind_t kf_inputbind_none = (kf_inputbind_t)-1;
8
-
struct _kf_inputbinds kf_inputbinds = {0};
9
10
kf_inputbind_t kf_addinput(char *id, KeyboardKey key, MouseButton mouse, GamepadButton gamepad, GamepadAxis axis)
11
{
12
kf_inputbind_t i = kf_inputbinds.count;
13
-
if (i >= KF_INPUTBIND_MAX || i == kf_inputbind_none)
14
{
15
fprintf(stderr, "error: max keybind count is 255.\n");
16
return -1;
···
37
}
38
}
39
40
-
return kf_inputbind_none;
41
}
42
43
int kf_checkkeypress(kf_inputbind_t id) { return kf_inputbinds.key[id] != KEY_NULL && IsKeyPressed(kf_inputbinds.key[id]); }
···
4
#include <stdio.h>
5
#include <string.h>
6
7
+
struct _kf_inputbinds kf_inputbinds = {
8
+
.count = 1,
9
+
};
10
11
kf_inputbind_t kf_addinput(char *id, KeyboardKey key, MouseButton mouse, GamepadButton gamepad, GamepadAxis axis)
12
{
13
kf_inputbind_t i = kf_inputbinds.count;
14
+
if (i >= KF_INPUTBIND_MAX)
15
{
16
fprintf(stderr, "error: max keybind count is 255.\n");
17
return -1;
···
38
}
39
}
40
41
+
return KF_INPUTBIND_NONE;
42
}
43
44
int kf_checkkeypress(kf_inputbind_t id) { return kf_inputbinds.key[id] != KEY_NULL && IsKeyPressed(kf_inputbinds.key[id]); }
+71
-24
src/main.c
+71
-24
src/main.c
···
1
#include "keraforge/input.h"
2
#include <raylib.h>
3
#include <raymath.h>
4
#include <keraforge.h>
···
8
static Camera2D cam;
9
static f32 dt = 0;
10
static struct kf_vec2(u32) select = { 0, 0 };
11
12
static kf_inputbind_t
13
inputbind_move_up,
14
inputbind_move_down,
15
inputbind_move_left,
16
-
inputbind_move_right
17
;
18
19
static void loadbinds()
20
{
21
-
inputbind_move_up = kf_addinput("move_up", KEY_W, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
22
-
inputbind_move_down = kf_addinput("move_down", KEY_S, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
23
-
inputbind_move_left = kf_addinput("move_left", KEY_A, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
24
-
inputbind_move_right = kf_addinput("move_right", KEY_D, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
25
}
26
27
-
static void _player_tick(struct kf_world *world, struct kf_actor *self)
28
{
29
-
bool w = kf_checkinputdown(inputbind_move_up), s = kf_checkinputdown(inputbind_move_down);
30
-
bool a = kf_checkinputdown(inputbind_move_left), d = kf_checkinputdown(inputbind_move_right);
31
struct kf_vec2(f32) v = {0, 0};
32
33
if (w && s)
···
48
kf_actor_addforce(self, kf_normalize_vec2(f32)(v));
49
50
if (IsKeyPressed(KEY_SPACE) && self->speedmod <= 1.0f)
51
-
self->speedmod = 3.0f;
52
53
kf_actor_move(world, self, dt);
54
}
···
72
SetTraceLogLevel(LOG_WARNING);
73
InitWindow(800, 600, "Keraforge");
74
SetTargetFPS(60);
75
76
kf_tiles.color[0] = GREEN;
77
kf_tiles.color[1] = BROWN;
78
kf_tiles.color[2] = GRAY;
79
kf_tiles.collide[2] = true;
80
kf_tiles.count = 3;
81
82
if (!DirectoryExists("data"))
83
MakeDirectory("data");
84
···
127
player->draw = _player_draw;
128
129
cam = (Camera2D){{GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f}, {0, 0}, 0, 2};
130
-
while (!WindowShouldClose())
131
{
132
if (IsWindowResized())
133
{
···
141
select.x = v.x / KF_TILE_SIZE_PX;
142
select.y = v.y / KF_TILE_SIZE_PX;
143
144
BeginDrawing();
145
ClearBackground(WHITE);
146
···
151
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
152
{
153
kf_tileid_t *t = kf_world_gettile(world, select.x, select.y);
154
-
if (++(*t) >= kf_tiles.count)
155
-
*t = 0;
156
-
}
157
-
else if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT))
158
-
{
159
-
kf_tileid_t *t = kf_world_gettile(world, select.x, select.y);
160
-
if (--(*t) >= kf_tiles.count)
161
-
*t = 0;
162
-
}
163
-
else if (IsMouseButtonDown(MOUSE_BUTTON_MIDDLE))
164
-
{
165
-
kf_tileid_t *t = kf_world_gettile(world, select.x, select.y);
166
-
if (*t)
167
-
*t = 0;
168
}
169
DrawRectangleLines(select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, WHITE);
170
}
171
player->draw(world, player);
172
EndMode2D();
173
174
DrawFPS(0, 0);
175
DrawText(TextFormat("%f", dt), 0, 20, 20, RED);
···
1
#include "keraforge/input.h"
2
+
#include "keraforge/ui.h"
3
#include <raylib.h>
4
#include <raymath.h>
5
#include <keraforge.h>
···
9
static Camera2D cam;
10
static f32 dt = 0;
11
static struct kf_vec2(u32) select = { 0, 0 };
12
+
static int selected_tile = 0;
13
+
14
+
static struct
15
+
{
16
+
enum { menu_none, menu_palette, menu_escape } menu;
17
+
} menu;
18
19
static kf_inputbind_t
20
inputbind_move_up,
21
inputbind_move_down,
22
inputbind_move_left,
23
+
inputbind_move_right,
24
+
inputbind_select,
25
+
inputbind_cancel,
26
+
inputbind_palette
27
;
28
29
static void loadbinds()
30
{
31
+
inputbind_move_up = kf_addinput("move_up", KEY_UP, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y);
32
+
inputbind_move_down = kf_addinput("move_down", KEY_DOWN, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y);
33
+
inputbind_move_left = kf_addinput("move_left", KEY_LEFT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X);
34
+
inputbind_move_right = kf_addinput("move_right", KEY_RIGHT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X);
35
+
inputbind_select = kf_addinput("select", KEY_Z, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN);
36
+
inputbind_cancel = kf_addinput("cancel", KEY_X, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN);
37
+
inputbind_palette = kf_addinput("palette", KEY_TAB, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_UP, GAMEPAD_AXIS_UNKNOWN);
38
}
39
40
+
static void _player_tick_move(struct kf_actor *self)
41
{
42
+
bool w = kf_checkinputdown(inputbind_move_up);
43
+
bool s = kf_checkinputdown(inputbind_move_down);
44
+
bool a = kf_checkinputdown(inputbind_move_left);
45
+
bool d = kf_checkinputdown(inputbind_move_right);
46
+
47
struct kf_vec2(f32) v = {0, 0};
48
49
if (w && s)
···
64
kf_actor_addforce(self, kf_normalize_vec2(f32)(v));
65
66
if (IsKeyPressed(KEY_SPACE) && self->speedmod <= 1.0f)
67
+
self->speedmod = 2.0f;
68
+
}
69
+
70
+
static void _player_tick(struct kf_world *world, struct kf_actor *self)
71
+
{
72
+
if (menu.menu == menu_none)
73
+
_player_tick_move(self);
74
75
kf_actor_move(world, self, dt);
76
}
···
94
SetTraceLogLevel(LOG_WARNING);
95
InitWindow(800, 600, "Keraforge");
96
SetTargetFPS(60);
97
+
SetExitKey(KEY_NULL);
98
99
+
kf_tiles.key[0] = "grass";
100
kf_tiles.color[0] = GREEN;
101
+
kf_tiles.key[1] = "dirt";
102
kf_tiles.color[1] = BROWN;
103
+
kf_tiles.key[2] = "stone";
104
kf_tiles.color[2] = GRAY;
105
kf_tiles.collide[2] = true;
106
kf_tiles.count = 3;
107
108
+
struct kf_uiconfig *uiconfig = kf_ui_getconfig();
109
+
uiconfig->select = inputbind_select;
110
+
uiconfig->cancel = inputbind_cancel;
111
+
uiconfig->up = inputbind_move_up;
112
+
uiconfig->down = inputbind_move_down;
113
+
114
if (!DirectoryExists("data"))
115
MakeDirectory("data");
116
···
159
player->draw = _player_draw;
160
161
cam = (Camera2D){{GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f}, {0, 0}, 0, 2};
162
+
int running = 1;
163
+
while (!WindowShouldClose() && running)
164
{
165
if (IsWindowResized())
166
{
···
174
select.x = v.x / KF_TILE_SIZE_PX;
175
select.y = v.y / KF_TILE_SIZE_PX;
176
177
+
if (kf_checkinputpress(inputbind_palette))
178
+
menu.menu = menu_palette;
179
+
else if (IsKeyPressed(KEY_ESCAPE) && menu.menu == menu_none)
180
+
menu.menu = menu_escape;
181
+
182
BeginDrawing();
183
ClearBackground(WHITE);
184
···
189
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
190
{
191
kf_tileid_t *t = kf_world_gettile(world, select.x, select.y);
192
+
*t = (kf_tileid_t)selected_tile;
193
}
194
DrawRectangleLines(select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, WHITE);
195
}
196
player->draw(world, player);
197
EndMode2D();
198
+
199
+
switch (menu.menu)
200
+
{
201
+
case menu_none:
202
+
break;
203
+
case menu_palette:
204
+
if (kf_ui_choice("Select tile", &kf_tiles.key[0], kf_tiles.count, &selected_tile))
205
+
menu.menu = menu_none;
206
+
break;
207
+
case menu_escape:
208
+
{
209
+
static int result = 0;
210
+
if (kf_ui_yesno("Exit game?", &result))
211
+
{
212
+
menu.menu = menu_none;
213
+
if (result == 0)
214
+
running = 0;
215
+
result = 0;
216
+
}
217
+
break;
218
+
}
219
+
}
220
221
DrawFPS(0, 0);
222
DrawText(TextFormat("%f", dt), 0, 20, 20, RED);
+82
src/ui.c
+82
src/ui.c
···
···
1
+
#include <keraforge.h>
2
+
#include <raylib.h>
3
+
4
+
static struct kf_uiconfig _kf_uiconfig = {
5
+
.select = KF_INPUTBIND_NONE,
6
+
.cancel = KF_INPUTBIND_NONE,
7
+
.up = KF_INPUTBIND_NONE,
8
+
.down = KF_INPUTBIND_NONE,
9
+
.fmt = " %s ",
10
+
.selectfmt = "> %s <",
11
+
.fontsize = 20,
12
+
.bg = BLACK,
13
+
.fg = WHITE,
14
+
.panelheight = 200,
15
+
.xpadding = 8,
16
+
.ypadding = 16,
17
+
};
18
+
19
+
struct kf_uiconfig *kf_ui_getconfig(void)
20
+
{
21
+
return &_kf_uiconfig;
22
+
}
23
+
24
+
int kf_ui_panel(char *title)
25
+
{
26
+
int y = GetScreenHeight() - _kf_uiconfig.panelheight;
27
+
DrawRectangle(0, y, GetScreenWidth(), _kf_uiconfig.panelheight, _kf_uiconfig.bg);
28
+
29
+
if (title)
30
+
{
31
+
y += _kf_uiconfig.ypadding;
32
+
DrawText(title, _kf_uiconfig.xpadding, y, _kf_uiconfig.fontsize, _kf_uiconfig.fg);
33
+
y += _kf_uiconfig.fontsize;
34
+
}
35
+
36
+
return y;
37
+
}
38
+
39
+
int kf_ui_choice(char *title, char **choices, int nchoices, int *choice)
40
+
{
41
+
int y = kf_ui_panel(title);
42
+
43
+
if (*choice >= nchoices)
44
+
goto skip_text;
45
+
46
+
for (int i = 0 ; i < nchoices ; i++)
47
+
{
48
+
char *c = choices[i];
49
+
50
+
DrawText(
51
+
TextFormat(i == *choice ? _kf_uiconfig.selectfmt : _kf_uiconfig.fmt, c),
52
+
_kf_uiconfig.xpadding,
53
+
y,
54
+
_kf_uiconfig.fontsize,
55
+
_kf_uiconfig.fg
56
+
);
57
+
58
+
y += _kf_uiconfig.fontsize;
59
+
if (y >= GetScreenHeight())
60
+
break;
61
+
}
62
+
63
+
skip_text:
64
+
65
+
if (kf_checkkeypress(_kf_uiconfig.select) || kf_checkkeypress(_kf_uiconfig.cancel))
66
+
return 1;
67
+
else if (kf_checkkeypress(_kf_uiconfig.down) && *choice < nchoices)
68
+
(*choice)++;
69
+
else if (kf_checkkeypress(_kf_uiconfig.up) && *choice > 0)
70
+
(*choice)--;
71
+
72
+
return 0;
73
+
}
74
+
75
+
int kf_ui_yesno(char *title, int *choice)
76
+
{
77
+
static char *yesno[] = { "yes", "no" };
78
+
return kf_ui_choice(title, yesno, 2, choice);
79
+
}
80
+
81
+
int kf_ui_textinput(char *title, char *text);
82
+
+25
todo
+25
todo
···
···
1
+
2
+
Dots (.) are to-do
3
+
Slashes (/) are in-progress
4
+
Xs (X) are done
5
+
Squiggly lines (~) are maybes
6
+
7
+
Keraforge 1.0
8
+
-------------
9
+
10
+
. Core
11
+
. World
12
+
/ Tiles
13
+
. Actors
14
+
. Dialogue
15
+
. Quests
16
+
. Combat
17
+
. Character Stats
18
+
. (De)Buffs
19
+
. Cutscenes
20
+
. Engine
21
+
. Map+Room editor
22
+
. Character creator
23
+
. Dialogue editor
24
+
. Quest creator
25
+
~ Scripting