diff options
| author | miolin <sash.kryuko2015@gmail.com> | 2026-11-01 17:30:16 +0300 |
|---|---|---|
| committer | miolin <sash.kryuko2015@gmail.com> | 2026-11-01 17:30:16 +0300 |
| commit | 2526b981585bb5621426d139832d9ff998ef8437 (patch) | |
| tree | b534f32524dff1d210543c038e22fa6ecbe2a4ca | |
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | bird.png | bin | 0 -> 87197 bytes | |||
| -rw-r--r-- | bird2.png | bin | 0 -> 151490 bytes | |||
| -rw-r--r-- | cloud_1.png | bin | 0 -> 140985 bytes | |||
| -rw-r--r-- | cloud_2.png | bin | 0 -> 179449 bytes | |||
| -rw-r--r-- | dune.png | bin | 0 -> 111531 bytes | |||
| -rw-r--r-- | flappybird.c | 394 | ||||
| -rw-r--r-- | makefile | 9 |
8 files changed, 405 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d380d7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +flappybird +flappybird_debug diff --git a/bird.png b/bird.png Binary files differnew file mode 100644 index 0000000..858d593 --- /dev/null +++ b/bird.png diff --git a/bird2.png b/bird2.png Binary files differnew file mode 100644 index 0000000..e2ffde8 --- /dev/null +++ b/bird2.png diff --git a/cloud_1.png b/cloud_1.png Binary files differnew file mode 100644 index 0000000..e2b572f --- /dev/null +++ b/cloud_1.png diff --git a/cloud_2.png b/cloud_2.png Binary files differnew file mode 100644 index 0000000..10ab8da --- /dev/null +++ b/cloud_2.png diff --git a/dune.png b/dune.png Binary files differnew file mode 100644 index 0000000..c948110 --- /dev/null +++ b/dune.png diff --git a/flappybird.c b/flappybird.c new file mode 100644 index 0000000..62fe936 --- /dev/null +++ b/flappybird.c @@ -0,0 +1,394 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <raylib.h> +#include <raymath.h> + +#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; +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..081ecc9 --- /dev/null +++ b/makefile @@ -0,0 +1,9 @@ +FLAGS = -lraylib -lm -ldl -lpthread -lX11 -lGL +flappybird : flappybird.c + gcc flappybird.c -o flappybird $(FLAGS) +run : flappybird + ./flappybird +flappybird_debug : flappybird.c + gcc flappybird.c -o flappybird_debug -ggdb $(FLAGS) +debug : flappybird_debug + gdb ./flappybird_debug |
