Mercurial > molko
changeset 250:8ef7fb7f14ad
rpg: add support for collisions with actions
While here, minimal cleanup in maps.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 01 Dec 2020 19:24:11 +0100 |
parents | f4dc208aa1e3 |
children | a6a850e65d23 |
files | doc/docs/specs/map.md examples/assets/maps/map-world.json examples/example-map/main.c libmlk-core/core/maths.c libmlk-rpg/rpg/map-file.c libmlk-rpg/rpg/map-file.h libmlk-rpg/rpg/map.c libmlk-rpg/rpg/map.h tools/map/main.c |
diffstat | 9 files changed, 204 insertions(+), 137 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/docs/specs/map.md Tue Dec 01 17:18:12 2020 +0100 +++ b/doc/docs/specs/map.md Tue Dec 01 19:24:11 2020 +0100 @@ -45,7 +45,7 @@ <int> ... layer|actions - <int>|<int>|<unsigned int>|<unsigned int>|<const char *> + <int>|<int>|<unsigned int>|<unsigned int>|<int>|<const char *> Description of directives: @@ -72,12 +72,17 @@ 4 columns and 6 the number of tiles must not exceed 24. For the layer action, defines an object to be implemented in the API. The - first 4 integers define the ``x``, ``y``, ``width`` and ``height`` dimensions - of the action rectangle respectively. The final string is an argument to pass - to the action. + first 4 integers define the `x`, `y`, `width`, `height` dimensions of the + action rectangle respectively. The fifth integer argument should be set to 1 + if it must collide with the player or 0 otherwise. The final string is an + argument to pass to the action and may be omitted. ## Examples +Some examples of valid maps. + +### Example of basic map + The following file is a map with 4 columns and 2 rows (it is probably small for a real game but at least it is readable for this example). The player starts at 10, 10, the background is using the same tile while the foreground is using @@ -105,3 +110,18 @@ 0 3 3 + +### Example of actions + +!!! note + For simplicity, we only focus on the `layer|actions` and assume that the + beginning of the map is valid. + +This layer of actions contains two blocking regions and one non-blocking action +that is named "game-over". + + layer|actions + 100|100|50|50|1 + 200|200|50|50|1 + 1680|2855|48|48|0|game-over +
--- a/examples/assets/maps/map-world.json Tue Dec 01 17:18:12 2020 +0100 +++ b/examples/assets/maps/map-world.json Tue Dec 01 19:24:11 2020 +0100 @@ -44,6 +44,11 @@ "name":"", "properties":[ { + "name":"block", + "type":"bool", + "value":false + }, + { "name":"exec", "type":"string", "value":"teleport:map-rebonai"
--- a/examples/example-map/main.c Tue Dec 01 17:18:12 2020 +0100 +++ b/examples/example-map/main.c Tue Dec 01 19:24:11 2020 +0100 @@ -44,7 +44,7 @@ #define W (1280) #define H (720) -static struct action *load_action(struct map *, int, int, int, int, const char *); +static void load_action(struct map *, int, int, int, int, const char *); struct map_state { struct map_file loader; @@ -192,7 +192,7 @@ game.inhibit = INHIBIT_STATE_INPUT; teleport_start(&fx->tp); - action_stack_add(¤t_map->actions, &fx->act); + action_stack_add(¤t_map->astack_par, &fx->act); } struct teleport_touch { @@ -260,13 +260,11 @@ return &touch->act; } -static struct action * +static void load_action(struct map *map, int x, int y, int w, int h, const char *id) { if (strncmp(id, "teleport:", 9) == 0) - return teleport_touch_new(map, x, y, w, h, id + 9); - - return NULL; + action_stack_add(&map->astack_par, teleport_touch_new(map, x, y, w, h, id + 9)); } static void
--- a/libmlk-core/core/maths.c Tue Dec 01 17:18:12 2020 +0100 +++ b/libmlk-core/core/maths.c Tue Dec 01 19:24:11 2020 +0100 @@ -21,8 +21,8 @@ bool maths_is_boxed(int x, int y, unsigned int w, unsigned int h, int px, int py) { - return px >= x && - py >= y && - px <= x + (int)w && - py <= y + (int)h; + return px > x && + py > y && + px < x + (int)w && + py < y + (int)h; }
--- a/libmlk-rpg/rpg/map-file.c Tue Dec 01 17:18:12 2020 +0100 +++ b/libmlk-rpg/rpg/map-file.c Tue Dec 01 19:24:11 2020 +0100 @@ -82,21 +82,41 @@ parse_actions(struct context *ctx) { char exec[128 + 1]; - int x = 0, y = 0; + int x = 0, y = 0, block = 0; unsigned int w = 0, h = 0; - while (fscanf(ctx->fp, "%d|%d|%u|%u|%128[^\n]\n", &x, &y, &w, &h, exec) == 5) { - struct action *act; + while (fscanf(ctx->fp, "%d|%d|%u|%u|%d|%128[^\n]\n", &x, &y, &w, &h, &block, exec) >= 5) { + struct map_block *reg; if (!ctx->mf->load_action) { - tracef(_("ignoring action %d,%d,%u,%u,%s"), x, y, w, h, exec); + tracef(_("ignoring action %d,%d,%u,%u,%d,%s"), x, y, w, h, block, exec); continue; } - if ((act = ctx->mf->load_action(ctx->map, x, y, w, h, exec))) - action_stack_add(&ctx->map->actions, act); + ctx->mf->load_action(ctx->map, x, y, w, h, exec); + + /* + * Actions do not have concept of collisions because they are + * not only used on maps. The map structure has its very own + * object to manage collisions but the .map file use the same + * directive for simplicity. So create a block region if the + * directive has one. + */ + if (block) { + if (!(reg = alloc_pool_new(&ctx->mf->blocks))) + return false; + + reg->x = x; + reg->y = y; + reg->w = w; + reg->h = h; + } } + /* Reference the blocks array from map_file. */ + ctx->map->blocks = ctx->mf->blocks.data; + ctx->map->blocksz = ctx->mf->blocks.size; + return true; } @@ -257,6 +277,9 @@ memset(map, 0, sizeof (*map)); + if (!alloc_pool_init(&file->blocks, sizeof (*map->blocks), NULL)) + return false; + if (!(ctx.fp = fopen(path, "r"))) return errorf("%s", strerror(errno)); @@ -280,6 +303,7 @@ free(file->layers[2].tiles); tileset_file_finish(&file->tileset_file); + alloc_pool_finish(&file->blocks); memset(file, 0, sizeof (*file)); }
--- a/libmlk-rpg/rpg/map-file.h Tue Dec 01 17:18:12 2020 +0100 +++ b/libmlk-rpg/rpg/map-file.h Tue Dec 01 19:24:11 2020 +0100 @@ -39,6 +39,7 @@ #include <stdbool.h> +#include <core/alloc.h> #include <core/sprite.h> #include <core/texture.h> @@ -65,32 +66,14 @@ /** * (+?) User function to create an action when it is listed in the map * definition. - * - * The returned value is owned by the user and is never free'ed from the - * map file itself. If the function is present and return a non-NULL - * action it is added into map. */ - struct action *(*load_action)(struct map *map, int x, int y, int w, int h, const char *exec); - - /** - * (*) Map title loaded from file. - */ - char title[MAP_FILE_TITLE_MAX]; + void (*load_action)(struct map *map, int x, int y, int w, int h, const char *exec); - /** - * (*) Map layers stored dynamically here. - */ - struct map_layer layers[MAP_LAYER_TYPE_NUM]; - - /** - * (*) Tileset file loader. - */ - struct tileset_file tileset_file; - - /** - * (*) Tileset referenced from the map. - */ - struct tileset tileset; + char title[MAP_FILE_TITLE_MAX]; /*!< \private */ + struct map_layer layers[MAP_LAYER_TYPE_NUM]; /*!< \private */ + struct tileset_file tileset_file; /*!< \private */ + struct tileset tileset; /*!< \private */ + struct alloc_pool blocks; /*!< \private */ }; /**
--- a/libmlk-rpg/rpg/map.c Tue Dec 01 17:18:12 2020 +0100 +++ b/libmlk-rpg/rpg/map.c Tue Dec 01 19:24:11 2020 +0100 @@ -42,7 +42,7 @@ * SPEED represents the number of pixels it must move per SEC. * SEC simply represends the number of milliseconds in one second. */ -#define SPEED 220 +#define SPEED 150 #define SEC 1000 /* @@ -92,15 +92,16 @@ [0xC] = 5 }; -struct collision { - int x; - int y; - unsigned int w; - unsigned int h; -}; - +/* + * Check if this block is usable for collision detection. For example if the + * player is moving upwards but the collision shape is below it is unnecessary + * to check. + */ static bool -is_collision_out(const struct map *map, struct collision *block, int drow, int dcol) +is_block_relevant(const struct map *map, + const struct map_block *block, + int drow, + int dcol) { if (drow) { /* Object outside of left-right bounds. */ @@ -125,6 +126,23 @@ return true; } +/* + * Determine if this collision shape is "closer" to the player by checking the + * new block coordinates with the previous one. + */ +static bool +is_block_better(const struct map_block *now, + const struct map_block *new, + int drow, + int dcol) +{ + return ((drow < 0 && new->y + new->h > now->y + now->h) || + (drow > 0 && new->y < now->y) || + (dcol < 0 && new->x + new->w > now->x + now->w) || + (dcol > 0 && new->x < now->x)); + +} + static void center(struct map *map) { @@ -263,7 +281,7 @@ static void find_block_iterate(const struct map *map, - struct collision *block, + struct map_block *block, int rowstart, int rowend, int colstart, @@ -274,10 +292,11 @@ assert(map); assert(block); + /* First, check with tiledefs. */ for (int r = rowstart; r <= rowend; ++r) { for (int c = colstart; c <= colend; ++c) { struct tileset_tiledef *td; - struct collision tmp; + struct map_block tmp; if (!(td = find_tiledef_by_row_column(map, r, c))) continue; @@ -289,13 +308,10 @@ tmp.h = td->h; /* This tiledef is out of context. */ - if (!is_collision_out(map, &tmp, drow, dcol)) + if (!is_block_relevant(map, &tmp, drow, dcol)) continue; - if ((drow < 0 && tmp.y + tmp.h > block->y + block->h) || - (drow > 0 && tmp.y < block->y) || - (dcol < 0 && tmp.x + tmp.w > block->x + block->w) || - (dcol > 0 && tmp.x < block->x)) { + if (is_block_better(block, &tmp, drow, dcol)) { block->x = tmp.x; block->y = tmp.y; block->w = tmp.w; @@ -303,10 +319,23 @@ } } } + + /* Now check if there are objects closer than tiledefs. */ + for (size_t i = 0; i < map->blocksz; ++i) { + const struct map_block *new = &map->blocks[i]; + + if (is_block_relevant(map, new, drow, dcol) && + is_block_better(block, new, drow, dcol)) { + block->x = new->x; + block->y = new->y; + block->w = new->w; + block->h = new->h; + } + } } static void -find_collision(const struct map *map, struct collision *block, int drow, int dcolumn) +find_collision(const struct map *map, struct map_block *block, int drow, int dcolumn) { assert((drow && !dcolumn) || (dcolumn && !drow)); @@ -360,7 +389,7 @@ static void move_x(struct map *map, int delta) { - struct collision block; + struct map_block block; find_collision(map, &block, 0, delta < 0 ? -1 : +1); @@ -384,7 +413,7 @@ static void move_y(struct map *map, int delta) { - struct collision block; + struct map_block block; find_collision(map, &block, delta < 0 ? -1 : +1, 0); @@ -562,10 +591,48 @@ { assert(map); - action_stack_update(&map->actions, ticks); + action_stack_update(&map->astack_par, ticks); + action_stack_update(&map->astack_seq, ticks); tileset_update(map->tileset, ticks); - move(map, ticks); + + /* No movements if the sequential actions are running. */ + if (action_stack_completed(&map->astack_seq)) + move(map, ticks); +} + +static void +draw_collide(const struct map *map) +{ + struct texture box = {0}; + + if (map->flags & MAP_FLAGS_SHOW_COLLIDE && texture_new(&box, 64, 64)) { + /* Draw collide box around player if requested. */ + texture_set_alpha_mod(&box, 100); + texture_set_blend_mode(&box, TEXTURE_BLEND_BLEND); + PAINTER_BEGIN(&box); + painter_set_color(0x4f8fbaff); + painter_clear(); + PAINTER_END(); + texture_scale(&box, 0, 0, 64, 64, + map->player_x - map->view_x, map->player_y - map->view_y, + map->player_sprite->cellw, map->player_sprite->cellh, 0.f); + + /* Do the same for every objects. */ + PAINTER_BEGIN(&box); + painter_set_color(0xa8ca58ff); + painter_clear(); + PAINTER_END(); + + for (size_t i = 0; i < map->blocksz; ++i) { + texture_scale(&box, 0, 0, 64, 64, + map->blocks[i].x - map->view_x, map->blocks[i].y - map->view_y, + map->blocks[i].w, map->blocks[i].h, + 0.f); + } + + texture_finish(&box); + } } void @@ -573,8 +640,6 @@ { assert(map); - struct texture box = {0}; - /* Draw the texture about background/foreground. */ draw_layer(map, &map->layers[MAP_LAYER_TYPE_BACKGROUND]); draw_layer(map, &map->layers[MAP_LAYER_TYPE_FOREGROUND]); @@ -586,21 +651,10 @@ map->player_y - map->view_y); draw_layer(map, &map->layers[MAP_LAYER_TYPE_ABOVE]); - - action_stack_draw(&map->actions); + draw_collide(map); - /* Draw collide box around player if requested. */ - if (map->flags & MAP_FLAGS_SHOW_COLLIDE && - texture_new(&box, map->player_sprite->cellw, map->player_sprite->cellh)) { - texture_set_alpha_mod(&box, 100); - texture_set_blend_mode(&box, TEXTURE_BLEND_BLEND); - PAINTER_BEGIN(&box); - painter_set_color(0x4f8fbaff); - painter_clear(); - PAINTER_END(); - texture_draw(&box, map->player_x - map->view_x, map->player_y - map->view_y); - texture_finish(&box); - } + action_stack_draw(&map->astack_par); + action_stack_draw(&map->astack_seq); } void @@ -608,7 +662,8 @@ { assert(map); - action_stack_finish(&map->actions); + action_stack_finish(&map->astack_par); + action_stack_finish(&map->astack_seq); memset(map, 0, sizeof (*map)); }
--- a/libmlk-rpg/rpg/map.h Tue Dec 01 17:18:12 2020 +0100 +++ b/libmlk-rpg/rpg/map.h Tue Dec 01 19:24:11 2020 +0100 @@ -61,6 +61,16 @@ }; /** + * \brief Map collision block. + */ +struct map_block { + int x; /*!< (+) Position in x. */ + int y; /*!< (+) Position in y. */ + unsigned int w; /*!< (+) Width. */ + unsigned int h; /*!< (+) Height. */ +}; + +/** * \brief Map object. * * The map object is used to move a player within the map according to the @@ -80,8 +90,13 @@ /* View options. */ enum map_flags flags; /*!< (+) View options. */ + /* Extra collisions blocks. */ + struct map_block *blocks; /*!< (+&?) Extra collisions. */ + size_t blocksz; /*!< (+) Number of collisions. */ + /* List of actions. */ - struct action_stack actions; /*!< (+) */ + struct action_stack astack_par; /*!< (+) Parallel actions. */ + struct action_stack astack_seq; /*!< (+) Blocking actions. */ /* Player. */ struct sprite *player_sprite; /*!< (+) The sprite to use */
--- a/tools/map/main.c Tue Dec 01 17:18:12 2020 +0100 +++ b/tools/map/main.c Tue Dec 01 19:24:11 2020 +0100 @@ -54,43 +54,17 @@ static const json_t * find_property(const json_t *props, const char *which) { - json_t *value; + const json_t *obj; size_t index; - json_array_foreach(props, index, value) { - if (!json_is_object(value)) + json_array_foreach(props, index, obj) { + if (!json_is_object(obj)) continue; - const json_t *key = json_object_get(value, "name"); - - if (json_is_string(key) && strcmp(json_string_value(key), which) == 0) - return value; - } - - return NULL; -} - -static const json_t * -find_action_exec(const json_t *props) -{ - assert(json_is_array(props)); + const json_t *key = json_object_get(obj, "name"); + const json_t *value = json_object_get(obj, "value"); - json_t *prop, *name, *value; - size_t i; - - json_array_foreach(props, i, prop) { - if (!json_is_object(prop)) - die("invalid property in object\n"); - - name = json_object_get(prop, "name"); - value = json_object_get(prop, "value"); - - if (!name || !json_is_string(name)) - die("invalid 'name' property in object properties"); - if (!value || !json_is_string(value)) - die("invalid 'value' property in object properties"); - - if (strcmp(json_string_value(name), "exec") == 0) + if (json_is_string(key) && value && strcmp(json_string_value(key), which) == 0) return value; } @@ -102,13 +76,8 @@ { const json_t *prop_title = find_property(props, "title"); - if (!prop_title) - return; - - const json_t *title = json_object_get(prop_title, "value"); - - if (title && json_is_string(title)) - printf("title|%s\n", json_string_value(title)); + if (prop_title && json_is_string(prop_title)) + printf("title|%s\n", json_string_value(prop_title)); } static void @@ -117,19 +86,13 @@ const json_t *prop_origin_x = find_property(props, "origin-x"); const json_t *prop_origin_y = find_property(props, "origin-y"); - if (!prop_origin_x || !prop_origin_y) - return; - - const json_t *origin_x = json_object_get(prop_origin_x, "value"); - const json_t *origin_y = json_object_get(prop_origin_y, "value"); - - if (!origin_x || !json_is_integer(origin_x) || - !origin_y || !json_is_integer(origin_y)) + if (!prop_origin_x || !json_is_integer(prop_origin_x) || + !prop_origin_y || !json_is_integer(prop_origin_y)) return; printf("origin|%d|%d\n", - (int)json_integer_value(origin_x), - (int)json_integer_value(origin_y)); + (int)json_integer_value(prop_origin_x), + (int)json_integer_value(prop_origin_y)); } static void @@ -164,7 +127,7 @@ json_t *width = json_object_get(object, "width"); json_t *height = json_object_get(object, "height"); json_t *props = json_object_get(object, "properties"); - const json_t *exec; + const json_t *exec, *block; if (!x || !json_is_number(x)) die("invalid 'x' property in object\n"); @@ -175,16 +138,20 @@ if (!height || !json_is_number(height)) die("invalid 'height' property in object\n"); + /* This is optional and set to 0 if not present. */ + block = find_property(props, "block"); + /* In tiled, those properties are float but we only use ints in MA */ - printf("%d|%d|%d|%d|", + printf("%d|%d|%d|%d|%d", (int)json_integer_value(x), (int)json_integer_value(y), (int)json_integer_value(width), - (int)json_integer_value(height) + (int)json_integer_value(height), + (int)json_is_true(block) ); - if ((exec = find_action_exec(props))) - printf("%s", json_string_value(exec)); + if ((exec = find_property(props, "exec")) && json_is_string(exec)) + printf("|%s", json_string_value(exec)); printf("\n"); }