#include #include #include #include #include #define PIPE_COUNT 10 #define PIPE_INTERVAL 300 #define PIPE_WIDTH 100 #define PIPE_GAP 150 #define BIRD_ANIMATION_FRAME_COUNT 4 #define BIRD_ANIMATION_FRAME_TIME 0.1 #define PATH "./" struct game_state { Vector2 bird_position; Vector2 bird_velocity; Texture2D texture; Rectangle bird_texture[BIRD_ANIMATION_FRAME_COUNT]; struct animation { int frame; double clock; } bird_animation; NPatchInfo pipe_texture_top; NPatchInfo pipe_texture_bottom; struct pipe { Vector2 position; } pipe[PIPE_COUNT]; struct death_state { double angle; Vector2 velocity; } death; struct background_state { Texture2D dune; Texture2D cloud[2]; double scroll; } background; }; void init_game_state(struct game_state *state); Rectangle dune_destination_to_source(int texture_width, int texture_height, Rectangle destination) { float q = 100 / destination.height; return (Rectangle) { 0, q ,texture_height * destination.width / destination.height ,texture_height - q * 2 }; } void draw_background(struct background_state *state) { int window_width = GetRenderWidth(); int window_height = GetRenderHeight(); ClearBackground((Color) { 135, 206, 235, 0 }); double y = window_height / 2; double height = window_height * 0.05; int offset = 1; DrawRectangle(0, window_height / 2, window_width, window_height / 2, (Color) { 243, 202, 35, 255 }); double scroll_speed = 0.2; for (int i = 0; i < 5; i++) { Rectangle destination = (Rectangle) { 0, y, window_width, height }; Rectangle source = dune_destination_to_source(state->dune.width, state->dune.height, destination); source.x = state->scroll * scroll_speed + offset; scroll_speed *= 2.0; offset = offset * 37 % 240; DrawTexturePro(state->dune ,source ,destination ,(Vector2) { 0, height } ,0 ,WHITE ); y += height * 0.8577; height *= 1.8; } double texture_width[3] = { 392, 392, 500 }; Texture2D texture[3] = { state->cloud[1], state->cloud[1], state->cloud[0] }; double texture_height[3] = { 280, 220, 265 }; double texture_y[3] = { 0, 280, 0 }; height = window_height / 10; y = window_height * 0.4; scroll_speed = 0.1; for (int j = 0; j < 8; j++) { int i = j % 3; Rectangle destination = (Rectangle) { 0, y, window_width, height }; Rectangle source = dune_destination_to_source(texture_width[i], texture_height[i], destination); source.y = texture_y[i]; source.x = state->scroll * scroll_speed + offset; offset = offset * 37 % 240; scroll_speed *= 1.2; DrawTexturePro(texture[i] ,source ,destination ,(Vector2) { 0, height } ,0 ,WHITE ); y -= height * 0.083; height *= 1.3; } } void animation_update(struct animation *animation, double dt, double frame_time, int frame_count) { animation->clock += dt; if (animation->clock > frame_time) { animation->frame = (animation->frame + 1) % frame_count; animation->clock = 0; } } double clamp(double x, double a, double b) { return x < a ? a : x > b ? b : x; } double rect_distance(Rectangle r, Vector2 p) { Vector2 dim = (Vector2) { r.width * 0.5, r.height * 0.5 }; p = (Vector2) { abs(p.x - r.x - r.width / 2), abs(p.y - r.y - r.height / 2) }; Vector2 d = Vector2Subtract(p, dim); d = (Vector2) { d.x < 0 ? 0 : d.x, d.y < 0 ? 0 : d.y }; return Vector2Length(d); } double pipe_height(double window_height, double pipe_buffer) { return rand() / (RAND_MAX / (window_height - pipe_buffer * 2)) + pipe_buffer; } Rectangle pipe_top(struct pipe pipe, double width, double gap, double window_height) { return (Rectangle) { pipe.position.x - width / 2, 0 ,width, pipe.position.y - gap / 2 }; } Rectangle pipe_bottom(struct pipe pipe, double width, double gap, double window_height) { return (Rectangle) { pipe.position.x - width / 2, pipe.position.y + gap / 2 ,width, window_height - pipe.position.y - gap / 2 }; } double pipe_last(struct pipe *pipe, int count) { if (count < 1) { return 0; } double last = pipe[0].position.x; for (int i = 1; i < count; i++) { last = pipe[i].position.x > last ? pipe[i].position.x : last; } return last; } double pipe_distance(struct pipe *pipe, int count, double width, double gap, double window_height, Vector2 p) { double d = INFINITY; for (int i = 0; i < count; i++) { double top_distance = rect_distance(pipe_top(pipe[i], width, gap, window_height), p) ,bottom_distance = rect_distance(pipe_bottom(pipe[i], width, gap, window_height), p); d = top_distance < d ? top_distance : d; d = bottom_distance < d ? bottom_distance : d; } return d; } enum game_screen { PLAY_SCREEN ,TAP_SPACE_SCREEN ,DEATH_SCREEN ,TEST_SCREEN }; #define BIRD_WIDTH 80 #define BIRD_HEIGHT (BIRD_WIDTH * 0.6) #define HIT_RADIUS (BIRD_HEIGHT * 0.4) void draw_game(struct game_state *state) { int window_width = GetRenderWidth(); int window_height = GetRenderHeight(); float angle = atan(state->bird_velocity.y / state->bird_velocity.x) * 180 / M_PI; if (state->bird_velocity.x < 0) { angle += 180; } draw_background(&state->background); DrawTexturePro( state->texture ,state->bird_texture[state->bird_animation.frame] ,(Rectangle) { state->bird_position.x, state->bird_position.y ,BIRD_WIDTH, BIRD_HEIGHT } ,(Vector2) { BIRD_WIDTH / 2, BIRD_HEIGHT / 2 } ,angle ,WHITE ); //DrawCircleLinesV(state->bird_position, HIT_RADIUS, PURPLE); for (int i = 0; i < PIPE_COUNT; i++) { DrawTextureNPatch( state->texture ,state->pipe_texture_top ,pipe_top(state->pipe[i], PIPE_WIDTH, PIPE_GAP, window_height) ,(Vector2) { 0, 0 } ,0 ,WHITE ); DrawTextureNPatch( state->texture ,state->pipe_texture_bottom ,pipe_bottom(state->pipe[i], PIPE_WIDTH, PIPE_GAP, window_height) ,(Vector2) { 0, 0 } ,0 ,WHITE ); } return; } enum game_screen play_game(struct game_state *state) { int window_width = GetRenderWidth(); int window_height = GetRenderHeight(); BeginDrawing(); ClearBackground(BLACK); draw_game(state); EndDrawing(); if (pipe_distance(state->pipe, PIPE_COUNT, PIPE_WIDTH, PIPE_GAP, window_height, state->bird_position) < HIT_RADIUS || state->bird_position.y >= window_height - BIRD_HEIGHT / 2 - 1 ) { state->death.angle = atan(state->bird_velocity.y / state->bird_velocity.x); state->death.velocity = (Vector2) { state->bird_velocity.x, -state->bird_velocity.x }; return DEATH_SCREEN; } double dt = GetFrameTime(); state->bird_position.y += state->bird_velocity.y * dt * 10; state->bird_velocity.y += 100 * dt; state->bird_position.y = clamp(state->bird_position.y, BIRD_HEIGHT / 2, window_height - BIRD_HEIGHT / 2); state->bird_velocity.y = clamp(state->bird_velocity.y, -100, 100); if (IsKeyPressed(KEY_SPACE)) { state->bird_velocity.y = -40; } for (int i = 0; i < PIPE_COUNT; i++) { state->pipe[i].position.x -= state->bird_velocity.x * dt * 2; if (state->pipe[i].position.x < -PIPE_WIDTH) { state->pipe[i].position.x = pipe_last(state->pipe, PIPE_COUNT) + PIPE_INTERVAL; state->pipe[i].position.y = pipe_height(window_height, 100); } } state->background.scroll += state->bird_velocity.x * dt * 0.4; animation_update(&state->bird_animation, dt, BIRD_ANIMATION_FRAME_TIME, BIRD_ANIMATION_FRAME_COUNT); return PLAY_SCREEN; } enum game_screen tap_space_screen(struct game_state *state) { state->bird_velocity.y = 0; int window_width = GetRenderWidth(); int window_height = GetRenderHeight(); BeginDrawing(); { draw_game(state); char *text = "SPACE"; int text_height = window_width / 20; int text_width = MeasureText(text, text_height); DrawText(text, (window_width - text_width) / 2, window_height * 2 / 3, text_height, WHITE); } EndDrawing(); double dt = GetFrameTime(); state->background.scroll += state->bird_velocity.x * dt * 0.4; if (IsKeyPressed(KEY_SPACE)) { state->bird_velocity.y = -40; return PLAY_SCREEN; } animation_update(&state->bird_animation, dt, BIRD_ANIMATION_FRAME_TIME, BIRD_ANIMATION_FRAME_COUNT); return TAP_SPACE_SCREEN; } enum game_screen death_screen(struct game_state *state) { int window_width = GetRenderWidth() ,window_height = GetRenderHeight(); BeginDrawing(); { ClearBackground((Color) { 135, 206, 235, 0 }); draw_game(state); double fade = clamp((state->bird_position.y - window_height) / window_height, 0, 1); DrawRectangle(0, 0, window_width, window_height, (Color) { 0, 0, 0, (fade) * 255 }); int text_height = window_height / 4; char *text = "YOU DIED"; int text_width = MeasureText(text, text_height); DrawText(text, window_width / 2 - text_width / 2, window_height / 2 - text_height / 2, text_height, (Color) { 255, 0, 0, clamp((fade - 0.5) * 2, 0, 1) * 255 }); } EndDrawing(); double dt = GetFrameTime(); state->death.angle += dt * 20; state->bird_velocity = (Vector2) { cos(state->death.angle), sin(state->death.angle) }; state->bird_position = Vector2Add(state->bird_position, Vector2Scale(state->death.velocity, dt * 8)); state->death.velocity.y += 200 * dt; if (state->bird_position.y > window_height * 10) { init_game_state(state); return TAP_SPACE_SCREEN; } return DEATH_SCREEN; } enum game_screen test_screen(struct game_state *state) { BeginDrawing(); draw_background(&state->background); EndDrawing(); double dt = GetFrameTime(); state->background.scroll += dt * 100; return TEST_SCREEN; } void init_game_state(struct game_state *state) { int window_width = GetRenderWidth(); int window_height = GetRenderHeight(); state->bird_position = (Vector2) { window_width / 2, window_height / 2 }; state->bird_velocity = (Vector2) { 40, -40 }; state->background.scroll = 0; for (int i = 0; i < PIPE_COUNT; i++) { state->pipe[i].position = (Vector2) { i * PIPE_INTERVAL + window_width + PIPE_WIDTH ,pipe_height(window_height, PIPE_GAP * 0.5) }; } } int main(int argc, char **argv) { const int window_width = 800; const int window_height = 450; InitWindow(window_width, window_height, "flappybird"); SetTargetFPS(60); struct game_state state; state.texture = LoadTexture(PATH "bird2.png"); GenTextureMipmaps(&state.texture); SetTextureFilter(state.texture, TEXTURE_FILTER_BILINEAR); for (int i = 0; i < BIRD_ANIMATION_FRAME_COUNT; i++) { state.bird_texture[i] = (Rectangle) { 2, 128 * (i == 3 ? 1 : i) + 9, -196, 110 }; } state.bird_animation.frame = 0; state.bird_animation.clock = 0; state.pipe_texture_bottom = (NPatchInfo) { (Rectangle) { 202, 2, 104, 124 } ,0, 47, 0, 0 ,NPATCH_NINE_PATCH }; state.pipe_texture_top = (NPatchInfo) { (Rectangle) { 315, 0, 104, 124 } ,0, 0, 0, 47 ,NPATCH_NINE_PATCH }; srand(666); state.background.dune = LoadTexture(PATH "dune.png"); state.background.cloud[0] = LoadTexture(PATH "cloud_1.png"); state.background.cloud[1] = LoadTexture(PATH "cloud_2.png"); GenTextureMipmaps(&state.background.dune); GenTextureMipmaps(&state.background.cloud[0]); GenTextureMipmaps(&state.background.cloud[1]); SetTextureFilter(state.background.dune, TEXTURE_FILTER_BILINEAR); SetTextureWrap(state.background.dune, TEXTURE_WRAP_REPEAT); SetTextureFilter(state.background.cloud[0], TEXTURE_FILTER_BILINEAR); SetTextureFilter(state.background.cloud[1], TEXTURE_FILTER_BILINEAR); init_game_state(&state); enum game_screen screen = TAP_SPACE_SCREEN; while (!WindowShouldClose()) { switch (screen) { case PLAY_SCREEN: screen = play_game(&state); break; case DEATH_SCREEN: screen = death_screen(&state); break; case TEST_SCREEN: screen = test_screen(&state); break; case TAP_SPACE_SCREEN: screen = tap_space_screen(&state); break; default: BeginDrawing(); ClearBackground(RED); EndDrawing(); break; } } CloseWindow(); UnloadTexture(state.texture); UnloadTexture(state.background.dune); UnloadTexture(state.background.cloud[0]); UnloadTexture(state.background.cloud[1]); return 0; }