# HG changeset patch # User David Demelier # Date 1605703589 -3600 # Node ID 71f989ae8de92dde44459829adbe47b51032d0e8 # Parent 836bac1419c7b11f45e4bee9d1a50ea018f0eb68 rpg: add support for animated tiles diff -r 836bac1419c7 -r 71f989ae8de9 examples/CMakeLists.txt --- a/examples/CMakeLists.txt Wed Nov 18 10:13:29 2020 +0100 +++ b/examples/CMakeLists.txt Wed Nov 18 13:46:29 2020 +0100 @@ -37,6 +37,11 @@ ) set( + ASSETS_MAPS_ANIMATIONS + ${examples_SOURCE_DIR}/assets/maps/animation-water.png +) + +set( ASSETS_TILESETS ${examples_SOURCE_DIR}/assets/maps/tileset-town.json ${examples_SOURCE_DIR}/assets/maps/tileset-world.json @@ -76,7 +81,7 @@ # These files just need to be copied. file(MAKE_DIRECTORY ${cmake_BINARY_DIR}/assets/maps) -foreach (s ${ASSETS_MAPS_SPRITES}) +foreach (s ${ASSETS_MAPS_SPRITES} ${ASSETS_MAPS_ANIMATIONS}) get_filename_component(basename ${s} NAME) set(output ${examples_BINARY_DIR}/assets/maps/${basename}) diff -r 836bac1419c7 -r 71f989ae8de9 examples/assets/maps/animation-water.png Binary file examples/assets/maps/animation-water.png has changed diff -r 836bac1419c7 -r 71f989ae8de9 examples/assets/maps/tileset-world.json --- a/examples/assets/maps/tileset-world.json Wed Nov 18 10:13:29 2020 +0100 +++ b/examples/assets/maps/tileset-world.json Wed Nov 18 13:46:29 2020 +0100 @@ -1,4 +1,12 @@ { "columns":24, + "editorsettings": + { + "export": + { + "format":"", + "target":"." + } + }, "image":"sprite-world.png", "imageheight":1392, "imagewidth":1152, @@ -160,6 +168,20 @@ } }, { + "id":190, + "properties":[ + { + "name":"animation-delay", + "type":"int", + "value":500 + }, + { + "name":"animation-file", + "type":"string", + "value":"animation-water.png" + }] + }, + { "id":191, "objectgroup": { diff -r 836bac1419c7 -r 71f989ae8de9 librpg/rpg/map.c --- a/librpg/rpg/map.c Wed Nov 18 10:13:29 2020 +0100 +++ b/librpg/rpg/map.c Wed Nov 18 13:46:29 2020 +0100 @@ -534,6 +534,7 @@ assert(map); init(map); + tileset_start(map->tileset); return true; } @@ -563,6 +564,7 @@ action_stack_update(&map->actions, ticks); + tileset_update(map->tileset, ticks); move(map, ticks); } diff -r 836bac1419c7 -r 71f989ae8de9 librpg/rpg/tileset-file.c --- a/librpg/rpg/tileset-file.c Wed Nov 18 10:13:29 2020 +0100 +++ b/librpg/rpg/tileset-file.c Wed Nov 18 13:46:29 2020 +0100 @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -35,7 +36,13 @@ #include "tileset.h" #define MAX_F(v) MAX_F_(v) -#define MAX_F_(v) "%" #v "c" +#define MAX_F_(v) "%" #v "[^|]" + +struct tileset_file_animation { + struct texture texture; + struct sprite sprite; + struct animation animation; +}; struct context { struct tileset_file *tf; @@ -50,6 +57,10 @@ */ unsigned int tilewidth; unsigned int tileheight; + + /* Number of rows/columns in the image. */ + unsigned int nrows; + unsigned int ncolumns; }; static int @@ -66,6 +77,20 @@ return 0; } +static int +anim_cmp(const void *d1, const void *d2) +{ + const struct tileset_animation *mtd1 = d1; + const struct tileset_animation *mtd2 = d2; + + if (mtd1->id < mtd2->id) + return -1; + if (mtd1->id > mtd2->id) + return 1; + + return 0; +} + static bool parse_tilewidth(struct context *ctx, const char *line) { @@ -111,6 +136,53 @@ } static bool +parse_animations(struct context *ctx, const char *line) +{ + (void)line; + + unsigned short id; + unsigned int delay; + char filename[FILENAME_MAX + 1], path[PATH_MAX]; + struct tileset *tileset = ctx->tileset; + struct tileset_file *tf = ctx->tf; + + while (fscanf(ctx->fp, "%hu|" MAX_F(FILENAME_MAX) "|%u", &id, filename, &delay) == 3) { + struct tileset_file_animation *tfa; + + /* + * We need two arrays because one must contains sprite, texture + * and the animation while the tileset user side API only use + * one animation reference. + */ + tf->tfasz++; + tf->tfas = allocator.realloc(tf->tfas, tf->tfasz * sizeof (*tf->tfas)); + tfa = &tf->tfas[tf->tfasz - 1]; + + /* This is the real user-side tileset array of animations. */ + tf->anims = allocator.realloc(tf->anims, tf->tfasz * sizeof (*tf->anims)); + + snprintf(path, sizeof (path), "%s/%s", ctx->basedir, filename); + + if (!image_open(&tfa->texture, path)) + return false; + + /* Initialize animation. */ + sprite_init(&tfa->sprite, &tfa->texture, ctx->tilewidth, ctx->tileheight); + animation_init(&tfa->animation, &tfa->sprite, delay); + + /* Finally store it in the tiledef. */ + tf->anims[tf->tfasz - 1].id = id; + tf->anims[tf->tfasz - 1].animation = &tfa->animation; + } + + qsort(tf->anims, tf->tfasz, sizeof (*tf->anims), anim_cmp); + tileset->anims = tf->anims; + tileset->animsz = tf->tfasz; + + return true; +} + +static bool parse_image(struct context *ctx, const char *line) { char path[PATH_MAX], *p; @@ -141,6 +213,7 @@ { "tilewidth", parse_tilewidth }, { "tileheight", parse_tileheight }, { "tiledefs", parse_tiledefs }, + { "animations", parse_animations }, { "image", parse_image } }; @@ -209,8 +282,13 @@ { assert(tf); + for (size_t i = 0; i < tf->tfasz; ++i) + texture_finish(&tf->tfas[i].texture); + texture_finish(&tf->image); free(tf->tiledefs); + free(tf->tfas); + free(tf->anims); memset(tf, 0, sizeof (*tf)); } diff -r 836bac1419c7 -r 71f989ae8de9 librpg/rpg/tileset-file.h --- a/librpg/rpg/tileset-file.h Wed Nov 18 10:13:29 2020 +0100 +++ b/librpg/rpg/tileset-file.h Wed Nov 18 13:46:29 2020 +0100 @@ -20,6 +20,7 @@ #define MOLKO_RPG_TILESET_FILE_H #include +#include #include #include @@ -32,6 +33,9 @@ struct tileset_tiledef *tiledefs; /*!< (*) Owned tile definitions. */ struct texture image; /*!< (*) Owned image file. */ struct sprite sprite; /*!< (*) Owned sprite. */ + struct tileset_file_animation *tfas; /*!< (*) Owned per tile animations. */ + size_t tfasz; /*!< (*) Onwed number of tiles. */ + struct tileset_animation *anims; /*!< (*) Owned animations array. */ }; bool diff -r 836bac1419c7 -r 71f989ae8de9 librpg/rpg/tileset.c --- a/librpg/rpg/tileset.c Wed Nov 18 10:13:29 2020 +0100 +++ b/librpg/rpg/tileset.c Wed Nov 18 13:46:29 2020 +0100 @@ -17,11 +17,37 @@ */ #include +#include +#include #include #include "tileset.h" +static inline int +anim_cmp(const void *d1, const void *d2) +{ + const struct tileset_animation *mtd1 = d1; + const struct tileset_animation *mtd2 = d2; + + if (mtd1->id < mtd2->id) + return -1; + if (mtd1->id > mtd2->id) + return 1; + + return 0; +} + +static inline const struct tileset_animation * +find(const struct tileset *ts, unsigned int r, unsigned int c) +{ + const struct tileset_animation key = { + .id = c + (r * ts->sprite->ncols) + }; + + return bsearch(&key, ts->anims, ts->animsz, sizeof (key), anim_cmp); +} + bool tileset_ok(const struct tileset *ts) { @@ -29,9 +55,39 @@ } void +tileset_start(struct tileset *ts) +{ + for (size_t i = 0; i < ts->animsz; ++i) { + struct tileset_animation *ta = &ts->anims[i]; + + if (ta->animation) + animation_start(ta->animation); + } +} + +void +tileset_update(struct tileset *ts, unsigned int ticks) +{ + for (size_t i = 0; i < ts->animsz; ++i) { + struct tileset_animation *ta = &ts->anims[i]; + + if (!ta->animation) + continue; + + if (animation_update(ta->animation, ticks)) + animation_start(ta->animation); + } +} + +void tileset_draw(const struct tileset *ts, unsigned int r, unsigned int c, int x, int y) { assert(ts); - sprite_draw(ts->sprite, r, c, x, y); + const struct tileset_animation *ta; + + if ((ta = find(ts, r, c))) + animation_draw(ta->animation, x, y); + else + sprite_draw(ts->sprite, r, c, x, y); } diff -r 836bac1419c7 -r 71f989ae8de9 librpg/rpg/tileset.h --- a/librpg/rpg/tileset.h Wed Nov 18 10:13:29 2020 +0100 +++ b/librpg/rpg/tileset.h Wed Nov 18 13:46:29 2020 +0100 @@ -30,19 +30,26 @@ * It can contains an animation and a collision mask. */ struct tileset_tiledef { - short id; /*!< (+) Tile index. */ + unsigned short id; /*!< (+) Tile index. */ short x; /*!< (+) Collision region starts in y. */ short y; /*!< (+) Collision region starts in y. */ unsigned short w; /*!< (+) Collision width. */ unsigned short h; /*!< (+) Collision height. */ }; +struct tileset_animation { + unsigned short id; /* (*) Tile index. */ + struct animation *animation; /* (+&?) Animation. */ +}; + /** * \brief Tileset definition. */ struct tileset { struct tileset_tiledef *tiledefs; /*!< (+&?) Per tile properties (must be sorted by id). */ size_t tiledefsz; /*!< (+) Number of tile properties. */ + struct tileset_animation *anims; /*!< (+&?) Per tile animations (must be sorted by id). */ + size_t animsz; /*!< (+) Number of tile animations. */ struct sprite *sprite; /*!< (+&) Sprite to generate the terrain. */ }; @@ -50,6 +57,12 @@ tileset_ok(const struct tileset *ts); void +tileset_start(struct tileset *ts); + +void +tileset_update(struct tileset *ts, unsigned int ticks); + +void tileset_draw(const struct tileset *ts, unsigned int r, unsigned int c, int x, int y); #endif /* !MOLKO_RPG_TILESET_H */ diff -r 836bac1419c7 -r 71f989ae8de9 tools/tileset/main.c --- a/tools/tileset/main.c Wed Nov 18 10:13:29 2020 +0100 +++ b/tools/tileset/main.c Wed Nov 18 13:46:29 2020 +0100 @@ -1,5 +1,5 @@ /* - * mlk-tileset.c -- convert tiled tilesets JSON files into custom files + * main.c -- convert tiled tilesets JSON files into custom files * * Copyright (c) 2020 David Demelier * @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -62,6 +63,50 @@ printf("image|%s\n", json_string_value(image)); } +static const json_t * +find_property_value(const json_t *array, const char *prop) +{ + const json_t *obj; + size_t i; + + json_array_foreach(array, i, obj) { + const json_t *name = json_object_get(obj, "name"); + + if (!name || !json_is_string(name)) + die("invalid property object\n"); + + if (strcmp(json_string_value(name), prop) == 0) + return json_object_get(obj, "value"); + } + + return NULL; +} + +static void +write_animation(const json_t *tile) +{ + const json_t *id = json_object_get(tile, "id"); + const json_t *properties = json_object_get(tile, "properties"); + const json_t *file = find_property_value(properties, "animation-file"); + const json_t *delay = find_property_value(properties, "animation-delay"); + + /* Animations are completely optional. */ + if (!json_is_array(properties)) + return; + + if (!json_is_integer(id)) + die("invalid 'id' property in tile\n"); + + if (json_is_string(file)) { + printf("%d|%s|", (int)json_integer_value(id), json_string_value(file)); + + if (json_is_integer(delay)) + printf("%d\n", (int)json_integer_value(delay)); + else + printf("10\n"); + } +} + static void write_tiledef(const json_t *tile) { @@ -71,10 +116,12 @@ const json_t *first = json_array_get(objects, 0); const json_t *x, *y, *w, *h; + /* Collisions are optional. */ + if (!json_is_object(objectgroup)) + return; + if (!json_is_integer(id)) die("invalid 'id' property in tile\n"); - if (!json_is_object(objectgroup)) - die("invalid 'objectgroup' property in tile\n"); if (!json_is_array(objects)) die("invalid 'objects' property in tile\n"); @@ -114,9 +161,31 @@ } } +static void +write_animations(const json_t *tiles) +{ + size_t index; + json_t *object; + + if (!json_is_array(tiles)) + return; + + puts("animations"); + + json_array_foreach(tiles, index, object) { + if (!json_is_object(object)) + die("tile is not an object\n"); + + write_animation(object); + } +} + int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + json_t *document; json_error_t error; @@ -130,6 +199,7 @@ write_dimensions(document); write_image(document); write_tiledefs(json_object_get(document, "tiles")); + write_animations(json_object_get(document, "tiles")); json_decref(document); }