diff --git a/tutorials/flappy-bird/step_7/CMakeLists.txt b/tutorials/flappy-bird/step_7/CMakeLists.txt new file mode 100644 index 00000000..3336581e --- /dev/null +++ b/tutorials/flappy-bird/step_7/CMakeLists.txt @@ -0,0 +1,78 @@ +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") + if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + message(FATAL_ERROR "Only Clang is supported (minimum LLVM 8.0)") + endif() +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_7/assets/config/game.config.maker.json b/tutorials/flappy-bird/step_7/assets/config/game.config.maker.json new file mode 100644 index 00000000..6690ed44 --- /dev/null +++ b/tutorials/flappy-bird/step_7/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_7/assets/fonts/sansation.ttf b/tutorials/flappy-bird/step_7/assets/fonts/sansation.ttf new file mode 100644 index 00000000..d85fbc81 Binary files /dev/null and b/tutorials/flappy-bird/step_7/assets/fonts/sansation.ttf differ diff --git a/tutorials/flappy-bird/step_7/assets/textures/player.png b/tutorials/flappy-bird/step_7/assets/textures/player.png new file mode 100644 index 00000000..1606cfae Binary files /dev/null and b/tutorials/flappy-bird/step_7/assets/textures/player.png differ diff --git a/tutorials/flappy-bird/step_7/data/linux/komodo_icon.png b/tutorials/flappy-bird/step_7/data/linux/komodo_icon.png new file mode 100644 index 00000000..89e5dd28 Binary files /dev/null and b/tutorials/flappy-bird/step_7/data/linux/komodo_icon.png differ diff --git a/tutorials/flappy-bird/step_7/data/linux/org.antara.gaming.sfml.flappybird.appdata.xml b/tutorials/flappy-bird/step_7/data/linux/org.antara.gaming.sfml.flappybird.appdata.xml new file mode 100644 index 00000000..7e6a1046 --- /dev/null +++ b/tutorials/flappy-bird/step_7/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_7/data/linux/org.antara.gaming.sfml.flappybird.desktop b/tutorials/flappy-bird/step_7/data/linux/org.antara.gaming.sfml.flappybird.desktop new file mode 100644 index 00000000..d525c088 --- /dev/null +++ b/tutorials/flappy-bird/step_7/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_7/data/osx/Packaging_CMakeDMGBackground.tif b/tutorials/flappy-bird/step_7/data/osx/Packaging_CMakeDMGBackground.tif new file mode 100644 index 00000000..91c4b130 Binary files /dev/null and b/tutorials/flappy-bird/step_7/data/osx/Packaging_CMakeDMGBackground.tif differ diff --git a/tutorials/flappy-bird/step_7/data/osx/Packaging_CMakeDMGSetup.scpt b/tutorials/flappy-bird/step_7/data/osx/Packaging_CMakeDMGSetup.scpt new file mode 100644 index 00000000..2bea22d8 --- /dev/null +++ b/tutorials/flappy-bird/step_7/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_7/data/osx/kmd_logo.icns b/tutorials/flappy-bird/step_7/data/osx/kmd_logo.icns new file mode 100644 index 00000000..5977224d Binary files /dev/null and b/tutorials/flappy-bird/step_7/data/osx/kmd_logo.icns differ diff --git a/tutorials/flappy-bird/step_7/data/osx/sfml_flappybird_install.cmake b/tutorials/flappy-bird/step_7/data/osx/sfml_flappybird_install.cmake new file mode 100644 index 00000000..2bda51ae --- /dev/null +++ b/tutorials/flappy-bird/step_7/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_7/flappy-bird.cpp b/tutorials/flappy-bird/step_7/flappy-bird.cpp new file mode 100644 index 00000000..3253a6ea --- /dev/null +++ b/tutorials/flappy-bird/step_7/flappy-bird.cpp @@ -0,0 +1,618 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// For convenience +using namespace antara::gaming; +using namespace std::string_literals; + +// Constants +struct flappy_bird_constants { + // UI + const unsigned long long font_size{32ull}; + + // Player + const float player_pos_x{400.0f}; + const float gravity{2000.f}; + const float jump_force{650.f}; + const float rotate_speed{100.f}; + const float max_angle{60.f}; + + // Pipes + const float gap_height{265.f}; + const float column_start_distance{700.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}}; +}; + +// Random number generator +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); + } +} + +// A Flappy Bird column which has two pipes +struct pipe { + entt::entity body{entt::null}; + entt::entity cap{entt::null}; + + // Destroy pipe + void destroy(entt::registry ®istry) { + registry.destroy(body); + registry.destroy(cap); + } +}; + +// Column is made of two pipes +struct column { + // Entities representing the Flappy Bird pipes + pipe top_pipe{entt::null}; + pipe bottom_pipe{entt::null}; + + // Destroy pipes and this column + void destroy(entt::registry ®istry, entt::entity entity) { + top_pipe.destroy(registry); + bottom_pipe.destroy(registry); + registry.destroy(entity); + } +}; + +// Logic functions +namespace { + void tag_game_scene(entt::registry ®istry, entt::entity entity, bool dynamic = false) { + // Tag game scene + registry.assign>(entity); + + // Tag dynamic + if(dynamic) registry.assign>(entity); + } + + // Returns a random gap start position Y + float get_random_gap_start_pos(const 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 functions +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); + tag_game_scene(registry, cap, true); + tag_game_scene(registry, body, true); + + // Construct a pipe with body and cap and return it + return {body, cap}; + } + + // 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); + tag_game_scene(registry, entity_column, true); + } + + // Factory for creating a Flappy Bird columns + void create_columns(entt::registry ®istry) noexcept { + // Retrieve constants + const auto constants = registry.ctx(); + + // Spawn columns out of the screen, out of the canvas + const float column_pos_offset = constants.column_start_distance + 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); + tag_game_scene(registry, 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); + tag_game_scene(registry, 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); + tag_game_scene(registry, ground); + } + } + + // Factory for creating the player + entt::entity create_player(entt::registry ®istry) { + // Retrieve constants + const auto[_, 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{constants.player_pos_x, canvas_height * 0.5f}); + registry.assign>(entity); + registry.assign>(entity); + tag_game_scene(registry, entity, true); + + return entity; + } +} + +// Column Logic System +class column_logic final : public ecs::logic_update_system { +public: + explicit column_logic(entt::registry ®istry) noexcept : system(registry) {} + + // Update, this will be called every tick + void update() noexcept final { + auto ®istry = 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 retrieve column position x + float column_pos_x = move_pipe(registry, col.top_pipe); + move_pipe(registry, col.bottom_pipe); + + // If column is out of the screen + if (column_pos_x < -constants.column_distance) { + // 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 furthest pipe's position X + float furthest_pipe_position(entt::registry ®istry) { + float furthest = 0.f; + + for (auto entity : registry.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 the x position + float 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.replace(pipe.body, new_pos_x, pos.y()); + + // Set cap position too + auto cap_pos = registry.get(pipe.cap); + registry.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; + } +}; + +// Name this system +REFL_AUTO (type(column_logic)); + +// Player Logic System +class player_logic final : public ecs::logic_update_system { +public: + player_logic(entt::registry ®istry, entt::entity player_) noexcept : system(registry), player_(player_) {} + + // Update, this will be called every tick + void update() noexcept final { + auto ®istry = entity_registry_; + + // Retrieve constants + const auto constants = registry.ctx(); + + // Get current position of the player + 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_tapped = input::virtual_input::is_tapped("jump"); + + // If jump is tapped, jump by adding jump force to the movement speed Y + if (jump_key_tapped) movement_speed_.set_y(-constants.jump_force); + + // Add movement speed to position to make the character move, but apply over time with delta time + pos += movement_speed_ * timer::time_step::get_fixed_delta_time(); + + // Do not let player to go out of the screen to top + if (pos.y() <= 0.f) { + pos.set_y(0.f); + movement_speed_.set_y(0.f); + } + + // Set the new position value + registry.replace(player_, pos); + + // ROTATION + // Retrieve props of the player + auto &props = registry.get(player_); + + // Increase the rotation a little by applying delta time + float new_rotation = props.rotation + constants.rotate_speed * timer::time_step::get_fixed_delta_time(); + + // If jump button is tapped, reset rotation, + // If rotation is higher than the max angle, set it to max angle + if (jump_key_tapped) + new_rotation = 0.f; + else if (props.rotation > constants.max_angle) + new_rotation = constants.max_angle; + + // Set the properties + registry.replace(player_, transform::properties{.rotation = new_rotation}); + } + +private: + entt::entity player_; + math::vec2f movement_speed_{0.f, 0.f}; +}; + +// Name this system +REFL_AUTO (type(player_logic)); + +// Collision Logic System +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_) {} + // Update, this will be called every tick + void update() noexcept final { + auto ®istry = entity_registry_; + + // Do not check anything if player is already dead + if (player_died_) return; + + // Check collision + check_player_pipe_collision(registry); + } + +private: + // Loop all columns to check collisions between player and the pipes + void check_player_pipe_collision(entt::registry ®istry) { + 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; + } + } + } + + entt::entity player_; + bool &player_died_; +}; + +// Name this system +REFL_AUTO (type(collision_logic)); + +// Game Scene +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 everything + 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 player requested to start the game + check_start_game_request(); + + // Check if player died + check_death(); + + // Check if player requested reset after death + check_reset_request(); + } + + // Check if start game is requested at the pause state + void check_start_game_request() { + // If game is not started yet and jump key is tapped + if (!started_playing_ && input::virtual_input::is_tapped("jump")) { + // Game starts, player started playing + started_playing_ = true; + resume_physics(); + } + } + + // Check if player died + void check_death() { + // If player died, game over, and pause physics + if (player_died_) { + player_died_ = false; + game_over_ = true; + pause_physics(); + } + } + + // Check if reset is requested at game over state + void check_reset_request() { + // If game is over, and jump key is pressed, reset game + if (game_over_ && input::virtual_input::is_tapped("jump")) reset_game(); + } + + // Initialize dynamic objects, this function is called at start and resets + void init_dynamic_objects(entt::registry ®istry) { + create_columns(registry); + + // Create player + auto player = create_player(registry); + + // Create logic systems + create_logic_systems(player); + + // Disable physics and everything at start to pause the game + pause_physics(); + + // Reset state variables + reset_state_variables(); + } + + // Create logic systems + void create_logic_systems(entt::entity player) { + system_manager_.create_system(); + system_manager_.create_system(player); + system_manager_.create_system(player, player_died_); + } + + // Reset state values + void reset_state_variables() { + started_playing_ = false; + player_died_ = false; + game_over_ = false; + } + + // Pause physics + void pause_physics() { + system_manager_.disable_systems(); + } + + // Resume physics + void resume_physics() { + system_manager_.enable_systems(); + } + + // Destroy dynamic objects + void destroy_dynamic_objects() { + // 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(); + } + + // Reset game + void reset_game() { + // Destroy all dynamic objects + destroy_dynamic_objects(); + + // Queue reset to reinitialize + this->need_reset_ = true; + } + + // Post update + void post_update() noexcept final { + // If reset is requested + if (need_reset_) { + // Reinitialize all these + init_dynamic_objects(entity_registry_); + need_reset_ = false; + } + } + + // System manager reference + ecs::system_manager &system_manager_; + + // States + bool started_playing_{false}; + bool player_died_{false}; + bool game_over_{false}; + bool need_reset_{false}; +}; + +// Game world +struct flappy_bird_world : world::app { + // Game entry point + flappy_bird_world() noexcept { + // Load the graphical system + auto &graphic_system = system_manager_.create_system(); + + // Load the resources system + entity_registry_.set(entity_registry_); + + // Load the input system with the window from the graphical system + system_manager_.create_system(graphic_system.get_window()); + + // Create virtual input system + system_manager_.create_system(); + + // Define the buttons for the jump action + input::virtual_input::create("jump", + {input::key::space, input::key::w, input::key::up}, + {input::mouse_button::left, input::mouse_button::right}); + + // 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 the world + flappy_bird_world game; + + // Run the game + return game.run(); +} \ No newline at end of file