From 433bab34fcd82aa081a4837b00b91c8dd82e34a4 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Wed, 23 Dec 2020 00:00:58 +0100 Subject: [PATCH] dynamic tiles, tile animations, door logic, spawn and victory --- MystNB.g1a | Bin 28004 -> 32064 bytes assets-fx/img/tilesheet.png | Bin 0 -> 860 bytes assets-fx/map/lv1.txt | 8 +- project.cfg | 4 +- src/animation.c | 81 +++++++++++- src/animation.h | 8 ++ src/engine.c | 245 ++++++++++++++++++++++++++++++++++-- src/engine.h | 44 ++++++- src/game.c | 115 +++++++++++++++++ src/game.h | 9 ++ src/main.c | 82 ++---------- 11 files changed, 501 insertions(+), 95 deletions(-) create mode 100644 assets-fx/img/tilesheet.png create mode 100644 src/game.c create mode 100644 src/game.h diff --git a/MystNB.g1a b/MystNB.g1a index ef54aee6b7eb0278130bd7044f31681bf1d0d49c..e7c8161761a6e97397346a241607c5139dfb9f90 100644 GIT binary patch delta 6351 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 delta 3839 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 = anim_frame(&anim_player, data->dir + 4, data->frame / 2); return 1; } + +//--- +// Tile animation functions +//--- + +int anim_tile_start(struct anim_data *data, int init) +{ + data->function = anim_tile_start; + data->frame = init ? 0 : (data->frame + 1) % 5; + data->duration = (data->frame == 0 ? 1000 : 150); + data->img = anim_frame(&anim_tiles, data->frame, 0); + return 0; +} + +int anim_tile_end(struct anim_data *data, int init) +{ + data->function = anim_tile_end; + data->frame = init ? 0 : (data->frame + 1) % 5; + data->duration = (data->frame == 0 ? 1000 : 150); + data->img = anim_frame(&anim_tiles, data->frame, 1); + return 0; +} + +int anim_door_closed(struct anim_data *data, GUNUSED int init) +{ + /* Init takes [data->dir] as input */ + data->function = anim_door_closed; + data->frame = 0 + 16 * (data->dir == DIR_LEFT); + data->duration = 1000; + data->img = anim_frame(&anim_tiles, data->frame & 15, + 2 + (data->frame >= 16)); + return 0; +} + +int anim_door_opening(struct anim_data *data, int init) +{ + data->function = anim_door_opening; + data->frame = init ? 1 + 16 * (data->dir == DIR_LEFT): data->frame + 1; + + if((data->frame & 15) == 6) + return anim_door_open(data, 1); + + data->duration = 75; + data->img = anim_frame(&anim_tiles, data->frame & 15, + 2 + (data->frame >= 16)); + return 1; + +} + +int anim_door_open(struct anim_data *data, GUNUSED int init) +{ + data->function = anim_door_open; + data->frame = 6 + 16 * (data->dir == DIR_LEFT); + data->duration = 1000; + data->img = anim_frame(&anim_tiles, data->frame & 15, + 2 + (data->frame >= 16)); + return 0; +} + +int anim_door_closing(struct anim_data *data, int init) +{ + data->function = anim_door_closing; + data->frame = init ? 7 + 16 * (data->dir == DIR_LEFT): data->frame + 1; + + if((data->frame & 15) == 11) + return anim_door_closed(data, 1); + + data->duration = 75; + data->img = anim_frame(&anim_tiles, data->frame & 15, + 2 + (data->frame >= 16)); + return 1; +} diff --git a/src/animation.h b/src/animation.h index 77e388f..d604599 100644 --- a/src/animation.h +++ b/src/animation.h @@ -7,9 +7,17 @@ struct anim_data; /* anim_function_t: Update function for each animation */ typedef int (anim_function_t)(struct anim_data *data, int init); + anim_function_t anim_player_idle; anim_function_t anim_player_walking; +anim_function_t anim_tile_start; +anim_function_t anim_tile_end; +anim_function_t anim_door_closed; +anim_function_t anim_door_opening; +anim_function_t anim_door_open; +anim_function_t anim_door_closing; + /* struct anim_frame: Subrectangle of an animation sheet */ struct anim_frame { diff --git a/src/engine.c b/src/engine.c index 078dde3..9c4e8d3 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,29 +1,201 @@ #include #include +#include #include "engine.h" #define CELL_X(x) (-2 + 10 * (x)) #define CELL_Y(y) (-3 + 10 * (y)) +//--- +// Utilities +//--- + + +/* Determine the cycle pattern for a certain door */ +static char const *map_door_cycle(struct map const *map, int x, int y) +{ + int door = map->tiles[y * map->w + x]; + int cycle_id = -1; + + if(door >= TILE_VDOOR && door < TILE_VDOOR + 8) + cycle_id = door - TILE_VDOOR; + else if(door >= TILE_HDOOR && door < TILE_HDOOR + 8) + cycle_id = door - TILE_HDOOR + 8; + else + return NULL; + + return map->door_cycle + map->door_cycle_index[cycle_id]; +} + +/* Check whether a cell of the map is walkable */ +static bool map_walkable(struct game const *game, int x, int y) +{ + /* Refuse to walk on walls */ + int tile = game->map->tiles[y * game->map->w + x]; + if(tile == TILE_WALL) return false; + + /* Refuse to walk on closed doors */ + for(int t = 0; t < game->dynamic_tile_count; t++) + { + struct dynamic_tile const *dt = &game->dynamic_tiles[t]; + if(dt->x != x || dt->y != y) continue; + + char const *cycle = map_door_cycle(game->map, x, y); + if(!dt->state2 && cycle[dt->state] == '#') + return false; + } + + return true; +} + +//--- +// Map loading +//--- + +void engine_load(struct game *game, struct map const *map) +{ + game->map = map; + game->time = 0; + + for(int p = 0; p < PLAYER_COUNT + 1; p++) + game->players[p] = NULL; + + memset(&game->dynamic_tiles, 0, + MAP_DYNAMIC_TILES * sizeof(struct dynamic_tile)); + + /* Create dynamic tiles for suitable tiles in the map */ + int t = 0; + + for(int y = 0; y < map->h && t < MAP_DYNAMIC_TILES; y++) + for(int x = 0; x < map->w && t < MAP_DYNAMIC_TILES; x++) + { + int tile = map->tiles[y*map->w + x]; + struct dynamic_tile *dt = &game->dynamic_tiles[t]; + + dt->y = y; + dt->x = x; + dt->state = 0; + + int is_dynamic = 1; + + if(tile == TILE_START) + { + dt->idle = !anim_tile_start(&dt->anim, 1); + } + else if(tile == TILE_END) + { + dt->idle = !anim_tile_end(&dt->anim, 1); + } + else if(tile >= TILE_KEY && tile < TILE_KEY + 8) + { + /* TODO: Initial keys' dynamic tile information */ + } + else if((tile >= TILE_VDOOR && tile < TILE_VDOOR + 8) || + (tile >= TILE_HDOOR && tile < TILE_HDOOR + 8)) + { + dt->anim.dir = (tile < TILE_HDOOR) ? DIR_UP : DIR_LEFT; + char const *cycle = map_door_cycle(map, x, y); + + if(cycle[dt->state] == '#') + dt->idle = !anim_door_closed(&dt->anim, 1); + else + dt->idle = !anim_door_open(&dt->anim, 1); + + } + else is_dynamic = 0; + + t += is_dynamic; + } + + game->dynamic_tile_count = t; +} + +void engine_spawn(struct game *game) +{ + struct map const *map = game->map; + int p = 0; + + for(int y = 0; y < map->h && game->players[p]; y++) + for(int x = 0; x < map->w && game->players[p]; x++) + { + /* Try to spawn a player at (x,y) */ + if(map->tiles[y*map->w + x] == TILE_START) + { + struct player *player = game->players[p]; + + player->y = y; + player->x = x; + player->dir = DIR_DOWN; + player->anim.dir = DIR_DOWN; + player->idle = !anim_player_idle(&player->anim, 1); + + p++; + } + } +} + +bool engine_all_players_idle(struct game *game) +{ + for(int p = 0; game->players[p]; p++) + { + if(!game->players[p]->idle) return false; + } + return true; +} + +bool engine_all_dynamic_tiles_idle(struct game *game) +{ + for(int t = 0; t < game->dynamic_tile_count; t++) + { + if(!game->dynamic_tiles[t].idle) return false; + } + return true; +} + +bool engine_wins(struct game *game, struct player *player) +{ + /* First check that the player is participating in this game */ + bool found = false; + for(int p = 0; game->players[p] && !found; p++) + { + if(game->players[p] == player) found = true; + } + if(!found) return false; + + int tile = game->map->tiles[player->y * game->map->w + player->x]; + return (tile == TILE_END); +} + //--- // Animations //--- -void engine_tick(struct game *game, int dt) +void engine_tick(struct game *game, int delta_time) { - game->time += dt; + game->time += delta_time; /* Update the animations for every player */ for(int p = 0; game->players[p]; p++) { struct player *player = game->players[p]; - player->anim.duration -= dt; + player->anim.duration -= delta_time; if(player->anim.duration > 0) continue; /* Call the animation function to generate the next frame */ player->idle = !player->anim.function(&player->anim, 0); } + + /* Update animations for dynamic tiles */ + for(int t = 0; t < game->dynamic_tile_count; t++) + { + struct dynamic_tile *dt = &game->dynamic_tiles[t]; + + dt->anim.duration -= delta_time; + if(dt->anim.duration > 0) continue; + + dt->idle = !dt->anim.function(&dt->anim, 0); + } } //--- @@ -37,11 +209,24 @@ static void engine_draw_player(struct player const *player) player->anim.img); } +static void engine_draw_dynamic_tile(struct dynamic_tile const *dt) +{ + /* The -10 is hardcoded to suit the format of the tilesheet */ + dframe(CELL_X(dt->x) + dt->anim.dx, + CELL_Y(dt->y) + dt->anim.dy - 10, + dt->anim.img); +} + void engine_draw(struct game const *game) { dclear(C_WHITE); dimage(0, 0, game->map->img); + for(int t = 0; t < game->dynamic_tile_count; t++) + { + engine_draw_dynamic_tile(&game->dynamic_tiles[t]); + } + for(int p = 0; game->players[p]; p++) { engine_draw_player(game->players[p]); @@ -52,13 +237,6 @@ void engine_draw(struct game const *game) // Physics //--- -/* Check whether a cell of the map is walkable */ -static int map_walkable(struct map const *map, int x, int y) -{ - int tile = map->tiles[y * map->w + x]; - return (tile != TILE_WALL); -} - int engine_move(struct game *game, struct player *player, int dir) { int dx = (dir == DIR_RIGHT) - (dir == DIR_LEFT); @@ -73,7 +251,7 @@ int engine_move(struct game *game, struct player *player, int dir) player->anim.dir = dir; /* Only move the player if the destination is walkable */ - if(!map_walkable(game->map, player->x + dx, player->y + dy)) + if(!map_walkable(game, player->x + dx, player->y + dy)) { /* If not, set the new idle animation */ if(dir != olddir) @@ -91,3 +269,48 @@ int engine_move(struct game *game, struct player *player, int dir) return 1; } + +void engine_finish_turn(struct game *game) +{ + /* TODO: Have players pick up keys and open corresponding doors */ + + /* Update door cycles */ + for(int t = 0; t < game->dynamic_tile_count; t++) + { + struct dynamic_tile *dt = &game->dynamic_tiles[t]; + char const *cycle = map_door_cycle(game->map, dt->x, dt->y); + if(!cycle) continue; + + /* Determine current open/closed status */ + int current_status = dt->state2 ? '.' : cycle[dt->state]; + + /* Move to next cycle state */ + dt->state++; + if(cycle[dt->state] == ' ' || cycle[dt->state] == 0) + dt->state = 0; + dt->state2 = 0; + + /* Determine next open/closed status (this might be different + from the cycle state if a player standing in the door + prevents if from closing) */ + int new_status = cycle[dt->state]; + + for(int p = 0; game->players[p]; p++) + { + struct player *player = game->players[p]; + if(player->x == dt->x && player->y == dt->y) + { + new_status = '.'; + dt->state2 = 1; + } + } + + if(new_status == current_status) continue; + + /* Set animations when changing status */ + if(new_status == '#') + dt->idle = !anim_door_closing(&dt->anim, 1); + else if(new_status == '.') + dt->idle = !anim_door_opening(&dt->anim, 1); + } +} diff --git a/src/engine.h b/src/engine.h index da7d5dd..1bf484c 100644 --- a/src/engine.h +++ b/src/engine.h @@ -2,12 +2,16 @@ #define _MYSTNB_ENGINE_H #include +#include #include #include "animation.h" /* Maximum number of players */ #define PLAYER_COUNT 1 +/* Maximum number of dynamic tiles on the map */ +#define MAP_DYNAMIC_TILES 32 + /* Time per engine tick (ms) */ #define ENGINE_TICK 25 @@ -24,7 +28,7 @@ struct player int x, y; /* Direction currently facing */ int dir; - /* Whether playing is currently idle (ie. can move) */ + /* Whether player is currently idle (ie. can move) */ int idle; /* Current animation function and data */ struct anim_data anim; @@ -63,17 +67,50 @@ enum map_tile TILE_HDOOR = 32, }; +/* struct dynamic_tile: Dynamic tile information */ +struct dynamic_tile +{ + /* Position */ + int x, y; + /* Current state */ + int state; + int state2; + /* Whether animation is idle */ + int idle; + /* Current animation */ + struct anim_data anim; +}; + /* struct game: A running game with a map and some players */ struct game { /* Current map */ - struct map *map; + struct map const *map; /* Players */ struct player *players[PLAYER_COUNT + 1]; /* Current game time (ms) */ int time; + /* Dynamic tiles */ + struct dynamic_tile dynamic_tiles[MAP_DYNAMIC_TILES]; + /* Number of dynamic tiles */ + int dynamic_tile_count; }; +/* Load map with no players */ +void engine_load(struct game *game, struct map const *map); + +/* Spawn all the players on the map */ +void engine_spawn(struct game *game); + +/* Check whether all players are idle */ +bool engine_all_players_idle(struct game *game); + +/* Check whether all dynamic tiles are idle */ +bool engine_all_dynamic_tiles_idle(struct game *game); + +/* Check whether a player stands on the exit */ +bool engine_wins(struct game *game, struct player *player); + /* Update animations */ void engine_tick(struct game *game, int dt); @@ -84,4 +121,7 @@ void engine_draw(struct game const *game); 0 otherwise (collisions or out-of-bounds moves) */ int engine_move(struct game *game, struct player *player, int direction); +/* Process automatic actions at the end of a turn */ +void engine_finish_turn(struct game *game); + #endif /* _MYSTNB_ENGINE_H */ diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..920242f --- /dev/null +++ b/src/game.c @@ -0,0 +1,115 @@ +#include +#include +#include +#include + +#include "game.h" +#include "engine.h" + +/* All maps */ + +extern struct map map_lv1; + +struct map *maps[] = { + &map_lv1, +}; + +/* Phases of a turn in the game */ + +enum turn_phase +{ + PHASE_IDLE = 0, + PHASE_MOVE = 1, + PHASE_UPDATE = 2, +}; + +/* Returns a direction to move in */ +static int get_inputs(void) +{ + int opt = GETKEY_DEFAULT & ~GETKEY_REP_ARROWS; + int timeout = 1; + + while(1) + { + key_event_t ev = getkey_opt(opt, &timeout); + if(ev.type == KEYEV_NONE) return -1; + + int key = ev.key; + if(key == KEY_DOWN) return DIR_DOWN; + if(key == KEY_RIGHT) return DIR_RIGHT; + if(key == KEY_UP) return DIR_UP; + if(key == KEY_LEFT) return DIR_LEFT; + } +} + +/* Notify play_level() that the next frame is due soon */ +static int callback_tick(volatile int *tick) +{ + *tick = 1; + return TIMER_CONTINUE; +} + +bool play_level(int level) +{ + if(level < 1 || level > 8) return false; + + /* Load the map */ + struct game game; + engine_load(&game, maps[level-1]); + + /* Create and spawn a solo player */ + struct player singleplayer; + game.players[0] = &singleplayer; + engine_spawn(&game); + + /* Start the frame timer */ + static volatile int tick = 1; + int t = timer_setup(TIMER_ANY, ENGINE_TICK*1000, callback_tick, &tick); + if(t < 0) return false; + timer_start(t); + + enum turn_phase phase = PHASE_IDLE; + bool succeeded = false; + + while(1) + { + while(!tick) sleep(); + tick = 0; + + engine_draw(&game); + dupdate(); + + int dir = get_inputs(); + + /* If the player inputs a control during IDLE, move them */ + if(phase == PHASE_IDLE && dir >= 0) + { + if(engine_move(&game, &singleplayer, dir)) + phase = PHASE_MOVE; + } + /* When the move is finished, perform a map update */ + else if(phase == PHASE_MOVE && engine_all_players_idle(&game)) + { + /* TODO: Winning condition */ + if(engine_wins(&game, &singleplayer)) + { + succeeded = true; + break; + } + + phase = PHASE_UPDATE; + engine_finish_turn(&game); + } + /* When update finishes, start another turn */ + else if(phase == PHASE_UPDATE && + engine_all_dynamic_tiles_idle(&game)) + { + phase = PHASE_IDLE; + } + + engine_tick(&game, ENGINE_TICK); + } + + timer_stop(t); + return succeeded; +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..bb817bb --- /dev/null +++ b/src/game.h @@ -0,0 +1,9 @@ +#ifndef _MYSTNB_GAME_H +#define _MYSTNB_GAME_H + +#include + +/* Play the level, returns whether player completed the level */ +bool play_level(int level); + +#endif /* _MYSTNB_GAME_H */ diff --git a/src/main.c b/src/main.c index 2372d0b..0d97a18 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,8 @@ #include #include -#include -#include +#include +#include "game.h" #include "engine.h" static void draw_menu(int selected) @@ -54,87 +54,19 @@ static int main_menu(void) selected--; if(key == KEY_RIGHT && selected < 8) selected++; + if(key == KEY_EXIT) + gint_osmenu(); } return selected; } -/* Returns a direction to move in */ -static int get_inputs(void) -{ - int opt = GETKEY_DEFAULT & ~GETKEY_REP_ARROWS; - int timeout = 1; - - while(1) - { - key_event_t ev = getkey_opt(opt, &timeout); - if(ev.type == KEYEV_NONE) return -1; - - int key = ev.key; - if(key == KEY_DOWN) return DIR_DOWN; - if(key == KEY_RIGHT) return DIR_RIGHT; - if(key == KEY_UP) return DIR_UP; - if(key == KEY_LEFT) return DIR_LEFT; - } -} - -static int callback_tick(volatile int *tick) -{ - *tick = 1; - return TIMER_CONTINUE; -} int main(void) { - GUNUSED int level = main_menu(); - - struct player singleplayer = { - .x = 2, - .y = 3, - .dir = DIR_DOWN, - .anim.function = anim_player_idle, - .anim.dir = DIR_DOWN, - }; - singleplayer.idle = !anim_player_idle(&singleplayer.anim, 1); - - extern struct map map_lv1; - - struct game game = { - .map = &map_lv1, - .players = { &singleplayer, NULL }, - .time = 0, - }; - - /* Global tick clock */ - static volatile int tick = 1; - - int t = timer_setup(TIMER_ANY, ENGINE_TICK*1000, callback_tick, &tick); - if(t >= 0) timer_start(t); - - int level_finished = 0; - while(!level_finished) + while(1) { - while(!tick) sleep(); - tick = 0; - - engine_draw(&game); - dupdate(); - - int dir = get_inputs(); - int turn_finished = 0; - - if(dir >= 0) - { - turn_finished = engine_move(&game, &singleplayer, dir); - } - if(turn_finished) - { - /* Update doors, etc */ - } - - engine_tick(&game, ENGINE_TICK); + GUNUSED int level = main_menu(); + play_level(1); } - - if(t >= 0) timer_stop(t); - return 1; }