From 73dc8df3f3edb108d20ad2bdd1a5f7a499430b15 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Mon, 24 Jan 2022 00:39:12 +0100 Subject: [PATCH] finale animation --- src/duet.h | 13 ++++++-- src/main.c | 66 +++++++++++++++++++++++++++++++++++--- src/menu.c | 2 +- src/physics.c | 12 +++---- src/render.c | 87 ++++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 154 insertions(+), 26 deletions(-) diff --git a/src/duet.h b/src/duet.h index acd517d..67ca5b5 100644 --- a/src/duet.h +++ b/src/duet.h @@ -109,6 +109,8 @@ typedef struct game { float time_transition; /* Time spent during episode transition */ float time_episode_transition; + /* Time sent during finale */ + float time_finale; /* Forced rotation speed for level transitions and death rewind */ float forced_player_rota; @@ -118,14 +120,19 @@ typedef struct game { // Rendering //--- -void dcircle(int x, int y, int r, int color, bool fill); +void dcircle(int x, int y, int r, int color, bool fill, int rep1, int rep2); void dtriangle(int x1, int y1, int x2, int y2, int x3, int y3, int color, int opacity); void drectoid(rect_t const *r, float extra_size, int color); -void render_player(int x, int y, float angle); +void render_player(int x, int y, float angle, int r, float opacity); + +void render_glow(int x, int y, int r1, int r2, int c1, int c2, float angle, + float opacity); + +void radial_fadeout(int x, int y, int r1, int r2, int c); //--- // Duet Text @@ -140,7 +147,7 @@ void duet_text_opt(int x, int y, int fg, int bg, int halign, int valign, //--- void player_position(float angle, - float *x1, float *y1, float *x2, float *y2); + float *x1, float *y1, float *x2, float *y2, int r); bool player_collision(game_t const *game); diff --git a/src/main.c b/src/main.c index 4b95a63..c96d64e 100644 --- a/src/main.c +++ b/src/main.c @@ -209,6 +209,15 @@ void game_loop(int current_episode, int current_level) float dt = FRAME_DURATION * time_direction; game.time += dt; + /* Finale */ + if(state == State_Finale) { + game.time_finale += dt; + if(game.time_finale >= 0.5) { + float t = game.time_finale - 0.5; + game.player_rota -= 1.57 * t / 3 * dt; + } + } + /* Input analysis */ bool rotate_left=false, rotate_right=false; @@ -228,6 +237,10 @@ void game_loop(int current_episode, int current_level) if (keydown(KEY_0)) { rotate_right = true; } + if ((keydown(KEY_0) || keydown(KEY_7)) && state == State_Finale && + game.time_finale >= 16.0) { + stop_playing = true; + } if (stop_playing) break; @@ -244,8 +257,10 @@ void game_loop(int current_episode, int current_level) current_level = 0; changed_episode = true; } - if(current_episode >= episode_count) - break; + if(current_episode >= episode_count) { + state = State_Finale; + continue; + } load_level(&game, episodes[current_episode].levels[current_level]); @@ -305,12 +320,16 @@ void game_loop(int current_episode, int current_level) dclear(C_BLACK); // Cool animated background + int bg_color = 4; + if(state == State_Finale) + bg_color = (game.time_finale >= 2) ? 0 : 4 - 2 * game.time_finale; + for(int i = 0; i < 10; i++) { int x=DWIDTH+2-40*i-20, y=20; rect_t rect = { .x=x, .y=y, .w=20, .h=20, .r=1.0*game.time, .opacity=256 }; - drectoid(&rect, 0, C_RGB(4, 4, 4)); + drectoid(&rect, 0, C_RGB(bg_color, bg_color, bg_color)); } for(int j = 1; j < 6; j++) { int lines = (j == 5) ? 24 : 40; @@ -336,7 +355,46 @@ void game_loop(int current_episode, int current_level) DTEXT_MIDDLE, episodes[current_episode].name, -1); } - render_player(PLAYER_X, DHEIGHT/2, game.player_rota); + if(state == State_Finale && game.time_finale >= 11.0) { + float t = game.time_finale - 11.0; + float rt = fminf(1.0, t / 4.0); + float opacity = fminf(1.0, t / 2.0); + render_glow(DWIDTH/2, DHEIGHT/2, 24*rt, 20*rt, C_RGB(15,12,13), + C_RGB(10,5,8), -t / 2.0, opacity); + } + + float opacity = 1.0; + int x = PLAYER_X; + int r = PLAYER_R; + + if(state == State_Finale) { + opacity = fmaxf(1 - game.time_finale / 2, 0); + /* Interpolate between x1 and x2 in 10.0 seconds along (1-x)² */ + int x1 = PLAYER_X, x2 = DWIDTH / 2; + float t = fminf(game.time_finale / 10.0, 1.0); + t = 1 - (1-t)*(1-t); + x = (1 - t) * x1 + t * x2; + /* Interpolate radius between PLAYER_R and 0 in 10.0 seconds */ + t = fmaxf(0.0, fminf(1.0, (game.time_finale - 2.0) / 10.0)); + r = PLAYER_R * (1 - t); + } + render_player(x, DHEIGHT/2, game.player_rota, r, opacity); + + /* Radial fade out */ + if(state == State_Finale && game.time_finale >= 12.0) { + float t = fminf(1.0, (game.time_finale - 11.0) / 4.0); + int r1 = PLAYER_SIZE + (12 - PLAYER_SIZE) * t; + int r2 = PLAYER_SIZE + (24 - PLAYER_SIZE) * t; + radial_fadeout(DWIDTH/2, DHEIGHT/2, r1, r2, C_BLACK); + } + /* Final text */ + if(state == State_Finale && game.time_finale >= 14.0) { + float t = fminf(1.0, (game.time_finale - 14.0) / 2.0); + int c = 31*t; + c = C_RGB(c, c, c); + duet_text_opt(DWIDTH/2 - 30, DHEIGHT/2, c, C_NONE, DTEXT_CENTER, + DTEXT_MIDDLE, "I am here", -1); + } float extra_size = 1 + sinf(6 * game.time); for(int i = 0; i < game.rect_count; i++) diff --git a/src/menu.c b/src/menu.c index e7c7144..d0507aa 100644 --- a/src/menu.c +++ b/src/menu.c @@ -102,7 +102,7 @@ int main_menu(int *episode, int *level) dclear(C_BLACK); dimage(330+scroll, DHEIGHT/2 - img_title.height / 2, &img_title); - render_player(268+scroll, DHEIGHT/2, time * 0.8); + render_player(268+scroll, DHEIGHT/2, time * 0.8, PLAYER_R, 1.0); int x=188+scroll, y=40; for(int i = 0; i < episode_count; i++) { diff --git a/src/physics.c b/src/physics.c index 17f8832..d066a59 100644 --- a/src/physics.c +++ b/src/physics.c @@ -1,24 +1,24 @@ #include "duet.h" void player_position(float angle, - float *x1, float *y1, float *x2, float *y2) + float *x1, float *y1, float *x2, float *y2, int r) { int x=PLAYER_X, y=DHEIGHT/2; float sin, cos; sincosf(angle, &sin, &cos); - if(x1) *x1 = x + sin * PLAYER_R; - if(y1) *y1 = y + cos * PLAYER_R; + if(x1) *x1 = x + sin * r; + if(y1) *y1 = y + cos * r; - if(x2) *x2 = x - sin * PLAYER_R; - if(y2) *y2 = y - cos * PLAYER_R; + if(x2) *x2 = x - sin * r; + if(y2) *y2 = y - cos * r; } bool player_collision(game_t const *game) { float x1, y1, x2, y2; - player_position(game->player_rota, &x1, &y1, &x2, &y2); + player_position(game->player_rota, &x1, &y1, &x2, &y2, PLAYER_R); for(int i = 0; i < game->rect_count; i++) { if(rect_circle_collide(&game->rects[i], x1, y1, PLAYER_SIZE)) diff --git a/src/render.c b/src/render.c index 4e6d694..857b7d0 100644 --- a/src/render.c +++ b/src/render.c @@ -1,20 +1,34 @@ #include "duet.h" #include +/* Extreme hacks for intersecting circles; we draw a pixel as [color], but if + there is already [rep1] on that pixel we turn it into [rep2], and if we see + [rep2] we don't paint over it */ +void dcircle_pixel(int x, int y, int color, int rep1, int rep2) +{ + if(x < 0 || x >= DWIDTH || y < 0 || y >= DHEIGHT) + return; + int i = DWIDTH * y + x; + if(gint_vram[i] == rep2) + return; + gint_vram[i] = (gint_vram[i] == rep1) ? rep2 : color; +} /* Bresenham algorithm */ -void dcircle(int cx, int cy, int r0, int color, bool fill) +void dcircle(int cx, int cy, int r0, int color, bool fill, int rep1, int rep2) { int x=-r0, y=0, e=2-2*r0, r=r0; do { - dpixel(cx-x, cy-y, color); - dpixel(cx+y, cy-x, color); - dpixel(cx+x, cy+y, color); - dpixel(cx-y, cy+x, color); + dcircle_pixel(cx-x, cy-y, color, rep1, rep2); + dcircle_pixel(cx+y, cy-x, color, rep1, rep2); + dcircle_pixel(cx+x, cy+y, color, rep1, rep2); + dcircle_pixel(cx-y, cy+x, color, rep1, rep2); if(fill) { - dline(cx+x, cy-y, cx-x, cy-y, color); - dline(cx+x, cy+y, cx-x, cy+y, color); + for(int sx = cx+x; sx <= cx-x; sx++) { + dcircle_pixel(sx, cy-y, color, rep1, rep2); + dcircle_pixel(sx, cy+y, color, rep1, rep2); + } } r = e; @@ -147,18 +161,67 @@ void drectoid(rect_t const *r, float extra_size, int color) dtriangle(x[2], y[2], x[3], y[3], x[0], y[0], color, r->opacity); } -void render_player(int x, int y, float angle) +void render_player(int x, int y, float angle, int r, float opacity) { - dcircle(x, y, PLAYER_R, C_RGB(10, 10, 10), false); + int c = 10 * opacity; + dcircle(x, y, r, C_RGB(c, c, c), false, -1, -1); float x1, y1, x2, y2; - player_position(angle, &x1, &y1, &x2, &y2); + player_position(angle, &x1, &y1, &x2, &y2, r); x1 = x1 - PLAYER_X + x; y1 = y1 - DHEIGHT/2 + y; x2 = x2 - PLAYER_X + x; y2 = y2 - DHEIGHT/2 + y; - dcircle(x1, y1, PLAYER_SIZE, C_RGB(0, 16, 26), true); - dcircle(x2, y2, PLAYER_SIZE, C_RGB(31, 6, 0), true); + int c1 = C_RGB(0, 16, 26); + int c2 = C_RGB(31, 6, 0); + int c3 = C_WHITE; + + dcircle(x1, y1, PLAYER_SIZE, c1, true, -1, -1); + dcircle(x2, y2, PLAYER_SIZE, c2, true, c1, c3); +} + +void render_glow(int x, int y, int r1, int r2, int color1, int color2, + float angle, float opacity) +{ + float s1, c1, s2, c2; + sincosf(angle, &s2, &c2); + + for(int i = 0; i < 16; i++) { + s1 = s2; + c1 = c2; + angle += M_PI / 8.0; + sincosf(angle, &s2, &c2); + + int r = (i & 1) ? r2 : r1; + int color = (i & 1) ? color2 : color1; + dtriangle(x, y, x+r*c2, y+r*s2, x+r*c1, y+r*s1, color, 256 * opacity); + } +} + +void radial_fadeout(int cx, int cy, int r1, int r2, int c) +{ + int x1 = max(0, cx - r2); + int x2 = min(DWIDTH-1, cx + r2); + int y1 = max(0, cy - r2); + int y2 = min(DHEIGHT-1, cy + r2); + + for(int y = y1; y <= y2; y++) + for(int x = x1; x <= x2; x++) { + int i = DWIDTH * y + x; + int r_sq = (x - cx) * (x - cx) + (y - cy) * (y - cy); + + if(r_sq < r1 * r1) + continue; + if(r_sq > r2 * r2) { + gint_vram[i] = c; + continue; + } + + float r = sqrtf(r_sq); + float opacity = fmaxf(0.0, fminf(1.0, (r - r1) / (r2 - r1))); + + gint_vram[i] = blend(gint_vram[i], c, 256*opacity); + } }