diff --git a/CMakeLists.txt b/CMakeLists.txt index cf3f26d..88d740c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ include(Fxconv) find_package(Gint 2.1 REQUIRED) set(SOURCES + src/animation.c src/main.c src/engine.c # ... diff --git a/MystNB.g1a b/MystNB.g1a index c7d3956..fd225e9 100644 Binary files a/MystNB.g1a and b/MystNB.g1a differ diff --git a/src/animation.c b/src/animation.c new file mode 100644 index 0000000..735afee --- /dev/null +++ b/src/animation.c @@ -0,0 +1,103 @@ +#include +#include +#include "animation.h" +#include "engine.h" + +//--- +// Generation of animation frames from sheets +//--- + +/* struct sheet: Structure of an image with animation frames */ +struct sheet +{ + /* Source image */ + bopti_image_t *img; + /* Width and height of single entry */ + int frame_w; + int frame_h; +}; + +extern bopti_image_t img_spritesheet; +struct sheet const anim_player = { + .img = &img_spritesheet, + .frame_w = 12, + .frame_h = 16, +}; + +/* anim_frame(): Get a frame from a sheet */ +static struct anim_frame anim_frame(struct sheet const *sheet, int col,int row) +{ + struct anim_frame f = { + .source = sheet->img, + .left = sheet->frame_w * col, + .top = sheet->frame_h * row, + .w = sheet->frame_w, + .h = sheet->frame_h, + }; + return f; +} + +void dframe(int x, int y, struct anim_frame const frame) +{ + dsubimage(x, y, frame.source, frame.left, frame.top, frame.w, frame.h, + DIMAGE_NONE); +} + +//--- +// Animation functions +//--- + +int anim_player_idle(struct anim_data *data, int init) +{ + /* Animation initialization; takes [data->dir] as input */ + if(init) + { + data->function = anim_player_idle; + data->frame = 0; + data->duration = 500; + } + else + { + data->frame = (data->frame + 1) % 2; + data->duration += 500; + } + + data->img = anim_frame(&anim_player, data->dir, data->frame); + data->dx = 0; + data->dy = 0; + return 0; +} + +int anim_player_walking(struct anim_data *data, int init) +{ + /* Animation initialization; takes [data->dir] as input */ + if(init) + { + data->function = anim_player_walking; + data->frame = 0; + data->duration = 50; + + int dx = (data->dir == DIR_LEFT) - (data->dir == DIR_RIGHT); + int dy = (data->dir == DIR_UP) - (data->dir == DIR_DOWN); + + data->dx = 10 * dx; + data->dy = 10 * dy; + } + else + { + data->dx -= sgn(data->dx); + data->dy -= sgn(data->dy); + + /* Animation finishes if both displacements are zero */ + if(!data->dx && !data->dy) + { + return anim_player_idle(data, 1); + } + + data->frame = (data->frame + 1) % 8; + data->duration += 50; + } + + data->img = anim_frame(&anim_player, data->dir + 4, data->frame / 2); + return 1; +} diff --git a/src/animation.h b/src/animation.h new file mode 100644 index 0000000..b1c7319 --- /dev/null +++ b/src/animation.h @@ -0,0 +1,42 @@ +#ifndef _MYSTNB_ANIMATION_H +#define _MYSTNB_ANIMATION_H + +#include + +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; + +/* struct anim_frame: Subrectangle of an animation sheet */ +struct anim_frame +{ + bopti_image_t *source; + int left, top; + int w, h; +}; + +/* struct anim_data: Data for currently-running animations */ +struct anim_data +{ + /* ANimation update function */ + anim_function_t *function; + /* Frame to draw */ + struct anim_frame img; + /* On-screen entity displacement */ + int dx, dy; + /* Animation direction */ + int dir; + /* Current frame */ + int frame; + /* Duration left until next frame; updated by the engine. Animation + function is called when it becomes negative or null */ + int duration; +}; + +/* Draw an animation frame */ +void dframe(int x, int y, struct anim_frame const frame); + +#endif /* _MYSTNB_ANIMATION_H */ diff --git a/src/engine.c b/src/engine.c index d5fe81d..0c79ea2 100644 --- a/src/engine.c +++ b/src/engine.c @@ -5,16 +5,36 @@ #define CELL_X(x) (-2 + 10 * (x)) #define CELL_Y(y) (-3 + 10 * (y)) +//--- +// Animations +//--- + +void engine_tick(struct game *game, int dt) +{ + game->time += dt; + + /* Update the animations for every player */ + for(int p = 0; game->players[p]; p++) + { + struct player *player = game->players[p]; + + player->anim.duration -= dt; + if(player->anim.duration > 0) continue; + + /* Call the animation function to generate the next frame */ + player->idle = !player->anim.function(&player->anim, 0); + } +} + //--- // Rendering //--- static void engine_draw_player(struct player const *player) { - extern bopti_image_t img_spritesheet; - - dsubimage(CELL_X(player->x) - 1, CELL_Y(player->y) - 5, - &img_spritesheet, player->dir * 12, 0, 12, 16, DIMAGE_NONE); + dframe(CELL_X(player->x) - 1 + player->anim.dx, + CELL_Y(player->y) - 5 + player->anim.dy, + player->anim.img); } void engine_draw(struct game const *game) @@ -42,15 +62,31 @@ int engine_move(struct game *game, struct player *player, int dir) { int dx = (dir == DIR_RIGHT) - (dir == DIR_LEFT); int dy = (dir == DIR_DOWN) - (dir == DIR_UP); + int olddir = player->dir; - /* Always update the direction */ + /* Don't move players that are already moving */ + if(!player->idle) return 0; + + /* Update the direction */ player->dir = 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)) return 0; + if(!map_walkable(game->map, player->x + dx, player->y + dy)) + { + /* If not, set the new idle animation */ + if(dir != olddir) + { + player->idle = !anim_player_idle(&player->anim,1); + } + return 0; + } player->x += dx; player->y += dy; + /* Set the walking animation */ + player->idle = !anim_player_walking(&player->anim, 1); + return 1; } diff --git a/src/engine.h b/src/engine.h index 2dc81bf..6e0393c 100644 --- a/src/engine.h +++ b/src/engine.h @@ -2,9 +2,15 @@ #define _MYSTNB_ENGINE_H #include +#include +#include "animation.h" +/* Maximum number of players */ #define PLAYER_COUNT 1 +/* Time per engine tick (ms) */ +#define ENGINE_TICK 25 + /* Directions */ #define DIR_DOWN 0 #define DIR_RIGHT 1 @@ -18,9 +24,10 @@ struct player int x, y; /* Direction currently facing */ int dir; - /* Animation and frame */ - struct animation const *anim; - int frame; + /* Whether playing is currently idle (ie. can move) */ + int idle; + /* Current animation function and data */ + struct anim_data anim; }; /* struct map: A map with moving doors, collectibles, and fog */ @@ -45,6 +52,9 @@ struct game int time; }; +/* Update animations */ +void engine_tick(struct game *game, int dt); + /* Draw the current game frame from scratch; does not dupdate() */ void engine_draw(struct game const *game); diff --git a/src/main.c b/src/main.c index 19f21d4..8dfb3d4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,7 @@ #include #include +#include +#include #include "engine.h" @@ -61,11 +63,14 @@ static int main_menu(void) static int get_inputs(void) { int opt = GETKEY_DEFAULT & ~GETKEY_REP_ARROWS; + int timeout = 1; while(1) { - int key = getkey_opt(opt, NULL).key; + 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; @@ -73,14 +78,25 @@ static int get_inputs(void) } } +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 + .y = 3, + .dir = DIR_DOWN, + .anim.function = anim_player_idle, + .anim.dir = DIR_DOWN, }; + singleplayer.idle = !anim_player_idle(&singleplayer.anim, 1); + struct map map = { .w = 13, .h = 7 @@ -91,22 +107,37 @@ int main(void) .time = 0, }; - int level_finished = 0; + /* Global tick clock */ + static volatile int tick = 1; + int t = timer_configure(TIMER_ANY, ENGINE_TICK*1000, + GINT_CALL(callback_tick, &tick)); + if(t >= 0) timer_start(t); + + int level_finished = 0; while(!level_finished) { - int turn_finished = 0; - while(!turn_finished) - { - engine_draw(&game); - dupdate(); + while(!tick) sleep(); + tick = 0; - int dir = get_inputs(); + 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 */ + } - /* Update doors, etc */ + engine_tick(&game, ENGINE_TICK); } + if(t >= 0) timer_stop(t); return 1; }