diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-12 03:32:58 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-12 03:32:58 +0800 |
| commit | 25bb4216fd726824c237a00c8ca0ee23b39d995e (patch) | |
| tree | 5377508861ba5ea09dde7e755c80c98cee94e696 | |
| parent | ca1f5cd724644aee01d01b4d12d52f8893e01e54 (diff) | |
add rpg module: grid walking actors and tile events
| -rw-r--r-- | README.md | 1 | ||||
| -rwxr-xr-x | build.sh | 6 | ||||
| -rw-r--r-- | kon/konrpg.c | 152 | ||||
| -rw-r--r-- | kon/konrpg.h | 104 |
4 files changed, 260 insertions, 3 deletions
@@ -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) | @@ -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 |
