changeset 218:71f989ae8de9

rpg: add support for animated tiles
author David Demelier <markand@malikania.fr>
date Wed, 18 Nov 2020 13:46:29 +0100
parents 836bac1419c7
children 98adc7dcdcb2
files examples/CMakeLists.txt examples/assets/maps/animation-water.png examples/assets/maps/tileset-world.json librpg/rpg/map.c librpg/rpg/tileset-file.c librpg/rpg/tileset-file.h librpg/rpg/tileset.c librpg/rpg/tileset.h tools/tileset/main.c
diffstat 9 files changed, 257 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- 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})
 
Binary file examples/assets/maps/animation-water.png has changed
--- 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":
             {
--- 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);
 }
 
--- 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 <string.h>
 
 #include <core/alloc.h>
+#include <core/animation.h>
 #include <core/error.h>
 #include <core/image.h>
 #include <core/util.h>
@@ -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));
 }
--- 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 <stdbool.h>
+#include <stddef.h>
 
 #include <core/plat.h>
 #include <core/sprite.h>
@@ -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
--- 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 <assert.h>
+#include <stdlib.h>
 
+#include <core/animation.h>
 #include <core/sprite.h>
 
 #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);
 }
--- 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 */
--- 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 <markand@malikania.fr>
  *
@@ -20,6 +20,7 @@
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdnoreturn.h>
+#include <string.h>
 
 #include <jansson.h>
 
@@ -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);
 }