From ccd0697ab3f41d6cb2e9623d993bdf90f1a1276f Mon Sep 17 00:00:00 2001 From: brxxh <11dac2t@huhn-online.de> Date: Sat, 31 May 2025 00:02:16 +0200 Subject: [PATCH] Some architectural changes --- examples/tappyplane/tappyplane.cpp | 553 ++++++++++++++++------------- 1 file changed, 316 insertions(+), 237 deletions(-) diff --git a/examples/tappyplane/tappyplane.cpp b/examples/tappyplane/tappyplane.cpp index 6e90794..4d4a5cc 100644 --- a/examples/tappyplane/tappyplane.cpp +++ b/examples/tappyplane/tappyplane.cpp @@ -17,9 +17,15 @@ #include #include +#include #include +#include +#include -#include +namespace TappyPlane { + +using SpriteName = std::tuple; +using SpriteMap = std::unordered_map; static const unsigned int FRAME_RATE = 60; @@ -56,23 +62,24 @@ struct Background { std::array sprites; paradiso::Renderer renderer{}; - paradiso::Bitmap image = - paradiso::BitmapIO::get().load("PNG/background.png"); - - void init() { - // For resetting. - scrolling_speed = INIT_SCROLLING_SPEED; - + void init(SpriteMap& sprite_map) { sprites = {paradiso::Sprite{ - .bitmap = image, + .bitmap = sprite_map["background"], .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, .scale = {paradiso::Vector2::make(1.0f, 1.0f)}}, paradiso::Sprite{ - .bitmap = image, + .bitmap = sprite_map["background"], .pivot = {paradiso::Vector2::make(2.0f, 0.0f)}, .scale = {paradiso::Vector2::make(1.0f, 1.0f)}}}; } + void reset() { + scrolling_speed = INIT_SCROLLING_SPEED; + + sprites[0].pivot.x() = 0.0f; + sprites[1].pivot.x() = 2.0f; + } + void update() { for (auto& sprite : sprites) { sprite.pivot.x() -= scrolling_speed; @@ -100,17 +107,9 @@ struct Ground { std::array sprites; paradiso::Renderer renderer{}; - // Maybe put this into 'init()' because we might use the other textures - // like snow, ice, ... and then we can parameterize this. - paradiso::Bitmap image = - paradiso::BitmapIO::get().load("PNG/groundGrass.png"); - AABB aabb{}; - void init() { - // For resetting. - scrolling_speed = INIT_SCROLLING_SPEED; - + void init(SpriteMap& sprite_map) { // The image's height is 71 px. float scale_y = 71.0f / WINDOW_HEIGHT; @@ -118,11 +117,11 @@ struct Ground { sprites = { paradiso::Sprite{ - .bitmap = image, + .bitmap = sprite_map["ground"], .pivot = {paradiso::Vector2::make(0.0f, pivot_y)}, .scale = {paradiso::Vector2::make(1.0f, scale_y)}}, paradiso::Sprite{ - .bitmap = image, + .bitmap = sprite_map["ground"], .pivot = {paradiso::Vector2::make(2.0f, pivot_y)}, .scale = {paradiso::Vector2::make(1.0f, scale_y)}}}; @@ -132,6 +131,13 @@ struct Ground { .size = {paradiso::Vector2::make(2.0f, scale_y)}}; } + void reset() { + scrolling_speed = INIT_SCROLLING_SPEED; + + sprites[0].pivot.x() = 0.0f; + sprites[1].pivot.x() = 2.0f; + } + void update() { for (auto& sprite : sprites) { sprite.pivot.x() -= scrolling_speed; @@ -153,7 +159,14 @@ struct Ground { struct Rocks { static constexpr float INIT_SCROLLING_SPEED = 0.006f; - static constexpr float GAP_SIZE = 1.6f; + static constexpr float GAP_SIZE = 1.5f; + + // The image's size is 108x239 px. + static constexpr float INIT_SCALE_X = 108.0f / WINDOW_WIDTH; + static constexpr float INIT_SCALE_Y = 239.0f / WINDOW_HEIGHT; + + static constexpr paradiso::Vector2 INIT_SCALE{ + paradiso::Vector2::make(INIT_SCALE_X, INIT_SCALE_Y)}; float scrolling_speed = INIT_SCROLLING_SPEED; @@ -163,45 +176,20 @@ struct Rocks { paradiso::Renderer top_renderer{}; paradiso::Renderer bottom_renderer{}; - // Maybe put this into 'init()' because we might use the other textures - // like snow, ice, ... and then we can parameterize this. - paradiso::Bitmap top_image = - paradiso::BitmapIO::get().load("PNG/rockGrassDown.png"); - paradiso::Bitmap bottom_image = - paradiso::BitmapIO::get().load("PNG/rockGrass.png"); - AABB top_aabb; AABB bottom_aabb; - void init(float x, bool reset_scrolling_speed = false) { - if (reset_scrolling_speed) { - scrolling_speed = INIT_SCROLLING_SPEED; - } + float init_pivot_x = 0.0f; - // The image's size is 108x239 px. - float scale_x = 108.0f / WINDOW_WIDTH; - float scale_y = 239.0f / WINDOW_HEIGHT; - auto scale = paradiso::Vector2{ - paradiso::Vector2::make(scale_x, scale_y)}; - - float top_position_y = - 1.0f - scale_y + (std::rand() % 60) / 100.0f + 0.04f; - float bottom_position_y = top_position_y - GAP_SIZE; - - top_sprite = paradiso::Sprite{ - .bitmap = top_image, - .pivot = {paradiso::Vector2::make(x, top_position_y)}, - .scale = scale}; - - bottom_sprite = paradiso::Sprite{ - .bitmap = bottom_image, - .pivot = {paradiso::Vector2::make(x, bottom_position_y)}, - .scale = scale}; + auto get_random_top_position_y() -> float { + return 1.0f - INIT_SCALE_Y + (std::rand() % 50) / 100.0f + 0.01f; + } + void init_aabbs() { // We'll make the width a quarter the width of the texture. // (If you want 1:1 size, you need to double the scale of the sprite.) auto size = paradiso::Vector2{ - paradiso::Vector2::make(scale_x / 2, scale_y * 2)}; + paradiso::Vector2::make(INIT_SCALE_X / 2, INIT_SCALE_Y * 2)}; auto top_position = paradiso::Vector2{paradiso::Vector2::make( @@ -217,6 +205,52 @@ struct Rocks { bottom_aabb = AABB{bottom_position, size}; } + void init(SpriteMap& sprite_map, float pivot_x) { + init_pivot_x = pivot_x; + + float top_position_y = get_random_top_position_y(); + float bottom_position_y = top_position_y - GAP_SIZE; + + top_sprite = paradiso::Sprite{.bitmap = sprite_map["rocks.top"], + .pivot = {paradiso::Vector2::make( + init_pivot_x, top_position_y)}, + .scale = INIT_SCALE}; + + bottom_sprite = + paradiso::Sprite{.bitmap = sprite_map["rocks.bottom"], + .pivot = {paradiso::Vector2::make( + init_pivot_x, bottom_position_y)}, + .scale = INIT_SCALE}; + + init_aabbs(); + } + + void reset() { + scrolling_speed = INIT_SCROLLING_SPEED; + + float top_position_y = get_random_top_position_y(); + float bottom_position_y = top_position_y - GAP_SIZE; + + top_sprite.pivot = paradiso::Vector2{ + paradiso::Vector2::make(init_pivot_x, top_position_y)}; + bottom_sprite.pivot = paradiso::Vector2{ + paradiso::Vector2::make(init_pivot_x, bottom_position_y)}; + + init_aabbs(); + } + + void jump_back() { + float top_position_y = get_random_top_position_y(); + float bottom_position_y = top_position_y - GAP_SIZE; + + top_sprite.pivot = paradiso::Vector2{ + paradiso::Vector2::make(1.2f, top_position_y)}; + bottom_sprite.pivot = paradiso::Vector2{ + paradiso::Vector2::make(1.2f, bottom_position_y)}; + + init_aabbs(); + } + void update() { top_sprite.pivot.x() -= scrolling_speed; bottom_sprite.pivot.x() -= scrolling_speed; @@ -231,14 +265,14 @@ struct Rocks { // \ // *------ the x we need. - // @Efficiency: We could just scroll it with the sprite. + // @Efficiency: We could just scroll the AABBs with the sprites. top_aabb.position.x() = top_sprite.pivot.x() - top_aabb.size.x() / 2; bottom_aabb.position.x() = bottom_sprite.pivot.x() - bottom_aabb.size.x() / 2; if (top_sprite.pivot.x() <= -1.2f) { // Reset. - init(1.2f); + jump_back(); return; } } @@ -267,6 +301,16 @@ struct Plane { static const unsigned int ANIM_INTERVAL = 10; + // The image's size is 88x73 px. + static constexpr float INIT_SCALE_X = 88.0f / WINDOW_WIDTH; + static constexpr float INIT_SCALE_Y = 73.0f / WINDOW_HEIGHT; + + static constexpr paradiso::Vector2 INIT_SCALE{ + paradiso::Vector2::make(INIT_SCALE_X, INIT_SCALE_Y)}; + + static constexpr paradiso::Vector2 INIT_PIVOT{ + paradiso::Vector2::make(0.0f, INIT_POSITION_Y)}; + // Animation sprites std::array sprites; std::array renderers; @@ -285,46 +329,45 @@ struct Plane { AABB aabb; - void init() { - // The image's size is 88x73 px. - float scale_x = 88.0f / WINDOW_WIDTH; - float scale_y = 73.0f / WINDOW_HEIGHT; + void init_aabb() { + // We'll make the size half the size of the texture. + // (If you want 1:1 size, you need to double the scale of the sprite.) + // We could use INIT_SCALE here, but we'll keep this for clarity. + auto size = paradiso::Vector2{ + paradiso::Vector2::make(INIT_SCALE_X, INIT_SCALE_Y)}; - auto pivot = paradiso::Vector2{ - paradiso::Vector2::make(0.0f, position_y)}; - auto scale = paradiso::Vector2{ - paradiso::Vector2::make(scale_x, scale_y)}; + // Center it. + auto position = paradiso::Vector2{paradiso::Vector2::make( + INIT_PIVOT.x() - size.x() / 2, INIT_PIVOT.y() - size.y() / 2)}; - sprites = { - paradiso::Sprite{.bitmap = paradiso::BitmapIO::get().load( - "PNG/Planes/planeRed1.png"), - .pivot = pivot, - .scale = scale}, - paradiso::Sprite{.bitmap = paradiso::BitmapIO::get().load( - "PNG/Planes/planeRed2.png"), - .pivot = pivot, - .scale = scale}, - paradiso::Sprite{.bitmap = paradiso::BitmapIO::get().load( - "PNG/Planes/planeRed3.png"), - .pivot = pivot, - .scale = scale}, - }; + aabb = AABB{position, size}; + } + + void init(SpriteMap& sprite_map) { + sprites = {paradiso::Sprite{.bitmap = sprite_map["plane.1"], + .pivot = INIT_PIVOT, + .scale = INIT_SCALE}, + paradiso::Sprite{.bitmap = sprite_map["plane.2"], + .pivot = INIT_PIVOT, + .scale = INIT_SCALE}, + paradiso::Sprite{.bitmap = sprite_map["plane.3"], + .pivot = INIT_PIVOT, + .scale = INIT_SCALE}}; + + init_aabb(); + } + + void reset() { + for (auto& sprite : sprites) { + sprite.pivot = INIT_PIVOT; + sprite.scale = INIT_SCALE; + } - // For resetting. position_y = INIT_POSITION_Y; velocity_y = INIT_VELOCITY_Y; rotation = INIT_ROTATION; - // We'll make the size half the size of the texture. - // (If you want 1:1 size, you need to double the scale of the sprite.) - auto size = paradiso::Vector2{ - paradiso::Vector2::make(scale_x, scale_y)}; - - // Center it. - auto position = paradiso::Vector2{paradiso::Vector2::make( - pivot.x() - size.x() / 2, pivot.y() - size.y() / 2)}; - - aabb = AABB{position, size}; + init_aabb(); } void update(const paradiso::Window::KeyboardInputStack& input) { @@ -363,7 +406,7 @@ struct Plane { // \ // *--- the y we need. - // @Efficiency: We could just add the velocity. + // @Efficiency: We could just add the velocity to the AABB's y position. aabb.position.y() = position_y - aabb.size.y() / 2; rotation = velocity_y * 500.0f; @@ -381,7 +424,7 @@ struct Plane { } }; -struct StartText { +struct StartUI { static const unsigned int TAP_ANIM_INTERVAL = 40; // textGetReady.png, tapLeft.png, tapRight.png @@ -395,43 +438,37 @@ struct StartText { unsigned int tap_anim_counter = 0; unsigned int current_tap_sprite = 0; - void init() { + void init(SpriteMap& sprite_map) { base_sprites = { - // textGetReady.png paradiso::Sprite{ - .bitmap = - paradiso::BitmapIO::get().load("PNG/UI/textGetReady.png"), + .bitmap = sprite_map["start_ui.text"], .pivot = {paradiso::Vector2::make(0.0f, 0.5f)}, // The image's size is 400x73 px. .scale = {paradiso::Vector2::make( 400.0f / WINDOW_WIDTH, 73.0f / WINDOW_HEIGHT)}}, - // tapLeft.png paradiso::Sprite{ - .bitmap = paradiso::BitmapIO::get().load("PNG/UI/tapLeft.png"), - .pivot = {paradiso::Vector2::make(0.5f, 0.0f)}, + .bitmap = sprite_map["tap_sign.left"], + .pivot = {paradiso::Vector2::make(-0.5f, 0.0f)}, // The image's size is 85x42 px. .scale = {paradiso::Vector2::make( 85.0f / WINDOW_WIDTH, 42.0f / WINDOW_HEIGHT)}}, - // tapRight.png paradiso::Sprite{ - .bitmap = paradiso::BitmapIO::get().load("PNG/UI/tapRight.png"), - .pivot = {paradiso::Vector2::make(-0.5f, 0.0f)}, + .bitmap = sprite_map["tap_sign.right"], + .pivot = {paradiso::Vector2::make(0.5f, 0.0f)}, // The image's size is 85x42 px. .scale = {paradiso::Vector2::make( 85.0f / WINDOW_WIDTH, 42.0f / WINDOW_HEIGHT)}}, }; tap_sprites = { - // tap.png paradiso::Sprite{ - .bitmap = paradiso::BitmapIO::get().load("PNG/UI/tap.png"), + .bitmap = sprite_map["tap.normal"], .pivot = {paradiso::Vector2::make(0.0f, -0.5f)}, // The image's size is 59x59 px. .scale = {paradiso::Vector2::make( 59.0f / WINDOW_WIDTH, 59.0f / WINDOW_HEIGHT)}}, - // tapTick.png paradiso::Sprite{ - .bitmap = paradiso::BitmapIO::get().load("PNG/UI/tapTick.png"), + .bitmap = sprite_map["tap.tick"], .pivot = {paradiso::Vector2::make(0.0f, -0.5f)}, // The image's size is 59x59 px. .scale = {paradiso::Vector2::make( @@ -469,19 +506,16 @@ struct StartText { } }; -struct GameOverText { +struct GameOverUI { paradiso::Sprite sprite; paradiso::Renderer renderer{}; - paradiso::Bitmap image = - paradiso::BitmapIO::get().load("PNG/UI/textGameOver.png"); - - void init() { + void init(SpriteMap& sprite_map) { // The image's size is 412x78 px. float scale_x = 412.0f / WINDOW_WIDTH; float scale_y = 78.0f / WINDOW_HEIGHT; - sprite = {.bitmap = image, + sprite = {.bitmap = sprite_map["game_over_ui.text"], .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, .scale = {paradiso::Vector2::make(scale_x, scale_y)}}; } @@ -500,155 +534,200 @@ struct GameOverText { enum class GameState { Start, Playing, GameOver }; -auto main() -> int { - std::srand(std::time(nullptr)); +struct App { + SpriteMap sprites; - auto canvas_size = paradiso::Size{.width = WINDOW_WIDTH * WINDOW_SCALE, - .height = WINDOW_HEIGHT * WINDOW_SCALE}; + static auto create() -> App { + auto app = App{}; - // Unser Fenster, auf das wir rendern. - auto window = paradiso::Window(); - window - .set_size(canvas_size) // ... Größe - .set_position(paradiso::Point{.x = 200, .y = 200}) // ... Position - .set_title("ParadiSO.TappyPlane") // ... Titel - .set_visible(true); // ... und jetzt anzeigen! + paradiso::BitmapIO::get().set_path( + paradiso::get_executable_path().parent_path().string() + "/assets"); - // Der Fenster Context. - auto context = paradiso::Context{}; + auto assets = std::array{ + SpriteName{"background", "PNG/background.png"}, + SpriteName{"ground", "PNG/groundGrass.png"}, + SpriteName{"rocks.top", "PNG/rockGrassDown.png"}, + SpriteName{"rocks.bottom", "PNG/rockGrass.png"}, + SpriteName{"plane.1", "PNG/Planes/planeRed1.png"}, + SpriteName{"plane.2", "PNG/Planes/planeRed2.png"}, + SpriteName{"plane.3", "PNG/Planes/planeRed3.png"}, + SpriteName{"tap_sign.left", "PNG/UI/tapRight.png"}, + SpriteName{"tap_sign.right", "PNG/UI/tapLeft.png"}, + SpriteName{"tap.normal", "PNG/UI/tap.png"}, + SpriteName{"tap.tick", "PNG/UI/tapTick.png"}, + SpriteName{"start_ui.text", "PNG/UI/textGetReady.png"}, + SpriteName{"game_over_ui.text", "PNG/UI/textGameOver.png"}, + }; - // Ein Shader (Schattierungsprogramm). - auto shader = paradiso::Shader{}; + for (const auto& [name, filename] : assets) { + std::print("{} : {} -> ", name, filename); - // Wir nutzen einen vorgefertigten Shader, der speziell für Sprites ist. - 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öße eingestellt - // werden. - context.set_viewport(paradiso::Rectangle{ - .position = paradiso::Point{.x = 0, .y = 0}, .size = canvas_size}); - - // Conflower blue - context.set_clearcolor(paradiso::RGBA::from_rgb(0x64, 0x95, 0xED)); - - // Der Asset Loader bekommt den Pfad. - paradiso::BitmapIO::get().set_path("assets"); - - // Wir initialisieren unsere Sachen. - auto game_state = GameState::Start; - - auto background = Background{}; - auto ground = Ground{}; - auto rocks1 = Rocks{}; - auto rocks2 = Rocks{}; - auto plane = Plane{}; - auto start_text = StartText{}; - auto game_over_text = GameOverText{}; - - background.init(); - ground.init(); - rocks1.init(1.2f, true); // We don't need to set this to true, - rocks2.init(2.4f, true); // but why not? - plane.init(); - start_text.init(); - game_over_text.init(); - - auto top_aabb = - AABB{.position = {paradiso::Vector2::make(-1.0f, 1.0f)}, - .size = {paradiso::Vector2::make(2.0f, 0.5f)}}; - - while (window.update([&](paradiso::Window& window) -> bool { - auto t1 = std::chrono::high_resolution_clock::now(); - - if (window.keyboard_input().size() && - window.keyboard_input().top().key == 'Q') { - // Update-Loop beenden. - return false; + auto bitmap = paradiso::BitmapIO::get().load(filename); + app.sprites[name] = bitmap; } - if (game_state == GameState::Start) { - if (window.keyboard_input().size() && - window.keyboard_input().top().key == ACTION_KEY && - window.keyboard_input().top().action == 1) { - game_state = GameState::Playing; + return app; + } - // Make the plane jump for the first time. - // @ToDo: The plane jumps too high because there was no gravity - // before. - plane.update(window.keyboard_input()); - } + void run() { + auto canvas_size = + paradiso::Size{.width = WINDOW_WIDTH * WINDOW_SCALE, + .height = WINDOW_HEIGHT * WINDOW_SCALE}; - background.update(); - ground.update(); - start_text.update(); - } else if (game_state == GameState::Playing) { - background.update(); - ground.update(); + // Unser Fenster, auf das wir rendern. + auto window = paradiso::Window(); + window + .set_size(canvas_size) // ... Größe + .set_position(paradiso::Point{.x = 200, .y = 200}) // ... Position + .set_title("ParadiSO.TappyPlane") // ... Titel + .set_visible(true); // ... und jetzt anzeigen! - rocks1.update(); - rocks2.update(); + // Der Fenster Context. + auto context = paradiso::Context{}; - plane.update(window.keyboard_input()); + // Ein Shader (Schattierungsprogramm). + auto shader = paradiso::Shader{}; - background.scrolling_speed *= SCROLLING_SPEED_INC_FACTOR; - ground.scrolling_speed *= SCROLLING_SPEED_INC_FACTOR; - rocks1.scrolling_speed *= SCROLLING_SPEED_INC_FACTOR; - rocks2.scrolling_speed *= SCROLLING_SPEED_INC_FACTOR; - - if (plane.aabb.is_colliding_with(rocks1.top_aabb) || - plane.aabb.is_colliding_with(rocks1.bottom_aabb) || - plane.aabb.is_colliding_with(rocks2.top_aabb) || - plane.aabb.is_colliding_with(rocks2.bottom_aabb) || - plane.aabb.is_colliding_with(top_aabb) || - plane.aabb.is_colliding_with(ground.aabb)) { - game_state = GameState::GameOver; - } - } else if (game_state == GameState::GameOver) { - if (window.keyboard_input().size() && - window.keyboard_input().top().key == ACTION_KEY) { - - // Reset everything. - background.init(); - ground.init(); - rocks1.init(1.2f, true); - rocks2.init(2.4f, true); - plane.init(); - game_state = GameState::Start; - } - } + // Wir nutzen einen vorgefertigten Shader, der speziell für Sprites ist. + 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öße eingestellt + // werden. context.set_viewport(paradiso::Rectangle{ .position = paradiso::Point{.x = 0, .y = 0}, .size = canvas_size}); - context.clear(); - background.draw(shader); + // Conflower blue + context.set_clearcolor(paradiso::RGBA::from_rgb(0x64, 0x95, 0xED)); - plane.draw(shader); + // Der Asset Loader bekommt den Pfad. + paradiso::BitmapIO::get().set_path("assets"); - rocks1.draw(shader); - rocks2.draw(shader); + // Wir initialisieren unsere Sachen. + auto game_state = GameState::Start; - ground.draw(shader); + auto background = Background{}; + auto ground = Ground{}; + auto rocks1 = Rocks{}; + auto rocks2 = Rocks{}; + auto plane = Plane{}; + auto start_ui = StartUI{}; + auto game_over_ui = GameOverUI{}; - // Kinda dumb that we check the game state here the second time but I - // like to seperate 'update' from 'draw'. - if (game_state == GameState::Start) { - start_text.draw(shader); - } else if (game_state == GameState::GameOver) { - game_over_text.draw(shader); - } + background.init(sprites); + ground.init(sprites); + rocks1.init(sprites, 1.2f); // We don't need to set this to true, + rocks2.init(sprites, 2.4f); // but why not? + plane.init(sprites); + start_ui.init(sprites); + game_over_ui.init(sprites); - // Einen kurzen Moment warten, um auf 60 FPS zu kommen. - 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); + auto top_aabb = + AABB{.position = {paradiso::Vector2::make(-1.0f, 1.0f)}, + .size = {paradiso::Vector2::make(2.0f, 0.5f)}}; - return true; - })) { - }; + while (window.update([&](paradiso::Window& window) -> bool { + auto t1 = std::chrono::high_resolution_clock::now(); + if (window.keyboard_input().size() && + window.keyboard_input().top().key == 'Q') { + // Update-Loop beenden. + return false; + } + + if (game_state == GameState::Start) { + if (window.keyboard_input().size() && + window.keyboard_input().top().key == ACTION_KEY && + window.keyboard_input().top().action == 1) { + game_state = GameState::Playing; + + // Make the plane jump for the first time. + // @ToDo: The plane jumps too high because there was no + // gravity before. + plane.update(window.keyboard_input()); + } + + background.update(); + ground.update(); + start_ui.update(); + } else if (game_state == GameState::Playing) { + background.update(); + ground.update(); + + rocks1.update(); + rocks2.update(); + + plane.update(window.keyboard_input()); + + // @ToDo: Should we really do this here? + background.scrolling_speed *= SCROLLING_SPEED_INC_FACTOR; + ground.scrolling_speed *= SCROLLING_SPEED_INC_FACTOR; + rocks1.scrolling_speed *= SCROLLING_SPEED_INC_FACTOR; + rocks2.scrolling_speed *= SCROLLING_SPEED_INC_FACTOR; + + if (plane.aabb.is_colliding_with(rocks1.top_aabb) || + plane.aabb.is_colliding_with(rocks1.bottom_aabb) || + plane.aabb.is_colliding_with(rocks2.top_aabb) || + plane.aabb.is_colliding_with(rocks2.bottom_aabb) || + plane.aabb.is_colliding_with(top_aabb) || + plane.aabb.is_colliding_with(ground.aabb)) { + game_state = GameState::GameOver; + } + } else if (game_state == GameState::GameOver) { + if (window.keyboard_input().size() && + window.keyboard_input().top().key == ACTION_KEY) { + + // Reset everything. + background.reset(); + ground.reset(); + rocks1.reset(); + rocks2.reset(); + plane.reset(); + game_state = GameState::Start; + } + } + + context.set_viewport( + paradiso::Rectangle{.position = paradiso::Point{.x = 0, .y = 0}, + .size = canvas_size}); + context.clear(); + + background.draw(shader); + + plane.draw(shader); + + rocks1.draw(shader); + rocks2.draw(shader); + + ground.draw(shader); + + // Kinda dumb that we check the game state here the second time but + // I like to seperate 'update' from 'draw'. + if (game_state == GameState::Start) { + start_ui.draw(shader); + } else if (game_state == GameState::GameOver) { + game_over_ui.draw(shader); + } + + // Einen kurzen Moment warten, um auf 60 FPS zu kommen. + 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); + + return true; + })) { + }; + } +}; + +} // namespace TappyPlane + +auto main() -> int { + std::srand(std::time(nullptr)); + auto app = TappyPlane::App::create(); + app.run(); return 0; }