diff --git a/tutorials/flappy-bird/step_8/CMakeLists.txt b/tutorials/flappy-bird/step_8/CMakeLists.txt new file mode 100644 index 00000000..a5122c42 --- /dev/null +++ b/tutorials/flappy-bird/step_8/CMakeLists.txt @@ -0,0 +1,76 @@ +if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + message(FATAL_ERROR "Prevented in-tree build. Please create a build directory outside of the source code and call cmake from there") +endif () + +##! Minimum version of the CMake. +cmake_minimum_required(VERSION 3.14) + +##! C++ Standard needed by the SDK is 17 +set(CMAKE_CXX_STANDARD 17) + +##! Our Project title, here flappy-bird. +project(flappy-bird DESCRIPTION "An awesome flappy-bird" LANGUAGES CXX) + +##! The SDK need's clang as main compiler. +if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + message(FATAL_ERROR "Only Clang is supported (minimum LLVM 8.0)") +endif () + +##! We will let know the SDK if our on Linux +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(LINUX TRUE) +endif () + +##! We include the module from CMake for fetching dependencies +include(FetchContent) + +##! We declare information about the dependance that we want to fetch. +FetchContent_Declare( + antara-gaming-sdk + URL https://github.com/KomodoPlatform/antara-gaming-sdk/archive/master.zip +) + +##! We set extras modules from the SDK that we want to use, here we will use the SFML module. +set(USE_SFML_ANTARA_WRAPPER ON) + +##! We fetch our dependence +FetchContent_MakeAvailable(antara-gaming-sdk) + +##! Calling this macros provided by the sdk will if you are on Apple init the environment for this OS (std::filesystem). +init_apple_env() + +##! Osx bundle icon +set(ICON) +configure_icon_osx(data/osx/kmd_logo.icns ICON kmd_logo.icns) + +##! We create the executable with the project name +add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${ICON} flappy-bird.cpp) + +##! Setting output directory +set_target_properties(${PROJECT_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/" + ) + +##! We link the SDK modules that we want to use to our executable +target_link_libraries(${PROJECT_NAME} PUBLIC antara::world antara::sfml antara::collisions) + +##! Move assets +if (WIN32) + file(COPY assets DESTINATION ${CMAKE_BINARY_DIR}/bin/) + ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory "${SFML_BINARY_DIR}/lib" "${CMAKE_BINARY_DIR}/bin/" + COMMENT "copying dlls …" + $ + ) + + ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${SFML_SOURCE_DIR}/extlibs/bin/x64/openal32.dll" "${CMAKE_BINARY_DIR}/bin/openal32.dll" + COMMENT "copying dlls …" + $ + ) +endif () + +if (APPLE) + file(COPY assets DESTINATION ${CMAKE_BINARY_DIR}/bin/${PROJECT_NAME}.app/Contents/Resources) +endif() \ No newline at end of file diff --git a/tutorials/flappy-bird/step_8/assets/config/game.config.maker.json b/tutorials/flappy-bird/step_8/assets/config/game.config.maker.json new file mode 100644 index 00000000..6690ed44 --- /dev/null +++ b/tutorials/flappy-bird/step_8/assets/config/game.config.maker.json @@ -0,0 +1 @@ +{"background_color":[0,0,0,255],"canvas_height":1080.0,"canvas_width":1920.0,"custom_canvas_height":true,"custom_canvas_width":true,"native_desktop_mode":false,"scale_mode":"fit","window_height":1080.0,"window_title":"game title","window_width":1920.0} \ No newline at end of file diff --git a/tutorials/flappy-bird/step_8/assets/textures/player.png b/tutorials/flappy-bird/step_8/assets/textures/player.png new file mode 100644 index 00000000..52dcda4a Binary files /dev/null and b/tutorials/flappy-bird/step_8/assets/textures/player.png differ diff --git a/tutorials/flappy-bird/step_8/data/linux/komodo_icon.png b/tutorials/flappy-bird/step_8/data/linux/komodo_icon.png new file mode 100644 index 00000000..89e5dd28 Binary files /dev/null and b/tutorials/flappy-bird/step_8/data/linux/komodo_icon.png differ diff --git a/tutorials/flappy-bird/step_8/data/linux/org.antara.gaming.sfml.flappybird.appdata.xml b/tutorials/flappy-bird/step_8/data/linux/org.antara.gaming.sfml.flappybird.appdata.xml new file mode 100644 index 00000000..7e6a1046 --- /dev/null +++ b/tutorials/flappy-bird/step_8/data/linux/org.antara.gaming.sfml.flappybird.appdata.xml @@ -0,0 +1,20 @@ + + org.antara.gaming.sfml.flappybird.desktop + MIT + MIT + flappy-bird + flappy-bird tutorial antara gaming sdk + +

