/** * paradiso - Paradigmen der Softwareentwicklung * * (c) Copyright 2023 Hartmut Seichter, Robin Rottstädt, brxxh (Hannes Brothuhn) * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #include "lib/image_loader.hpp" const int frame_rate = 60; bool game_over = false; float risky_pos_x; float risky_pos_max_x; float risky_pos_bottom_y; float risky_pos_top_y; float risky_pos_bottom_max_y; float risky_pos_top_max_y; struct Background { paradiso::Sprite backgroundLeft; paradiso::Sprite backgroundRight; paradiso::Sprite grassLeft; paradiso::Sprite grassRight; paradiso::Sprite* scrolling[2] = {&backgroundLeft, &backgroundRight}; paradiso::Renderer renderer{}; Background() { auto backgroundImage = paradiso::BitmapIO::get().load("background-day.png"); backgroundLeft = paradiso::Sprite{ .bitmap = backgroundImage, .pivot = {paradiso::Vector2::make(0.0f, 0.16f)}, .scale = {paradiso::Vector2::make(1.01f, 1.3f)}}; backgroundRight = paradiso::Sprite{ .bitmap = backgroundImage, .pivot = {paradiso::Vector2::make(2.018f, 0.16f)}, .scale = {paradiso::Vector2::make(1.01f, 1.3f)}}; auto grassImage = paradiso::BitmapIO::get().load("base.png"); grassLeft = paradiso::Sprite{ .bitmap = grassImage, .pivot = {paradiso::Vector2::make(0.0f, -1.0f)}, .scale = {paradiso::Vector2::make(1.0f, 0.33333f)}}; grassRight = paradiso::Sprite{ .bitmap = grassImage, .pivot = {paradiso::Vector2::make(2.0f, -1.0f)}, .scale = {paradiso::Vector2::make(1.0f, 0.33333f)}}; } void draw(const paradiso::Shader& shader) { for (auto sprite : scrolling) { if (game_over == false) { if (sprite->pivot.x() <= -2.0f) { sprite->pivot.x() += 4.0f; } sprite->pivot.x() -= 0.002f; } shader.set_uniform("pivot", sprite->pivot); shader.set_uniform("scale", sprite->scale); shader.set_uniform("rotation", sprite->rotation); renderer.draw(*sprite, shader); } } }; struct Pipe { paradiso::Sprite pipe_top; paradiso::Sprite pipe_bottom; paradiso::Renderer renderer1{}; paradiso::Renderer renderer2{}; bool paused = false; int pipe_spawn_rand_int = rand() % 80 + 15; float pipe_spawn_rand = float(pipe_spawn_rand_int) / 100; bool pos_reset = false; Pipe() { auto pipe_image = paradiso::BitmapIO::get().load("pipe-green.png"); pipe_top = paradiso::Sprite{ .bitmap = pipe_image, .pivot = {paradiso::Vector2::make(1.4f, pipe_spawn_rand + 1.0f)}, .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 320.0f)) / 700.0f) * 2.25f)}, .rotation = 3.1415926f}; pipe_bottom = paradiso::Sprite{ .bitmap = pipe_image, .pivot = {paradiso::Vector2::make(1.4f, pipe_spawn_rand - 1.5f)}, .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 320.0f)) / 700.0f) * 2.25f)}}; paused = true; } void update() { if (game_over == true) { paused = true; } if (paused == true) { return; } else { pipe_spawn_rand_int = rand() % 80 + 15; pipe_spawn_rand = float(pipe_spawn_rand_int) / 100; pipe_top.pivot.x() -= 0.02f; pipe_bottom.pivot.x() -= 0.02f; risky_pos_x = pipe_top.pivot.x(); risky_pos_max_x = pipe_top.pivot.x() + ((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f; risky_pos_top_y = pipe_top.pivot.y(); risky_pos_top_max_y = pipe_top.pivot.y() + 10.0f; risky_pos_bottom_y = pipe_bottom.pivot.y(); risky_pos_bottom_max_y = pipe_bottom.pivot.y() - 10.0f; if (pipe_top.pivot.x() <= -1.4f || pipe_bottom.pivot.x() <= -1.4f) { pos_reset = true; if (pos_reset == true) { pipe_top.pivot.y() = pipe_spawn_rand + 1.0f; pipe_bottom.pivot.y() = pipe_spawn_rand - 1.5; pos_reset = false; } pipe_top.pivot.x() = 1.4f; pipe_bottom.pivot.x() = 1.4f; } } } void draw(const paradiso::Shader& shader) { shader.set_uniform("pivot", pipe_bottom.pivot); shader.set_uniform("scale", pipe_bottom.scale); renderer1.draw(pipe_bottom, shader); shader.set_uniform("pivot", pipe_top.pivot); shader.set_uniform("scale", pipe_top.scale); shader.set_uniform("rotation", pipe_top.rotation); renderer1.draw(pipe_top, shader); } }; struct Grass { paradiso::Sprite grassLeft; paradiso::Sprite grassRight; paradiso::Sprite* scrolling[2] = {&grassLeft, &grassRight}; paradiso::Renderer renderer1{}; paradiso::Renderer renderer2{}; Grass() { auto grassImage = paradiso::BitmapIO::get().load("base.png"); grassLeft = paradiso::Sprite{ .bitmap = grassImage, .pivot = {paradiso::Vector2::make(0.0f, -0.9f)}, .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 504.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 112.0f)) / 700.0f) * 2.25f)}}; grassRight = paradiso::Sprite{ .bitmap = grassImage, .pivot = {paradiso::Vector2::make(1.002f, -0.9f)}, .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 504.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 112.0f)) / 700.0f) * 2.25f)}}; } void draw(const paradiso::Shader& shader) { if (game_over == false) { grassLeft.pivot.x() -= 0.02f; grassRight.pivot.x() -= 0.02f; if (grassRight.pivot.x() <= 0.0f) { grassLeft.pivot.x() = 1.002f; } if (grassRight.pivot.x() <= -1.002f) { grassRight.pivot.x() = 1.002f; } } shader.set_uniform("pivot", grassLeft.pivot); shader.set_uniform("scale", grassLeft.scale); shader.set_uniform("rotation", grassLeft.rotation); shader.set_uniform("pivot", grassRight.pivot); shader.set_uniform("scale", grassRight.scale); shader.set_uniform("rotation", grassRight.rotation); renderer1.draw(grassLeft, shader); renderer2.draw(grassRight, shader); } }; struct QuickWings { paradiso::Renderer renderer1{}; paradiso::Renderer renderer2{}; paradiso::Renderer renderer3{}; std::array birds; unsigned int flap = 0; unsigned int flapSpeed = 15; // How many ticks per flap unsigned int flapCounter = 0; // How many ticks since last flap float velocity = 0.0f; const float max_velocity = 0.02f; const float gravity = -0.002f; const float move_up_velocity = 0.02f; bool move_up = false; bool paused = true; const float max_pos = 0.95f; const float min_pos = -0.5f; float pos = 0.34f; float rotation = 0.0f; int collision_counter = 0; QuickWings() { float scaleh = 0.08f; float scalew = 0.158f; birds = { paradiso::Sprite{ .bitmap = paradiso::BitmapIO::get().load("yellowbird-downflap.png"), .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, .scale = {paradiso::Vector2::make(scalew, scaleh)}}, paradiso::Sprite{ .bitmap = paradiso::BitmapIO::get().load("yellowbird-midflap.png"), .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, .scale = {paradiso::Vector2::make(scalew, scaleh)}}, paradiso::Sprite{ .bitmap = paradiso::BitmapIO::get().load("yellowbird-upflap.png"), .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, .scale = {paradiso::Vector2::make(scalew, scaleh)}}}; } void draw(const paradiso::Shader& shader) { // Update flap state if (flapCounter < flapSpeed) { flapCounter++; } else { flapCounter = 0; flap = (flap + 1) % birds.size(); } auto bird = birds[flap]; bird.pivot.y() = pos; bird.rotation = rotation; shader.set_uniform("pivot", bird.pivot); shader.set_uniform("scale", bird.scale); shader.set_uniform("rotation", bird.rotation); switch (flap) { case 0: renderer1.draw(bird, shader); break; case 1: renderer2.draw(bird, shader); break; case 2: renderer3.draw(bird, shader); break; } } void update() { if (game_over == true) { paused = true; } // Stop game if (paused) { return; } else { // Apply gravity velocity += gravity; if (move_up) velocity += move_up_velocity - gravity; // Cap velocity if (velocity > max_velocity) velocity = max_velocity; if (velocity < -max_velocity) velocity = -max_velocity; // Cap position pos += velocity; if (pos < min_pos) { pos = min_pos; velocity = 0.0f; game_over = true; } if (pos > max_pos) { pos = max_pos; velocity = 0.0f; game_over = true; } // Update rotation rotation = velocity * 10.0f; } float final_risky_pos_top_y = risky_pos_top_y - 1.06f; float final_risky_pos_bottom_y = risky_pos_bottom_y + 1.06f; if (risky_pos_x - 0.3f <= 0.0f && risky_pos_max_x >= 0.0f) { if (pos >= final_risky_pos_top_y && pos <= risky_pos_top_max_y) { game_over = true; } if (pos <= final_risky_pos_bottom_y && pos >= risky_pos_bottom_max_y) { if (collision_counter == 0) { collision_counter++; } else { collision_counter = 2; } if (collision_counter == 2) { game_over = true; } } } } // keyboard handler void on_keyboard(const paradiso::Window::KeyboardInputStack& input) { if (input.size()) { paused = false; if (paused == false) { bool pressed_up = input.top().key == ' ' || input.top().key == 'W'; if (input.top().action == 1 && pressed_up) { move_up = true; } else if (input.top().action == 0 && pressed_up) { move_up = false; } } else { return; } } } }; struct Message { paradiso::Sprite messageSprite; paradiso::Renderer renderer{}; bool start = false; float pos = 100.0f; Message() { auto messageImage = paradiso::BitmapIO::get().load("message.png"); messageSprite = paradiso::Sprite{ .bitmap = messageImage, .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, .scale = {paradiso::Vector2::make(0.8f, 0.8f)} }; }; void draw(const paradiso::Shader& shader) { shader.set_uniform("pivot", messageSprite.pivot); shader.set_uniform("scale", messageSprite.scale); renderer.draw(messageSprite, shader); } void update() { if (start == true) { messageSprite.pivot.y() = pos; } } void on_keyboard(const paradiso::Window::KeyboardInputStack& input) { if (input.size()) { start = true; } } }; struct GameOverMessage { paradiso::Sprite messageSprite; paradiso::Renderer renderer{}; GameOverMessage() { auto messageImage = paradiso::BitmapIO::get().load("gameover.png"); messageSprite = paradiso::Sprite{ .bitmap = messageImage, .pivot = {paradiso::Vector2::make(0.0f, 0.4f)}, .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 192.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 42.0f)) / 700.0f) * 2.25f)} }; }; void draw(const paradiso::Shader& shader) { shader.set_uniform("pivot", messageSprite.pivot); shader.set_uniform("scale", messageSprite.scale); renderer.draw(messageSprite, shader); } }; auto main() -> int { std::srand(std::time(nullptr)); // Ausgabefenster ... sieht aus als wäre es auf dem Stack auto window = paradiso::Window(); auto size = paradiso::Size{.width = 500, .height = 700}; /* window.set_resizecallback([](auto& w) -> void { w.set_size(paradiso::Size{.width = 405, .height = 720}); }); */ window .set_size(size) // ... Grösse .set_position(paradiso::Point{.x = 1920 / 2 - 500 / 2, .y = 1080 / 2 - 700 / 2}) // ... Position .set_title("PardiSO.FlappyBird") // ... Titel .set_visible(true); // ... und jetzt anzeigen! // der Fenster Kontext auto ctx = paradiso::Context{}; // ein Shader (Schattierungsprogramm) auto shader = paradiso::Shader{}; // wir nutzen einen vorgefertigten shader shader.load_preset(paradiso::Shader::Preset::Sprite); // ein viewport stellt die Sicht der Kamera dar, d.h. bei quadratischen // Pixeln sollte hier auch eine dementsprechende Grösse eingestellt // werden ctx.set_viewport(paradiso::Rectangle{ .position = paradiso::Point{.x = 0, .y = 0}, .size = size}); // nothing beats a classic look ctx.set_clearcolor(paradiso::RGBA::from_rgb(0x00, 0x00, 0x00)); // Asset loader bekommt den Pfad paradiso::BitmapIO::get().set_path("assets"); // Load auto background = Background{}; auto grass = Grass{}; auto quickwingsapp = QuickWings{}; auto pipe = Pipe{}; auto message = Message{}; auto gameover = GameOverMessage{}; // timer // das update führt den hier mitgegebnen Ausdruck innerhalb der internen // Updates des Fensters auf. Es wird hier auch explizit ein bool gefordert // damit das update auch zu jederzeit unterbrochen werden kann while (window.update([&](paradiso::Window& w) -> bool { ctx.set_viewport(paradiso::Rectangle{ .position = paradiso::Point{.x = 0, .y = 0}, .size = size}); ctx.clear(); auto t1 = std::chrono::high_resolution_clock::now(); // Keyboard and state change + update if (quickwingsapp.paused == false) { pipe.paused = false; } pipe.update(); quickwingsapp.on_keyboard(w.keyboard_input()); quickwingsapp.update(); message.on_keyboard(w.keyboard_input()); message.update(); // Draw background.draw(shader); pipe.draw(shader); grass.draw(shader); message.draw(shader); if (game_over == true) { gameover.draw(shader); } quickwingsapp.draw(shader); // wait for frame rate auto t2 = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(t2 - t1); auto wait = std::chrono::microseconds(1000000 / frame_rate) - duration; std::this_thread::sleep_for(wait); // Quit return !(w.keyboard_input().size() && w.keyboard_input().top().key == 'Q'); })) { }; return 0; }