/** * 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 "lib/image_loader.hpp" const int frame_rate = 60; bool game_over = false; struct Background { paradiso::Sprite backgroundLeft; paradiso::Sprite backgroundRight; paradiso::Sprite grassLeft; paradiso::Sprite grassRight; paradiso::Sprite* scrolling[2] = {&backgroundLeft, &backgroundRight}; 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 (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); } } paradiso::Renderer renderer{}; }; struct Grass { paradiso::Sprite grassLeft; paradiso::Sprite grassRight; paradiso::Sprite* scrolling[2] = {&grassLeft, &grassRight}; Grass() { auto grassImage = paradiso::BitmapIO::get().load("base.png"); grassLeft = paradiso::Sprite{ .bitmap = grassImage, .pivot = {paradiso::Vector2::make(1.0f, -0.9f)}, .scale = {paradiso::Vector2::make(2.0f, 0.33333f)}}; grassRight = paradiso::Sprite{ .bitmap = grassImage, .pivot = {paradiso::Vector2::make(1.0f, -0.9f)}, .scale = {paradiso::Vector2::make(2.6f, 0.33333f)}}; } void draw(const paradiso::Shader& shader) { for (auto sprite : scrolling) { if (sprite->pivot.x() <= -1.0f) { sprite->pivot.x() += 2.0f; } sprite->pivot.x() -= 0.03f; shader.set_uniform("pivot", sprite->pivot); shader.set_uniform("scale", sprite->scale); shader.set_uniform("rotation", sprite->rotation); renderer.draw(*sprite, shader); } } paradiso::Renderer renderer{}; }; 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.04f; const float gravity = -0.004f; const float move_up_velocity = 0.04f; 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; 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; } } // 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; } } } }; // TODO: finish this 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 { // 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 message = Message{}; auto background = Background{}; auto grass = Grass{}; auto quickwingsapp = QuickWings{}; 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 quickwingsapp.on_keyboard(w.keyboard_input()); quickwingsapp.update(); message.on_keyboard(w.keyboard_input()); message.update(); // Draw background.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; }