From 30ab7bae0a6746365c86c1ca81b9aa1ea98a4775 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Fri, 21 Aug 2020 11:15:33 +0200 Subject: [PATCH] add player animations --- CMakeLists.txt | 1 + MystNB.g1a | Bin 32920 -> 33556 bytes src/animation.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++ src/animation.h | 42 ++++++++++++++++++++ src/engine.c | 48 +++++++++++++++++++--- src/engine.h | 16 ++++++-- src/main.c | 51 +++++++++++++++++++----- 7 files changed, 242 insertions(+), 19 deletions(-) create mode 100644 src/animation.c create mode 100644 src/animation.h 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 c7d39564d44201c09e1c8e55a85a895ffbaa6c16..fd225e9364a4f535bcd82fd60a1b639be21fbf83 100644 GIT binary patch delta 4139 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 NibS|WNhVwq{|B0WlY#&M delta 3521 zcmZ8keN0qW7QgQ?4)X;L1B~M^GB^$&i?hsCQ4oyk8Diiro@V-1E*k_ufZ;mpE`mOuRkv&(YrQ_pj?h-{UXs$55!_&Cwf)TcfhGxBtC-SP*>! z(la4T=-7H+rPmxHl;F8M5DI`$<+Mt#@)IKX2)#T&h|;gpi)1c^+u)%-(;PX!!V4=r zp~hY)FwbWKRmgtmw)=!e4TqtSeG$6j7@^zUMEXmVAY@-2@Cop!4*xm39t+O*oF6&= z;ruL6QDysg#Z;wB+i^EoF@BNNcYUk{pTmRZKALZZTEqvui!wVqpV3vSk}?)j!w`*mKKmZ$?eUHI**sn=~u>Ct{G z}B!3`XNCREh@d7`cgf>?aYDe(+^5(VDvKlWWRHGJeqrJ}F>e-~y6{$L>$dCa8FKKqZ-usOyQ)aF%?L!B7&k)* z^3Cq4udW$GTijvQn)#x*enMkJ1Z@Jfpv%xPq6)m1&@4Oel7N6LZk|2CUo0qYEDDN5 zyYT=~a~HrEYti=fc2EZrJW-|MM~1r9BOQ0z3X>~AQS7iifKda<@!dY1wFLc!Mx^jnO!%?rNs+WxR6iipgp`>q%(B5OuJ^Ls4e!J)3&ZCH zA=2cA0zus8V>)XxCl0HPlWMDxlLqT0^#t4pfJwGs?O7Q~ohe;HmKn8bZh>}!&@D6J zv#W7DGK0`Y7l19|oQK}Qa&5cR6Vaj2E7@P0ZF7w5d8iU4h7UGo`#|YL+C7s}?IxpG zz;}_Qo4e&|K51!kAYq#MUdxa39a=0Vgz{2(5>Vu4L7{N-93d!|wU&*fIuiqt2F} zzq;&?Vq3omCr+cn0<&y9`&~^d_6sisc3qpk+eSj`NhhU+G zT~Caw)yI3oF&tlD-u%yv{8T@i5}vtARk^!HDM#GRhIeNvc~|6l+4SxwV%|$OS&${} zQn8l>_r!b)8~@cgPN?e>OJ=j&Od8u$C)oIue9O;uu)}WTDRb#q_OI`m`Ou{U&}B*f5O>90 zBLb!`#x9#+8O52(0=>L;ZE+nB_)c+wra-Tdm(92ImF&kzSQ@d~l9RCo_y&nMg8f9v zfhEzhRYvIr5$}7mwAJuaW2{`uGRjVKZEsnhSo4h8%Vlz>e5+XOXA9+xnv%VQmOMa% z`I)<-PkDMwJ{4Y6m<1z$wM)+c^q#68K-S7kEMwnM5u|fpIVXXAH6T*P{`~^KkXov~ z)Krv)(*o_G>H}isfHYkFfnea}+s6-=Wmi(|fpcPV=}`E>`KgZ3HdNx$j5K@bUBSrb zmhuklU-BV`B6Zi?j_7{wo0Zo7EpvHEv>TsVW(o+drE{xf?@EW3JYXqDb{P1EN%C`( zaO0NhSi_M@nt?n}+`Iwu~V?JOI z7zbv6MI1VMz{SqH4`^rtVu6~P*`#~_>Y=^9$n4~~#*6av!1*Jv086X~YhQgt2LESk_Gu+HQhJQ2tfb)&R5s*8s$D=_T~J0Ggn^8aM{P zroD}SVWER{u&V{eAX9)&0}}vVshzUPQ_129J6kxFX5KNhlC~XVtN32Y;b{&<0(grOEBa)UlH1V1eQi$I&bH*TcnQ2jMoMVq^Ei8z+cA`Equ zLhgwSWr}SrEu4Fzj%`vA+JyXHMC~pvNRM0c6w2magwii%w6_Xb^_7IyHvoSGMhHDc zfg<;&q}XsVD(qt=s1gn;AD +#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; }