Written in c++17

+
+ org.antara.gaming.sfml.flappybird.desktop + https://github.com/KomodoPlatform/antara-gaming-sdk + + + https://www.freedesktop.org/software/appstream/docs/images/scr-examples/geany-good.png + + + + org.antara.gaming.sfml.flappybird.desktop + +
\ No newline at end of file diff --git a/tutorials/flappy-bird/step_8/data/linux/org.antara.gaming.sfml.flappybird.desktop b/tutorials/flappy-bird/step_8/data/linux/org.antara.gaming.sfml.flappybird.desktop new file mode 100644 index 00000000..d525c088 --- /dev/null +++ b/tutorials/flappy-bird/step_8/data/linux/org.antara.gaming.sfml.flappybird.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Application +Name=flappy-bird +Exec=flappy-bird +Icon=komodo_icon +Categories=Game; \ No newline at end of file diff --git a/tutorials/flappy-bird/step_8/data/osx/Packaging_CMakeDMGBackground.tif b/tutorials/flappy-bird/step_8/data/osx/Packaging_CMakeDMGBackground.tif new file mode 100644 index 00000000..91c4b130 Binary files /dev/null and b/tutorials/flappy-bird/step_8/data/osx/Packaging_CMakeDMGBackground.tif differ diff --git a/tutorials/flappy-bird/step_8/data/osx/Packaging_CMakeDMGSetup.scpt b/tutorials/flappy-bird/step_8/data/osx/Packaging_CMakeDMGSetup.scpt new file mode 100644 index 00000000..2bea22d8 --- /dev/null +++ b/tutorials/flappy-bird/step_8/data/osx/Packaging_CMakeDMGSetup.scpt @@ -0,0 +1,57 @@ +on run argv + set image_name to item 1 of argv + + tell application "Finder" + tell disk image_name + + -- wait for the image to finish mounting + set open_attempts to 0 + repeat while open_attempts < 4 + try + open + delay 1 + set open_attempts to 5 + close + on error errStr number errorNumber + set open_attempts to open_attempts + 1 + delay 10 + end try + end repeat + delay 5 + + -- open the image the first time and save a DS_Store with just + -- background and icon setup + open + set current view of container window to icon view + set theViewOptions to the icon view options of container window + set background picture of theViewOptions to file ".background:background.tif" + set arrangement of theViewOptions to not arranged + set icon size of theViewOptions to 128 + delay 5 + close + + -- next setup the position of the app and Applications symlink + -- plus hide all the window decorationPackaging_CMakeDMGBackground.tif + open + update without registering applications + tell container window + set sidebar width to 0 + set statusbar visible to false + set toolbar visible to false + set the bounds to { 400, 100, 900, 465 } + set position of item "flappy-bird.app" to { 133, 200 } + set position of item "Applications" to { 378, 200 } + end tell + update without registering applications + delay 5 + close + + -- one last open and close so you can see everything looks correct + open + delay 5 + close + + end tell + delay 1 +end tell +end run \ No newline at end of file diff --git a/tutorials/flappy-bird/step_8/data/osx/kmd_logo.icns b/tutorials/flappy-bird/step_8/data/osx/kmd_logo.icns new file mode 100644 index 00000000..5977224d Binary files /dev/null and b/tutorials/flappy-bird/step_8/data/osx/kmd_logo.icns differ diff --git a/tutorials/flappy-bird/step_8/data/osx/sfml_flappybird_install.cmake b/tutorials/flappy-bird/step_8/data/osx/sfml_flappybird_install.cmake new file mode 100644 index 00000000..2bda51ae --- /dev/null +++ b/tutorials/flappy-bird/step_8/data/osx/sfml_flappybird_install.cmake @@ -0,0 +1,35 @@ +if (APPLE) + set_target_properties(${PROJECT_NAME} PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "${PROJECT_NAME}" + RESOURCE data/osx/${PROJECT_NAME}.icns + MACOSX_BUNDLE_ICON_FILE ${PROJECT_NAME} + MACOSX_BUNDLE_SHORT_VERSION_STRING 0.0.1 + MACOSX_BUNDLE_LONG_VERSION_STRING 0.0.1 + MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in") + add_custom_command(TARGET ${PROJECT_NAME} + POST_BUILD COMMAND + ${CMAKE_INSTALL_NAME_TOOL} -add_rpath "@executable_path/../Frameworks/" + $) +endif () + +if (APPLE) + install(TARGETS ${PROJECT_NAME} + BUNDLE DESTINATION . COMPONENT Runtime + RUNTIME DESTINATION bin COMPONENT Runtime + ) + + # Note Mac specific extension .app + set(APPS "\${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}.app") + + # Directories to look for dependencies + set(DIRS ${CMAKE_BINARY_DIR}) + + install(CODE "include(BundleUtilities) + fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\")") + + set(CPACK_GENERATOR "DRAGNDROP") + set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/data/osx/Packaging_CMakeDMGSetup.scpt") + set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_CURRENT_SOURCE_DIR}/data/osx/Packaging_CMakeDMGBackground.tif") + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") + include(CPack) +endif () \ No newline at end of file diff --git a/tutorials/flappy-bird/step_8/flappy-bird.cpp b/tutorials/flappy-bird/step_8/flappy-bird.cpp new file mode 100644 index 00000000..e460000e --- /dev/null +++ b/tutorials/flappy-bird/step_8/flappy-bird.cpp @@ -0,0 +1,522 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//! For convenience +using namespace antara::gaming; + +namespace { + std::random_device rd; // Will be used to obtain a seed for the random number engine + std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd() + float random_float(float lower, float higher) { + std::uniform_real_distribution dist(lower, higher); + return dist(gen); + } +} + +struct flappy_bird_constants { + // Controls + const input::mouse_button jump_button{input::mouse_button::left}; + // Player + const float gravity{2000.f}; + const float jump_force{500.f}; + + // Pipes + const float gap_height{200.f}; + const float column_min{0.2f}; + const float column_max{0.8f}; + const float column_thickness{100.f}; + const float column_distance{400.f}; + const std::size_t column_count{6}; + const float pipe_cap_extra_width{10.f}; + const float pipe_cap_height{50.f}; + const graphics::color pipe_color{92, 181, 61}; + const graphics::outline_color pipe_outline_color{2.0f, graphics::color{76, 47, 61}}; + const float scroll_speed{200.f}; + const std::string player_image_name{"player.png"}; + + // Background + const float ground_thickness{100.0f}; + const float grass_thickness{20.0f}; + const graphics::color background_color{82, 189, 199}; + const graphics::color ground_color{220, 209, 143}; + const graphics::color grass_color{132, 227, 90}; + const graphics::outline_color grass_outline_color{2.0f, graphics::color{76, 47, 61}}; +}; + +// A Flappy Bird column which has two pipes +struct pipe { + entt::entity body{entt::null}; + entt::entity cap{entt::null}; + + void destroy(entt::registry ®istry) { + registry.destroy(body); + registry.destroy(cap); + } +}; + +struct column { + //! Entities representing the Flappy Bird pipes + pipe top_pipe{entt::null}; + pipe bottom_pipe{entt::null}; + + void destroy(entt::registry ®istry, entt::entity entity) { + top_pipe.destroy(registry); + bottom_pipe.destroy(registry); + registry.destroy(entity); + } +}; + +//! Contains all the function that will be used for logic and factory +namespace { + // Factory for pipes, requires to know if it's a top one, position x of the column, and the gap starting position Y + pipe create_pipe(entt::registry ®istry, bool is_top, float pos_x, float gap_start_pos_y) { + // Retrieve constants + const auto canvas_height = registry.ctx().canvas.size.y(); + const auto constants = registry.ctx(); + + // PIPE BODY + // Top pipe is at Y: 0 and bottom pipe is at canvas_height, bottom of the canvas + transform::position_2d body_pos{pos_x, is_top ? 0.f : canvas_height}; + + // Size X is the column thickness, + // Size Y is the important part. + // If it's a top pipe, gap_start_pos_y should be bottom of the rectangle + // So half size should be gap_start_pos_y since center of the rectangle is at 0. + // If it's the bottom pipe, top of the rectangle will be at gap_start_pos_y + gap_height + // So half size should be canvas_height - (gap_start_pos_y + gap_height) + // Since these are half-sizes, and the position is at the screen border, we multiply these sizes by two + math::vec2f body_size{constants.column_thickness, + is_top ? + gap_start_pos_y * 2.0f : + (canvas_height - (gap_start_pos_y + constants.gap_height)) * 2.0f}; + + auto body = geometry::blueprint_rectangle(registry, body_size, constants.pipe_color, body_pos, constants.pipe_outline_color); + + // PIPE CAP + // Let's prepare the pipe cap + // Size of the cap is defined in constants + math::vec2f cap_size{constants.column_thickness + constants.pipe_cap_extra_width, constants.pipe_cap_height}; + + // Position, X is same as the body. Bottom of the cap is aligned with bottom of the body, + // or start of the gap, we will use start of the gap here, minus half of the cap height + transform::position_2d cap_pos{body_pos.x(), + is_top ? + gap_start_pos_y - constants.pipe_cap_height * 0.5f : + gap_start_pos_y + constants.gap_height + constants.pipe_cap_height * 0.5f + }; + + // Construct the cap + auto cap = geometry::blueprint_rectangle(registry, cap_size, constants.pipe_color, cap_pos, constants.pipe_outline_color); + + // Set layers, cap should be in front of body + registry.assign>(cap); + registry.assign>(body); + registry.assign>(cap); + registry.assign>(body); + registry.assign>(cap); + registry.assign>(body); + + // Construct a pipe with body and cap and return it + return {body, cap}; + } + + // Returns a random gap start position Y + float get_random_gap_start_pos(entt::registry ®istry) { + //! Retrieve constants + const auto canvas_height = registry.ctx().canvas.size.y(); + const auto constants = registry.ctx(); + + float top_limit = canvas_height * constants.column_min; + float bottom_limit = canvas_height * constants.column_max - constants.gap_height; + + return random_float(top_limit, bottom_limit); + } + + // Factory to create single column + void create_column(entt::registry ®istry, float pos_x) noexcept { + // Create a fresh entity for a new column + auto entity_column = registry.create(); + + // Get a random gap start position Y, between pipes + float gap_start_pos_y = get_random_gap_start_pos(registry); + + // Create pipes, is_top variable is false for bottom one + auto top_pipe = create_pipe(registry, true, pos_x, gap_start_pos_y); + auto bottom_pipe = create_pipe(registry, false, pos_x, gap_start_pos_y); + + // Make a column from these two pipes and mark it as "column" + registry.assign(entity_column, top_pipe, bottom_pipe); + registry.assign>(entity_column); + registry.assign>(entity_column); + registry.assign>(entity_column); + } + + //! Factory for creating a Flappy Bird columns + void create_columns(entt::registry ®istry) noexcept { + //! Retrieve constants + const auto canvas_width = registry.ctx().canvas.size.x(); + const auto constants = registry.ctx(); + + // Spawn columns out of the screen, out of the canvas + const float column_pos_offset = canvas_width + constants.column_thickness * 2.0f; + + // Create the columns + for(std::size_t i = 0; i < constants.column_count; ++i) { + // Horizontal position (X) increases for every column, keeping the distance + float pos_x = column_pos_offset + i * constants.column_distance; + + create_column(registry, pos_x); + } + } + + //! Factory for creating a Flappy Bird background + void create_background(entt::registry ®istry) noexcept { + //! Retrieve constants + const auto[canvas_width, canvas_height] = registry.ctx().canvas.size; + const auto constants = registry.ctx(); + + // Create Sky + { + // Sky is whole canvas so position is middle of it + transform::position_2d pos{canvas_width * 0.5f, canvas_height * 0.5f}; + + // And the size is full canvas + math::vec2f size{canvas_width, canvas_height}; + + auto sky = geometry::blueprint_rectangle(registry, size, constants.background_color, pos); + registry.assign>(sky); + registry.assign>(sky); + } + + // Create Grass + { + // Ground expands to whole canvas width so position is middle of it, + // But position Y is at top of the ground, so it's canvas height minus ground thickness + transform::position_2d pos{canvas_width * 0.5f, canvas_height - constants.ground_thickness}; + + // Size X is full canvas but the height is defined in constants + // We also make it a bit longer by adding the thickness of the outline to hide the outline at sides + math::vec2f size{canvas_width + constants.grass_outline_color.thickness * 2.0f, constants.grass_thickness}; + + auto grass = geometry::blueprint_rectangle(registry, size, constants.grass_color, pos, constants.grass_outline_color); + registry.assign>(grass); + registry.assign>(grass); + } + + // Create Ground + { + // Ground expands to whole canvas width so position is middle of it, + // But position Y is at bottom of the screen so it's full canvas_height minus half of the ground thickness + transform::position_2d pos{canvas_width * 0.5f, canvas_height - constants.ground_thickness * 0.5f}; + + // Size X is full canvas but the height is defined in constants + math::vec2f size{canvas_width, constants.ground_thickness}; + + auto ground = geometry::blueprint_rectangle(registry, size, constants.ground_color, pos); + registry.assign>(ground); + registry.assign>(ground); + } + } + + //! Factory for creating the player + entt::entity create_player(entt::registry ®istry) { + //! Retrieve constants + const auto [canvas_width, canvas_height] = registry.ctx().canvas.size; + const auto constants = registry.ctx(); + + auto entity = graphics::blueprint_sprite(registry, + graphics::sprite{constants.player_image_name.c_str()}, + transform::position_2d{canvas_width * 0.2f, canvas_height * 0.2f}); + registry.assign>(entity); + registry.assign>(entity); + registry.assign>(entity); + registry.assign>(entity); + + return entity; + } +} + +class column_logic final : public ecs::logic_update_system { +public: + explicit column_logic(entt::registry ®istry) noexcept : system(registry) { } + + void update() noexcept final { + auto& registry = entity_registry_; + + //! Retrieve constants + const auto constants = registry.ctx(); + + // Loop all columns + for(auto entity : registry.view()) { + auto& col = registry.get(entity); + + // Move pipes, and check if they are out of the screen + bool col_out_of_screen = move_pipe(registry, col.top_pipe) || move_pipe(registry, col.bottom_pipe); + + // If column is out of the screen + if(col_out_of_screen) { + // Remove this column + col.destroy(registry, entity); + + // Create a new column at far end + create_column(registry, furthest_pipe_position(registry) + constants.column_distance); + } + } + } + +private: + // Find the most far pipe's position X + float furthest_pipe_position(entt::registry ®istry) { + float furthest = 0.f; + + auto view = registry.view(); + + for(auto entity : view) { + auto& col = registry.get(entity); + float x = entity_registry_.get(col.top_pipe.body).x(); + if(x > furthest) furthest = x; + } + + return furthest; + } + + // Move the pipe and return if it's out of the screen + bool move_pipe(entt::registry ®istry, pipe& pipe) { + // Retrieve constants + const auto constants = registry.ctx(); + + // Get current position of the pipe + auto pos = registry.get(pipe.body); + + // Shift pos X to left by scroll_speed but multiplying with dt because we do this so many times a second, + // Delta time makes sure that it's applying over time, so in one second it will move scroll_speed pixels + auto new_pos_x = pos.x() - constants.scroll_speed * timer::time_step::get_fixed_delta_time(); + + // Set the new position value + registry.assign_or_replace(pipe.body, new_pos_x, pos.y()); + + // Set cap position too + auto cap_pos = registry.get(pipe.cap); + registry.assign_or_replace(pipe.cap, new_pos_x, cap_pos.y()); + + // Return the info about if this pipe is out of the screen + return new_pos_x < -constants.column_thickness * 2.0f; + } +}; + +//! Give a name to our system +REFL_AUTO(type(column_logic)); + +class player_logic final : public ecs::logic_update_system { +public: + player_logic(entt::registry ®istry, entt::entity player_) noexcept : system(registry), player(player_) { } + + void update() noexcept final { + auto& registry = entity_registry_; + + //! Retrieve constants + const auto constants = registry.ctx(); + + // Get current position of the pipe + auto pos = registry.get(player); + + // Add gravity to movement speed, multiply with delta time to apply it over time + movement_speed.set_y(movement_speed.y() + constants.gravity * timer::time_step::get_fixed_delta_time()); + + // Check if jump key is tapped + bool jump_key_pressed = input::is_mouse_button_pressed(constants.jump_button); + bool jump_key_tapped = jump_key_pressed && !jump_key_pressed_last_tick; + jump_key_pressed_last_tick = jump_key_pressed; + + // If jump is tapped, add jump force to the movement speed + if (jump_key_tapped) movement_speed.set_y(-constants.jump_force); + + // Add movement speed to position, but apply over time with delta time + pos += movement_speed * timer::time_step::get_fixed_delta_time(); + + // Set the new position value + registry.assign_or_replace(player, pos); + } + +private: + entt::entity player; + math::vec2f movement_speed{0.f, 0.f}; + bool jump_key_pressed_last_tick = false; +}; + +//! Give a name to our system +REFL_AUTO(type(player_logic)); + +class collision_logic final : public ecs::logic_update_system { +public: + collision_logic(entt::registry ®istry, entt::entity player_, bool& player_died_) noexcept : system(registry), player(player_), player_died(player_died_) { } + + void update() noexcept final { + auto& registry = entity_registry_; + + // Do not check anything if player is already dead + if(player_died) return; + + // Loop all columns to check collisions with the pipes + for(auto entity : registry.view>()) { + // Check collision between player and a collidable object + if(collisions::basic_collision_system::query_rect(registry, player, entity)) { + // Mark player died as true + player_died = true; + } + } + } + +private: + entt::entity player; + bool& player_died; +}; + +//! Give a name to our system +REFL_AUTO(type(collision_logic)); + +class game_scene final : public scenes::base_scene { +public: + game_scene(entt::registry ®istry, ecs::system_manager& system_manager_) noexcept : base_scene(registry), system_manager(system_manager_) { + //! Set the constants that will be used in the program + registry.set(); + + //! Create the columns + create_background(registry); + init_dynamic_objects(registry); + } + + //! Scene name + std::string scene_name() noexcept final { + return "game_scene"; + } + +private: + //! Update the game every tick + void update() noexcept final { + // Retrieve constants + const auto constants = entity_registry_.ctx(); + + // Check if jump key is tapped + bool jump_key_pressed = input::is_mouse_button_pressed(constants.jump_button); + bool jump_key_tapped = jump_key_pressed && !jump_key_pressed_last_tick; + jump_key_pressed_last_tick = jump_key_pressed; + + // If game is not started yet and jump key is tapped + if(!started_playing && jump_key_tapped) { + // Game starts, player started playing + started_playing = true; + resume_physics(); + } + + // If player died, game over, and pause physics + if(player_died) { + player_died = false; + game_over = true; + pause_physics(); + } + + // If game is over, and jump key is pressed, reset game + if(game_over && jump_key_pressed) reset_game(); + } + + void init_dynamic_objects(entt::registry ®istry) { + create_columns(registry); + + auto player = create_player(registry); + + //! Create systems + system_manager.create_system(); + system_manager.create_system(player); + + // Disable physics and everything at start + pause_physics(); + + // Collision system + system_manager.create_system(player, player_died); + + // Reset state values + started_playing = false; + player_died = false; + game_over = false; + } + + void pause_physics() { + system_manager.disable_systems(); + } + + void resume_physics() { + system_manager.enable_systems(); + } + + void destroy_all() { + //! Retrieve the collection of entities from the game scene + auto view = entity_registry_.view>(); + + //! Iterate the collection and destroy each entities + entity_registry_.destroy(view.begin(), view.end()); + + //! Delete systems + system_manager.mark_systems(); + } + + void reset_game() { + destroy_all(); + this->need_reset = true; + } + + void post_update() noexcept final { + if(need_reset) { + //! Reinitialize all these + init_dynamic_objects(entity_registry_); + need_reset = false; + } + } + + ecs::system_manager& system_manager; + + // States + bool started_playing{false}; + bool player_died{false}; + bool game_over{false}; + bool jump_key_pressed_last_tick{false}; + bool need_reset{false}; +}; + +//! Game world +struct flappy_bird_world : world::app { + //! Game entry point + flappy_bird_world() noexcept { + //! Load our graphical system + auto &graphic_system = system_manager_.create_system(); + + //! Load our resources system + entity_registry_.set(entity_registry_); + + //! Load our input system with the window from the graphical system + system_manager_.create_system(graphic_system.get_window()); + + //! Load the scenes manager + auto &scene_manager = system_manager_.create_system(); + + //! Change the current_scene to "game_scene" by pushing it. + scene_manager.change_scene(std::make_unique(entity_registry_, system_manager_), true); + } +}; + +int main() { + //! Declare our world + flappy_bird_world game; + + //! Run the game + return game.run(); +} \ No newline at end of file