summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rwxr-xr-xbuild.sh6
-rw-r--r--kon/konrpg.c152
-rw-r--r--kon/konrpg.h104
4 files changed, 260 insertions, 3 deletions
diff --git a/README.md b/README.md
index 9a8da19..7e47c92 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ The engine modules are a `.h` and a `.c` pair, they depend on each other a bit,
| tile | tilemaps and sprite sheets |
| 2d | what any 2D game needs: a scrolling camera, frame animation, drawing frames of a sprite sheet |
| platformer | a box that runs and jumps through a tilemap, with coyote time and jump buffering (adds tile) |
+| rpg | top-down games on tilemaps: actors that walk a tile at a time, events on tiles (talk, touch, step), directions (adds tile) |
| 3d | first person kit: mouse look body, walls from a tilemap, editable hill terrain (adds tile) |
| sector | maps made of rooms (vertices, floors, ceilings, walls) with entities like doors and buttons, in a text format |
| world | one place to move bodies through sector maps, terrain, tilemaps and your own boxes, cylinders, spheres and planes (needs 3d, sector) |
diff --git a/build.sh b/build.sh
index 5d1c4f8..78fc5d3 100755
--- a/build.sh
+++ b/build.sh
@@ -12,7 +12,7 @@ developer=0
usage()
{
echo "usage: build.sh [-d] [-D] [-p platform] [-m module,...] [-o objdir] [target ...]"
- echo "modules: font toml tile pack ui debug assets image audio mic model 2d platformer 3d sector world"
+ echo "modules: font toml tile pack ui debug assets image audio mic model 2d platformer rpg 3d sector world"
echo "platforms: native linux bsd windows"
echo "targets: all run clean tools pack examples"
exit 1
@@ -77,7 +77,7 @@ addobj()
for m in $modules; do
DEFS="$DEFS -DKON_MODULE_$(echo "$m" | tr 'a-z' 'A-Z')"
case $m in
- font|toml|tile|pack|ui|debug|2d|platformer|3d|sector|world) addobj "kon$m" ;;
+ font|toml|tile|pack|ui|debug|2d|platformer|rpg|3d|sector|world) addobj "kon$m" ;;
assets) addobj konassets ;;
model) addobj konmodel; addobj konassets ;;
image) addobj stb_image ;;
@@ -106,7 +106,7 @@ need()
case " $modules " in *" debug "*) need font; need assets ;; esac
case " $modules " in *" ui "*) need font ;; esac
case " $modules " in *" world "*) need 3d; need sector; need tile ;; esac
-case " $modules " in *" platformer "*) need tile ;; esac
+case " $modules " in *" platformer "*|*" rpg "*) need tile ;; esac
case " $modules " in *" 3d "*) need tile ;; esac
case " $modules " in *" model "*) need assets ;; esac
case " $DEFS " in *" -DKON_MODULE_ASSETS "*) need pack ;; esac
diff --git a/kon/konrpg.c b/kon/konrpg.c
new file mode 100644
index 0000000..f56c098
--- /dev/null
+++ b/kon/konrpg.c
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konrpg.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "konrpg.h"
+
+/*** directions ***/
+
+void kon_dirDelta(kon_dir_t dir, int *dx, int *dy)
+{
+ *dx = 0;
+ *dy = 0;
+ switch (dir) {
+ case KON_DIR_DOWN: *dy = 1; break;
+ case KON_DIR_LEFT: *dx = -1; break;
+ case KON_DIR_RIGHT: *dx = 1; break;
+ case KON_DIR_UP: *dy = -1; break;
+ }
+}
+
+kon_dir_t kon_dirOpposite(kon_dir_t dir)
+{
+ switch (dir) {
+ case KON_DIR_DOWN: return KON_DIR_UP;
+ case KON_DIR_LEFT: return KON_DIR_RIGHT;
+ case KON_DIR_RIGHT: return KON_DIR_LEFT;
+ case KON_DIR_UP: break;
+ }
+ return KON_DIR_DOWN;
+}
+
+/*** actors ***/
+
+void kon_actorInit(kon_actor_t *actor, int column, int row, kon_dir_t facing)
+{
+ kon_actor_t zero = {0};
+
+ *actor = zero;
+ actor->column = actor->fromColumn = column;
+ actor->row = actor->fromRow = row;
+ actor->facing = facing;
+ actor->progress = 1.0f;
+ actor->speed = 4.0f;
+}
+
+int kon_actorWalk(kon_actor_t *actor, const kon_tilemap_t *map, const uint8_t *solid, int solidCount, kon_dir_t dir, kon_blockFn blocked, void *user)
+{
+ int dx, dy, column, row, id;
+
+ if (!map || actor->moving) return 0;
+ actor->facing = dir;
+ kon_dirDelta(dir, &dx, &dy);
+ column = actor->column + dx;
+ row = actor->row + dy;
+
+ if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return 0;
+ id = kon_tilemapAt(map, column, row);
+ if (solid && id < solidCount && solid[id]) return 0;
+ if (blocked && blocked(column, row, user)) return 0;
+
+ actor->fromColumn = actor->column;
+ actor->fromRow = actor->row;
+ actor->column = column;
+ actor->row = row;
+ actor->moving = 1;
+ actor->progress = actor->carry * actor->speed;
+ if (actor->progress >= 1.0f) actor->progress = 0.999f;
+ actor->carry = 0.0f;
+ return 1;
+}
+
+int kon_actorUpdate(kon_actor_t *actor, float dt)
+{
+ if (!actor->moving) {
+ actor->carry = 0.0f;
+ return 0;
+ }
+ if (!(dt > 0.0f) || !(actor->speed > 0.0f)) return 0;
+
+ actor->progress += actor->speed * dt;
+ if (actor->progress < 1.0f) return 0;
+
+ actor->carry = (actor->progress - 1.0f) / actor->speed;
+ actor->progress = 1.0f;
+ actor->moving = 0;
+ return 1;
+}
+
+void kon_actorPixel(const kon_actor_t *actor, const kon_tilemap_t *map, float *x, float *y)
+{
+ float t = actor->moving ? actor->progress : 1.0f;
+ float fromX = (float)(actor->fromColumn * map->tileWidth), fromY = (float)(actor->fromRow * map->tileHeight);
+ float toX = (float)(actor->column * map->tileWidth), toY = (float)(actor->row * map->tileHeight);
+
+ *x = fromX + (toX - fromX) * t;
+ *y = fromY + (toY - fromY) * t;
+}
+
+void kon_actorFront(const kon_actor_t *actor, int *column, int *row)
+{
+ int dx, dy;
+
+ kon_dirDelta(actor->facing, &dx, &dy);
+ *column = actor->column + dx;
+ *row = actor->row + dy;
+}
+
+/*** events ***/
+
+int kon_eventAt(const kon_tileEvent_t *events, int count, int column, int row, int trigger)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (events[i].column != column || events[i].row != row) continue;
+ if (trigger >= 0 && (int)events[i].trigger != trigger) continue;
+ return i;
+ }
+ return -1;
+}
+
+int kon_eventBlocks(int column, int row, void *user)
+{
+ const kon_eventList_t *list = user;
+ int i;
+
+ for (i = 0; i < list->count; i++) {
+ if (list->events[i].blocks && list->events[i].column == column && list->events[i].row == row) return 1;
+ }
+ return 0;
+}
diff --git a/kon/konrpg.h b/kon/konrpg.h
new file mode 100644
index 0000000..3eb1820
--- /dev/null
+++ b/kon/konrpg.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konrpg.h
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef KONRPG_H
+#define KONRPG_H
+
+#include "kontile.h"
+
+/*** directions ***/
+
+/* the order of the rows in an RPG Maker character sheet */
+typedef enum kon_dir {
+ KON_DIR_DOWN,
+ KON_DIR_LEFT,
+ KON_DIR_RIGHT,
+ KON_DIR_UP
+} kon_dir_t;
+
+/* one tile in that direction, y grows downwards */
+void kon_dirDelta(kon_dir_t dir, int *dx, int *dy);
+kon_dir_t kon_dirOpposite(kon_dir_t dir);
+
+/*** actors ***/
+
+/* something that walks a tile at a time. column, row is the tile it is on, or heading for while it moves.
+ the game picks the direction every frame, kon_actorWalk starts a step when it is standing still and kon_actorUpdate moves it along */
+typedef struct kon_actor {
+ int column, row;
+ int fromColumn, fromRow;
+ kon_dir_t facing;
+ int moving;
+ float progress; /* 0 to 1 along the current step */
+ float speed; /* tiles a second */
+
+ /* private */
+ float carry;
+} kon_actor_t;
+
+/* return non-zero if something other than the tilemap stands in the way of a step onto column, row */
+typedef int (*kon_blockFn)(int column, int row, void *user);
+
+void kon_actorInit(kon_actor_t *actor, int column, int row, kon_dir_t facing);
+
+/* turns to face dir, then starts a step if the tile ahead is inside the map, not solid (see kon_tilemapBoxHits for solid) and not
+ blocked by the callback, which may be NULL. does nothing but turn while a step is going. returns 1 if a step started */
+int kon_actorWalk(kon_actor_t *actor, const kon_tilemap_t *map, const uint8_t *solid, int solidCount, kon_dir_t dir, kon_blockFn blocked, void *user);
+
+/* returns 1 on the frame the actor arrives on a tile. time left over at the end of a step is kept for the next one,
+ so holding a direction walks at exactly speed at any frame rate */
+int kon_actorUpdate(kon_actor_t *actor, float dt);
+
+/* where the actor's top left is in map pixels, in between tiles while it moves */
+void kon_actorPixel(const kon_actor_t *actor, const kon_tilemap_t *map, float *x, float *y);
+
+/* the tile the actor is looking at, the one it is on while it moves */
+void kon_actorFront(const kon_actor_t *actor, int *column, int *row);
+
+/*** events ***/
+
+typedef enum kon_trigger {
+ KON_TRIGGER_TALK, /* the actor faces it and presses the action key */
+ KON_TRIGGER_TOUCH, /* the actor walks into it */
+ KON_TRIGGER_STEP /* the actor arrives on it */
+} kon_trigger_t;
+
+/* a thing on the map the game reacts to. id means whatever the game wants, a switch into its own script, a door, a chest.
+ blocks makes it solid like a person or a rock. to move one just change its column and row */
+typedef struct kon_tileEvent {
+ int column, row;
+ kon_trigger_t trigger;
+ int id;
+ int blocks;
+} kon_tileEvent_t;
+
+/* index of the first event on the tile with that trigger, or -1. trigger -1 matches any */
+int kon_eventAt(const kon_tileEvent_t *events, int count, int column, int row, int trigger);
+
+/* 1 if a blocking event is on the tile, for kon_actorWalk. user is a kon_eventList_t */
+typedef struct kon_eventList { const kon_tileEvent_t *events; int count; } kon_eventList_t;
+int kon_eventBlocks(int column, int row, void *user);
+
+#endif