diff options
420 files changed, 20727 insertions, 3332 deletions
diff --git a/.clang-format b/.clang-format index 1ee37ec..9ebf218 100644 --- a/.clang-format +++ b/.clang-format @@ -24,6 +24,9 @@ AlignEscapedNewlines: DontAlign BreakBeforeBinaryOperators: All AlwaysBreakTemplateDeclarations: Yes PackConstructorInitializers: CurrentLine +# only option that doesn't result in copious indentation +AlignAfterOpenBracket: BlockIndent +SpaceBeforeCpp11BracedList: true ... # vim: ft=yaml @@ -16,3 +16,4 @@ CTestTestfile.cmake _deps CMakeUserPresets.json compile.sh +asset/jetpack_joyride diff --git a/.gitmodules b/.gitmodules index bd6e7f7..8155600 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,3 +26,7 @@ path = lib/whereami/lib url = https://github.com/gpakosz/whereami shallow = true +[submodule "lib/fontconfig"] + path = lib/fontconfig + url = https://gitlab.freedesktop.org/fontconfig/fontconfig.git + shallow = true @@ -17,17 +17,21 @@ GENERATE_LATEX = NO LAYOUT_FILE = src/doc/layout.xml TAB_SIZE = 2 -HTML_INDEX_NUM_ENTRIES = 2 +HTML_INDEX_NUM_ENTRIES = 999 HTML_EXTRA_STYLESHEET = src/doc/style.css +SHOW_HEADERFILE = NO +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO -USE_MDFILE_AS_MAINPAGE = ./readme.md REPEAT_BRIEF = NO -INTERNAL_DOCS = YES -EXTRACT_PRIVATE = YES EXTRACT_STATIC = YES HIDE_UNDOC_NAMESPACES = YES HIDE_UNDOC_CLASSES = YES QUIET = YES +WARNINGS = NO +# set these to NO for user-only docs +INTERNAL_DOCS = YES +EXTRACT_PRIVATE = YES diff --git a/asset/texture/circle.png b/asset/texture/circle.png Binary files differnew file mode 100755 index 0000000..0a92ac7 --- /dev/null +++ b/asset/texture/circle.png diff --git a/asset/texture/img.png b/asset/texture/img.png Binary files differindex 43b1eca..649a3f1 100644 --- a/asset/texture/img.png +++ b/asset/texture/img.png diff --git a/asset/texture/square.png b/asset/texture/square.png Binary files differnew file mode 100755 index 0000000..d07ec98 --- /dev/null +++ b/asset/texture/square.png diff --git a/asset/texture/test_ap43.png b/asset/texture/test_ap43.png Binary files differnew file mode 100644 index 0000000..e758ed7 --- /dev/null +++ b/asset/texture/test_ap43.png diff --git a/contributing.md b/contributing.md index 77a2908..d217410 100644 --- a/contributing.md +++ b/contributing.md @@ -17,6 +17,23 @@ that you can click on to open them. working/compiling version of the project - Pull requests for new code include either automated tests for the new code or an explanation as to why the code can not (reliably) be tested +- Non-bugfix pull requests must be approved by at least 2 reviewers before being + merged +- Pull requests should have the following labels (where appropriate) + |label|meaning| + |:-:|-| + |`fix me`|has feedback that should be resolved/discussed by its author| + |`review me`|needs additional reviewers (minimum of 2 per PR)| + |`do not review`|is actively being worked on or not ready for feedback| + |`high priority`|should be worked on before all the others| + - PRs start with the `review me` label + - Reviewers— + - Add the `fix me` label after adding comments + - Authors— + - Remove the `review me` label if the pull request has enough reviewers + - Add the `do not review` label while processing feedback / pushing + additional commits + <!-- - TODO: tagging / versions --> @@ -160,40 +177,28 @@ that you can click on to open them. ``` </td></tr></table></details> - <details><summary> - <code>using namespace</code> may not be used in header files (.h, .hpp), only - in source files (.cpp). + <a href="https://en.cppreference.com/w/cpp/language/using_declaration">Using-declarations</a> + may not be used in header files (<code>.h</code>, <code>.hpp</code>), only in + source files (<code>.cpp</code>). </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> example.h: ```cpp namespace crepe { - void foo(); + std::string foo(); } ``` - example.cpp: - ```cpp - #include "example.h" - using namespace crepe; - void foo() {} - ``` </td><td> example.h: ```cpp + using namespace std; + namespace crepe { - template <typename T> - T foo(); + string foo(); } ``` - - example.hpp: - ```cpp - #include "example.h" - using namespace crepe; - template <typename T> - T foo(); - ``` </td></tr></table></details> - <details><summary> @@ -495,6 +500,12 @@ that you can click on to open them. </td></tr></table></details> - <details><summary> Ensure const-correctness + + > [!IMPORTANT] + > C-style APIs that work on (possibly internal) references to structs can be + > called from const member functions in C++. If the compiler allows you to + > mark a function as `const` even though it has side effects, it should + > **not** be marked as `const`. </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> ```cpp @@ -626,15 +637,21 @@ that you can click on to open them. </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> ```cpp + void Foo::bar() { } + void Foo::set_value(int value) { this->value = value; + this->bar(); } ``` </td><td> ```cpp + void Foo::bar() { } + void Foo::set_value(int new_value) { value = new_value; + bar(); } ``` </td></tr></table></details> @@ -795,6 +812,49 @@ that you can click on to open them. ``` </td></tr></table></details> - Do not implement new classes as singletons +- <details><summary> + Retrieving the first or last indices for iterators with a known or expected + size should be done using <code>.front()</code> or <code>.back()</code> + instead of by index + </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + + ```cpp + vector<int> foo = { 1, 2, 3 }; + int bar = foo.first(); + ``` + </td><td> + + ```cpp + vector<int> foo = { 1, 2, 3 }; + int bar = foo[0]; + ``` + </td></tr></table></details> +- <details><summary> + Always explicitly check against <code>NULL</code> (for C APIs) or + <code>nullptr</code> (for C++ APIs) when checking if a pointer is valid + </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + + ```cpp + string foo = "Hello world"; + if (foo.c_str() == nullptr) + // ... + + void * bar = malloc(); + if (bar == NULL) + // ... + ``` + </td><td> + + ```cpp + string foo = "Hello world"; + if (!foo.c_str()) + // ... + + void * bar = malloc(); + if (!bar) + // ... + ``` + </td></tr></table></details> ## CMakeLists-specific @@ -823,6 +883,11 @@ that you can click on to open them. parameter of `TEST()` / `TEST_F()` macro) - Test source files match their suite name (or test fixture name in the case of tests that use a fixture) +- Tests that measure time or use delays must be [disabled][gtest-disable] (by + prepending `DISABLED_` to the suite or case name). + + These tests will still be compiled, but will only run when the `test_main` + binary is run with the `--gtest_also_run_disabled_tests` flag. # Structure @@ -842,6 +907,8 @@ that you can click on to open them. # Documentation +[Doxygen commands](https://www.doxygen.nl/manual/commands.html) + - All documentation is written in U.S. English - <details><summary> Doxygen commands are used with a backslash instead of an at-sign. @@ -927,6 +994,52 @@ that you can click on to open them. Foo & operator=(Foo &&) = delete; ``` </td></tr></table></details> +- Do not use markdown headings in Doxygen + +## Documenting features + +Engine features are small 'building blocks' that the user (game developer) may +reference when building a game with the engine. Features do not necessarily map +1-1 to engine components or systems. If a component or system has a single, +distinct feature it should be named after that feature, not the component or +system itself. + +The sources for these pages are located under `src/doc/feature/`, and have the +following format: + +- A feature description which explains— + - the purpose and function of the feature (focus on what it enables or + achieves for the user) + - additional information about when to implement the feature, such as specific + use cases or scenarios +- A list of 'see also' references to relevant classes and/or types +- A **minimal** example to demonstrate how the feature is used. The example + should be written such that the following is clear to the reader: + - Which headers need to be included to utilize the feature + - *Why* the example works, not what is happening in the example + - Where is this code supposed to be called (e.g. inside scene/script + functions) + - Which restrictions should be kept in mind (e.g. copy/move semantics, max + component instances, speed considerations) + +Features should be documented as clear and concise as possible, so the following +points should be kept in mind: + +- <details><summary> + If a page expands on an example from another page, directly reference the + other page using a cross-reference (`\ref`) in a `\note` block at the top of + the page. + </summary> + + ``` + \note This page builds on top of the example shown in \ref feature_script + ``` + </details> +- When explaining the usage of specific functions, qualify them such that + Doxygen is able to add a cross-reference or manually add a reference using the + `\ref` command. +- Users will likely copy-paste examples as-is, so do this yourself to check if + the example code actually works! # Libraries @@ -934,4 +1047,8 @@ that you can click on to open them. subdirectory - When adding new submodules, please set the `shallow` option to `true` in the [.gitmodules](./.gitmodules) file +- When adding new libraries, please update the library version table in + [readme\.md](./readme.md) + +[gtest-disable]: https://google.github.io/googletest/advanced.html#temporarily-disabling-tests diff --git a/game/.crepe-root b/game/.crepe-root new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/game/.crepe-root diff --git a/game/.gitignore b/game/.gitignore new file mode 100644 index 0000000..2bd69c0 --- /dev/null +++ b/game/.gitignore @@ -0,0 +1 @@ +asset diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt new file mode 100644 index 0000000..a94beca --- /dev/null +++ b/game/CMakeLists.txt @@ -0,0 +1,124 @@ +cmake_minimum_required(VERSION 3.28) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CMAKE_BUILD_TYPE Release) +project(game C CXX) + +add_subdirectory(../src crepe) + +add_executable(main) + +target_sources(main PUBLIC + # enemy + enemy/BattleScript.cpp + enemy/EnemyPool.cpp + enemy/EnemyBulletScript.cpp + enemy/EnemyBulletSubScene.cpp + enemy/EnemyBulletPool.cpp + enemy/EnemySubScene.cpp + enemy/EnemyScript.cpp + + #background + background/AquariumSubScene.cpp + background/AquariumScript.cpp + background/BackgroundSubScene.cpp + background/ForestParallaxScript.cpp + background/ForestSubScene.cpp + background/HallwaySubScene.cpp + background/StartSubScene.cpp + background/HallwayScript.cpp + + # mainscenes + GameScene.cpp + menus/shop/ShopMenuScene.cpp + menus/shop/ShopLoadScript.cpp + menus/shop/ButtonBuySelectBubbleScript.cpp + menus/shop/ButtonBuySelectBulletScript.cpp + menus/mainmenu/MainMenuScene.cpp + PreviewScene.cpp + main.cpp + + # missile + missile/MissilePool.cpp + missile/MissileScript.cpp + missile/MissileSubScene.cpp + missile/AlertSubScene.cpp + missile/AlertScript.cpp + missile/SpawnEvent.cpp + + #scheduling + scheduler/ObjectsScheduler.cpp + + # Preview + preview/SmokeSubScene.cpp + preview/NpcSubScene.cpp + preview/NpcScript.cpp + preview/PrevPlayerSubScene.cpp + preview/PrevPlayerScript.cpp + preview/PreviewStopRecSubScript.cpp + preview/PreviewStartRecSubScript.cpp + preview/PreviewReplaySubScript.cpp + + # scripts + GameScene.cpp + MoveCameraManualyScript.cpp + StartGameScript.cpp + QuitScript.cpp + + # player + player/PlayerScript.cpp + player/PlayerSubScene.cpp + player/PlayerBulletPool.cpp + player/PlayerBulletScript.cpp + player/PlayerBulletSubScene.cpp + player/PlayerEndScript.cpp + player/PlayerAudioScript.cpp + + # workers + workers/WorkersSubScene.cpp + workers/WorkerScript.cpp + workers/PanicFromPlayerScript.cpp + workers/CollisionScript.cpp + + # menus + menus/BannerSubScene.cpp + menus/ButtonSubScene.cpp + menus/IButtonScript.cpp + menus/ButtonSetShopSubScript.cpp + menus/ButtonSetMainMenuSubScript.cpp + menus/ButtonReplaySubScript.cpp + menus/ButtonNextMainMenuSubScript.cpp + menus/FloatingWindowSubScene.cpp + menus/IFloatingWindowScript.cpp + menus/ButtonShowCreditsSubScript.cpp + menus/mainmenu/ButtonTransitionPreviewSubScript.cpp + menus/mainmenu/ITransitionScript.cpp + menus/mainmenu/TransitionStartSubScript.cpp + menus/mainmenu/CreditsSubScene.cpp + menus/mainmenu/CreditsSubScript.cpp + menus/endgame/EndGameSubScene.cpp + menus/endgame/EndGameSubScript.cpp + + # coins + coins/CoinSubScene.cpp + coins/CoinPoolSubScene.cpp + coins/CoinSystemScript.cpp + coins/CoinScript.cpp + + # hud + hud/HudSubScene.cpp + hud/HudScript.cpp + hud/SpeedScript.cpp + + #random + Random.cpp +) + +add_subdirectory(background) +add_subdirectory(prefab) + +target_link_libraries(main PUBLIC crepe) +target_include_directories(main PRIVATE .) + diff --git a/game/Config.h b/game/Config.h new file mode 100644 index 0000000..d2b5fc4 --- /dev/null +++ b/game/Config.h @@ -0,0 +1,60 @@ +#pragma once + +#include "types.h" + +static constexpr int SORT_IN_LAY_BACK_BACKGROUND = 3; // For all scenes +static constexpr int SORT_IN_LAY_BACKGROUND = 4; // For all scenes +static constexpr int SORT_IN_LAY_FORE_BACKGROUND = 5; // For all scenes +static constexpr int SORT_IN_LAY_PARTICLES_BACKGROUND = 6; // For all scenes +static constexpr int SORT_IN_LAY_COINS = 7; // Only for GameScene +static constexpr int SORT_IN_LAY_OBSTACLES = 8; // Only for GameScene +static constexpr int SORT_IN_LAY_WORKERS_BACK = 9; // Only for GameScene +static constexpr int SORT_IN_LAY_PLAYER = 10; // Only for GameScene +static constexpr int SORT_IN_LAY_WORKERS_FRONT = 12; // Only for GameScene +static constexpr int SORT_IN_LAY_PARTICLES_FOREGROUND = 15; // Only for GameScene + +static constexpr int COLL_LAY_BOT_TOP = 1; // Only for GameScene +static constexpr int COLL_LAY_BOT_LOW = 2; // Only for GameScene +static constexpr int COLL_LAY_BOT_HIGH = 3; // Only for GameScene +static constexpr int COLL_LAY_PLAYER = 4; // Only for GameScene +static constexpr int COLL_LAY_WALL_FRAGS = 5; // Only for GameScene +static constexpr int COLL_LAY_ZAPPER = 6; // Only for GameScene +static constexpr int COLL_LAY_LASER = 7; // Only for GameScene +static constexpr int COLL_LAY_MISSILE = 8; // Only for GameScene +static constexpr int COLL_LAY_BULLET = 9; // Only for GameScene +static constexpr int COLL_LAY_ENEMY = 10; // Only for GameScene +static constexpr int COLL_LAY_PLAYER_BULLET = 11; // Only for GameScene + +static constexpr float GAME_HEIGHT = 800; // In game units +static constexpr float HALLWAY_HEIGHT = 450; // In game units + +static constexpr float VIEWPORT_X = 1100; // In game units +// 'GAME_HEIGHT' (below) should be replaced by '500' when game development is finished +static constexpr float VIEWPORT_Y = 500; // In game units + +// Font settings +static constexpr const char * FONT = "Jetpackia"; +static constexpr crepe::vec2 FONTOFFSET = {0, 0}; + +// Amount of coins in game +static constexpr const char * TOTAL_COINS_GAME = "total_coins_game"; + +// Amount of coins in current run +static constexpr const char * TOTAL_COINS_RUN = "total_coins_run"; + +// Distance +static constexpr const char * DISTANCE_GAME = "distance_game"; +static constexpr const char * DISTANCE_RUN = "distance_run"; + +// Player config +static constexpr const char * PLAYER_NAME = "player"; +static constexpr int PLAYER_SPEED = 7500; // In game units +static constexpr float PLAYER_GRAVITY_SCALE = 2.2; // factor +static constexpr float PLAYER_HELP_KICK_SCALE = 0.2; // factor +static constexpr float PLAYER_HELP_KICK_MAX = 0.3; // factor + +static constexpr const char * CAMERA_NAME = "camera"; +// Jetpack particles +static constexpr const char * JETPACK_PARTICLES = "jetpack_particles"; + +static constexpr bool DISABLE_REPLAY = false; diff --git a/game/EngineConfig.h b/game/EngineConfig.h new file mode 100644 index 0000000..6a03a14 --- /dev/null +++ b/game/EngineConfig.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Config.h" + +#include <crepe/api/Config.h> + +static const crepe::Config ENGINE_CONFIG { + .log { + .level = crepe::Log::Level::DEBUG, + }, + .physics { + // this division factor is now the amount of seconds it approximately takes to naturally + // fall from the ceiling to floor + .gravity = HALLWAY_HEIGHT / 0.5, + }, + .window_settings { + .window_title = "Jetpack joyride clone", + }, +}; diff --git a/game/Events.h b/game/Events.h new file mode 100644 index 0000000..cf0be68 --- /dev/null +++ b/game/Events.h @@ -0,0 +1,5 @@ +#pragma once + +#include "api/Event.h" + +struct EndGameEvent : public crepe::Event {}; diff --git a/game/GameScene.cpp b/game/GameScene.cpp new file mode 100644 index 0000000..7803c9d --- /dev/null +++ b/game/GameScene.cpp @@ -0,0 +1,130 @@ +#include "GameScene.h" +#include "Config.h" +#include "StartGameScript.h" +#include "coins/CoinPoolSubScene.h" +#include "coins/CoinSystemScript.h" + +#include "background/BackgroundSubScene.h" +#include "enemy/BattleScript.h" +#include "enemy/EnemyBulletPool.h" +#include "enemy/EnemyBulletSubScene.h" +#include "enemy/EnemyPool.h" +#include "enemy/EnemySubScene.h" +#include "hud/HudScript.h" +#include "hud/HudSubScene.h" +#include "hud/SpeedScript.h" +#include "menus/endgame/EndGameSubScene.h" +#include "missile/MissilePool.h" +#include "missile/SpawnEvent.h" +#include "player/PlayerBulletPool.h" +#include "player/PlayerBulletSubScene.h" +#include "player/PlayerSubScene.h" +#include "prefab/ZapperPoolSubScene.h" +#include "scheduler/ObjectsScheduler.h" +#include "workers/WorkersSubScene.h" + +#include <cmath> +#include <crepe/api/AI.h> +#include <crepe/api/Animator.h> +#include <crepe/api/Asset.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void GameScene::load_scene() { + BackgroundSubScene background(*this); + + GameObject camera = new_object(CAMERA_NAME, "camera", vec2(650, 0)); + camera.add_component<Camera>( + ivec2(990, 720), vec2(VIEWPORT_X, VIEWPORT_Y), + Camera::Data { + .bg_color = Color::BLACK, + } + ); + //camera.add_component<BehaviorScript>().set_script<MoveCameraManualyScript>(); + camera.add_component<BehaviorScript>().set_script<CoinSystemScript>(); + camera.add_component<BehaviorScript>().set_script<HudScript>(); + camera.add_component<BehaviorScript>().set_script<SpeedScript>(); + camera.add_component<BehaviorScript>().set_script<BattleScript>(); + camera.add_component<BehaviorScript>().set_script<MissileSpawnEventHandler>(); + camera.add_component<BehaviorScript>().set_script<ObjectsScheduler>(); + + camera.add_component<Rigidbody>(Rigidbody::Data {}); + AI & enemy_path_1 = camera.add_component<AI>(400); + enemy_path_1.make_oval_path(100, 100, camera.transform.position, 1.5708, true); + AI & enemy_path_2 = camera.add_component<AI>(400); + enemy_path_2.make_oval_path(100, 100, {0, 0}, 1.5708, true); + AI & enemy_path_3 = camera.add_component<AI>(400); + enemy_path_3.make_oval_path(100, 100, {0, 0}, 1.5708, true); + // camer.add_component<AI> + PlayerSubScene player(*this); + MissilePool missile_pool(*this); + WorkersSubScene workers(*this); + + GameObject floor = new_object("floor", "game_world", vec2(0, 325)); + floor.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = COLL_LAY_BOT_TOP, + }); + floor.add_component<BoxCollider>(vec2(INFINITY, 200)); + GameObject floor_low = new_object("floor_low", "game_world", vec2(0, 350)); + floor_low.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = COLL_LAY_BOT_LOW, + }); + floor_low.add_component<BoxCollider>(vec2(INFINITY, 200)); + GameObject floor_high = new_object("floor_high", "game_world", vec2(0, 300)); + floor_high.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = COLL_LAY_BOT_HIGH, + }); + GameObject ceiling = new_object("ceiling", "game_world", vec2(0, -325)); + ceiling.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = COLL_LAY_BOT_TOP, + }); + ceiling.add_component<BoxCollider>(vec2(INFINITY, 200)); + + ZapperPoolSubScene {*this}; + + GameObject start_game_script = new_object("start_game_script", "script", vec2(0, 0)); + start_game_script.add_component<BehaviorScript>().set_script<StartGameScript>(); + + //create coin pool + CoinPoolSubScene coin_system; + coin_system.create_coins(*this); + EnemyBulletPool enemy_bullet_pool; + enemy_bullet_pool.create_bullets(*this); + PlayerBulletPool player_bullet_pool; + player_bullet_pool.create_bullets(*this); + EnemyPool enemy_pool; + enemy_pool.create_enemies(*this); + HudSubScene hud; + hud.create(*this); + + GameObject background_music = new_object("background_music", "audio", vec2(0, 0)); + Asset background_music_asset {"asset/music/level.ogg"}; + background_music.add_component<AudioSource>(background_music_asset); + + GameObject boom_audio = new_object("boom_audio", "audio", vec2(0, 0)); + Asset boom_audio_asset {"asset/sfx/window_smash.ogg"}; + boom_audio.add_component<AudioSource>(boom_audio_asset); + + EndGameSubScene endgamewindow; + endgamewindow.create(*this); +} + +string GameScene::get_name() const { return "scene1"; } diff --git a/game/GameScene.h b/game/GameScene.h new file mode 100644 index 0000000..16e2919 --- /dev/null +++ b/game/GameScene.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Scene.h> +#include <string> + +class GameScene : public crepe::Scene { +public: + void load_scene(); + + std::string get_name() const; +}; diff --git a/game/MoveCameraManualyScript.cpp b/game/MoveCameraManualyScript.cpp new file mode 100644 index 0000000..9d75a75 --- /dev/null +++ b/game/MoveCameraManualyScript.cpp @@ -0,0 +1,23 @@ +#include "MoveCameraManualyScript.h" + +using namespace crepe; +using namespace std; + +void MoveCameraManualyScript::init() { + subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { + return this->keypressed(ev); + }); +} + +bool MoveCameraManualyScript::keypressed(const KeyPressEvent & event) { + if (event.key == Keycode::RIGHT) { + Transform & cam = this->get_components_by_name<Transform>("camera").front(); + cam.position.x += 100; + return true; + } else if (event.key == Keycode::LEFT) { + Transform & cam = this->get_components_by_name<Transform>("camera").front(); + cam.position.x -= 100; + return true; + } + return false; +} diff --git a/game/MoveCameraManualyScript.h b/game/MoveCameraManualyScript.h new file mode 100644 index 0000000..5a09055 --- /dev/null +++ b/game/MoveCameraManualyScript.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Script.h> + +class MoveCameraManualyScript : public crepe::Script { +public: + void init(); + +private: + bool keypressed(const crepe::KeyPressEvent & event); +}; diff --git a/game/PreviewScene.cpp b/game/PreviewScene.cpp new file mode 100644 index 0000000..bc28192 --- /dev/null +++ b/game/PreviewScene.cpp @@ -0,0 +1,182 @@ +#include "PreviewScene.h" + +#include "Config.h" +#include "background/AquariumSubScene.h" +#include "background/ForestSubScene.h" +#include "background/HallwaySubScene.h" +#include "background/StartSubScene.h" +#include "hud/HudScript.h" +#include "hud/HudSubScene.h" +#include "hud/SpeedScript.h" +#include "menus/ButtonSubScene.h" +#include "missile/MissilePool.h" +#include "missile/SpawnEvent.h" +#include "preview/NpcSubScene.h" +#include "preview/PrevPlayerSubScene.h" +#include "preview/SmokeSubScene.h" + +#include "missile/MissileSubScene.h" + +#include <cmath> +#include <crepe/api/Animator.h> +#include <crepe/api/Asset.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> + +#include <crepe/ValueBroker.h> +#include <crepe/manager/SaveManager.h> +#include <crepe/types.h> +#include <iostream> + +using namespace crepe; +using namespace std; + +void PreviewScene::load_scene() { + + StartSubScene start; + HallwaySubScene hallway; + ForestSubScene forest; + AquariumSubScene aquarium; + + float begin_x = 400; + + begin_x = start.create(*this, begin_x); + + begin_x = hallway.create(*this, begin_x, 1, Color::YELLOW); + + begin_x = aquarium.create(*this, begin_x); + + begin_x = hallway.create(*this, begin_x, 2, Color::GREEN); + + GameObject camera = new_object("camera", "camera", vec2(650, 0)); + camera.add_component<Camera>( + ivec2(990, 720), vec2(VIEWPORT_X, VIEWPORT_Y), + Camera::Data { + .bg_color = Color::BLACK, + } + ); + + camera.add_component<BehaviorScript>().set_script<MissileSpawnEventHandler>(); + camera.add_component<BehaviorScript>().set_script<HudScript>(); + camera.add_component<BehaviorScript>().set_script<SpeedScript>(); + + GameObject floor = new_object("floor", "game_world", vec2(0, 325)); + floor.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = COLL_LAY_BOT_TOP, + }); + floor.add_component<BoxCollider>(vec2(INFINITY, 200)); + GameObject floor_low = new_object("floor_low", "game_world", vec2(0, 350)); + floor_low.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = COLL_LAY_BOT_LOW, + }); + floor_low.add_component<BoxCollider>(vec2(INFINITY, 200)); + GameObject floor_high = new_object("floor_high", "game_world", vec2(0, 300)); + floor_high.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = COLL_LAY_BOT_HIGH, + }); + GameObject ceiling = new_object("ceiling", "game_world", vec2(0, -325)); + ceiling.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = COLL_LAY_BOT_TOP, + }); + ceiling.add_component<BoxCollider>(vec2(INFINITY, 200)); + + GameObject world = this->new_object("world", "TAG", vec2 {0, 0}, 0, 1); + world.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::STATIC, + .collision_layer = 100, + }); + + world.add_component<BoxCollider>(vec2(100, INFINITY), vec2(VIEWPORT_X, VIEWPORT_Y)); + world.add_component<BoxCollider>(vec2(100, INFINITY), vec2(100, VIEWPORT_Y)); + + PrevPlayerSubScene player(*this); + NpcSubScene npc(*this); + SmokeSubScene smoke(*this); + MissilePool mpool(*this); + + HudSubScene hud; + hud.create(*this); + + const float Y_POS_BUTTONS = -220; + const float X_POS_BUTTONS = -150; + const float X_POS_BUTTONS_SPACING = 145; + ButtonSubScene button; + button.create( + *this, + ButtonSubScene::Data { + .text = "BACK", + .text_width = 60, + .position = {X_POS_BUTTONS, Y_POS_BUTTONS}, + .script_type = ButtonSubScene::ScriptSelect::NEXT, + .button_type = ButtonSubScene::ButtonSelect::BACK, + .scale = 0.6, + .worldspace = false, + .tag = "Next button", + .sorting_layer_offset = 20, + } + ); + + button.create( + *this, + ButtonSubScene::Data { + .text = "START REC", + .text_width = 130, + .position = {X_POS_BUTTONS + X_POS_BUTTONS_SPACING, Y_POS_BUTTONS}, + .script_type = ButtonSubScene::ScriptSelect::PREVIEW_START, + .button_type = ButtonSubScene::ButtonSelect::LARGE, + .scale = 0.6, + .worldspace = false, + .tag = "Next button", + .sorting_layer_offset = 20, + .btn_side_color = ButtonSubScene::ButtonSideColor::YELLOW, + } + ); + + button.create( + *this, + ButtonSubScene::Data { + .text = "STOP REC", + .text_width = 120, + .position = {X_POS_BUTTONS + X_POS_BUTTONS_SPACING * 2, Y_POS_BUTTONS}, + .script_type = ButtonSubScene::ScriptSelect::PREVIEW_STOP, + .button_type = ButtonSubScene::ButtonSelect::LARGE, + .scale = 0.6, + .worldspace = false, + .tag = "Next button", + .sorting_layer_offset = 20, + .btn_side_color = ButtonSubScene::ButtonSideColor::BLUE, + } + ); + + button.create( + *this, + ButtonSubScene::Data { + .text = "REPLAY", + .text_width = 90, + .position = {X_POS_BUTTONS + X_POS_BUTTONS_SPACING * 3, Y_POS_BUTTONS}, + .script_type = ButtonSubScene::ScriptSelect::PREVIEW_REPLAY, + .button_type = ButtonSubScene::ButtonSelect::LARGE, + .scale = 0.6, + .worldspace = false, + .tag = "Next button", + .sorting_layer_offset = 20, + .btn_side_color = ButtonSubScene::ButtonSideColor::ORANGE, + } + ); +} + +string PreviewScene::get_name() const { return "preview scene"; } diff --git a/game/PreviewScene.h b/game/PreviewScene.h new file mode 100644 index 0000000..afe911e --- /dev/null +++ b/game/PreviewScene.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Scene.h> +#include <string> + +class PreviewScene : public crepe::Scene { +public: + void load_scene(); + + std::string get_name() const; +}; diff --git a/game/QuitScript.cpp b/game/QuitScript.cpp new file mode 100644 index 0000000..0c9f55a --- /dev/null +++ b/game/QuitScript.cpp @@ -0,0 +1,21 @@ + + +#include "QuitScript.h" + +#include <crepe/api/Event.h> +#include <crepe/api/KeyCodes.h> + +using namespace crepe; + +bool QuitScript::on_event(const KeyPressEvent & ev) { + if (Keycode::ESCAPE == ev.key) { + trigger_event<ShutDownEvent>(ShutDownEvent {}); + } + return false; +} + +void QuitScript::init() { + subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { + return this->on_event(ev); + }); +} diff --git a/game/QuitScript.h b/game/QuitScript.h new file mode 100644 index 0000000..b79a744 --- /dev/null +++ b/game/QuitScript.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Script.h> + +class QuitScript : public crepe::Script { +private: + bool on_event(const crepe::KeyPressEvent & ev); + +public: + void init(); +}; diff --git a/game/Random.cpp b/game/Random.cpp new file mode 100644 index 0000000..64cf1f3 --- /dev/null +++ b/game/Random.cpp @@ -0,0 +1,29 @@ +#include <cstdlib> + +#include "Random.h" + +float Random::f(float upper, float lower) { + float range = upper - lower; + float x = ((float) rand() / (float) (RAND_MAX)) * range; + return x + lower; +} + +double Random::d(double upper, double lower) { + double range = upper - lower; + double x = ((double) rand() / (double) (RAND_MAX)) * range; + return x + lower; +} + +int Random::i(int upper, int lower) { + int range = upper - lower; + int x = rand() % range; + return x + lower; +} + +unsigned Random::u(unsigned upper, unsigned lower) { + unsigned range = upper - lower; + unsigned x = rand() % range; + return x + lower; +} + +bool Random::b() { return rand() % 2; } diff --git a/game/Random.h b/game/Random.h new file mode 100644 index 0000000..94f98d2 --- /dev/null +++ b/game/Random.h @@ -0,0 +1,10 @@ +#pragma once + +class Random { +public: + static float f(float upper = 1.0, float lower = 0.0); + static double d(double upper = 1.0, double lower = 0.0); + static int i(int upper, int lower = 0); + static unsigned u(unsigned upper, unsigned lower = 0); + static bool b(); +}; diff --git a/game/StartGameScript.cpp b/game/StartGameScript.cpp new file mode 100644 index 0000000..6d47e65 --- /dev/null +++ b/game/StartGameScript.cpp @@ -0,0 +1,75 @@ +#include "StartGameScript.h" +#include "Config.h" +#include "api/BehaviorScript.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Sprite.h> + +using namespace crepe; +using namespace std; + +void StartGameScript::fixed_update(crepe::duration_t dt) { + Transform & player_transform = this->get_components_by_name<Transform>("player").front(); + // Create hole in wall and activate panic lamp + if (player_transform.position.x > 75 && !this->created_hole) { + Sprite & lamp_sprite = this->get_components_by_name<Sprite>("start_end").back(); + lamp_sprite.active = true; + Sprite & hole_sprite = this->get_components_by_name<Sprite>("start_hole").front(); + hole_sprite.active = true; + + RefVector<Rigidbody> frags_rg + = this->get_components_by_tag<Rigidbody>("wall_fragment"); + RefVector<Sprite> frags_sprite = this->get_components_by_tag<Sprite>("wall_fragment"); + for (Rigidbody & frag_rg : frags_rg) { + frag_rg.active = true; + } + for (Sprite & frag_sprite : frags_sprite) { + frag_sprite.active = true; + } + + RefVector<ParticleEmitter> smoke_emitters + = this->get_components_by_name<ParticleEmitter>("smoke_particles"); + for (ParticleEmitter & emitter : smoke_emitters) { + emitter.active = true; + } + + AudioSource & boom_audio + = this->get_components_by_name<AudioSource>("boom_audio").front(); + boom_audio.play(); + + BehaviorScript & player_audio_script + = this->get_components_by_name<BehaviorScript>("player_audio").front(); + player_audio_script.active = true; + + this->created_hole = true; + } + + // Take jetpack from jetpack stand + if (player_transform.position.x > 275 && !this->took_jetpack) { + Animator & jetpack_stand_anim + = this->get_components_by_name<Animator>("start_begin").back(); + jetpack_stand_anim.next_anim(); + Sprite & jetpack_sprite = this->get_components_by_name<Sprite>("player").back(); + jetpack_sprite.active = true; + + AudioSource & background_music + = this->get_components_by_name<AudioSource>("background_music").front(); + background_music.play(true); + + this->took_jetpack = true; + } + + // Start camera movement, enable player jumping and disable this script + if (player_transform.position.x > 500) { + Rigidbody & rb = this->get_components_by_name<Rigidbody>("camera").front(); + rb.data.linear_velocity = vec2(PLAYER_SPEED * 0.02, 0); + BehaviorScript & player_script + = this->get_components_by_name<BehaviorScript>("player").front(); + player_script.active = true; + BehaviorScript & this_script + = this->get_components_by_name<BehaviorScript>("start_game_script").front(); + this_script.active = false; + } +} diff --git a/game/StartGameScript.h b/game/StartGameScript.h new file mode 100644 index 0000000..ad62e1a --- /dev/null +++ b/game/StartGameScript.h @@ -0,0 +1,12 @@ +#pragma once + +#include <crepe/api/Script.h> + +class StartGameScript : public crepe::Script { +public: + void fixed_update(crepe::duration_t dt); + +private: + bool created_hole = false; + bool took_jetpack = false; +}; diff --git a/game/background/AquariumScript.cpp b/game/background/AquariumScript.cpp new file mode 100644 index 0000000..e698e3a --- /dev/null +++ b/game/background/AquariumScript.cpp @@ -0,0 +1,26 @@ +#include "AquariumScript.h" + +#include "../Config.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void AquariumScript::fixed_update(crepe::duration_t dt) { + Transform & trans_cam = this->get_components_by_name<Transform>("camera").front(); + + float cam_left_x = trans_cam.position.x - VIEWPORT_X / 2; + + if (cam_left_x > this->start_x + this->lenght) { + //Move whole background 12000 to the right + RefVector<Transform> trans = this->get_components_by_tag<Transform>("background_aqua"); + for (Transform & tran : trans) { + tran.position.x += 12000; + } + this->start_x += 12000; + } +} diff --git a/game/background/AquariumScript.h b/game/background/AquariumScript.h new file mode 100644 index 0000000..b068628 --- /dev/null +++ b/game/background/AquariumScript.h @@ -0,0 +1,12 @@ +#pragma once + +#include <crepe/api/Script.h> + +class AquariumScript : public crepe::Script { +public: + void fixed_update(crepe::duration_t dt); + +private: + float start_x = 10200; + const float lenght = 3000; +}; diff --git a/game/background/AquariumSubScene.cpp b/game/background/AquariumSubScene.cpp new file mode 100644 index 0000000..2a07daf --- /dev/null +++ b/game/background/AquariumSubScene.cpp @@ -0,0 +1,225 @@ +#include "AquariumSubScene.h" + +#include "../Config.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +float AquariumSubScene::create(Scene & scn, float begin_x) { + this->add_background(scn, begin_x); + + GameObject aquarium_begin + = scn.new_object("aquarium_begin", "background_aqua", vec2(begin_x, 0)); + Asset aquarium_begin_asset {"asset/background/aquarium/glassTubeFG_1_TVOS.png"}; + aquarium_begin.add_component<Sprite>( + aquarium_begin_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 600; + + GameObject aquarium_middle_1 + = scn.new_object("aquarium_middle", "background_aqua", vec2(begin_x, 0)); + Asset aquarium_middle_1_asset {"asset/background/aquarium/glassTubeFG_3_TVOS.png"}; + aquarium_middle_1.add_component<Sprite>( + aquarium_middle_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 2, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 400; + + this->add_background(scn, begin_x - 200); + this->add_bubbles(aquarium_middle_1, vec2(-400, 300), 2, 0.7f); + this->add_bubbles(aquarium_middle_1, vec2(-100, 300), 4, 1.0f); + this->add_bubbles(aquarium_middle_1, vec2(500, 300), 4, 0.9f); + + GameObject aquarium_middle_2 + = scn.new_object("aquarium_middle", "background_aqua", vec2(begin_x, 0)); + Asset aquarium_middle_2_asset {"asset/background/aquarium/glassTubeFG_3_TVOS.png"}; + aquarium_middle_2.add_component<Sprite>( + aquarium_middle_2_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 3, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 400; + + this->add_bubbles(aquarium_middle_2, vec2(300, 300), 2, 0.6f); + + GameObject aquarium_middle_3 + = scn.new_object("aquarium_middle", "background_aqua", vec2(begin_x, 0)); + Asset aquarium_middle_3_asset {"asset/background/aquarium/glassTubeFG_3_TVOS.png"}; + aquarium_middle_3.add_component<Sprite>( + aquarium_middle_3_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 4, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 400; + + this->add_background(scn, begin_x - 200); + + GameObject aquarium_middle_4 + = scn.new_object("aquarium_middle", "background_aqua", vec2(begin_x, 0)); + Asset aquarium_middle_4_asset {"asset/background/aquarium/glassTubeFG_3_TVOS.png"}; + aquarium_middle_4.add_component<Sprite>( + aquarium_middle_4_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 600; + + this->add_background(scn, begin_x); + this->add_bubbles(aquarium_middle_4, vec2(175, 300), 4, 1.0f); + this->add_bubbles(aquarium_middle_4, vec2(200, 300), 4, 0.7f); + + GameObject aquarium_end + = scn.new_object("aquarium_end", "background_aqua", vec2(begin_x, 0)); + Asset aquarium_end_asset {"asset/background/aquarium/glassTubeFG_2_TVOS.png"}; + aquarium_end.add_component<Sprite>( + aquarium_end_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 600; + + return begin_x; +} + +void AquariumSubScene::add_background(Scene & scn, float begin_x) { + GameObject bg_1 = scn.new_object("aquarium_bg_1", "background_aqua", vec2(begin_x, 0)); + Asset bg_1_1_asset {"asset/background/aquarium/AquariumBG1_1_TVOS.png"}; + bg_1.add_component<Sprite>( + bg_1_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 400), + .position_offset = vec2(-200, 100), + } + ); + Asset bg_1_2_asset {"asset/background/aquarium/AquariumBG1_2_TVOS.png"}; + bg_1.add_component<Sprite>( + bg_1_2_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 400), + .position_offset = vec2(200, 100), + } + ); + GameObject bg_2 = scn.new_object("aquarium_bg_2", "background_aqua", vec2(begin_x, 0)); + Asset bg_2_1_asset {"asset/background/aquarium/AquariumBG2_1_TVOS.png"}; + bg_2.add_component<Sprite>( + bg_2_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 3, + .size = vec2(0, 400), + .position_offset = vec2(200, -50), + } + ); + Asset bg_2_2_asset {"asset/background/aquarium/AquariumBG2_2_TVOS.png"}; + bg_2.add_component<Sprite>( + bg_2_2_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 3, + .size = vec2(0, 400), + .position_offset = vec2(-200, -50), + } + ); + GameObject bg_3 = scn.new_object("aquarium_bg_3", "background_aqua", vec2(begin_x, 0)); + Asset bg_3_1_asset {"asset/background/aquarium/AquariumBG3_1_TVOS.png"}; + bg_3.add_component<Sprite>( + bg_3_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 400), + .position_offset = vec2(200, -200), + } + ); + Asset bg_3_2_asset {"asset/background/aquarium/AquariumBG3_2_TVOS.png"}; + bg_3.add_component<Sprite>( + bg_3_2_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 400), + .position_offset = vec2(-200, -200), + } + ); +} + +void AquariumSubScene::add_bubbles( + GameObject & obj, vec2 offset, int order_in_layer, float scale +) { + Sprite & sprite = obj.add_component<Sprite>( + Asset {"asset/background/aquarium/bubble.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = order_in_layer, + .size = vec2(0, 12.5), + .scale_offset = scale, + } + ); + obj.add_component<ParticleEmitter>( + sprite, + ParticleEmitter::Data { + .offset = offset, + .max_particles = 20, + .emission_rate = 1.2, + .min_speed = 50, + .max_speed = 100, + .min_angle = 265, + .max_angle = 275, + .force_over_time = vec2(0, -50), + } + ); + Sprite & sprite_small = obj.add_component<Sprite>( + Asset {"asset/background/aquarium/bubble.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = order_in_layer, + .size = vec2(0, 7.5), + .scale_offset = scale, + } + ); + obj.add_component<ParticleEmitter>( + sprite_small, + ParticleEmitter::Data { + .offset = offset, + .max_particles = 20, + .emission_rate = 0.8, + .min_speed = 50, + .max_speed = 100, + .min_angle = 265, + .max_angle = 275, + .force_over_time = vec2(0, -50), + } + ); +} diff --git a/game/background/AquariumSubScene.h b/game/background/AquariumSubScene.h new file mode 100644 index 0000000..9dbb04e --- /dev/null +++ b/game/background/AquariumSubScene.h @@ -0,0 +1,18 @@ +#pragma once + +#include <crepe/types.h> + +namespace crepe { +class Scene; +class GameObject; +} // namespace crepe + +class AquariumSubScene { +public: + float create(crepe::Scene & scn, float begin_x); + +private: + void add_background(crepe::Scene & scn, float begin_x); + void + add_bubbles(crepe::GameObject & obj, crepe::vec2 offset, int order_in_layer, float scale); +}; diff --git a/game/background/BackgroundSubScene.cpp b/game/background/BackgroundSubScene.cpp new file mode 100644 index 0000000..4bbd977 --- /dev/null +++ b/game/background/BackgroundSubScene.cpp @@ -0,0 +1,37 @@ +#include "BackgroundSubScene.h" +#include "AquariumScript.h" +#include "AquariumSubScene.h" +#include "ForestSubScene.h" +#include "HallwayScript.h" +#include "HallwaySubScene.h" +#include "StartSubScene.h" + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Color.h> +#include <crepe/api/Scene.h> + +using namespace crepe; +using namespace std; + +BackgroundSubScene::BackgroundSubScene(Scene & scn) { + StartSubScene start; + HallwaySubScene hallway; + ForestSubScene forest; + AquariumSubScene aquarium; + + float begin_x = 400; + + begin_x = start.create(scn, begin_x); + + begin_x = hallway.create(scn, begin_x, 1, Color::YELLOW); + + begin_x = forest.create(scn, begin_x, "1"); + + begin_x += 3000; + + begin_x = aquarium.create(scn, begin_x); + + GameObject scripts = scn.new_object("scrips_background", "background"); + scripts.add_component<BehaviorScript>().set_script<HallwayScript>(); + scripts.add_component<BehaviorScript>().set_script<AquariumScript>(); +} diff --git a/game/background/BackgroundSubScene.h b/game/background/BackgroundSubScene.h new file mode 100644 index 0000000..06bdac4 --- /dev/null +++ b/game/background/BackgroundSubScene.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class BackgroundSubScene { +public: + BackgroundSubScene(crepe::Scene & scn); +}; diff --git a/game/background/CMakeLists.txt b/game/background/CMakeLists.txt new file mode 100644 index 0000000..1d705f5 --- /dev/null +++ b/game/background/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources(main PUBLIC + AquariumSubScene.cpp + BackgroundSubScene.cpp + ForestParallaxScript.cpp + ForestSubScene.cpp + HallwaySubScene.cpp + StartSubScene.cpp +) + diff --git a/game/background/ForestParallaxScript.cpp b/game/background/ForestParallaxScript.cpp new file mode 100644 index 0000000..7470da2 --- /dev/null +++ b/game/background/ForestParallaxScript.cpp @@ -0,0 +1,55 @@ +#include "ForestParallaxScript.h" + +#include "../Config.h" + +using namespace crepe; +using namespace std; + +ForestParallaxScript::ForestParallaxScript( + float begin_x, float end_x, std::string unique_bg_name +) + : begin_x(begin_x), + end_x(end_x), + name(unique_bg_name) {} + +void ForestParallaxScript::fixed_update(crepe::duration_t dt) { + RefVector<Transform> vec_2 + = this->get_components_by_name<Transform>("forest_bg_2_" + name); + RefVector<Transform> vec_3 + = this->get_components_by_name<Transform>("forest_bg_3_" + name); + + for (Transform & t : vec_2) { + if (t.position.x > end_x - 400) { + t.position.x = begin_x - 400; + } + } + for (Transform & t : vec_3) { + if (t.position.x > end_x - 400) { + t.position.x = begin_x - 400; + } + } + + //Move whole background 12000 to the right + Transform & trans_cam = this->get_components_by_name<Transform>("camera").front(); + + float cam_left_x = trans_cam.position.x - VIEWPORT_X / 2; + + if (cam_left_x > this->start_x + this->lenght) { + //Move whole background 12000 to the right + RefVector<Transform> trans + = this->get_components_by_tag<Transform>("background_forest"); + for (Transform & tran : trans) { + tran.position.x += 12000; + } + this->start_x += 12000; + + RefVector<Transform> trans_back + = this->get_components_by_tag<Transform>("forest_background"); + for (Transform & tran : trans_back) { + tran.position.x += 12000; + } + + begin_x += 12000; + end_x += 12000; + } +} diff --git a/game/background/ForestParallaxScript.h b/game/background/ForestParallaxScript.h new file mode 100644 index 0000000..d45fdd9 --- /dev/null +++ b/game/background/ForestParallaxScript.h @@ -0,0 +1,17 @@ +#pragma once + +#include <crepe/api/Script.h> + +class ForestParallaxScript : public crepe::Script { +public: + ForestParallaxScript(float begin_x, float end_x, std::string unique_bg_name); + + void fixed_update(crepe::duration_t dt); + +private: + float begin_x; + float end_x; + const std::string name; + float start_x = 4200; + const float lenght = 3000; +}; diff --git a/game/background/ForestSubScene.cpp b/game/background/ForestSubScene.cpp new file mode 100644 index 0000000..83e48dd --- /dev/null +++ b/game/background/ForestSubScene.cpp @@ -0,0 +1,169 @@ +#include "ForestSubScene.h" +#include "ForestParallaxScript.h" + +#include "../Config.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +float ForestSubScene::create(Scene & scn, float begin_x, std::string unique_bg_name) { + GameObject script = scn.new_object("forest_script", "background_forest"); + script.add_component<BehaviorScript>().set_script<ForestParallaxScript>( + begin_x - 400, begin_x + 3000 + 400, unique_bg_name + ); + + this->add_background(scn, begin_x, unique_bg_name); + + GameObject begin = scn.new_object("forest_begin", "background_forest", vec2(begin_x, 0)); + Asset begin_asset {"asset/background/forest/forestFG_1_TVOS.png"}; + begin.add_component<Sprite>( + begin_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 800; + + this->add_background(scn, begin_x, unique_bg_name); + + GameObject middle_1 + = scn.new_object("forest_middle", "background_forest", vec2(begin_x, 0)); + Asset middle_1_asset {"asset/background/forest/forestFG_3_TVOS.png"}; + middle_1.add_component<Sprite>( + middle_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 2, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 800; + + this->add_background(scn, begin_x, unique_bg_name); + + GameObject middle_2 + = scn.new_object("forest_middle", "background_forest", vec2(begin_x, 0)); + Asset middle_2_asset {"asset/background/forest/forestFG_3_TVOS.png"}; + middle_2.add_component<Sprite>( + middle_2_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 3, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 800; + + this->add_background(scn, begin_x, unique_bg_name); + + GameObject end = scn.new_object("forest_end", "background_forest", vec2(begin_x, 0)); + Asset end_asset {"asset/background/forest/forestFG_2_TVOS.png"}; + end.add_component<Sprite>( + end_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 600; + + this->add_background(scn, begin_x + 200, unique_bg_name); + + return begin_x; +} + +void ForestSubScene::add_background(Scene & scn, float begin_x, std::string name) { + GameObject bg_1 + = scn.new_object("forest_bg_1_" + name, "forest_background", vec2(begin_x, 0)); + Asset bg_1_asset {"asset/background/forest/forestBG1_1_TVOS.png"}; + bg_1.add_component<Sprite>( + bg_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 2, + .size = vec2(0, 800), + } + ); + GameObject bg_2 + = scn.new_object("forest_bg_2_" + name, "forest_background", vec2(begin_x, 0)); + Asset bg_2_1_asset {"asset/background/forest/forestBG2_1_TVOS.png"}; + bg_2.add_component<Sprite>( + bg_2_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 400), + .position_offset = vec2(200, 0), + } + ); + Asset bg_2_2_asset {"asset/background/forest/forestBG2_2_TVOS.png"}; + bg_2.add_component<Sprite>( + bg_2_2_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 400), + .position_offset = vec2(-200, 0), + } + ); + GameObject bg_3 + = scn.new_object("forest_bg_3_" + name, "forest_background", vec2(begin_x, 0)); + Asset bg_3_1_asset {"asset/background/forest/forestBG3_1_TVOS.png"}; + bg_3.add_component<Sprite>( + bg_3_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 200), + .position_offset = vec2(300, 0), + } + ); + Asset bg_3_2_asset {"asset/background/forest/forestBG3_2_TVOS.png"}; + bg_3.add_component<Sprite>( + bg_3_2_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 200), + .position_offset = vec2(100, 0), + } + ); + Asset bg_3_3_asset {"asset/background/forest/forestBG3_3_TVOS.png"}; + bg_3.add_component<Sprite>( + bg_3_3_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 200), + .position_offset = vec2(-100, 0), + } + ); + Asset bg_3_4_asset {"asset/background/forest/forestBG3_4_TVOS.png"}; + bg_3.add_component<Sprite>( + bg_3_4_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 200), + .position_offset = vec2(-300, 0), + } + ); + + bg_2.add_component<Rigidbody>(Rigidbody::Data { + .linear_velocity = vec2(30, 0), + }); + bg_3.add_component<Rigidbody>(Rigidbody::Data { + .linear_velocity = vec2(40, 0), + }); +} diff --git a/game/background/ForestSubScene.h b/game/background/ForestSubScene.h new file mode 100644 index 0000000..0a04001 --- /dev/null +++ b/game/background/ForestSubScene.h @@ -0,0 +1,15 @@ +#pragma once + +#include <string> + +namespace crepe { +class Scene; +} + +class ForestSubScene { +public: + float create(crepe::Scene & scn, float begin_x, std::string unique_bg_name); + +private: + void add_background(crepe::Scene & scn, float begin_x, std::string name); +}; diff --git a/game/background/HallwayScript.cpp b/game/background/HallwayScript.cpp new file mode 100644 index 0000000..a5bb94c --- /dev/null +++ b/game/background/HallwayScript.cpp @@ -0,0 +1,70 @@ +#include "HallwayScript.h" + +#include "../Config.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void HallwayScript::fixed_update(crepe::duration_t dt) { + Transform & trans_cam = this->get_components_by_name<Transform>("camera").front(); + + float cam_left_x = trans_cam.position.x - VIEWPORT_X / 2; + + if (cam_left_x > this->start_x + this->lenght) { + //Move whole background 6000 to the right + RefVector<Transform> trans = this->get_components_by_tag<Transform>("background_hall"); + for (Transform & tran : trans) { + tran.position.x += 6000; + } + this->start_x += 6000; + + //Change sector number + Animator & anim = this->get_components_by_name<Animator>("hallway_begin").front(); + int column = (current_sector - 1) / 4; + int row = (current_sector - 1) % 4; + anim.set_anim(column); + for (int i = 0; i < row; i++) { + anim.next_anim(); + } + RefVector<Sprite> sprites = this->get_components_by_name<Sprite>("hallway_begin"); + switch (current_sector % 7) { + case 0: + sprites[1].get().data.color = Color::YELLOW; + sprites[2].get().data.color = Color::YELLOW; + break; + case 1: + sprites[1].get().data.color = Color::MAGENTA; + sprites[2].get().data.color = Color::MAGENTA; + break; + case 2: + sprites[1].get().data.color = Color::CYAN; + sprites[2].get().data.color = Color::CYAN; + break; + case 3: + sprites[1].get().data.color = Color::GREEN; + sprites[2].get().data.color = Color::GREEN; + break; + case 4: + sprites[1].get().data.color = Color::RED; + sprites[2].get().data.color = Color::RED; + break; + case 5: + sprites[1].get().data.color = Color::BLUE; + sprites[2].get().data.color = Color::BLUE; + break; + case 6: + sprites[1].get().data.color = Color::WHITE; + sprites[2].get().data.color = Color::WHITE; + break; + } + current_sector++; + if (current_sector > 16) { + current_sector = 1; + } + } +} diff --git a/game/background/HallwayScript.h b/game/background/HallwayScript.h new file mode 100644 index 0000000..04b2933 --- /dev/null +++ b/game/background/HallwayScript.h @@ -0,0 +1,13 @@ +#pragma once + +#include <crepe/api/Script.h> + +class HallwayScript : public crepe::Script { +public: + void fixed_update(crepe::duration_t dt); + +private: + float start_x = 1200; + const float lenght = 3000; + int current_sector = 2; +}; diff --git a/game/background/HallwaySubScene.cpp b/game/background/HallwaySubScene.cpp new file mode 100644 index 0000000..31af2d5 --- /dev/null +++ b/game/background/HallwaySubScene.cpp @@ -0,0 +1,167 @@ +#include "HallwaySubScene.h" + +#include "../Config.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +using namespace crepe; +using namespace std; + +float HallwaySubScene::create( + Scene & scn, float begin_x, unsigned int sector_num, Color sector_color +) { + GameObject begin = scn.new_object("hallway_begin", "background_hall", vec2(begin_x, 0)); + Asset begin_asset {"asset/background/hallway/hallway1FG_1_TVOS.png"}; + begin.add_component<Sprite>( + begin_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 600; + + this->add_sector_number(begin, vec2(-200, 0), sector_num, sector_color); + this->add_lamp(begin, vec2(330, -120), 11); + this->add_lamp(begin, vec2(430, -120), 9); + + GameObject middle_1 + = scn.new_object("hallway_middle", "background_hall", vec2(begin_x, 0)); + Asset middle_asset {"asset/background/hallway/hallway1FG_2_TVOS.png"}; + middle_1.add_component<Sprite>( + middle_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 2, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 600; + + GameObject middle_2 + = scn.new_object("hallway_middle", "background_hall", vec2(begin_x, 0)); + Asset middle_asset_2 {"asset/background/hallway/hallway1FG_2_TVOS.png"}; + middle_2.add_component<Sprite>( + middle_asset_2, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 3, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 200; + + GameObject middle_3 + = scn.new_object("hallway_middle", "background_hall", vec2(begin_x, 0)); + Asset middle_asset_3 {"asset/background/hallway/hallway1FG_2_TVOS.png"}; + middle_3.add_component<Sprite>( + middle_asset_3, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 4, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 400; + + this->add_lamp(middle_3, vec2(0, -120)); + + GameObject middle_4 + = scn.new_object("hallway_middle", "background_hall", vec2(begin_x, 0)); + Asset middle_asset_4 {"asset/background/hallway/hallway1FG_2_TVOS.png"}; + middle_4.add_component<Sprite>( + middle_asset_4, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 600; + + GameObject end = scn.new_object("hallway_end", "background_hall", vec2(begin_x, 0)); + Asset end_asset {"asset/background/hallway/hallway1FG_1_TVOS.png"}; + end.add_component<Sprite>( + end_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 600; + + return begin_x; +} + +void HallwaySubScene::add_lamp(GameObject & obj, vec2 offset, unsigned int fps) { + Asset lamp_asset {"asset/background/hallway/alarmLight_TVOS.png"}; + obj.add_component<Sprite>( + lamp_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 100), + .position_offset = offset, + } + ); + Asset lamp_glow_asset {"asset/background/hallway/alarmGlow_TVOS.png"}; + Sprite & lamp_glow_sprite = obj.add_component<Sprite>( + lamp_glow_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 300), + .position_offset = offset - vec2(65, -30), + } + ); + obj.add_component<Animator>( + lamp_glow_sprite, ivec2(422, 384), uvec2(6, 1), + Animator::Data { + .fps = fps, + .looping = true, + } + ); +} + +void HallwaySubScene::add_sector_number( + GameObject & obj, vec2 offset, unsigned int sector_num, Color sector_color +) { + Asset sector_text_asset {"asset/background/hallway/sectorText_TVOS.png"}; + obj.add_component<Sprite>( + sector_text_asset, + Sprite::Data { + .color = sector_color, + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 100), + .position_offset = offset, + } + ); + Asset sector_num_asset {"asset/background/hallway/sectorNumbers_TVOS.png"}; + Sprite & sector_num_sprite = obj.add_component<Sprite>( + sector_num_asset, + Sprite::Data { + .color = sector_color, + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 100), + .position_offset = offset + vec2(200, 0), + } + ); + Animator & sector_num_anim = obj.add_component<Animator>( + sector_num_sprite, ivec2(256, 128), uvec2(4, 4), Animator::Data {} + ); + int column = (sector_num - 1) / 4; + int row = (sector_num - 1) % 4; + sector_num_anim.set_anim(column); + for (int i = 0; i < row; i++) { + sector_num_anim.next_anim(); + } + sector_num_anim.pause(); +} diff --git a/game/background/HallwaySubScene.h b/game/background/HallwaySubScene.h new file mode 100644 index 0000000..c38b4a9 --- /dev/null +++ b/game/background/HallwaySubScene.h @@ -0,0 +1,24 @@ +#pragma once + +#include <crepe/types.h> + +namespace crepe { +class Scene; +class GameObject; +class Color; +} // namespace crepe + +class HallwaySubScene { +public: + float create( + crepe::Scene & scn, float begin_x, unsigned int sector_num, crepe::Color sector_color + ); + +private: + void add_lamp(crepe::GameObject & obj, crepe::vec2 offset, unsigned int fps = 10); + + void add_sector_number( + crepe::GameObject & obj, crepe::vec2 offset, unsigned int sector_num, + crepe::Color sector_color + ); +}; diff --git a/game/background/StartSubScene.cpp b/game/background/StartSubScene.cpp new file mode 100644 index 0000000..ba80517 --- /dev/null +++ b/game/background/StartSubScene.cpp @@ -0,0 +1,527 @@ +#include "StartSubScene.h" + +#include "../Config.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +using namespace crepe; +using namespace std; + +float StartSubScene::create(Scene & scn, float begin_x) { + this->create_wall_fragments(scn, begin_x - 300); + + GameObject begin = scn.new_object("start_begin", "background", vec2(begin_x, 0)); + Asset begin_asset {"asset/background/start/titleFG_1_TVOS.png"}; + begin.add_component<Sprite>( + begin_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, GAME_HEIGHT), + } + ); + GameObject hole = scn.new_object("start_hole", "background", vec2(begin_x - 250, 140)); + Asset hole_asset {"asset/background/start/titleWallHole.png"}; + Sprite & hole_sprite = hole.add_component<Sprite>( + hole_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 200), + } + ); + hole_sprite.active = false; + begin_x += 700; + + this->add_table(begin, vec2(-150, 150)); + this->add_light(begin, vec2(-125, -150)); + this->add_jetpack_stand(begin, vec2(-125, 200)); + + GameObject end = scn.new_object("start_end", "background", vec2(begin_x, 0)); + Asset end_asset {"asset/background/start/titleFG_2_TVOS.png"}; + end.add_component<Sprite>( + end_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, GAME_HEIGHT), + } + ); + begin_x += 100; + + this->add_lamp(end, vec2(-350, -95)); + + return begin_x; +} + +void StartSubScene::add_lamp(GameObject & obj, vec2 offset, unsigned int fps) { + Asset lamp_asset {"asset/background/start/alarmLight_TVOS.png"}; + obj.add_component<Sprite>( + lamp_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 100), + .position_offset = offset, + } + ); + Asset lamp_glow_asset {"asset/background/start/alarmGlow_TVOS.png"}; + Sprite & lamp_glow_sprite = obj.add_component<Sprite>( + lamp_glow_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 300), + .position_offset = offset - vec2(65, -55), + } + ); + lamp_glow_sprite.active = false; + obj.add_component<Animator>( + lamp_glow_sprite, ivec2(422, 384), uvec2(6, 1), + Animator::Data { + .fps = fps, + .looping = true, + } + ); +} + +void StartSubScene::add_table(GameObject & obj, vec2 offset) { + Asset table_asset {"asset/background/start/table.png"}; + obj.add_component<Sprite>( + table_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 100), + .position_offset = offset, + } + ); + Asset gramophone_asset {"asset/background/start/gramophone_TVOS.png"}; + Sprite & gramophone_sprite = obj.add_component<Sprite>( + gramophone_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 100), + .position_offset = offset + vec2(0, -50), + } + ); + obj.add_component<Animator>( + gramophone_sprite, ivec2(64, 128), uvec2(2, 1), + Animator::Data { + .fps = 10, + .looping = true, + } + ); +} + +void StartSubScene::add_light(crepe::GameObject & obj, crepe::vec2 offset) { + Asset light_asset {"asset/background/start/title_light_TVOS.png"}; + obj.add_component<Sprite>( + light_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 200), + .position_offset = offset, + } + ); + Asset light_glow_asset {"asset/background/start/lightEffect2.png"}; + obj.add_component<Sprite>( + light_glow_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 100), + .position_offset = offset + vec2(0, 75), + } + ); + Asset light_effect_asset {"asset/background/start/lightEffect.png"}; + obj.add_component<Sprite>( + light_effect_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 0, + .size = vec2(0, 100), + .position_offset = offset + vec2(0, 350), + } + ); +} + +void StartSubScene::add_jetpack_stand(crepe::GameObject & obj, crepe::vec2 offset) { + Asset jetpack_stand_asset {"asset/background/start/JetpackStand.png"}; + Sprite & jetpeck_stand_sprite = obj.add_component<Sprite>( + jetpack_stand_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 70), + .position_offset = offset, + } + ); + obj.add_component<Animator>( + jetpeck_stand_sprite, ivec2(34, 46), uvec2(2, 1), + Animator::Data { + .fps = 10, + .looping = true, + } + ) + .pause(); + Asset do_not_steal = {"asset/background/start/doNotTouchSign_TVOS.png"}; + obj.add_component<Sprite>( + do_not_steal, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 1, + .size = vec2(0, 100), + .position_offset = offset + vec2(-75, -25), + } + ); +} + +void StartSubScene::create_wall_fragments(crepe::Scene & scn, float begin_x) { + GameObject frag_1 = scn.new_object("frag_1", "wall_fragment", vec2(begin_x, 200)); + Asset frag_1_asset {"asset/background/start/StartWall_frag1.png"}; + Sprite & frag_1_sprite = frag_1.add_component<Sprite>( + frag_1_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_1_sprite.active = false; + Rigidbody & frag_1_rb = frag_1.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(400, -400), + .linear_velocity_coefficient = vec2(0.5, 0.6), + .angular_velocity = 500, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_1_rb.active = false; + frag_1.add_component<CircleCollider>(25); + + GameObject frag_2 = scn.new_object("frag_2", "wall_fragment", vec2(begin_x, 180)); + Asset frag_2_asset {"asset/background/start/StartWall_frag2.png"}; + Sprite & frag_2_sprite = frag_2.add_component<Sprite>( + frag_2_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_2_sprite.active = false; + Rigidbody & frag_2_rb = frag_2.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(400, -400), + .linear_velocity_coefficient = vec2(0.35, 0.4), + .angular_velocity = 400, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_2_rb.active = false; + frag_2.add_component<CircleCollider>(55); + + GameObject frag_3 = scn.new_object("frag_3", "wall_fragment", vec2(begin_x, 170)); + Asset frag_3_asset {"asset/background/start/StartWall_frag3.png"}; + Sprite & frag_3_sprite = frag_3.add_component<Sprite>( + frag_3_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_3_sprite.active = false; + Rigidbody & frag_3_rb = frag_3.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(400, -400), + .linear_velocity_coefficient = vec2(0.3, 0.3), + .angular_velocity = 300, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_3_rb.active = false; + frag_3.add_component<CircleCollider>(35); + + GameObject frag_4 = scn.new_object("frag_4", "wall_fragment", vec2(begin_x, 160)); + Asset frag_4_asset {"asset/background/start/StartWall_frag4.png"}; + Sprite & frag_4_sprite = frag_4.add_component<Sprite>( + frag_4_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_4_sprite.active = false; + Rigidbody & frag_4_rb = frag_4.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(700, -400), + .linear_velocity_coefficient = vec2(0.2, 0.2), + .angular_velocity = 200, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_4_rb.active = false; + frag_4.add_component<CircleCollider>(60); + + GameObject frag_5 = scn.new_object("frag_5", "wall_fragment", vec2(begin_x, 150)); + Asset frag_5_asset {"asset/background/start/StartWall_frag5.png"}; + Sprite & frag_5_sprite = frag_5.add_component<Sprite>( + frag_5_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_5_sprite.active = false; + Rigidbody & frag_5_rb = frag_5.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(600, -500), + .linear_velocity_coefficient = vec2(0.25, 0.15), + .angular_velocity = 100, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_5_rb.active = false; + frag_5.add_component<CircleCollider>(5); + + GameObject frag_6 = scn.new_object("frag_6", "wall_fragment", vec2(begin_x, 140)); + Asset frag_6_asset {"asset/background/start/StartWall_frag6.png"}; + Sprite & frag_6_sprite = frag_6.add_component<Sprite>( + frag_6_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_6_sprite.active = false; + Rigidbody & frag_6_rb = frag_6.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(300, -800), + .linear_velocity_coefficient = vec2(0.35, 0.25), + .angular_velocity = 100, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_6_rb.active = false; + frag_6.add_component<CircleCollider>(30); + + GameObject frag_7 = scn.new_object("frag_7", "wall_fragment", vec2(begin_x, 130)); + Asset frag_7_asset {"asset/background/start/StartWall_frag7.png"}; + Sprite & frag_7_sprite = frag_7.add_component<Sprite>( + frag_7_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_7_sprite.active = false; + Rigidbody & frag_7_rb = frag_7.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(400, -500), + .linear_velocity_coefficient = vec2(0.45, 0.6), + .angular_velocity = 800, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_7_rb.active = false; + frag_7.add_component<CircleCollider>(45); + + GameObject frag_8 = scn.new_object("frag_8", "wall_fragment", vec2(begin_x, 120)); + Asset frag_8_asset {"asset/background/start/StartWall_frag8.png"}; + Sprite & frag_8_sprite = frag_8.add_component<Sprite>( + frag_8_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_8_sprite.active = false; + Rigidbody & frag_8_rb = frag_8.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(400, -400), + .linear_velocity_coefficient = vec2(0.5, 0.6), + .angular_velocity = 500, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_8_rb.active = false; + frag_8.add_component<CircleCollider>(25); + + GameObject frag_9 = scn.new_object("frag_9", "wall_fragment", vec2(begin_x, 110)); + Asset frag_9_asset {"asset/background/start/StartWall_frag9.png"}; + Sprite & frag_9_sprite = frag_9.add_component<Sprite>( + frag_9_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_9_sprite.active = false; + Rigidbody & frag_9_rb = frag_9.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(200, -400), + .linear_velocity_coefficient = vec2(0.5, 0.25), + .angular_velocity = 500, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_9_rb.active = false; + frag_9.add_component<CircleCollider>(15); + + GameObject frag_10 = scn.new_object("frag_10", "wall_fragment", vec2(begin_x, 100)); + Asset frag_10_asset {"asset/background/start/StartWall_frag10.png"}; + Sprite & frag_10_sprite = frag_10.add_component<Sprite>( + frag_10_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_10_sprite.active = false; + Rigidbody & frag_10_rb = frag_10.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(400, -900), + .linear_velocity_coefficient = vec2(0.35, 0.4), + .angular_velocity = 300, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_10_rb.active = false; + frag_10.add_component<CircleCollider>(60); + + GameObject frag_11 = scn.new_object("frag_11", "wall_fragment", vec2(begin_x, 70)); + Asset frag_11_asset {"asset/background/start/StartWall_frag11.png"}; + Sprite & frag_11_sprite = frag_11.add_component<Sprite>( + frag_11_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_11_sprite.active = false; + Rigidbody & frag_11_rb = frag_11.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(600, -400), + .linear_velocity_coefficient = vec2(0.3, 0.3), + .angular_velocity = 200, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_11_rb.active = false; + frag_11.add_component<CircleCollider>(5); + + GameObject frag_12 = scn.new_object("frag_12", "wall_fragment", vec2(begin_x, 80)); + Asset frag_12_asset {"asset/background/start/StartWall_frag12.png"}; + Sprite & frag_12_sprite = frag_12.add_component<Sprite>( + frag_12_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND, + .order_in_layer = 5, + .size = vec2(0, 50), + } + ); + frag_12_sprite.active = false; + Rigidbody & frag_12_rb = frag_12.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .linear_velocity = vec2(500, -800), + .linear_velocity_coefficient = vec2(0.25, 0.15), + .angular_velocity = 100, + .angular_velocity_coefficient = 0.55, + .elasticity_coefficient = 0.5, + .collision_layers = {COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_WALL_FRAGS, + }); + frag_12_rb.active = false; + frag_12.add_component<CircleCollider>(50); + + GameObject smoke_particles_1 + = scn.new_object("smoke_particles", "particle_emitter", vec2(begin_x - 100, 200)); + Asset smoke_asset_1 {"asset/particles/smoke.png"}; + Sprite & smoke_sprite_1 = smoke_particles_1.add_component<Sprite>( + smoke_asset_1, + Sprite::Data { + .color = Color(255, 255, 255, 50), + .sorting_in_layer = SORT_IN_LAY_PARTICLES_FOREGROUND, + .order_in_layer = 0, + .size = vec2(0, 100), + } + ); + ParticleEmitter & emitter_1 = smoke_particles_1.add_component<ParticleEmitter>( + smoke_sprite_1, + ParticleEmitter::Data { + .emission_rate = 20, + .min_speed = 40, + .max_speed = 100, + .min_angle = -30, + .max_angle = 10, + .end_lifespan = 4, + } + ); + emitter_1.active = false; + + GameObject smoke_particles_2 + = scn.new_object("smoke_particles", "particle_emitter", vec2(begin_x - 100, 200)); + Asset smoke_asset_2 {"asset/particles/smoke.png"}; + Sprite & smoke_sprite_2 = smoke_particles_2.add_component<Sprite>( + smoke_asset_2, + Sprite::Data { + .color = Color(255, 255, 255, 50), + .sorting_in_layer = SORT_IN_LAY_PARTICLES_FOREGROUND, + .order_in_layer = 0, + .size = vec2(0, 70), + } + ); + ParticleEmitter & emitter_2 = smoke_particles_2.add_component<ParticleEmitter>( + smoke_sprite_2, + ParticleEmitter::Data { + .emission_rate = 30, + .min_speed = 40, + .max_speed = 100, + .min_angle = -45, + .max_angle = 5, + .end_lifespan = 3, + } + ); + emitter_2.active = false; +} diff --git a/game/background/StartSubScene.h b/game/background/StartSubScene.h new file mode 100644 index 0000000..c83e3d5 --- /dev/null +++ b/game/background/StartSubScene.h @@ -0,0 +1,20 @@ +#pragma once + +#include <crepe/types.h> + +namespace crepe { +class Scene; +class GameObject; +} // namespace crepe + +class StartSubScene { +public: + float create(crepe::Scene & scn, float begin_x); + +private: + void add_lamp(crepe::GameObject & obj, crepe::vec2 offset, unsigned int fps = 10); + void add_table(crepe::GameObject & obj, crepe::vec2 offset); + void add_light(crepe::GameObject & obj, crepe::vec2 offset); + void add_jetpack_stand(crepe::GameObject & obj, crepe::vec2 offset); + void create_wall_fragments(crepe::Scene & scn, float begin_x); +}; diff --git a/game/coins/CoinPoolSubScene.cpp b/game/coins/CoinPoolSubScene.cpp new file mode 100644 index 0000000..f8b5b70 --- /dev/null +++ b/game/coins/CoinPoolSubScene.cpp @@ -0,0 +1,13 @@ +#include "CoinPoolSubScene.h" +#include "CoinSubScene.h" + +using namespace crepe; +using namespace std; + +void CoinPoolSubScene::create_coins(crepe::Scene & scn) { + int amount = 0; + CoinSubScene coin; + while (amount < this->MAXIMUM_AMOUNT) { + amount = coin.create(scn, amount); + } +} diff --git a/game/coins/CoinPoolSubScene.h b/game/coins/CoinPoolSubScene.h new file mode 100644 index 0000000..07626d6 --- /dev/null +++ b/game/coins/CoinPoolSubScene.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Scene.h> + +class CoinPoolSubScene { +public: + void create_coins(crepe::Scene & scn); + +private: + static constexpr int MAXIMUM_AMOUNT = 100; +}; diff --git a/game/coins/CoinScript.cpp b/game/coins/CoinScript.cpp new file mode 100644 index 0000000..514f4de --- /dev/null +++ b/game/coins/CoinScript.cpp @@ -0,0 +1,71 @@ +#include "CoinScript.h" + +#include "manager/SaveManager.h" + +#include "../Config.h" +#include "../hud/HudScript.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Sprite.h> + +using namespace crepe; +using namespace std; + +bool CoinScript::on_collision(const CollisionEvent & collisionData) { + if (collisionData.info.other.metadata.tag != "coin") return false; + if (!this->get_components_by_name<Sprite>(collisionData.info.other.metadata.name) + .front() + .get() + .active) + return false; + this->get_components_by_name<Sprite>(collisionData.info.other.metadata.name) + .front() + .get() + .active + = false; + this->get_components_by_name<CircleCollider>(collisionData.info.other.metadata.name) + .front() + .get() + .active + = false; + this->amount++; + + AudioSource & audio = this->get_components_by_id<AudioSource>( + collisionData.info.other.metadata.game_object_id + ) + .front(); + audio.play(); + + this->get_components_by_name<Sprite>(collisionData.info.other.metadata.name) + .back() + .get() + .active + = true; + this->get_components_by_name<Animator>(collisionData.info.other.metadata.name) + .back() + .get() + .active + = true; + + return false; +} + +void CoinScript::init() { + this->subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { + return this->on_collision(ev); + }); +} + +void CoinScript::fixed_update(crepe::duration_t dt) { + this->trigger_event(GetCoinEvent { + .amount_of_coins = this->amount, + }); +} + +bool CoinScript::save() { + SaveManager & savemgr = this->get_save_manager(); + savemgr.set(TOTAL_COINS_RUN, this->amount); + return false; +} diff --git a/game/coins/CoinScript.h b/game/coins/CoinScript.h new file mode 100644 index 0000000..5718025 --- /dev/null +++ b/game/coins/CoinScript.h @@ -0,0 +1,14 @@ +#pragma once + +#include "api/Script.h" + +class CoinScript : public crepe::Script { +public: + void init() override; + void fixed_update(crepe::duration_t dt) override; + bool on_collision(const crepe::CollisionEvent & collisionData); + bool save(); + +private: + int amount = 0; +}; diff --git a/game/coins/CoinSubScene.cpp b/game/coins/CoinSubScene.cpp new file mode 100644 index 0000000..d154819 --- /dev/null +++ b/game/coins/CoinSubScene.cpp @@ -0,0 +1,64 @@ +#include "CoinSubScene.h" + +#include "../Config.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> + +using namespace crepe; +using namespace std; + +int CoinSubScene::create(Scene & scn, int coin_counter) { + vec2 size = {20, 20}; + + string unique_name = "coin_" + to_string(coin_counter++); + + GameObject coin = scn.new_object(unique_name.c_str(), "coin", vec2 {650, 0}, 0, 1); + coin.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::KINEMATIC, + .kinematic_collision = false, + .collision_layers = {COLL_LAY_PLAYER}, + }); + coin.add_component<CircleCollider>((size.x / 2) - 3).active = false; + crepe::OptionalRef<crepe::Sprite> coin_sprite = coin.add_component<Sprite>( + Asset {"asset/coin/coin1_TVOS.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_COINS, + .order_in_layer = 0, + .size = size, + } + ); + coin_sprite->active = false; + coin.add_component<Animator>( + coin_sprite, ivec2 {32, 32}, uvec2 {8, 1}, + Animator::Data { + .fps = 15, + .looping = true, + } + ); + coin.add_component<AudioSource>(Asset {"asset/sfx/coin_pickup_1.ogg"}).volume = 3; + + Sprite & pick_up = coin.add_component<Sprite>( + Asset {"asset/coin/coinCollect1_TVOS.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_COINS, + .order_in_layer = 1, + .size = size * 2, + } + ); + pick_up.active = false; + coin.add_component<Animator>( + pick_up, ivec2 {64, 64}, uvec2 {5, 1}, + Animator::Data { + .fps = 5, + .looping = false, + } + ) + .active + = false; + + return coin_counter; +} diff --git a/game/coins/CoinSubScene.h b/game/coins/CoinSubScene.h new file mode 100644 index 0000000..7a1c60a --- /dev/null +++ b/game/coins/CoinSubScene.h @@ -0,0 +1,12 @@ +#pragma once + +#include <crepe/api/GameObject.h> + +namespace crepe { +class Scene; +} + +class CoinSubScene { +public: + int create(crepe::Scene & scn, int coin_counter); +}; diff --git a/game/coins/CoinSystemScript.cpp b/game/coins/CoinSystemScript.cpp new file mode 100644 index 0000000..f9816c9 --- /dev/null +++ b/game/coins/CoinSystemScript.cpp @@ -0,0 +1,258 @@ +#include "CoinSystemScript.h" + +#include <random> + +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> + +using namespace crepe; +using namespace std; + +void CoinSystemScript::init() { engine.seed(rd()); } + +void CoinSystemScript::add_location(const crepe::vec2 & location) { + coin_locations.push_back(CoinData(location)); +} + +float CoinSystemScript::preset_1(const vec2 & begin_position) { + vec2 top = {begin_position.x, begin_position.y - (this->ROW_OFFSET_1)}; + vec2 bottom = {begin_position.x, begin_position.y + (this->ROW_OFFSET_1)}; + + // Add locations for the top row + for (int i = 0; i < COLUM_AMOUNT_1; ++i) { + add_location(top); + top.x += this->COLUM_OFFSET_1; + } + + // Add locations for the bottom row + bottom.x += this->COLUM_OFFSET_1 * COLUM_AMOUNT_1; + for (int i = 0; i < COLUM_AMOUNT_1; ++i) { + add_location(bottom); + bottom.x += this->COLUM_OFFSET_1; + } + + // Add locations for the next set of the top row + top.x += this->COLUM_OFFSET_1 * COLUM_AMOUNT_1; + for (int i = 0; i < COLUM_AMOUNT_1; ++i) { + add_location(top); + top.x += this->COLUM_OFFSET_1; + } + + // Add locations for the next set of the bottom row + bottom.x += this->COLUM_OFFSET_1 * COLUM_AMOUNT_1; + for (int i = 0; i < COLUM_AMOUNT_1; ++i) { + add_location(bottom); + bottom.x += this->COLUM_OFFSET_1; + } + + return bottom.x - begin_position.x; +} + +float CoinSystemScript::preset_2(const vec2 & begin_position) { + vec2 top + = {begin_position.x + this->COLUM_OFFSET_2, begin_position.y - this->ROW_OFFSET_2}; + vec2 middle = begin_position; + vec2 bottom + = {begin_position.x + this->COLUM_OFFSET_2, begin_position.y + this->ROW_OFFSET_2}; + + // Add locations for the next set of the bottom row + for (int i = 0; i < COLUM_AMOUNT_2 - 2; ++i) { + add_location(bottom); + bottom.x += this->COLUM_OFFSET_2; + } + + // Add locations for the next set of the middle row + for (int i = 0; i < COLUM_AMOUNT_2; ++i) { + add_location(middle); + middle.x += this->COLUM_OFFSET_2; + } + + // Add locations for the next set of the top row + for (int i = 0; i < COLUM_AMOUNT_2 - 2; ++i) { + add_location(top); + top.x += this->COLUM_OFFSET_2; + } + + return middle.x - begin_position.x; +} + +float CoinSystemScript::preset_3(const vec2 & begin_position) { + vec2 location = {begin_position.x, begin_position.y - (this->ROW_OFFSET_3)}; + + // Add locations for the top row + for (int i = 0; i < COLUM_AMOUNT_3; ++i) { + add_location(location); + location.x += this->COLUM_OFFSET_3; + } + + // Add locations for the bottom row + location.y += this->ROW_OFFSET_3; + location.x += this->COLUM_OFFSET_3; + for (int i = 0; i < COLUM_AMOUNT_3; ++i) { + add_location(location); + location.x += this->COLUM_OFFSET_3; + } + + // Add locations for the next set of the top row + location.y += this->ROW_OFFSET_3; + location.x += this->COLUM_OFFSET_3; + for (int i = 0; i < COLUM_AMOUNT_3; ++i) { + add_location(location); + location.x += this->COLUM_OFFSET_3; + } + + return location.x - begin_position.x; +} + +float CoinSystemScript::preset_4(const vec2 & begin_position) { + vec2 location = {begin_position.x, begin_position.y + (this->ROW_OFFSET_4)}; + + // Add locations for the top row + for (int i = 0; i < COLUM_AMOUNT_4; ++i) { + add_location(location); + location.x += this->COLUM_OFFSET_4; + } + + // Add locations for the bottom row + location.y -= this->ROW_OFFSET_4; + location.x += this->COLUM_OFFSET_4; + for (int i = 0; i < COLUM_AMOUNT_4; ++i) { + add_location(location); + location.x += this->COLUM_OFFSET_4; + } + + // Add locations for the next set of the top row + location.y -= this->ROW_OFFSET_4; + location.x += this->COLUM_OFFSET_4; + for (int i = 0; i < COLUM_AMOUNT_4; ++i) { + add_location(location); + location.x += this->COLUM_OFFSET_4; + } + + return location.x - begin_position.x; +} + +float CoinSystemScript::preset_5(const vec2 & begin_position) { + vec2 location = {begin_position.x, begin_position.y - ROW_OFFSET_5 / 2}; + for (int i = 0; i < COLUM_AMOUNT_5; ++i) { + add_location(location); + location.x += this->COLUM_OFFSET_5; + } + return location.x - begin_position.x; +} + +void CoinSystemScript::frame_update(crepe::duration_t dt) { + this->despawn_coins(); + this->generate_locations(); + this->spawn_coins(); +} + +void CoinSystemScript::despawn_coins() { + // Get the current x-position of the CoinSystem's Transform component + float position = this->get_component<Transform>().position.x; + + // Retrieve all active coin sprites tagged as "coin" + RefVector<Sprite> coin_sprites = this->get_components_by_tag<Sprite>("coin"); + + for (Sprite & coin_sprite : coin_sprites) { + if (!coin_sprite.active) continue; // Skip inactive sprites + + // Retrieve the corresponding Transform, Metadata, and CircleCollider components + Transform & coin_transform + = this->get_components_by_id<Transform>(coin_sprite.game_object_id).front().get(); + Metadata & coin_metadata + = this->get_components_by_id<Metadata>(coin_sprite.game_object_id).front().get(); + CircleCollider & coin_collider + = this->get_components_by_id<CircleCollider>(coin_sprite.game_object_id) + .front() + .get(); + + // Check if the coin is out of bounds based on DESPAWN_DISTANCE + if (coin_transform.position.x < position - this->DESPAWN_DISTANCE) { + // Find the coin in the coin_locations vector using its name + auto it = std::find_if( + coin_locations.begin(), coin_locations.end(), + [&coin_metadata](const CoinData & data) { + return data.name == coin_metadata.name; + } + ); + + // If a match is found, erase it from coin_locations + if (it != coin_locations.end()) { + coin_locations.erase(it); + coin_sprite.active = false; + coin_collider.active = false; + } + } + } +} + +void CoinSystemScript::spawn_coins() { + // Get the current x-position of the CoinSystem's Transform component + float position = this->get_component<Transform>().position.x; + + // Iterate through the list of coin locations + for (auto & coin : coin_locations) { + // Skip this coin if it is already active + if (coin.active) continue; + // Skip this coin if it is not within the defined spawn area + if (coin.start_location.x < this->SPAWN_DISTANCE + position + || coin.start_location.x > this->SPAWN_AREA + this->SPAWN_DISTANCE + position) + continue; + + // Retrieve all sprites tagged as "coin" + RefVector<Sprite> coin_sprites = this->get_components_by_tag<Sprite>("coin"); + + // Check for an available (inactive) coin sprite + for (Sprite & coin_sprite : coin_sprites) { + // Skip this sprite if it is already active + if (coin_sprite.active) continue; + if (coin_sprite.data.order_in_layer == 1) { + coin_sprite.active = false; + continue; + } + + // Found an available (inactive) coin sprite + // Retrieve its associated components + Transform & coin_transform + = this->get_components_by_id<Transform>(coin_sprite.game_object_id) + .front() + .get(); + Metadata & coin_metadata + = this->get_components_by_id<Metadata>(coin_sprite.game_object_id) + .front() + .get(); + CircleCollider & coin_collider + = this->get_components_by_id<CircleCollider>(coin_sprite.game_object_id) + .front() + .get(); + + // Assign data and set active + coin.name = coin_metadata.name; + coin.active = true; + coin_sprite.active = true; + coin_collider.active = true; + coin_transform.position = coin.start_location; + + // Break out of the inner loop since we've assigned this coin to an available sprite + break; + } + } +} + +void CoinSystemScript::generate_locations() { + float position = this->get_component<Transform>().position.x; + if (position + SPAWN_DISTANCE + SYSTEM_POSITION_OFFSET < this->system_position) return; + + std::discrete_distribution<int> dist(weights.begin(), weights.end()); + int selected_index = dist(engine); + + std::uniform_real_distribution<float> space_dist(SPAWN_SPACING_MIN, SPAWN_SPACING_MAX); + float spacing = space_dist(engine); + + // Call the corresponding function and return the new x position + this->system_position += functions[selected_index]({this->system_position, 0}); + this->system_position += spacing; +} diff --git a/game/coins/CoinSystemScript.h b/game/coins/CoinSystemScript.h new file mode 100644 index 0000000..5c94273 --- /dev/null +++ b/game/coins/CoinSystemScript.h @@ -0,0 +1,107 @@ +#pragma once + +#include <random> +#include <string> + +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> + +class CoinSystemScript : public crepe::Script { +private: + struct CoinData { + crepe::vec2 start_location = {0, 0}; + std::string name = ""; + bool active = false; + CoinData(crepe::vec2 start_location) + : start_location(start_location), + name(""), + active(false) {} + }; + +public: + CoinSystemScript() {}; + void init() override; + void frame_update(crepe::duration_t dt) override; + +private: + void add_location(const crepe::vec2 & location); + void despawn_coins(); + void spawn_coins(); + void generate_locations(); + float preset_1(const crepe::vec2 & begin_position); + float preset_2(const crepe::vec2 & begin_position); + float preset_3(const crepe::vec2 & begin_position); + float preset_4(const crepe::vec2 & begin_position); + float preset_5(const crepe::vec2 & begin_position); + +private: + std::vector<std::function<float(const crepe::vec2 &)>> functions + = {[this](const crepe::vec2 & pos) { return preset_1(pos); }, + [this](const crepe::vec2 & pos) { return preset_2(pos); }, + [this](const crepe::vec2 & pos) { return preset_3(pos); }, + [this](const crepe::vec2 & pos) { return preset_4(pos); }, + [this](const crepe::vec2 & pos) { return preset_5(pos); }}; + std::vector<int> weights = {20, 20, 20, 20, 20}; + std::random_device rd; + std::default_random_engine engine; + float system_position = 1400; + static constexpr float SYSTEM_POSITION_OFFSET = 200; + +private: + static constexpr float SPAWN_SPACING_MIN = 400; + static constexpr float SPAWN_SPACING_MAX = 1000; + static constexpr float SPAWN_DISTANCE = 600; + static constexpr float DESPAWN_DISTANCE = 600; + static constexpr float SPAWN_AREA = 50; + std::vector<CoinData> coin_locations; + +private: + // preset one settings + // ***** ***** + // + // + // + // ***** ***** + static constexpr float ROW_OFFSET_1 = 100; + static constexpr float COLUM_OFFSET_1 = 25; + static constexpr int COLUM_AMOUNT_1 = 5; + +private: + // preset two settings + // + // ******** + // ********** + // ******** + // + static constexpr float ROW_OFFSET_2 = 25; + static constexpr float COLUM_OFFSET_2 = 25; + static constexpr int COLUM_AMOUNT_2 = 10; + // preset three settings + // *** + // + // *** + // + // *** + static constexpr float ROW_OFFSET_3 = 100; + static constexpr float COLUM_OFFSET_3 = 25; + static constexpr int COLUM_AMOUNT_3 = 3; + // preset four settings + // *** + // + // *** + // + // *** + static constexpr float ROW_OFFSET_4 = 100; + static constexpr float COLUM_OFFSET_4 = 25; + static constexpr int COLUM_AMOUNT_4 = 3; + // preset five settings + // + // *** + // + static constexpr float ROW_OFFSET_5 = 25; + static constexpr float COLUM_OFFSET_5 = 25; + static constexpr int COLUM_AMOUNT_5 = 3; +}; diff --git a/game/enemy/BattleScript.cpp b/game/enemy/BattleScript.cpp new file mode 100644 index 0000000..798cbb9 --- /dev/null +++ b/game/enemy/BattleScript.cpp @@ -0,0 +1,61 @@ +#include "BattleScript.h" +#include "EnemyConfig.h" +#include "EnemyScript.h" +#include "api/Transform.h" +#include <crepe/api/AI.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Metadata.h> +using namespace std; +using namespace crepe; + +BattleScript::BattleScript() { engine.seed(rd()); } +void BattleScript::init() { + std::uniform_int_distribution<int> dist(2, 10); + int random_enemy_amount = dist(this->engine); + // this->create_battle(random_enemy_amount); + this->subscribe<BattleStartEvent>([this](const BattleStartEvent & e) -> bool { + return this->create_battle(e); + }); +} +void BattleScript::fixed_update(duration_t dt) { + if (!battle_active) return; + bool enemies_alive = false; + RefVector<AI> enemy_ai = this->get_components_by_tag<AI>("enemy"); + + for (AI & ai : enemy_ai) { + if (ai.active) { + enemies_alive = true; + } + } + if (!enemies_alive) { + this->battle_active = false; + this->trigger_event<BattleWonEvent>(); + } +} +bool BattleScript::create_battle(const BattleStartEvent & e) { + this->battle_active = e.battle; + this->spawn_enemies(e.num_enemies); + return false; +} +void BattleScript::spawn_enemies(int amount) { + RefVector<AI> enemy_ai = this->get_components_by_tag<AI>("enemy"); + std::uniform_real_distribution<float> dist(70, 150); + int spawned = 0; + for (int i = 0; i < ENEMY_POOL_MAX; i++) { + AI & ai = enemy_ai[i]; + Transform & enemy_transform + = this->get_components_by_id<Transform>(ai.game_object_id).front(); + if (ai.active == true || enemy_transform.position != ENEMY_POOL_LOCATION) continue; + this->queue_event<SpawnEnemyEvent>( + SpawnEnemyEvent { + .speed = dist(engine), + .column = i, + }, + ai.game_object_id + ); + spawned++; + if (spawned >= amount) { + return; + } + } +} diff --git a/game/enemy/BattleScript.h b/game/enemy/BattleScript.h new file mode 100644 index 0000000..57aa16c --- /dev/null +++ b/game/enemy/BattleScript.h @@ -0,0 +1,26 @@ +#pragma once + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> +#include <random> +struct BattleWonEvent : public crepe::Event {}; + +struct BattleStartEvent : public crepe::Event { +public: + int num_enemies = 0; + bool battle = false; +}; +class BattleScript : public crepe::Script { +public: + BattleScript(); + void init() override; + void fixed_update(crepe::duration_t dt) override; + +private: + bool battle_active = false; + std::random_device rd; + std::default_random_engine engine; + void spawn_enemies(int amount); + bool create_battle(const BattleStartEvent & e); +}; diff --git a/game/enemy/EnemyBulletPool.cpp b/game/enemy/EnemyBulletPool.cpp new file mode 100644 index 0000000..3ee4816 --- /dev/null +++ b/game/enemy/EnemyBulletPool.cpp @@ -0,0 +1,11 @@ +#include "EnemyBulletPool.h" +#include "EnemyBulletSubScene.h" +using namespace std; + +void EnemyBulletPool::create_bullets(crepe::Scene & scn) { + EnemyBulletSubScene bullet; + int amount = 0; + while (amount < this->MAXIMUM_AMOUNT) { + amount = bullet.create(scn, amount); + } +} diff --git a/game/enemy/EnemyBulletPool.h b/game/enemy/EnemyBulletPool.h new file mode 100644 index 0000000..ee53fc4 --- /dev/null +++ b/game/enemy/EnemyBulletPool.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Scene.h> + +class EnemyBulletPool { +public: + void create_bullets(crepe::Scene & scn); + +private: + static constexpr int MAXIMUM_AMOUNT = 20; +}; diff --git a/game/enemy/EnemyBulletScript.cpp b/game/enemy/EnemyBulletScript.cpp new file mode 100644 index 0000000..d208a88 --- /dev/null +++ b/game/enemy/EnemyBulletScript.cpp @@ -0,0 +1,40 @@ +#include "EnemyBulletScript.h" +#include <crepe/api/Camera.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Rigidbody.h> +#include <iostream> + +#include "EnemyConfig.h" +using namespace crepe; +using namespace std; +void EnemyBulletScript::init() { + this->subscribe<CollisionEvent>([this](const CollisionEvent & e) -> bool { + return this->on_collide(e); + }); +} +void EnemyBulletScript::fixed_update(crepe::duration_t dt) { + Transform & transform = this->get_component<Transform>(); + Camera & camera = this->get_components_by_name<Camera>("camera").front(); + Transform & cam_transform = this->get_components_by_name<Transform>("camera").front(); + Rigidbody & bullet_body = this->get_component<Rigidbody>(); + //move + transform.position += bullet_body.data.linear_velocity * dt.count(); + vec2 half_screen = camera.viewport_size / 2; + float despawn_location = cam_transform.position.x - half_screen.x - 50; + if (transform.position.x < despawn_location) { + this->despawn_bullet(); + } +} + +void EnemyBulletScript::despawn_bullet() { + Transform & transform = this->get_component<Transform>(); + Rigidbody & bullet_body = this->get_component<Rigidbody>(); + bullet_body.active = false; + transform.position = ENEMY_BULLET_POOL_LOCATION; +} + +bool EnemyBulletScript::on_collide(const CollisionEvent & e) { + //cout << "collision happened with " << e.info.other.metadata.tag << endl; + this->despawn_bullet(); + return false; +} diff --git a/game/enemy/EnemyBulletScript.h b/game/enemy/EnemyBulletScript.h new file mode 100644 index 0000000..7dab751 --- /dev/null +++ b/game/enemy/EnemyBulletScript.h @@ -0,0 +1,11 @@ +#pragma once +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Script.h> + +class EnemyBulletScript : public crepe::Script { +public: + void init() override; + void fixed_update(crepe::duration_t dt) override; + bool on_collide(const crepe::CollisionEvent & e); + void despawn_bullet(); +}; diff --git a/game/enemy/EnemyBulletSubScene.cpp b/game/enemy/EnemyBulletSubScene.cpp new file mode 100644 index 0000000..bc31ba8 --- /dev/null +++ b/game/enemy/EnemyBulletSubScene.cpp @@ -0,0 +1,52 @@ +#include <string> + +#include "../Config.h" +#include "EnemyConfig.h" +#include <crepe/api/AI.h> +#include <crepe/api/Animator.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +#include "../Random.h" +#include "EnemyBulletScript.h" +#include "EnemyBulletSubScene.h" +#include "EnemyScript.h" +#include "api/Color.h" +using namespace crepe; +using namespace std; +int EnemyBulletSubScene::create(Scene & scn, int counter) { + string unique_name = "enemy_bullet_" + to_string(counter++); + GameObject bullet = scn.new_object( + unique_name.c_str(), "enemy_bullet", ENEMY_BULLET_POOL_LOCATION, 0, 1 + ); + + Rigidbody & bullet_body = bullet.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 0, + .body_type = Rigidbody::BodyType::KINEMATIC, + .linear_velocity = vec2 {-300, 0}, + .kinematic_collision = false, + .collision_layers = {COLL_LAY_MISSILE, COLL_LAY_ZAPPER}, + .collision_layer = COLL_LAY_BULLET + }); + bullet_body.active = false; + BoxCollider & bullet_collider = bullet.add_component<BoxCollider>(vec2(40, 10)); + //bullet_collider.active = false; + Asset bullet_asset {"asset/other_effects/effect_smgbullet_x2.png"}; + Sprite & bullet_sprite = bullet.add_component<Sprite>( + bullet_asset, + Sprite::Data { + .color = Color::BLUE, + .flip = {true, false}, + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .order_in_layer = 1, + .size = vec2(60, 0), + } + ); + bullet.add_component<BehaviorScript>().set_script<EnemyBulletScript>(); + return counter; +} diff --git a/game/enemy/EnemyBulletSubScene.h b/game/enemy/EnemyBulletSubScene.h new file mode 100644 index 0000000..ac78ad9 --- /dev/null +++ b/game/enemy/EnemyBulletSubScene.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class EnemyBulletSubScene { +public: + int create(crepe::Scene & scn, int counter); +}; diff --git a/game/enemy/EnemyConfig.h b/game/enemy/EnemyConfig.h new file mode 100644 index 0000000..f9fb469 --- /dev/null +++ b/game/enemy/EnemyConfig.h @@ -0,0 +1,8 @@ +#pragma once +#include <crepe/types.h> + +//button config +// static constexpr crepe::vec2 PLAYER_BULLET_POOL_LOCATION = {0, -850}; +static constexpr crepe::vec2 ENEMY_BULLET_POOL_LOCATION = {0, -750}; +static constexpr crepe::vec2 ENEMY_POOL_LOCATION = {0, -650}; +static constexpr int ENEMY_POOL_MAX = 12; diff --git a/game/enemy/EnemyPool.cpp b/game/enemy/EnemyPool.cpp new file mode 100644 index 0000000..135fc35 --- /dev/null +++ b/game/enemy/EnemyPool.cpp @@ -0,0 +1,10 @@ +#include "EnemyPool.h" +#include "EnemySubScene.h" +using namespace std; +void EnemyPool::create_enemies(crepe::Scene & scn) { + EnemySubScene enemy; + int amount = 0; + while (amount < ENEMY_POOL_MAX) { + amount = enemy.create(scn, amount); + } +} diff --git a/game/enemy/EnemyPool.h b/game/enemy/EnemyPool.h new file mode 100644 index 0000000..cfd0b1c --- /dev/null +++ b/game/enemy/EnemyPool.h @@ -0,0 +1,8 @@ +#pragma once + +#include "EnemyConfig.h" +#include <crepe/api/Scene.h> +class EnemyPool { +public: + void create_enemies(crepe::Scene & scn); +}; diff --git a/game/enemy/EnemyScript.cpp b/game/enemy/EnemyScript.cpp new file mode 100644 index 0000000..0822c29 --- /dev/null +++ b/game/enemy/EnemyScript.cpp @@ -0,0 +1,206 @@ +#include "EnemyScript.h" +#include "../Config.h" +#include "../Random.h" +#include "../enemy/EnemyConfig.h" +#include "api/Color.h" +#include "api/Sprite.h" +#include <crepe/api/AI.h> +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> +#include <random> +using namespace crepe; +using namespace std; +EnemyScript::EnemyScript() { + engine.seed(rd()); + this->last_fired = std::chrono::steady_clock::now(); + this->shot_delay = std::chrono::duration<float>(1.5 + Random::f(2, 0)); +} +void EnemyScript::init() { + Metadata & meta = this->get_component<Metadata>(); + this->subscribe<SpawnEnemyEvent>( + [this](const SpawnEnemyEvent & e) -> bool { return this->spawn_enemy(e); }, + meta.game_object_id + ); + this->subscribe<CollisionEvent>([this](const CollisionEvent & e) -> bool { + return this->on_collide(e); + }); +}; +void EnemyScript::fixed_update(duration_t dt) { + if (!spawned) return; + auto now = std::chrono::steady_clock::now(); + std::chrono::duration<float> elapsed_hit = now - last_hit; + //hit blink timer + if (elapsed_hit > blink_time) { + set_hit_blink(false); + } + Transform & transform = this->get_component<Transform>(); + if (!this->alive) { + Camera & camera = this->get_components_by_name<Camera>("camera").front(); + Transform & cam_transform = this->get_components_by_name<Transform>("camera").front(); + vec2 half_screen = camera.viewport_size / 2; + float x_value = cam_transform.position.x - half_screen.x - 100; + if (transform.position.x < x_value) { + this->despawn_enemy(); + } + return; + } + + Transform & player_transform = this->get_components_by_name<Transform>("player").front(); + Rigidbody & enemy_body = this->get_component<Rigidbody>(); + AI & ai_component = this->get_component<AI>(); + + float direction_to_player_y = player_transform.position.y - transform.position.y; + float distance_to_player_y = std::abs(direction_to_player_y); + + float adjustment_speed = speed * (distance_to_player_y / MAX_DISTANCE); + adjustment_speed = std::clamp(adjustment_speed, MIN_SPEED, MAX_SPEED); + Rigidbody & player_body = this->get_components_by_tag<Rigidbody>("player").front(); + // move path nodes + for (vec2 & path_node : ai_component.path) { + path_node.y += (direction_to_player_y > 0 ? 1 : -1) * adjustment_speed * dt.count(); + path_node.x += player_body.data.linear_velocity.x * dt.count(); + } + //bullet fire logic: + + std::chrono::duration<float> elapsed = now - last_fired; + if (elapsed > shot_delay) { + this->shoot(transform.position); + last_fired = now; + this->shot_delay = std::chrono::duration<float>(Random::f(3, 1.5)); + } +} + +bool EnemyScript::spawn_enemy(const SpawnEnemyEvent & e) { + + this->speed = e.speed; + this->alive = true; + this->spawned = true; + this->health = 2; + RefVector<Animator> animators = this->get_components<Animator>(); + for (Animator & anim : animators) { + anim.active = false; + anim.set_anim(0); + } + RefVector<Sprite> sprites = this->get_components<Sprite>(); + for (Sprite & sprite : sprites) { + sprite.data.position_offset.x = 0; + } + Sprite & jetpack = sprites[2]; + jetpack.data.position_offset.x = 20; + Sprite & gun = sprites[3]; + gun.data.position_offset.x = -20; + AI & ai_component = this->get_component<AI>(); + Transform & transform = this->get_component<Transform>(); + Camera & camera = this->get_components_by_name<Camera>("camera").front(); + Transform & cam_transform = this->get_components_by_name<Transform>("camera").front(); + Rigidbody & rb = this->get_component<Rigidbody>(); + rb.data.collision_layers = {COLL_LAY_BOT_TOP, COLL_LAY_PLAYER_BULLET}; + rb.data.collision_layer = COLL_LAY_ENEMY; + vec2 half_screen = camera.viewport_size / 2; + float x_value = cam_transform.position.x + half_screen.x - 40 * (1 + e.column); + uniform_real_distribution<float> dist( + cam_transform.position.y - half_screen.y + 100, + cam_transform.position.y + half_screen.y - 100 + ); + float random_height = dist(engine); + vec2 spawn_location + = {cam_transform.position.x + camera.viewport_size.x / 2 + 100, random_height}; + transform.position = spawn_location; + ai_component.path.clear(); + ai_component.make_oval_path(10, 30, vec2 {x_value, random_height}, 1.5708, true); + ai_component.active = true; + this->last_fired = std::chrono::steady_clock::now(); + + return false; +} + +void EnemyScript::set_hit_blink(bool status) { + RefVector<Sprite> sprites = this->get_components<Sprite>(); + for (Sprite & sprite : sprites) { + if (status) { + sprite.data.color = Color::RED; + continue; + } + sprite.data.color = Color::WHITE; + } +} + +bool EnemyScript::on_collide(const CollisionEvent & e) { + if (!this->alive) return false; + if (e.info.other.metadata.tag == "player_bullet") { + this->health--; + last_hit = std::chrono::steady_clock::now(); + //Sprite& sprite; + set_hit_blink(true); + if (health <= 0) { + this->death(); + } + } + + //body_animator.play(); + + return false; +} +void EnemyScript::death() { + + Rigidbody & rb = this->get_component<Rigidbody>(); + Transform & tr = this->get_component<Transform>(); + RefVector<Animator> animators = this->get_components<Animator>(); + for (Animator & anim : animators) { + anim.active = false; + anim.set_anim(3); + } + RefVector<Sprite> sprites = this->get_components<Sprite>(); + for (Sprite & sprite : sprites) { + sprite.data.position_offset.x = 15; + } + rb.data.linear_velocity_coefficient = {0.5, 1}; + rb.data.collision_layers = {COLL_LAY_BOT_TOP}; + rb.data.collision_layer = 0; + + rb.data.gravity_scale = 1; + tr.rotation = 90; + AI & ai = this->get_component<AI>(); + ai.active = false; + this->alive = false; + AI & ai_component = this->get_component<AI>(); + ai_component.active = false; +} +void EnemyScript::despawn_enemy() { + Transform & transform = this->get_component<Transform>(); + Rigidbody & rb = this->get_component<Rigidbody>(); + rb.data.gravity_scale = 0; + rb.data.linear_velocity = {0, 0}; + transform.rotation = 0; + transform.position = ENEMY_POOL_LOCATION; + + this->spawned = false; +} + +void EnemyScript::shoot(const vec2 & location) { + RefVector<Transform> bullet_transforms + = this->get_components_by_tag<Transform>("enemy_bullet"); + + for (Transform & bullet_pos : bullet_transforms) { + if (bullet_pos.position.x == 0 && bullet_pos.position.y == -750) { + + bullet_pos.position = location; + bullet_pos.position.x -= 20; + Rigidbody & bullet_body + = this->get_components_by_id<Rigidbody>(bullet_pos.game_object_id).front(); + BoxCollider bullet_collider + = this->get_components_by_id<BoxCollider>(bullet_pos.game_object_id).front(); + bullet_collider.active = true; + bullet_body.active = true; + AudioSource & audio = this->get_component<AudioSource>(); + audio.play(); + return; + } + } +} diff --git a/game/enemy/EnemyScript.h b/game/enemy/EnemyScript.h new file mode 100644 index 0000000..be71a78 --- /dev/null +++ b/game/enemy/EnemyScript.h @@ -0,0 +1,37 @@ +#pragma once +#include <chrono> +#include <crepe/api/Camera.h> +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> +#include <random> +struct SpawnEnemyEvent : public crepe::Event { + float speed = 0; + int column = 0; +}; +class EnemyScript : public crepe::Script { +public: + EnemyScript(); + void init() override; + void fixed_update(crepe::duration_t dt) override; + void shoot(const crepe::vec2 & position); + bool on_collide(const crepe::CollisionEvent & collisionData); + void despawn_enemy(); + bool spawn_enemy(const SpawnEnemyEvent & e); + void death(); + void set_hit_blink(bool status); + +private: + std::random_device rd; + std::default_random_engine engine; + bool alive = false; + bool spawned = false; + float speed = 50; + int health = 2; + const float MIN_SPEED = 20; + const float MAX_SPEED = 150; + const float MAX_DISTANCE = 200; + std::chrono::time_point<std::chrono::steady_clock> last_fired; + std::chrono::time_point<std::chrono::steady_clock> last_hit; + std::chrono::duration<float> shot_delay = std::chrono::duration<float>(0); + std::chrono::duration<float> blink_time = std::chrono::duration<float>(0.1); +}; diff --git a/game/enemy/EnemySubScene.cpp b/game/enemy/EnemySubScene.cpp new file mode 100644 index 0000000..c6aecec --- /dev/null +++ b/game/enemy/EnemySubScene.cpp @@ -0,0 +1,117 @@ +#include <string> + +#include <crepe/api/AI.h> +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +#include "../Config.h" +#include "EnemyConfig.h" +#include "EnemyScript.h" +#include "EnemySubScene.h" +using namespace crepe; +using namespace std; +//#TODO add sound +int EnemySubScene::create(Scene & scn, int enemy_counter) { + + string unique_name = "enemy_" + to_string(enemy_counter++); + GameObject enemy = scn.new_object(unique_name.c_str(), "enemy", ENEMY_POOL_LOCATION, 0, 1); + + enemy.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 0, + .body_type = Rigidbody::BodyType::DYNAMIC, + .max_linear_velocity = 400, + .collision_layers = {COLL_LAY_BOT_TOP, COLL_LAY_PLAYER_BULLET}, + .collision_layer = COLL_LAY_ENEMY, + + }); + // normal body + Asset enemy_body_asset {"asset/workers/worker2Body.png"}; + enemy.add_component<BoxCollider>(vec2(40, 60)); + Sprite & enemy_body_sprite = enemy.add_component<Sprite>( + enemy_body_asset, + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 0, + .size = vec2(0, 50), + } + ); + Animator & body_animator = enemy.add_component<Animator>( + enemy_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = 5, + .col = 1, + .row = 0, + .looping = false, + } + ); + body_animator.pause(); + + Asset enemy_head_asset {"asset/workers/worker2Head.png"}; + Sprite & enemy_head_sprite = enemy.add_component<Sprite>( + enemy_head_asset, + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 1, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + enemy.add_component<Animator>( + enemy_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = 5, + .looping = true, + } + ); + + //jetpack + //enemy.add_component<CircleCollider>(25, vec2(0, -20)); + Asset enemy_jetpack_asset {"asset/barry/jetpackDefault.png"}; + Sprite & enemy_jetpack_sprite = enemy.add_component<Sprite>( + enemy_jetpack_asset, + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 2, + .size = vec2(0, 60), + .position_offset = vec2(20, 0), + } + ); + enemy_jetpack_sprite.active = true; + enemy.add_component<Animator>( + enemy_jetpack_sprite, ivec2(32, 44), uvec2(4, 4), + Animator::Data { + .fps = 5, + .looping = true, + } + ); + //gun + Asset enemy_pistol_asset {"asset/workers/gun.png"}; + Sprite & enemy_pistol_sprite = enemy.add_component<Sprite>( + enemy_pistol_asset, + Sprite::Data { + .flip = {false, false}, + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 2, + .size = vec2(0, 20), + .position_offset = vec2(-20, 0), + } + ); + enemy.add_component<AudioSource>(Asset("asset/sfx/bike_gun_2.ogg")).volume = 0.1; + AI & ai_component = enemy.add_component<AI>(3000); + ai_component.path_follow_on(); + ai_component.active = false; + BehaviorScript & enemy_script + = enemy.add_component<BehaviorScript>().set_script<EnemyScript>(); + //enemy_script.active = false; + return enemy_counter; +} diff --git a/game/enemy/EnemySubScene.h b/game/enemy/EnemySubScene.h new file mode 100644 index 0000000..3899250 --- /dev/null +++ b/game/enemy/EnemySubScene.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class EnemySubScene { +public: + int create(crepe::Scene & scn, int enemy_counter); +}; diff --git a/game/hud/HudConfig.h b/game/hud/HudConfig.h new file mode 100644 index 0000000..facc298 --- /dev/null +++ b/game/hud/HudConfig.h @@ -0,0 +1,33 @@ +#pragma once +#include <crepe/types.h> + +static constexpr crepe::vec2 TOP_LEFT = {-530, -230}; +static constexpr const char * HUD_DISTANCE = "hud_distance"; +static constexpr const char * HUD_BEST = "hud_best"; +static constexpr const char * HUD_COINS = "hud_coins"; +static constexpr const char * HUD_FPS = "hud_fps"; + +// Distance +static constexpr const char * DISTANCE_PLACEHOLDER = "0000m"; +static constexpr const char * DISTANCE_UNIT = "m"; +static constexpr int DISTANCE_LENGTH = 5; +static constexpr float DISTANCE_CHAR_WIDTH = 12; +static constexpr float STEP_SIZE_DISTANCE = 100; + +// BEST +static constexpr const char * BEST = "BEST:"; +static constexpr int BEST_LENGTH = 5; +static constexpr float BEST_CHAR_WIDTH = 10; +static constexpr crepe::vec2 BEST_OFFSET = {0, 25}; + +// COINS +static constexpr const char * COINS = "0000"; +static constexpr int COINS_LENGTH = 4; +static constexpr float COINS_CHAR_WIDTH = 10; +static constexpr crepe::vec2 COINS_OFFSET = {0, 50}; + +// FPS +static constexpr const char * FPS = "00"; +static constexpr int FPS_LENGTH = 2; +static constexpr float FPS_CHAR_WIDTH = 10; +static constexpr crepe::vec2 FPS_OFFSET = {1030, 0}; diff --git a/game/hud/HudScript.cpp b/game/hud/HudScript.cpp new file mode 100644 index 0000000..e4aeae7 --- /dev/null +++ b/game/hud/HudScript.cpp @@ -0,0 +1,98 @@ +#include "HudScript.h" +#include "HudConfig.h" + +#include "../Config.h" +#include "../Events.h" +#include "api/KeyCodes.h" +#include "menus/endgame/EndGameSubScript.h" + +#include <climits> + +#include <crepe/api/Text.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/SaveManager.h> + +using namespace crepe; +using namespace std; + +void HudScript::init() { + savemgr = &this->get_save_manager(); + savemgr->set(TOTAL_COINS_RUN, 0); + Text & txt = this->get_components_by_name<Text>(HUD_BEST).front(); + string record + = BEST + to_string(savemgr->get<int>(DISTANCE_GAME, 0).get()) + DISTANCE_UNIT; + txt.text = record; + txt.dimensions = {BEST_CHAR_WIDTH * record.size(), (BEST_CHAR_WIDTH) * 2}; + txt.offset + = TOP_LEFT + FONTOFFSET + BEST_OFFSET + vec2 {record.size() * BEST_CHAR_WIDTH / 2, 0}; + + this->subscribe<GetCoinEvent>([this](const GetCoinEvent e) -> bool { + return this->get_coin(e); + }); + this->subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { + return this->toggle_fps(ev); + }); + this->subscribe<EndGameEvent>([this](const EndGameEvent e) -> bool { + return this->save(); + }); +} + +bool HudScript::toggle_fps(crepe::KeyPressEvent ev) { + if (ev.key != Keycode::D1) return false; + Text & txt_fps = this->get_components_by_name<Text>(HUD_FPS).front(); + this->show_fps = !this->show_fps; + if (this->show_fps) { + txt_fps.active = true; + } else { + txt_fps.active = false; + } + return true; +} + +void HudScript::frame_update(crepe::duration_t dt) { + + // Distance + Text & txt_dt = this->get_components_by_name<Text>(HUD_DISTANCE).front(); + Transform & tf = this->get_components_by_name<Transform>(PLAYER_NAME).front(); + string distance + = to_string(static_cast<int>(tf.position.x / STEP_SIZE_DISTANCE)) + DISTANCE_UNIT; + this->distance_st = distance; + txt_dt.text = distance; + txt_dt.dimensions = {DISTANCE_CHAR_WIDTH * distance.size(), (DISTANCE_CHAR_WIDTH) * 2}; + txt_dt.offset + = TOP_LEFT + FONTOFFSET + vec2 {distance.size() * DISTANCE_CHAR_WIDTH / 2, 0}; + + // Coins + Text & txt_co = this->get_components_by_name<Text>(HUD_COINS).front(); + string amount_of_coins = to_string(this->coin_amount); + this->coin_amount_st = amount_of_coins; + txt_co.text = amount_of_coins; + txt_co.dimensions = {COINS_CHAR_WIDTH * amount_of_coins.size(), (COINS_CHAR_WIDTH) * 2}; + txt_co.offset = TOP_LEFT + FONTOFFSET + COINS_OFFSET + + vec2 {amount_of_coins.size() * COINS_CHAR_WIDTH / 2, 0}; + + // FPS + Text & txt_fps = this->get_components_by_name<Text>(HUD_FPS).front(); + float fps = this->get_loop_timer().get_fps(); + string fps_amount = to_string(this->get_loop_timer().get_fps()); + txt_fps.text = fps_amount; + txt_fps.dimensions = {FPS_CHAR_WIDTH * fps_amount.size(), (FPS_CHAR_WIDTH) * 2}; + txt_fps.offset = TOP_LEFT + FONTOFFSET + FPS_OFFSET + + vec2 {fps_amount.size() * FPS_CHAR_WIDTH / 2, 0}; + if (fps >= 30) txt_fps.data.text_color = Color::YELLOW; + if (fps >= 50) txt_fps.data.text_color = Color::GREEN; + if (fps < 30) txt_fps.data.text_color = Color::RED; +} + +bool HudScript::get_coin(const GetCoinEvent e) { + this->coin_amount = e.amount_of_coins; + return true; +} + +bool HudScript::save() { + SaveManager & savemgr = this->get_save_manager(); + savemgr.set(TOTAL_COINS_RUN, this->coin_amount); + savemgr.set(DISTANCE_RUN, this->distance_st); + this->trigger_event<ShowScoreEvent>(); + return false; +} diff --git a/game/hud/HudScript.h b/game/hud/HudScript.h new file mode 100644 index 0000000..2b789db --- /dev/null +++ b/game/hud/HudScript.h @@ -0,0 +1,25 @@ +#pragma once + +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> +#include <crepe/manager/SaveManager.h> + +struct GetCoinEvent : public crepe::Event { + int amount_of_coins; +}; + +class HudScript : public crepe::Script { +public: + void init() override; + void frame_update(crepe::duration_t dt) override; + bool get_coin(const GetCoinEvent e); + bool toggle_fps(crepe::KeyPressEvent ev); + bool save(); + +private: + crepe::SaveManager * savemgr; + bool show_fps = false; + int coin_amount = 0; + std::string coin_amount_st = ""; + std::string distance_st = ""; +}; diff --git a/game/hud/HudSubScene.cpp b/game/hud/HudSubScene.cpp new file mode 100644 index 0000000..dcc07b4 --- /dev/null +++ b/game/hud/HudSubScene.cpp @@ -0,0 +1,68 @@ +#include "HudSubScene.h" +#include "HudConfig.h" + +#include "../Config.h" + +#include <crepe/api/GameObject.h> +#include <crepe/api/Text.h> + +using namespace crepe; +using namespace std; + +void HudSubScene::create(Scene & scn) { + + // Distance + GameObject hud_dis = scn.new_object(HUD_DISTANCE); + + crepe::vec2 size_distance + = {DISTANCE_CHAR_WIDTH * DISTANCE_LENGTH, (DISTANCE_CHAR_WIDTH) * 2}; + hud_dis.add_component<Text>( + size_distance, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + TOP_LEFT + FONTOFFSET + vec2 {DISTANCE_LENGTH * DISTANCE_CHAR_WIDTH / 2, 0}, + DISTANCE_PLACEHOLDER + ); + + // Best + GameObject hud_best = scn.new_object(HUD_BEST); + crepe::vec2 size_best = {BEST_CHAR_WIDTH * BEST_LENGTH, (BEST_CHAR_WIDTH) * 2}; + hud_best.add_component<Text>( + size_best, FONT, + Text::Data { + .world_space = false, + .text_color = Color::GREY, + }, + TOP_LEFT + FONTOFFSET + BEST_OFFSET + vec2 {BEST_LENGTH * BEST_CHAR_WIDTH / 2, 0}, BEST + ); + + // Coins + GameObject hud_coin = scn.new_object(HUD_COINS); + crepe::vec2 size_coin = {COINS_CHAR_WIDTH * COINS_LENGTH, (COINS_CHAR_WIDTH) * 2}; + hud_coin.add_component<Text>( + size_coin, FONT, + Text::Data { + .world_space = false, + .text_color = Color::GOLD, + }, + TOP_LEFT + FONTOFFSET + COINS_OFFSET + vec2 {COINS_LENGTH * COINS_CHAR_WIDTH / 2, 0}, + COINS + ); + + // Fps + GameObject hud_fps = scn.new_object(HUD_FPS); + crepe::vec2 size_fps = {FPS_CHAR_WIDTH * FPS_LENGTH, (FPS_CHAR_WIDTH) * 2}; + hud_fps + .add_component<Text>( + size_fps, FONT, + Text::Data { + .world_space = false, + .text_color = Color::GREEN, + }, + TOP_LEFT + FONTOFFSET + FPS_OFFSET + vec2 {FPS_LENGTH * FPS_CHAR_WIDTH / 2, 0}, FPS + ) + .active + = false; +} diff --git a/game/hud/HudSubScene.h b/game/hud/HudSubScene.h new file mode 100644 index 0000000..0cd368e --- /dev/null +++ b/game/hud/HudSubScene.h @@ -0,0 +1,8 @@ +#pragma once + +#include <crepe/api/Scene.h> + +class HudSubScene { +public: + void create(crepe::Scene & scn); +}; diff --git a/game/hud/SpeedScript.cpp b/game/hud/SpeedScript.cpp new file mode 100644 index 0000000..2ced47a --- /dev/null +++ b/game/hud/SpeedScript.cpp @@ -0,0 +1,42 @@ +#include "SpeedScript.h" + +#include "../Events.h" +#include "api/BehaviorScript.h" +#include <crepe/api/Event.h> +#include <crepe/api/KeyCodes.h> +#include <crepe/manager/LoopTimerManager.h> + +using namespace crepe; +using namespace std; + +void SpeedScript::init() { + this->subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { + if (ev.key != Keycode::HOME) return false; + LoopTimerManager & lp = this->get_loop_timer(); + this->toggle = !this->toggle; + if (this->toggle) { + this->timescale = lp.get_time_scale(); + lp.set_time_scale(0); + } else { + lp.set_time_scale(this->timescale); + } + + return true; + }); + this->subscribe<EndGameEvent>([this](const EndGameEvent e) { + this->get_component<BehaviorScript>().active = false; + return false; + }); +} + +void SpeedScript::fixed_update(crepe::duration_t dt) { + LoopTimerManager & lp = this->get_loop_timer(); + if (this->get_key_state(Keycode::PAGE_UP)) { + if (lp.get_time_scale() >= 2) return; + lp.set_time_scale(lp.get_time_scale() + 0.1); + } + if (this->get_key_state(Keycode::PAGE_DOWN)) { + if (lp.get_time_scale() <= 0.5) return; + lp.set_time_scale(lp.get_time_scale() - 0.1); + } +} diff --git a/game/hud/SpeedScript.h b/game/hud/SpeedScript.h new file mode 100644 index 0000000..b40f7cc --- /dev/null +++ b/game/hud/SpeedScript.h @@ -0,0 +1,15 @@ +#pragma once + +#include <crepe/api/Script.h> +#include <crepe/manager/SaveManager.h> + +class SpeedScript : public crepe::Script { +public: + void init() override; + void fixed_update(crepe::duration_t dt) override; + +private: + crepe::SaveManager * savemgr; + bool toggle = false; + float timescale = 1; +}; diff --git a/game/main.cpp b/game/main.cpp new file mode 100644 index 0000000..95cb35c --- /dev/null +++ b/game/main.cpp @@ -0,0 +1,27 @@ +#include <cstdlib> + +#include <crepe/api/Engine.h> +#include <crepe/api/Script.h> + +#include "EngineConfig.h" +#include "GameScene.h" +#include "PreviewScene.h" +#include "menus/mainmenu/MainMenuScene.h" +#include "menus/shop/ShopMenuScene.h" + +using namespace crepe; + +int main() { + srand(time(NULL)); + + Config::get_instance() = ENGINE_CONFIG; + + Engine gameloop; + + gameloop.add_scene<MainMenuScene>(); + gameloop.add_scene<ShopMenuScene>(); + gameloop.add_scene<GameScene>(); + gameloop.add_scene<PreviewScene>(); + + return gameloop.main(); +} diff --git a/game/makefile b/game/makefile new file mode 100644 index 0000000..3fedf7f --- /dev/null +++ b/game/makefile @@ -0,0 +1,5 @@ +.PHONY: FORCE + +format: FORCE + $(MAKE) -C .. $@ + diff --git a/game/menus/BannerSubScene.cpp b/game/menus/BannerSubScene.cpp new file mode 100644 index 0000000..006a829 --- /dev/null +++ b/game/menus/BannerSubScene.cpp @@ -0,0 +1,49 @@ +#include "BannerSubScene.h" +#include "MenusConfig.h" + +#include "../Config.h" + +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Text.h> + +using namespace crepe; +using namespace std; + +void BannerSubScene::create(Scene & scn, const Data & data) { + GameObject menu_banner = scn.new_object("menu_banner", "", {0, -414}); + menu_banner.add_component<Sprite>( + Asset("asset/ui/settings_container/top_middle_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {1100, 88}, + } + ); + menu_banner.add_component<Sprite>( + Asset("asset/ui/settings_container/top_2_middle_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {1100, 66}, + .position_offset {0, 77}, + } + ); + menu_banner.add_component<Sprite>( + Asset("asset/ui/settings_container/banner_bottom.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {1100, 7}, + .position_offset {0, 113}, + } + ); + crepe::vec2 size + = {data.banner_title_width, (data.banner_title_width / data.banner_title.size()) * 2}; + + menu_banner.add_component<Text>( + size, FONT, + Text::Data { + .world_space = true, + .text_color = Color::WHITE, + }, + data.banner_title_offset + FONTOFFSET, data.banner_title + ); +} diff --git a/game/menus/BannerSubScene.h b/game/menus/BannerSubScene.h new file mode 100644 index 0000000..c194dfc --- /dev/null +++ b/game/menus/BannerSubScene.h @@ -0,0 +1,20 @@ +#pragma once + +#include <crepe/api/GameObject.h> +#include <crepe/types.h> + +namespace crepe { +class Scene; +} + +class BannerSubScene { +public: + struct Data { + const std::string & banner_title = "NODATA"; + const float banner_title_width = 100; + const crepe::vec2 & banner_title_offset = {0, 0}; + }; + +public: + void create(crepe::Scene & scn, const Data & data); +}; diff --git a/game/menus/ButtonNextMainMenuSubScript.cpp b/game/menus/ButtonNextMainMenuSubScript.cpp new file mode 100644 index 0000000..63a2777 --- /dev/null +++ b/game/menus/ButtonNextMainMenuSubScript.cpp @@ -0,0 +1,44 @@ +#include "ButtonNextMainMenuSubScript.h" +#include "ButtonReplaySubScript.h" +#include "MenusConfig.h" +#include "ValueBroker.h" + +#include "manager/SaveManager.h" + +#include "../Config.h" + +#include <crepe/api/AudioSource.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void ButtonNextMainMenuSubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool ButtonNextMainMenuSubScript::on_button_press(const ButtonPressEvent & e) { + RefVector<AudioSource> audios + = this->get_components_by_name<AudioSource>("background_music"); + + for (AudioSource & audio : audios) { + audio.stop(); + } + + this->trigger_event<DeleteRecordingEvent>(); + SaveManager & savemgr = this->get_save_manager(); + + ValueBroker<int> coins = savemgr.get<int>(TOTAL_COINS_RUN, 0); + ValueBroker<int> coins_game = savemgr.get<int>(TOTAL_COINS_GAME, 0); + savemgr.set(TOTAL_COINS_GAME, coins_game.get() + coins.get()); + + ValueBroker<int> distance = savemgr.get<int>(DISTANCE_RUN, 0); + ValueBroker<int> distance_game = savemgr.get<int>(DISTANCE_GAME, 0); + if (distance.get() > distance_game.get()) savemgr.set(DISTANCE_GAME, distance.get()); + + this->set_next_scene(MAINMENU_SCENE); + return false; +} diff --git a/game/menus/ButtonNextMainMenuSubScript.h b/game/menus/ButtonNextMainMenuSubScript.h new file mode 100644 index 0000000..3bc3f52 --- /dev/null +++ b/game/menus/ButtonNextMainMenuSubScript.h @@ -0,0 +1,14 @@ +#pragma once + +#include "IButtonScript.h" + +#include <crepe/api/Script.h> + +class ButtonNextMainMenuSubScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + +protected: + bool transition = false; +}; diff --git a/game/menus/ButtonReplaySubScript.cpp b/game/menus/ButtonReplaySubScript.cpp new file mode 100644 index 0000000..01cccbf --- /dev/null +++ b/game/menus/ButtonReplaySubScript.cpp @@ -0,0 +1,43 @@ +#include "ButtonReplaySubScript.h" +#include "Config.h" +#include "MenusConfig.h" + +#include "../Events.h" +#include <crepe/api/AudioSource.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void ButtonReplaySubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); + this->subscribe<EndGameEvent>([this](const EndGameEvent & e) { + return this->set_recording(); + }); + this->subscribe<DeleteRecordingEvent>([this](const DeleteRecordingEvent & e) { + return this->delete_recording(); + }); + if (DISABLE_REPLAY) return; + replay.record_start(); +} + +bool ButtonReplaySubScript::on_button_press(const ButtonPressEvent & e) { + if (DISABLE_REPLAY) return false; + replay.play(this->recording); + return false; +} + +bool ButtonReplaySubScript::set_recording() { + if (DISABLE_REPLAY) return false; + this->recording = replay.record_end(); + return false; +} + +bool ButtonReplaySubScript::delete_recording() { + if (DISABLE_REPLAY) return false; + replay.release(this->recording); + return false; +} diff --git a/game/menus/ButtonReplaySubScript.h b/game/menus/ButtonReplaySubScript.h new file mode 100644 index 0000000..3eb8aa9 --- /dev/null +++ b/game/menus/ButtonReplaySubScript.h @@ -0,0 +1,21 @@ +#pragma once + +#include "IButtonScript.h" + +#include <crepe/api/Script.h> + +struct DeleteRecordingEvent : public crepe::Event {}; + +class ButtonReplaySubScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + +private: + crepe::recording_t recording = 0; + bool set_recording(); + bool delete_recording(); + +protected: + bool transition = false; +}; diff --git a/game/menus/ButtonSetMainMenuSubScript.cpp b/game/menus/ButtonSetMainMenuSubScript.cpp new file mode 100644 index 0000000..1c6bcb2 --- /dev/null +++ b/game/menus/ButtonSetMainMenuSubScript.cpp @@ -0,0 +1,23 @@ +#include "ButtonSetMainMenuSubScript.h" +#include "MenusConfig.h" + +#include <crepe/api/AudioSource.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void ButtonSetMainMenuSubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool ButtonSetMainMenuSubScript::on_button_press(const ButtonPressEvent & e) { + RefVector<AudioSource> audios + = this->get_components_by_name<AudioSource>("background_music"); + + this->set_next_scene(MAINMENU_SCENE); + return false; +} diff --git a/game/menus/ButtonSetMainMenuSubScript.h b/game/menus/ButtonSetMainMenuSubScript.h new file mode 100644 index 0000000..2fb2634 --- /dev/null +++ b/game/menus/ButtonSetMainMenuSubScript.h @@ -0,0 +1,14 @@ +#pragma once + +#include "IButtonScript.h" + +#include <crepe/api/Script.h> + +class ButtonSetMainMenuSubScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + +protected: + bool transition = false; +}; diff --git a/game/menus/ButtonSetShopSubScript.cpp b/game/menus/ButtonSetShopSubScript.cpp new file mode 100644 index 0000000..4f395eb --- /dev/null +++ b/game/menus/ButtonSetShopSubScript.cpp @@ -0,0 +1,17 @@ +#include "ButtonSetShopSubScript.h" +#include "MenusConfig.h" + +using namespace crepe; +using namespace std; + +void ButtonSetShopSubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool ButtonSetShopSubScript::on_button_press(const ButtonPressEvent & e) { + this->set_next_scene(SHOP_SCENE); + return false; +} diff --git a/game/menus/ButtonSetShopSubScript.h b/game/menus/ButtonSetShopSubScript.h new file mode 100644 index 0000000..4017a4c --- /dev/null +++ b/game/menus/ButtonSetShopSubScript.h @@ -0,0 +1,14 @@ +#pragma once + +#include "IButtonScript.h" + +#include <crepe/api/Script.h> + +class ButtonSetShopSubScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + +protected: + bool transition = false; +}; diff --git a/game/menus/ButtonShowCreditsSubScript.cpp b/game/menus/ButtonShowCreditsSubScript.cpp new file mode 100644 index 0000000..ec0e980 --- /dev/null +++ b/game/menus/ButtonShowCreditsSubScript.cpp @@ -0,0 +1,20 @@ +#include "ButtonShowCreditsSubScript.h" +#include "MenusConfig.h" +#include "mainmenu/CreditsSubScript.h" +#include <crepe/api/AudioSource.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void ButtonShowCreditsSubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool ButtonShowCreditsSubScript::on_button_press(const ButtonPressEvent & e) { + this->trigger_event<ShowCreditsEvent>(); + return false; +} diff --git a/game/menus/ButtonShowCreditsSubScript.h b/game/menus/ButtonShowCreditsSubScript.h new file mode 100644 index 0000000..3c73c44 --- /dev/null +++ b/game/menus/ButtonShowCreditsSubScript.h @@ -0,0 +1,14 @@ +#pragma once + +#include "IButtonScript.h" + +#include <crepe/api/Script.h> + +class ButtonShowCreditsSubScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + +protected: + bool transition = false; +}; diff --git a/game/menus/ButtonSubScene.cpp b/game/menus/ButtonSubScene.cpp new file mode 100644 index 0000000..1fe6b03 --- /dev/null +++ b/game/menus/ButtonSubScene.cpp @@ -0,0 +1,266 @@ +#include "ButtonSubScene.h" +#include "ButtonNextMainMenuSubScript.h" +#include "ButtonReplaySubScript.h" +#include "ButtonSetMainMenuSubScript.h" +#include "ButtonSetShopSubScript.h" +#include "ButtonShowCreditsSubScript.h" +#include "IButtonScript.h" +#include "MenusConfig.h" + +#include "../preview/PreviewReplaySubScript.h" +#include "../preview/PreviewStartRecSubScript.h" +#include "../preview/PreviewStopRecSubScript.h" +#include "api/Asset.h" +#include "mainmenu/ButtonTransitionPreviewSubScript.h" + +#include "../Config.h" +#include "mainmenu/CreditsSubScript.h" +#include "menus/shop/ButtonBuySelectBubbleScript.h" +#include "menus/shop/ButtonBuySelectBulletScript.h" + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Button.h> +#include <crepe/api/Color.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Text.h> + +using namespace crepe; +using namespace std; + +void ButtonSubScene::create(Scene & scn, const Data & data) { + GameObject button_object + = scn.new_object("button", data.tag, data.position, 0, data.scale); + this->set_button_overlay(button_object, data); + this->btn_text(button_object, data); + this->set_script(button_object, data); + this->set_icon(button_object, data); +} + +void ButtonSubScene::btn_text(crepe::GameObject & button_object, const Data & data) { + + crepe::vec2 size = {data.text_width, (data.text_width / data.text.size()) * 2}; + button_object.add_component<Text>( + size, FONT, + Text::Data { + .world_space = data.worldspace, + .text_color = Color::WHITE, + }, + data.text_offset + FONTOFFSET, data.text + ); +} + +void ButtonSubScene::set_script(crepe::GameObject & button_object, const Data & data) { + switch (data.script_type) { + case ScriptSelect::PREVIEW: + button_object.add_component<BehaviorScript>() + .set_script<ButtonTransitionPreviewSubScript>(); + break; + case ScriptSelect::SHOP: + button_object.add_component<BehaviorScript>().set_script<ButtonSetShopSubScript>(); + break; + case ScriptSelect::MAINMENU: + button_object.add_component<BehaviorScript>() + .set_script<ButtonSetMainMenuSubScript>(); + break; + case ScriptSelect::NEXT: + button_object.add_component<BehaviorScript>() + .set_script<ButtonNextMainMenuSubScript>(); + break; + case ScriptSelect::REPLAY: + button_object.add_component<BehaviorScript>().set_script<ButtonReplaySubScript>(); + break; + case ScriptSelect::CREDITS_BACK: + button_object.add_component<BehaviorScript>().set_script<CreditsSubScript>(data.tag + ); + break; + case ScriptSelect::CREDITS_SHOW: + button_object.add_component<BehaviorScript>() + .set_script<ButtonShowCreditsSubScript>(); + break; + case ScriptSelect::PREVIEW_REPLAY: + button_object.add_component<BehaviorScript>().set_script<PreviewReplaySubScript>(); + break; + case ScriptSelect::PREVIEW_START: + button_object.add_component<BehaviorScript>().set_script<PreviewStartRecSubScript>( + ); + break; + case ScriptSelect::PREVIEW_STOP: + button_object.add_component<BehaviorScript>().set_script<PreviewStopRecSubScript>( + ); + break; + case ScriptSelect::SHOP_BULLET: + button_object.add_component<BehaviorScript>() + .set_script<ButtonBuySelectBulletScript>(); + break; + case ScriptSelect::SHOP_BUBBLE: + button_object.add_component<BehaviorScript>() + .set_script<ButtonBuySelectBubbleScript>(); + break; + case ScriptSelect::NONE: + button_object.add_component<BehaviorScript>().set_script<IButtonScript>(); + break; + } +} + +void ButtonSubScene::set_icon(crepe::GameObject & button_object, const Data & data) { + switch (data.icon_type) { + case IconSelect::SHOP: + button_object.add_component<Sprite>( + Asset("asset/ui/buttonCoinsSmall.png"), + Sprite::Data { + .sorting_in_layer + = STARTING_SORTING_IN_LAYER + 3 + data.sorting_layer_offset, + .size = ICON_SIZE, + .position_offset = data.icon_offset, + .world_space = data.worldspace, + } + ); + break; + case IconSelect::COINS: + button_object.add_component<Sprite>( + Asset("asset/ui/buttonCoinsSmall.png"), + Sprite::Data { + .sorting_in_layer + = STARTING_SORTING_IN_LAYER + 3 + data.sorting_layer_offset, + .size = ICON_SIZE, + .position_offset = data.icon_offset, + .world_space = data.worldspace, + } + ); + break; + case IconSelect::NONE: + break; + } +} + +void ButtonSubScene::set_button_overlay(crepe::GameObject & button_object, const Data & data) { + switch (data.button_type) { + case ButtonSelect::LARGE: + this->large_btn_overlay(button_object, data); + break; + case ButtonSelect::BACK: + this->back_btn_overlay(button_object, data); + break; + case ButtonSelect::NEXT: + this->next_btn_overlay(button_object, data); + break; + } +} + +void ButtonSubScene::large_btn_overlay(crepe::GameObject & button_object, const Data & data) { + button_object.add_component<Sprite>( + Asset("asset/ui/buttonBacking.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset, + .size = LARGE_OVERLAY_SIZE, + .world_space = data.worldspace, + } + ); + button_object.add_component<Button>(LARGE_OVERLAY_SIZE, Button::Data {}); + if (!data.color_side) return; + this->btn_color_side(button_object, SIDE_PANEL_OFFSET, data); +} + +void ButtonSubScene::back_btn_overlay(crepe::GameObject & button_object, const Data & data) { + button_object.add_component<Sprite>( + Asset("asset/ui/backbuttonright.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset, + .size = SMALL_OVERLAY_SIZE_RIGHT, + .position_offset = {20, 0}, + .world_space = data.worldspace, + } + ); + button_object.add_component<Sprite>( + Asset("asset/ui/backbuttonleft.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset, + .size = SMALL_OVERLAY_SIZE_LEFT, + .position_offset = {-80, 0}, + .world_space = data.worldspace, + } + ); + button_object.add_component<Button>( + vec2 { + SMALL_OVERLAY_SIZE_LEFT.x + SMALL_OVERLAY_SIZE_RIGHT.x, SMALL_OVERLAY_SIZE_LEFT.y + }, + Button::Data {} + ); +} + +void ButtonSubScene::next_btn_overlay(crepe::GameObject & button_object, const Data & data) { + button_object.add_component<Sprite>( + Asset("asset/ui/backbuttonright.png"), + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset, + .size = SMALL_OVERLAY_SIZE_RIGHT, + .position_offset = {-20, 0}, + .world_space = data.worldspace, + } + ); + button_object.add_component<Sprite>( + Asset("asset/ui/backbuttonleft.png"), + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset, + .size = SMALL_OVERLAY_SIZE_LEFT, + .position_offset = {80, 0}, + .world_space = data.worldspace, + } + ); + button_object.add_component<Button>( + vec2 { + SMALL_OVERLAY_SIZE_LEFT.x + SMALL_OVERLAY_SIZE_RIGHT.x, SMALL_OVERLAY_SIZE_LEFT.y + }, + Button::Data {} + ); +} + +void ButtonSubScene::btn_color_side( + crepe::GameObject & button_object, const vec2 & offset, const Data & data +) { + Asset * selected; + Asset blue = Asset("asset/ui/buttonSmallBlue.png"); + Asset orange = Asset("asset/ui/buttonSmallOrange.png"); + Asset purple = Asset("asset/ui/buttonSmallPurple.png"); + Asset yellow = Asset("asset/ui/buttonSmallYellow.png"); + switch (data.btn_side_color) { + case ButtonSideColor::BLUE: + selected = &blue; + break; + case ButtonSideColor::ORANGE: + selected = &orange; + break; + case ButtonSideColor::PURPLE: + selected = &purple; + break; + case ButtonSideColor::YELLOW: + selected = &yellow; + break; + case ButtonSideColor::NONE: + selected = &blue; + break; + } + + button_object.add_component<Sprite>( + *selected, + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 2 + data.sorting_layer_offset, + .size = SIDE_PANEL_SIZE, + .position_offset = offset, + .world_space = data.worldspace, + } + ); + button_object.add_component<Sprite>( + *selected, + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 2 + data.sorting_layer_offset, + .size = SIDE_PANEL_SIZE, + .position_offset = {-offset.x, offset.y}, + .world_space = data.worldspace, + } + ); +} diff --git a/game/menus/ButtonSubScene.h b/game/menus/ButtonSubScene.h new file mode 100644 index 0000000..d4c7223 --- /dev/null +++ b/game/menus/ButtonSubScene.h @@ -0,0 +1,84 @@ +#pragma once + +#include <crepe/api/GameObject.h> + +#include <string> + +namespace crepe { +class Scene; +} + +class ButtonSubScene { +public: + //script enum + enum class ScriptSelect { + PREVIEW, + SHOP, + MAINMENU, + NEXT, + REPLAY, + CREDITS_SHOW, + CREDITS_BACK, + PREVIEW_START, + PREVIEW_STOP, + PREVIEW_REPLAY, + SHOP_BULLET, + SHOP_BUBBLE, + NONE, + }; + //icon enum + enum class IconSelect { + SHOP, + COINS, + NONE, + }; + //icon enum + enum class ButtonSelect { + BACK, + NEXT, + LARGE, + }; + + enum class ButtonSideColor { + BLUE, + ORANGE, + PURPLE, + YELLOW, + NONE, + }; + //data struct + struct Data { + const std::string & text = "NODATA"; + const crepe::vec2 & text_offset = {0, 0}; + const float text_width = 200; + const crepe::vec2 & icon_offset = {0, 0}; + const IconSelect icon_type = IconSelect::NONE; + const crepe::vec2 & position = {0, 0}; + const ScriptSelect script_type = ScriptSelect::NONE; + const ButtonSelect button_type = ButtonSelect::LARGE; + const float scale = 1; + const bool worldspace = true; + const bool color_side = true; + const std::string & tag = ""; + const int sorting_layer_offset = 0; + const ButtonSideColor btn_side_color = ButtonSideColor::NONE; + }; + +public: + void create(crepe::Scene & scn, const Data & data); + +private: + void large_btn_overlay(crepe::GameObject & button_object, const Data & data); + void back_btn_overlay(crepe::GameObject & button_object, const Data & data); + void next_btn_overlay(crepe::GameObject & button_object, const Data & data); + void btn_color_side( + crepe::GameObject & button_object, const crepe::vec2 & offset, const Data & data + ); + void btn_text(crepe::GameObject & button_object, const Data & data); + void set_script(crepe::GameObject & button_object, const Data & data); + void set_icon(crepe::GameObject & button_object, const Data & data); + void set_button_overlay(crepe::GameObject & button_object, const Data & data); + +private: + static constexpr crepe::vec2 SIDE_PANEL_OFFSET = {113, 0}; +}; diff --git a/game/menus/FloatingWindowSubScene.cpp b/game/menus/FloatingWindowSubScene.cpp new file mode 100644 index 0000000..4420bfa --- /dev/null +++ b/game/menus/FloatingWindowSubScene.cpp @@ -0,0 +1,220 @@ + +#include "FloatingWindowSubScene.h" +#include "MenusConfig.h" + +#include <crepe/api/Camera.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void FloatingWindowSubScene::create(Scene & scn, const Data & data) { + const vec2 SIZE = {data.width, data.width * 0.75f}; + const vec2 POSITION_CORRECTION = vec2 {0, -SIZE.y / 2} + data.offset; + const float THICKNESS_BANNER = 34; + const float MIDDLE_OFFSET_FACTOR_TICKNESS = 0.83; + const float MIDDLE_OFFSET_FACTOR_OFFSET = 1.2; + const float MIDDLE_OFFSET_FACTOR_MIDDLE_WIDTH = 0.86; + const float MIDDLE_OFFSET_OFFSET_ADDITION = -0.5; + const float BOTTOM_OFFSET_X = 3; + const float BOTTOM_OFFSET_Y = -3; + + GameObject floatingwindow = scn.new_object("FloatingWindow", data.group_tag); + + // Top_middle + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_middle_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {SIZE.x, THICKNESS_BANNER}, + .position_offset = POSITION_CORRECTION + vec2 {0, 0}, + .world_space = false, + } + ); + + // Top_Left + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_left_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {THICKNESS_BANNER, THICKNESS_BANNER}, + .position_offset + = POSITION_CORRECTION + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2, 0}, + .world_space = false, + } + ); + + // Top_Right + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_right_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {THICKNESS_BANNER, THICKNESS_BANNER}, + .position_offset + = POSITION_CORRECTION + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2, 0}, + .world_space = false, + } + ); + + // Top_middle_2 + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_2_middle_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {SIZE.x, THICKNESS_BANNER}, + .position_offset = POSITION_CORRECTION + vec2 {0, THICKNESS_BANNER}, + .world_space = false, + } + ); + + // Top_Left_2 + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_2_left_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {THICKNESS_BANNER, THICKNESS_BANNER}, + .position_offset = POSITION_CORRECTION + + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2, THICKNESS_BANNER}, + .world_space = false, + } + ); + + // Top_Right_2 + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_2_right_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {THICKNESS_BANNER, THICKNESS_BANNER}, + .position_offset + = POSITION_CORRECTION + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2, THICKNESS_BANNER}, + .world_space = false, + } + ); + + // Top_middle_3 + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_3_middle_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {SIZE.x, THICKNESS_BANNER}, + .position_offset = POSITION_CORRECTION + vec2 {0, THICKNESS_BANNER * 2}, + .world_space = false, + } + ); + + // Top_Left_3 + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_3_left_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {THICKNESS_BANNER, THICKNESS_BANNER}, + .position_offset + = POSITION_CORRECTION + + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2, THICKNESS_BANNER * 2}, + .world_space = false, + } + ); + + // Top_Right_3 + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/top_3_right_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {THICKNESS_BANNER, THICKNESS_BANNER}, + .position_offset + = POSITION_CORRECTION + + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2, THICKNESS_BANNER * 2}, + .world_space = false, + } + ); + + // Middle_Mid + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/middle_mid_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 7, + .size + = {SIZE.x * MIDDLE_OFFSET_FACTOR_OFFSET * MIDDLE_OFFSET_FACTOR_MIDDLE_WIDTH + + data.width_middle_offset, + SIZE.y}, + .position_offset + = POSITION_CORRECTION + + vec2 {0, THICKNESS_BANNER * 3 + SIZE.y / 2 - THICKNESS_BANNER / 2}, + .world_space = false, + } + ); + + // Middle_Left + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/middle_left_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS, SIZE.y}, + .position_offset + = POSITION_CORRECTION + + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2 * MIDDLE_OFFSET_FACTOR_OFFSET - MIDDLE_OFFSET_OFFSET_ADDITION, THICKNESS_BANNER * 3 + SIZE.y / 2 - THICKNESS_BANNER / 2}, + .world_space = false, + } + ); + + // Middle_Right + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/middle_right_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size = {THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS, SIZE.y}, + .position_offset + = POSITION_CORRECTION + + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2 * MIDDLE_OFFSET_FACTOR_OFFSET + MIDDLE_OFFSET_OFFSET_ADDITION, THICKNESS_BANNER * 3 + SIZE.y / 2 - THICKNESS_BANNER / 2}, + .world_space = false, + } + ); + + // Bot_Middle + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/bot_middle_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 7, + .size + = {SIZE.x * MIDDLE_OFFSET_FACTOR_OFFSET * MIDDLE_OFFSET_FACTOR_MIDDLE_WIDTH + + data.width_middle_offset, + THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS}, + .position_offset + = POSITION_CORRECTION + vec2 {0, THICKNESS_BANNER * 3 + SIZE.y + BOTTOM_OFFSET_Y}, + .world_space = false, + } + ); + + // Bot_Left + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/bot_left_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size + = {THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS, + THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS}, + .position_offset + = POSITION_CORRECTION + + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2 - BOTTOM_OFFSET_X, THICKNESS_BANNER * 3 + SIZE.y + BOTTOM_OFFSET_Y}, + .world_space = false, + } + ); + + // Bot_Right + floatingwindow.add_component<Sprite>( + Asset("asset/ui/settings_container/bot_right_setting.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8, + .size + = {THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS, + THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS}, + .position_offset + = POSITION_CORRECTION + + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2 + BOTTOM_OFFSET_X, THICKNESS_BANNER * 3 + SIZE.y + BOTTOM_OFFSET_Y}, + .world_space = false, + } + ); +} diff --git a/game/menus/FloatingWindowSubScene.h b/game/menus/FloatingWindowSubScene.h new file mode 100644 index 0000000..7b9de96 --- /dev/null +++ b/game/menus/FloatingWindowSubScene.h @@ -0,0 +1,17 @@ +#pragma once + +#include <crepe/api/Scene.h> +#include <crepe/types.h> + +class FloatingWindowSubScene { +public: + struct Data { + const std::string group_tag = ""; + float width = 200; + crepe::vec2 offset = {0, 0}; + float width_middle_offset = 0; + }; + +public: + void create(crepe::Scene & scn, const Data & data); +}; diff --git a/game/menus/IButtonScript.cpp b/game/menus/IButtonScript.cpp new file mode 100644 index 0000000..34efbd0 --- /dev/null +++ b/game/menus/IButtonScript.cpp @@ -0,0 +1,32 @@ +#include "IButtonScript.h" + +#include "system/InputSystem.h" + +#include <crepe/api/Sprite.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void IButtonScript::init() { + this->subscribe<ButtonExitEvent>([this](const ButtonExitEvent & e) { + return this->on_button_exit(e); + }); + this->subscribe<ButtonEnterEvent>([this](const ButtonEnterEvent & e) { + return this->on_button_enter(e); + }); +} +bool IButtonScript::on_button_exit(const ButtonExitEvent & e) { + RefVector<Sprite> sprites = this->get_components<Sprite>(); + for (Sprite & sprite : sprites) { + sprite.data.color = Color {255, 255, 255, 255}; + } + return false; +} +bool IButtonScript::on_button_enter(const ButtonEnterEvent & e) { + RefVector<Sprite> sprites = this->get_components<Sprite>(); + for (Sprite & sprite : sprites) { + sprite.data.color = Color {200, 200, 200, 255}; + } + return false; +} diff --git a/game/menus/IButtonScript.h b/game/menus/IButtonScript.h new file mode 100644 index 0000000..e45375b --- /dev/null +++ b/game/menus/IButtonScript.h @@ -0,0 +1,10 @@ +#pragma once + +#include <crepe/api/Script.h> + +class IButtonScript : public virtual crepe::Script { +public: + virtual void init(); + virtual bool on_button_exit(const crepe::ButtonExitEvent & e); + virtual bool on_button_enter(const crepe::ButtonEnterEvent & e); +}; diff --git a/game/menus/IFloatingWindowScript.cpp b/game/menus/IFloatingWindowScript.cpp new file mode 100644 index 0000000..4b538ef --- /dev/null +++ b/game/menus/IFloatingWindowScript.cpp @@ -0,0 +1,22 @@ +#include "IFloatingWindowScript.h" + +#include <crepe/api/Sprite.h> +#include <crepe/types.h> + +using namespace crepe; + +void IFloatingWindowScript::init() { this->disable_all_sprites(); } + +void IFloatingWindowScript::disable_all_sprites() { + RefVector<Sprite> sprites = this->get_components_by_tag<Sprite>(this->tag); + for (Sprite & sprite : sprites) { + sprite.active = false; + } +} + +void IFloatingWindowScript::enable_all_sprites() { + RefVector<Sprite> sprites = this->get_components_by_tag<Sprite>(this->tag); + for (Sprite & sprite : sprites) { + sprite.active = true; + } +} diff --git a/game/menus/IFloatingWindowScript.h b/game/menus/IFloatingWindowScript.h new file mode 100644 index 0000000..e39378f --- /dev/null +++ b/game/menus/IFloatingWindowScript.h @@ -0,0 +1,15 @@ +#pragma once + +#include <crepe/api/Script.h> + +#include <string> + +class IFloatingWindowScript : public virtual crepe::Script { +public: + virtual void init(); + void disable_all_sprites(); + void enable_all_sprites(); + +protected: + std::string tag = ""; +}; diff --git a/game/menus/MenusConfig.h b/game/menus/MenusConfig.h new file mode 100644 index 0000000..3e357a5 --- /dev/null +++ b/game/menus/MenusConfig.h @@ -0,0 +1,16 @@ +#pragma once +#include <crepe/types.h> + +//generic menu config +static constexpr int STARTING_SORTING_IN_LAYER = 7; +//Scene names +static constexpr const char * START_SCENE = "scene1"; +static constexpr const char * PREVIEW_SCENE = "preview scene"; +static constexpr const char * SHOP_SCENE = "shopmenu"; +static constexpr const char * MAINMENU_SCENE = "mainmenu"; +//button config +static constexpr crepe::vec2 LARGE_OVERLAY_SIZE = {250, 100}; +static constexpr crepe::vec2 SMALL_OVERLAY_SIZE_RIGHT = {150, 100}; +static constexpr crepe::vec2 SMALL_OVERLAY_SIZE_LEFT = {50, 100}; +static constexpr crepe::vec2 SIDE_PANEL_SIZE = {50, 150}; +static constexpr crepe::vec2 ICON_SIZE = {50, 50}; diff --git a/game/menus/endgame/EndGameSubScene.cpp b/game/menus/endgame/EndGameSubScene.cpp new file mode 100644 index 0000000..b33072a --- /dev/null +++ b/game/menus/endgame/EndGameSubScene.cpp @@ -0,0 +1,127 @@ + +#include "EndGameSubScene.h" +#include "EndGameSubScript.h" + +#include "../../Config.h" +#include "../ButtonSubScene.h" +#include "../FloatingWindowSubScene.h" + +#include <string> + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Text.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void EndGameSubScene::create(Scene & scn) { + + const std::string TAG = "end_game_tag"; + GameObject script = scn.new_object("script"); + script.add_component<BehaviorScript>().set_script<EndGameSubScript>(TAG); + + // Window + FloatingWindowSubScene window; + window.create( + scn, + FloatingWindowSubScene::Data { + .group_tag = TAG, + .width = 500, + .offset = {0, -50}, + .width_middle_offset = -2, + } + ); + + // Titel + const string TITEL_STRING = "GAME OVER"; + GameObject titel = scn.new_object("titel", TAG); + crepe::vec2 size = {200, (200.0f / TITEL_STRING.size()) * 2}; + titel.add_component<Text>( + size, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {0, -207} + FONTOFFSET, TITEL_STRING + ); + + const float Y_SPACING = 50; + const float Y_OFFSET = -100; + + // Gold gathered + const string GOLD_STRING = "gold:0"; + GameObject gold = scn.new_object("gold_endgame", TAG); + crepe::vec2 size_gold = {200, (200.0f / GOLD_STRING.size()) * 2}; + gold.add_component<Text>( + size_gold, FONT, + Text::Data { + .world_space = false, + .text_color = Color::GOLD, + }, + vec2 {0, Y_OFFSET} + FONTOFFSET, GOLD_STRING + ); + + // Distance + const string DISTANCE_STRING = "0M"; + GameObject distance = scn.new_object("distance_endgame", TAG); + crepe::vec2 size_distance = {200, (200.0f / DISTANCE_STRING.size()) * 2}; + distance.add_component<Text>( + size_distance, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {0, Y_SPACING + Y_OFFSET} + FONTOFFSET, DISTANCE_STRING + ); + + // Highscore + const string HIGHSCORE_STRING = "NEW HIGHSCORE"; + GameObject highscore = scn.new_object("highscore_endgame", "highscore_tag_end"); + crepe::vec2 size_highscore = {200, (200.0f / HIGHSCORE_STRING.size()) * 2}; + highscore + .add_component<Text>( + size_highscore, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {0, Y_SPACING * 2 + Y_OFFSET} + FONTOFFSET, HIGHSCORE_STRING + ) + .active + = false; + + // Buttons + vec2 button_position = {190, 190}; + ButtonSubScene button; + button.create( + scn, + ButtonSubScene::Data { + .text = "NEXT", + .text_width = 100, + .position = button_position, + .script_type = ButtonSubScene::ScriptSelect::NEXT, + .button_type = ButtonSubScene::ButtonSelect::NEXT, + .scale = 0.6, + .worldspace = false, + .tag = TAG, + .sorting_layer_offset = 20, + } + ); + + button.create( + scn, + ButtonSubScene::Data { + .text = "REPLAY", + .text_width = 150, + .position = {-button_position.x, button_position.y}, + .script_type = ButtonSubScene::ScriptSelect::REPLAY, + .button_type = ButtonSubScene::ButtonSelect::BACK, + .scale = 0.6, + .worldspace = false, + .tag = TAG, + .sorting_layer_offset = 20, + } + ); +} diff --git a/game/menus/endgame/EndGameSubScene.h b/game/menus/endgame/EndGameSubScene.h new file mode 100644 index 0000000..204f3b7 --- /dev/null +++ b/game/menus/endgame/EndGameSubScene.h @@ -0,0 +1,9 @@ +#pragma once + +#include <crepe/api/Scene.h> + +class EndGameSubScene { + +public: + void create(crepe::Scene & scn); +}; diff --git a/game/menus/endgame/EndGameSubScript.cpp b/game/menus/endgame/EndGameSubScript.cpp new file mode 100644 index 0000000..6793f3e --- /dev/null +++ b/game/menus/endgame/EndGameSubScript.cpp @@ -0,0 +1,101 @@ +#include "EndGameSubScript.h" + +#include "../../Config.h" +#include "../../Events.h" +#include "../ButtonReplaySubScript.h" +#include "../IFloatingWindowScript.h" +#include "ValueBroker.h" +#include "manager/SaveManager.h" + +#include <string> + +#include <crepe/api/Button.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Text.h> +#include <crepe/types.h> + +using namespace crepe; + +EndGameSubScript::EndGameSubScript(const std::string & tag) { this->tag = tag; } + +void EndGameSubScript::init() { + this->disable_all(); + this->subscribe<EndGameEvent>([this](const EndGameEvent e) { return this->enable_all(); }); + this->subscribe<EndGameEvent>([this](const EndGameEvent e) { + return this->reset_timescale(); + }); + this->subscribe<ShowScoreEvent>([this](const ShowScoreEvent e) { + return this->showscore(); + }); +} + +bool EndGameSubScript::disable_all() { + IFloatingWindowScript::disable_all_sprites(); + RefVector<Button> buttons = this->get_components_by_tag<Button>(this->tag); + for (Button & button : buttons) { + button.active = false; + } + RefVector<Text> texts = this->get_components_by_tag<Text>(this->tag); + for (Text & text : texts) { + text.active = false; + } + return false; +} + +bool EndGameSubScript::enable_all() { + IFloatingWindowScript::enable_all_sprites(); + RefVector<Button> buttons = this->get_components_by_tag<Button>(this->tag); + for (Button & button : buttons) { + button.active = true; + } + RefVector<Text> texts = this->get_components_by_tag<Text>(this->tag); + for (Text & text : texts) { + text.active = true; + } + return false; +} + +bool EndGameSubScript::reset_timescale() { + this->get_loop_timer().set_time_scale(1); + return false; +} + +bool EndGameSubScript::showscore() { + // Gather text + Text & coins_text = this->get_components_by_name<Text>("gold_endgame").front().get(); + Text & distance_text + = this->get_components_by_name<Text>("distance_endgame").front().get(); + Text & highscore_text + = this->get_components_by_name<Text>("highscore_endgame").front().get(); + highscore_text.active = false; + + // Gather saved data + SaveManager & savemgr = this->get_save_manager(); + ValueBroker<std::string> coins = savemgr.get<std::string>(TOTAL_COINS_RUN, "0"); + ValueBroker<std::string> distance = savemgr.get<std::string>(DISTANCE_RUN, "0"); + int distance_run = savemgr.get<int>(DISTANCE_RUN, 0).get(); + int distance_game = savemgr.get<int>(DISTANCE_GAME, 0).get(); + + // Show highscore + if (distance_run > distance_game) highscore_text.active = true; + + const float CHAR_SIZE_DIS = 20; + // Show distance + std::string distance_string = "DISTANCE:" + distance.get(); + distance_text.text = distance_string; + crepe::vec2 size_distance + = {CHAR_SIZE_DIS * distance_string.size(), + (CHAR_SIZE_DIS * distance_string.size() / distance_string.size()) * 2}; + distance_text.dimensions = size_distance; + + const float CHAR_SIZE_COIN = 16; + // Show coins + std::string coins_string = "Coins:" + coins.get(); + coins_text.text = coins_string; + crepe::vec2 size_coins + = {CHAR_SIZE_COIN * coins_string.size(), + (CHAR_SIZE_COIN * coins_string.size() / coins_string.size()) * 2}; + coins_text.dimensions = size_coins; + + return false; +} diff --git a/game/menus/endgame/EndGameSubScript.h b/game/menus/endgame/EndGameSubScript.h new file mode 100644 index 0000000..c25f6ca --- /dev/null +++ b/game/menus/endgame/EndGameSubScript.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../IFloatingWindowScript.h" + +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> + +struct ShowScoreEvent : public crepe::Event {}; + +class EndGameSubScript : public IFloatingWindowScript { +public: + EndGameSubScript(const std::string & tag); + void init() override; + bool disable_all(); + bool enable_all(); + bool reset_timescale(); + bool showscore(); +}; diff --git a/game/menus/mainmenu/ButtonTransitionPreviewSubScript.cpp b/game/menus/mainmenu/ButtonTransitionPreviewSubScript.cpp new file mode 100644 index 0000000..4c4dfc1 --- /dev/null +++ b/game/menus/mainmenu/ButtonTransitionPreviewSubScript.cpp @@ -0,0 +1,23 @@ +#include "ButtonTransitionPreviewSubScript.h" + +#include "../MenusConfig.h" + +using namespace crepe; +using namespace std; + +void ButtonTransitionPreviewSubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool ButtonTransitionPreviewSubScript::on_button_press(const ButtonPressEvent & e) { + if (!this->transition) this->transition = true; + return false; +} + +const char * ButtonTransitionPreviewSubScript::get_scene_name() const { + // Provide the next scene defined in MainMenuConfig + return PREVIEW_SCENE; +} diff --git a/game/menus/mainmenu/ButtonTransitionPreviewSubScript.h b/game/menus/mainmenu/ButtonTransitionPreviewSubScript.h new file mode 100644 index 0000000..d6d8149 --- /dev/null +++ b/game/menus/mainmenu/ButtonTransitionPreviewSubScript.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ITransitionScript.h" + +#include "../IButtonScript.h" + +class ButtonTransitionPreviewSubScript : public ITransitionScript, public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + const char * get_scene_name() const override; +}; diff --git a/game/menus/mainmenu/CreditsSubScene.cpp b/game/menus/mainmenu/CreditsSubScene.cpp new file mode 100644 index 0000000..65576ee --- /dev/null +++ b/game/menus/mainmenu/CreditsSubScene.cpp @@ -0,0 +1,132 @@ + +#include "CreditsSubScene.h" +#include "CreditsSubScript.h" + +#include "../../Config.h" +#include "../ButtonSubScene.h" +#include "../FloatingWindowSubScene.h" + +#include <string> + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Text.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void CreditsSubScene::create(Scene & scn) { + + const std::string TAG = "credits_tag"; + GameObject script = scn.new_object("script"); + script.add_component<BehaviorScript>().set_script<CreditsSubScript>(TAG); + + // Window + FloatingWindowSubScene window; + window.create( + scn, + FloatingWindowSubScene::Data { + .group_tag = TAG, + .width = 500, + .offset = {150, -50}, + .width_middle_offset = -2, + } + ); + + // Titel + const string TITEL_STRING = "Credits"; + GameObject titel = scn.new_object("titel", TAG); + crepe::vec2 size = {200, (200.0f / TITEL_STRING.size()) * 2}; + titel.add_component<Text>( + size, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {150, -207} + FONTOFFSET, TITEL_STRING + ); + + // Buttons + vec2 button_position = {190, 190}; + ButtonSubScene button; + button.create( + scn, + ButtonSubScene::Data { + .text = "Back", + .text_width = 150, + .position = {-button_position.x + 150, button_position.y}, + .script_type = ButtonSubScene::ScriptSelect::CREDITS_BACK, + .button_type = ButtonSubScene::ButtonSelect::BACK, + .scale = 0.6, + .worldspace = false, + .tag = TAG, + .sorting_layer_offset = 20, + } + ); + + const float SIZE_CHAR_NAMES = 10; + const float Y_OFFSET_NAMES_BEGIN = 100; + const float Y_OFFSET_NAMES = 30; + const string LOEK = "Loek Le Blansch"; + crepe::vec2 size_loek + = {LOEK.size() * SIZE_CHAR_NAMES, (LOEK.size() * SIZE_CHAR_NAMES / LOEK.size()) * 2}; + titel.add_component<Text>( + size_loek, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {150, -207 + Y_OFFSET_NAMES + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, LOEK + ); + + const string WOUTER = "Wouter Boerenkamps"; + crepe::vec2 size_wouter + = {WOUTER.size() * SIZE_CHAR_NAMES, + (WOUTER.size() * SIZE_CHAR_NAMES / WOUTER.size()) * 2}; + titel.add_component<Text>( + size_wouter, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {150, -207 + Y_OFFSET_NAMES * 2 + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, WOUTER + ); + + const string JARO = "Jaro Rutjes"; + crepe::vec2 size_jaro + = {JARO.size() * SIZE_CHAR_NAMES, (JARO.size() * SIZE_CHAR_NAMES / JARO.size()) * 2}; + titel.add_component<Text>( + size_jaro, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {150, -207 + Y_OFFSET_NAMES * 3 + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, JARO + ); + + const string MAX = "Max Smits"; + crepe::vec2 size_max + = {MAX.size() * SIZE_CHAR_NAMES, (MAX.size() * SIZE_CHAR_NAMES / MAX.size()) * 2}; + titel.add_component<Text>( + size_max, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {150, -207 + Y_OFFSET_NAMES * 4 + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, MAX + ); + + const string NIELS = "Niels Stunnebrink"; + crepe::vec2 size_niels + = {NIELS.size() * SIZE_CHAR_NAMES, (NIELS.size() * SIZE_CHAR_NAMES / NIELS.size()) * 2 + }; + titel.add_component<Text>( + size_niels, FONT, + Text::Data { + .world_space = false, + .text_color = Color::WHITE, + }, + vec2 {150, -207 + Y_OFFSET_NAMES * 5 + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, NIELS + ); +} diff --git a/game/menus/mainmenu/CreditsSubScene.h b/game/menus/mainmenu/CreditsSubScene.h new file mode 100644 index 0000000..e7ff735 --- /dev/null +++ b/game/menus/mainmenu/CreditsSubScene.h @@ -0,0 +1,9 @@ +#pragma once + +#include <crepe/api/Scene.h> + +class CreditsSubScene { + +public: + void create(crepe::Scene & scn); +}; diff --git a/game/menus/mainmenu/CreditsSubScript.cpp b/game/menus/mainmenu/CreditsSubScript.cpp new file mode 100644 index 0000000..4224dc8 --- /dev/null +++ b/game/menus/mainmenu/CreditsSubScript.cpp @@ -0,0 +1,58 @@ +#include "CreditsSubScript.h" + +#include "../../Events.h" +#include "../ButtonReplaySubScript.h" +#include "../IFloatingWindowScript.h" + +#include <string> + +#include <crepe/api/Button.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Text.h> +#include <crepe/types.h> + +using namespace crepe; + +CreditsSubScript::CreditsSubScript(const std::string & tag) { this->tag = tag; } + +void CreditsSubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); + this->subscribe<ShowCreditsEvent>([this](const ShowCreditsEvent & e) { + this->enable_all(); + return false; + }); + this->disable_all(); +} + +bool CreditsSubScript::disable_all() { + IFloatingWindowScript::disable_all_sprites(); + RefVector<Button> buttons = this->get_components_by_tag<Button>(this->tag); + for (Button & button : buttons) { + button.active = false; + } + RefVector<Text> texts = this->get_components_by_tag<Text>(this->tag); + for (Text & text : texts) { + text.active = false; + } + return false; +} + +bool CreditsSubScript::enable_all() { + IFloatingWindowScript::enable_all_sprites(); + RefVector<Button> buttons = this->get_components_by_tag<Button>(this->tag); + for (Button & button : buttons) { + button.active = true; + } + RefVector<Text> texts = this->get_components_by_tag<Text>(this->tag); + for (Text & text : texts) { + text.active = true; + } + return false; +} + +bool CreditsSubScript::on_button_press(const ButtonPressEvent & e) { + return this->disable_all(); +} diff --git a/game/menus/mainmenu/CreditsSubScript.h b/game/menus/mainmenu/CreditsSubScript.h new file mode 100644 index 0000000..81f941a --- /dev/null +++ b/game/menus/mainmenu/CreditsSubScript.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../IButtonScript.h" +#include "../IFloatingWindowScript.h" + +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> + +struct ShowCreditsEvent : public crepe::Event {}; + +class CreditsSubScript : public IFloatingWindowScript, public IButtonScript { +public: + CreditsSubScript(const std::string & tag); + void init() override; + bool disable_all(); + bool enable_all(); + bool on_button_press(const crepe::ButtonPressEvent & e); +}; diff --git a/game/menus/mainmenu/ITransitionScript.cpp b/game/menus/mainmenu/ITransitionScript.cpp new file mode 100644 index 0000000..54b0875 --- /dev/null +++ b/game/menus/mainmenu/ITransitionScript.cpp @@ -0,0 +1,30 @@ +#include "ITransitionScript.h" +#include "MainMenuConfig.h" + +#include "../../Config.h" +#include "../MenusConfig.h" + +#include <crepe/api/Camera.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void ITransitionScript::frame_update(crepe::duration_t delta_time) { + if (this->transition) { + // cout << "transition:" << velocity << std::endl; + Transform & cam = this->get_components_by_name<Transform>(CAMERA_NAME).front(); + RefVector<Transform> info_tf = this->get_components_by_tag<Transform>(MENU_INFO_TAG); + for (Transform & tf : info_tf) { + tf.position.y -= VELOCITY_INFO_UP * delta_time.count(); + } + if (velocity < VELOCITY_MAX && cam.position.x < SLOW_DOWN) + velocity += VELOCITY_STEP * delta_time.count(); + else if (velocity > 20) velocity -= VELOCITY_STEP * delta_time.count(); + if (cam.position.x < END) cam.position.x += (velocity * delta_time.count()); + if (cam.position.x >= END) { + this->set_next_scene(this->get_scene_name()); + } + } +} diff --git a/game/menus/mainmenu/ITransitionScript.h b/game/menus/mainmenu/ITransitionScript.h new file mode 100644 index 0000000..9a2ef90 --- /dev/null +++ b/game/menus/mainmenu/ITransitionScript.h @@ -0,0 +1,15 @@ +#pragma once + +#include <crepe/api/Script.h> + +class ITransitionScript : public virtual crepe::Script { +public: + void frame_update(crepe::duration_t delta_time) override; + virtual const char * get_scene_name() const = 0; + +private: + float velocity = 20; + +protected: + bool transition = false; +}; diff --git a/game/menus/mainmenu/MainMenuConfig.h b/game/menus/mainmenu/MainMenuConfig.h new file mode 100644 index 0000000..f4ca5a6 --- /dev/null +++ b/game/menus/mainmenu/MainMenuConfig.h @@ -0,0 +1,19 @@ +#pragma once +#include <crepe/types.h> + +//main menu config +static constexpr float STARTMAP_OFFSET = 50; +static constexpr crepe::vec2 MENU_OFFSET = {0, 0}; +static constexpr float MENU_BUTTON_SPACING = 10; +static constexpr const char * MENU_BUTTON_NAME = "menu_button_background"; +static constexpr crepe::vec2 MENU_OFFSET_BUTTON = {-400, -200}; +static constexpr crepe::vec2 MENU_OFFSET_BUTTON_BACKGROUND = {-400, 0}; +static constexpr const char * MENU_INFO_TAG = "menu_info"; +static constexpr crepe::vec2 MENU_OFFSET_INFO = {350, -365}; +static constexpr crepe::vec2 MENU_OFFSET_INFO_BACKGROUND = {350, -365}; //375 +//Moving to new scene (Start and Preview) +static constexpr float SLOW_DOWN = 200; +static constexpr float END = 300; +static constexpr float VELOCITY_MAX = 200; +static constexpr float VELOCITY_STEP = 200; +static constexpr float VELOCITY_INFO_UP = 40; diff --git a/game/menus/mainmenu/MainMenuScene.cpp b/game/menus/mainmenu/MainMenuScene.cpp new file mode 100644 index 0000000..c5d2030 --- /dev/null +++ b/game/menus/mainmenu/MainMenuScene.cpp @@ -0,0 +1,137 @@ + +#include "MainMenuScene.h" +#include "CreditsSubScene.h" +#include "MainMenuConfig.h" +#include "QuitScript.h" +#include "TransitionStartSubScript.h" + +#include "../ButtonSubScene.h" +#include "../MenusConfig.h" + +#include "../../Config.h" +#include "../../background/HallwaySubScene.h" +#include "../../background/StartSubScene.h" + +#include "../endgame/EndGameSubScene.h" + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Camera.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Sprite.h> +#include <crepe/manager/SaveManager.h> + +using namespace crepe; +using namespace std; + +void MainMenuScene::load_scene() { + ButtonSubScene button; + + GameObject camera_object = this->new_object(CAMERA_NAME); + camera_object.add_component<Camera>( + ivec2(990, 720), vec2(1100, 800), + Camera::Data { + .bg_color = Color::BLACK, + } + ); + camera_object.add_component<BehaviorScript>().set_script<TransitionStartSubScript>(); + camera_object.add_component<BehaviorScript>().set_script<QuitScript>(); + + //Button menu + GameObject menu_button = this->new_object(MENU_BUTTON_NAME, MENU_BUTTON_NAME, MENU_OFFSET); + menu_button.add_component<Sprite>( + Asset("asset/ui/background.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 0, + .size = {300, 860}, + .position_offset = MENU_OFFSET_BUTTON_BACKGROUND, + } + ); + + vec2 pos_btn = MENU_OFFSET_BUTTON; + + //Preview btn + button.create( + *this, + ButtonSubScene::Data { + .text = "PREVIEW", + .text_width = 200, + .position = pos_btn, + .script_type = ButtonSubScene::ScriptSelect::PREVIEW, + .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE, + } + ); + + //Shop btn + pos_btn.y += MENU_BUTTON_SPACING + LARGE_OVERLAY_SIZE.y; + button.create( + *this, + ButtonSubScene::Data { + .text = "SHOP", + .text_offset = {-20, 0}, + .text_width = 110, + .icon_offset = {60, 0}, + .icon_type = ButtonSubScene::IconSelect::SHOP, + .position = pos_btn, + .script_type = ButtonSubScene::ScriptSelect::SHOP, + .btn_side_color = ButtonSubScene::ButtonSideColor::ORANGE, + } + ); + + //Credits btn + pos_btn.y += MENU_BUTTON_SPACING + LARGE_OVERLAY_SIZE.y; + button.create( + *this, + ButtonSubScene::Data { + .text = "CREDITS", + .text_offset = {0, 0}, + .text_width = 200, + .position = pos_btn, + .script_type = ButtonSubScene::ScriptSelect::CREDITS_SHOW, + .btn_side_color = ButtonSubScene::ButtonSideColor::BLUE, + } + ); + + //Start of map + StartSubScene start; + HallwaySubScene hallway; + float begin_x = start.create(*this, STARTMAP_OFFSET); + begin_x = hallway.create(*this, begin_x, 1, Color::YELLOW); + + //INFO menu + GameObject menu_info + = this->new_object("MENU_INFO_BACKGROUND", MENU_INFO_TAG, MENU_OFFSET); + menu_info.add_component<Sprite>( + Asset("asset/ui/itemsButtonBlankDark.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 0, + .size = {250, 80}, + .position_offset = MENU_OFFSET_INFO, + .world_space = false, + } + ); + SaveManager & savemgr = this->get_save_manager(); + string number = std::to_string(savemgr.get<int>(TOTAL_COINS_GAME, 0).get()); + float amount_number = static_cast<float>(number.size()); + // savemgr.set(COIN_GAME_AMOUNT, amount); + button.create( + *this, + ButtonSubScene::Data { + .text = number, + .text_offset = {-10 - (amount_number - 1) * 10, 0}, + .text_width = amount_number * 20, + .icon_offset = {60, 0}, + .icon_type = ButtonSubScene::IconSelect::COINS, + .position = MENU_OFFSET_INFO, + .script_type = ButtonSubScene::ScriptSelect::SHOP, + .scale = 0.6, + .worldspace = false, + .color_side = false, + .tag = MENU_INFO_TAG, + } + ); + + CreditsSubScene creditscene; + creditscene.create(*this); +} + +string MainMenuScene::get_name() const { return MAINMENU_SCENE; } diff --git a/game/menus/mainmenu/MainMenuScene.h b/game/menus/mainmenu/MainMenuScene.h new file mode 100644 index 0000000..1eea90e --- /dev/null +++ b/game/menus/mainmenu/MainMenuScene.h @@ -0,0 +1,10 @@ +#pragma once + +#include <crepe/api/Scene.h> +#include <string> + +class MainMenuScene : public crepe::Scene { +public: + void load_scene(); + std::string get_name() const; +}; diff --git a/game/menus/mainmenu/TransitionStartSubScript.cpp b/game/menus/mainmenu/TransitionStartSubScript.cpp new file mode 100644 index 0000000..f737f7f --- /dev/null +++ b/game/menus/mainmenu/TransitionStartSubScript.cpp @@ -0,0 +1,16 @@ +#include "TransitionStartSubScript.h" + +#include "../MenusConfig.h" + +using namespace crepe; +using namespace std; + +void TransitionStartSubScript::fixed_update(crepe::duration_t dt) { + if (this->get_key_state(Keycode::SPACE) && this->transition == false) + this->transition = true; +} + +const char * TransitionStartSubScript::get_scene_name() const { + // Provide the next scene defined in MainMenuConfig + return START_SCENE; +} diff --git a/game/menus/mainmenu/TransitionStartSubScript.h b/game/menus/mainmenu/TransitionStartSubScript.h new file mode 100644 index 0000000..f9862ea --- /dev/null +++ b/game/menus/mainmenu/TransitionStartSubScript.h @@ -0,0 +1,9 @@ +#pragma once + +#include "ITransitionScript.h" + +class TransitionStartSubScript : public ITransitionScript { +public: + void fixed_update(crepe::duration_t dt) override; + const char * get_scene_name() const override; +}; diff --git a/game/menus/shop/ButtonBuySelectBubbleScript.cpp b/game/menus/shop/ButtonBuySelectBubbleScript.cpp new file mode 100644 index 0000000..741afde --- /dev/null +++ b/game/menus/shop/ButtonBuySelectBubbleScript.cpp @@ -0,0 +1,34 @@ +#include "ButtonBuySelectBubbleScript.h" +#include "../MenusConfig.h" +#include "Config.h" +#include "ValueBroker.h" +#include "manager/SaveManager.h" +#include "menus/shop/Shopconfig.h" + +using namespace crepe; +using namespace std; + +void ButtonBuySelectBubbleScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool ButtonBuySelectBubbleScript::on_button_press(const ButtonPressEvent & e) { + SaveManager & save = this->get_save_manager(); + ValueBroker<int> buy_bullet = save.get<int>(BUY_BUBBLE_SAVE, 0); + if (!buy_bullet.get()) { + ValueBroker<int> coins = save.get<int>(TOTAL_COINS_GAME, 0); + if (coins.get() >= 1000) { + int coin = coins.get(); + coin -= 1000; + save.set(TOTAL_COINS_GAME, coin); + save.set(BUY_BUBBLE_SAVE, 1); + } + } else { + save.set(JETPACK_PARTICLES, 1); + } + this->trigger_event<ShopUpdate>(); + return false; +} diff --git a/game/menus/shop/ButtonBuySelectBubbleScript.h b/game/menus/shop/ButtonBuySelectBubbleScript.h new file mode 100644 index 0000000..ce276ef --- /dev/null +++ b/game/menus/shop/ButtonBuySelectBubbleScript.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../IButtonScript.h" + +#include <crepe/api/Script.h> + +class ButtonBuySelectBubbleScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + +protected: + bool transition = false; +}; diff --git a/game/menus/shop/ButtonBuySelectBulletScript.cpp b/game/menus/shop/ButtonBuySelectBulletScript.cpp new file mode 100644 index 0000000..d30849c --- /dev/null +++ b/game/menus/shop/ButtonBuySelectBulletScript.cpp @@ -0,0 +1,34 @@ +#include "ButtonBuySelectBulletScript.h" +#include "../MenusConfig.h" +#include "Config.h" +#include "ValueBroker.h" +#include "manager/SaveManager.h" +#include "menus/shop/Shopconfig.h" + +using namespace crepe; +using namespace std; + +void ButtonBuySelectBulletScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool ButtonBuySelectBulletScript::on_button_press(const ButtonPressEvent & e) { + SaveManager & save = this->get_save_manager(); + ValueBroker<int> buy_bullet = save.get<int>(BUY_BULLET_SAVE, 0); + if (!buy_bullet.get()) { + ValueBroker<int> coins = save.get<int>(TOTAL_COINS_GAME, 0); + if (coins.get() >= 0) { + int coin = coins.get(); + coin -= 0; + save.set(TOTAL_COINS_GAME, coin); + save.set(BUY_BULLET_SAVE, 1); + } + } else { + save.set(JETPACK_PARTICLES, 0); + } + this->trigger_event<ShopUpdate>(); + return false; +} diff --git a/game/menus/shop/ButtonBuySelectBulletScript.h b/game/menus/shop/ButtonBuySelectBulletScript.h new file mode 100644 index 0000000..86de0ac --- /dev/null +++ b/game/menus/shop/ButtonBuySelectBulletScript.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../IButtonScript.h" + +#include <crepe/api/Script.h> + +class ButtonBuySelectBulletScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + +protected: + bool transition = false; +}; diff --git a/game/menus/shop/ShopLoadScript.cpp b/game/menus/shop/ShopLoadScript.cpp new file mode 100644 index 0000000..a545fe2 --- /dev/null +++ b/game/menus/shop/ShopLoadScript.cpp @@ -0,0 +1,126 @@ +#include "ShopLoadScript.h" +#include "Shopconfig.h" +#include "api/Button.h" +#include "api/Sprite.h" +#include "api/Text.h" +#include "manager/SaveManager.h" +#include <crepe/ValueBroker.h> + +using namespace crepe; +using namespace std; + +void ShopLoadScript::init() { + this->update(); + this->subscribe<ShopUpdate>([this](const ShopUpdate e) { return this->update(); }); +} + +bool ShopLoadScript::update() { + SaveManager & save = this->get_save_manager(); + ValueBroker<int> buy_bullet = save.get<int>(BUY_BULLET_SAVE, 0); + ValueBroker<int> buy_bubble = save.get<int>(BUY_BUBBLE_SAVE, 0); + + if (buy_bullet.get()) { + auto sprites = this->get_components_by_tag<Sprite>(BUY_BULLET); + for (auto sprite : sprites) { + sprite.get().active = false; + } + auto buttons = this->get_components_by_tag<Button>(BUY_BULLET); + for (auto btn : buttons) { + btn.get().active = false; + } + auto texts = this->get_components_by_tag<Text>(BUY_BULLET); + for (auto txt : texts) { + txt.get().active = false; + } + auto sprites1 = this->get_components_by_tag<Sprite>(SELECT_BULLET); + for (auto sprite : sprites1) { + sprite.get().active = true; + } + auto buttons1 = this->get_components_by_tag<Button>(SELECT_BULLET); + for (auto btn : buttons1) { + btn.get().active = true; + } + auto texts1 = this->get_components_by_tag<Text>(SELECT_BULLET); + for (auto txt : texts1) { + txt.get().active = true; + } + } else { + auto sprites = this->get_components_by_tag<Sprite>(SELECT_BULLET); + for (auto sprite : sprites) { + sprite.get().active = false; + } + auto buttons = this->get_components_by_tag<Button>(SELECT_BULLET); + for (auto btn : buttons) { + btn.get().active = false; + } + auto texts = this->get_components_by_tag<Text>(SELECT_BULLET); + for (auto txt : texts) { + txt.get().active = false; + } + auto sprites1 = this->get_components_by_tag<Sprite>(BUY_BULLET); + for (auto sprite : sprites1) { + sprite.get().active = true; + } + auto buttons1 = this->get_components_by_tag<Button>(BUY_BULLET); + for (auto btn : buttons1) { + btn.get().active = true; + } + auto texts1 = this->get_components_by_tag<Text>(BUY_BULLET); + for (auto txt : texts1) { + txt.get().active = true; + } + } + + if (buy_bubble.get()) { + auto sprites = this->get_components_by_tag<Sprite>(BUY_BUBBLE); + for (auto sprite : sprites) { + sprite.get().active = false; + } + auto buttons = this->get_components_by_tag<Button>(BUY_BUBBLE); + for (auto btn : buttons) { + btn.get().active = false; + } + auto texts = this->get_components_by_tag<Text>(BUY_BUBBLE); + for (auto txt : texts) { + txt.get().active = false; + } + auto sprites1 = this->get_components_by_tag<Sprite>(SELECT_BUBBLE); + for (auto sprite : sprites1) { + sprite.get().active = true; + } + auto buttons1 = this->get_components_by_tag<Button>(SELECT_BUBBLE); + for (auto btn : buttons1) { + btn.get().active = true; + } + auto texts1 = this->get_components_by_tag<Text>(SELECT_BUBBLE); + for (auto txt : texts1) { + txt.get().active = true; + } + } else { + auto sprites = this->get_components_by_tag<Sprite>(SELECT_BUBBLE); + for (auto sprite : sprites) { + sprite.get().active = false; + } + auto buttons = this->get_components_by_tag<Button>(SELECT_BUBBLE); + for (auto btn : buttons) { + btn.get().active = false; + } + auto texts = this->get_components_by_tag<Text>(SELECT_BUBBLE); + for (auto txt : texts) { + txt.get().active = false; + } + auto sprites1 = this->get_components_by_tag<Sprite>(BUY_BUBBLE); + for (auto sprite : sprites1) { + sprite.get().active = true; + } + auto buttons1 = this->get_components_by_tag<Button>(BUY_BUBBLE); + for (auto btn : buttons1) { + btn.get().active = true; + } + auto texts1 = this->get_components_by_tag<Text>(BUY_BUBBLE); + for (auto txt : texts1) { + txt.get().active = true; + } + } + return false; +} diff --git a/game/menus/shop/ShopLoadScript.h b/game/menus/shop/ShopLoadScript.h new file mode 100644 index 0000000..b17bf1c --- /dev/null +++ b/game/menus/shop/ShopLoadScript.h @@ -0,0 +1,10 @@ +#pragma once + +#include <crepe/api/Script.h> +#include <crepe/manager/SaveManager.h> + +class ShopLoadScript : public crepe::Script { +public: + void init() override; + bool update(); +}; diff --git a/game/menus/shop/ShopMenuScene.cpp b/game/menus/shop/ShopMenuScene.cpp new file mode 100644 index 0000000..4975a95 --- /dev/null +++ b/game/menus/shop/ShopMenuScene.cpp @@ -0,0 +1,326 @@ + +#include "ShopMenuScene.h" + +#include "../../Config.h" +#include "../ButtonSubScene.h" +#include "../MenusConfig.h" +#include "api/BehaviorScript.h" +#include "menus/BannerSubScene.h" +#include "menus/shop/ShopLoadScript.h" +#include "types.h" + +#include "Shopconfig.h" +#include <crepe/api/Camera.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Text.h> + +using namespace crepe; +using namespace std; + +void ShopMenuScene::load_scene() { + GameObject camera_object = this->new_object(CAMERA_NAME); + camera_object.add_component<Camera>( + ivec2(990, 720), vec2(1100, 800), + Camera::Data { + .bg_color = Color::RED, + } + ); + BannerSubScene banner; + banner.create( + *this, + { + .banner_title = "SHOP", + .banner_title_width = 200, + .banner_title_offset = {0, 65}, + } + ); + GameObject menu_background = this->new_object("menu_background"); + menu_background.add_component<Sprite>( + Asset("asset/ui/background.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 0, + .size = {1100, 860}, + .position_offset {0}, + } + ); + menu_background.add_component<BehaviorScript>().set_script<ShopLoadScript>(); + + ButtonSubScene button; + button.create( + *this, + ButtonSubScene::Data { + .text = "BACK", + .text_width = 115, + .position = {-400, -350}, + .script_type = ButtonSubScene::ScriptSelect::MAINMENU, + .button_type = ButtonSubScene::ButtonSelect::BACK, + .scale = 0.8, + .sorting_layer_offset = 1, + } + ); + + const float CHAR_SIZE = 16; + const float CHAR_SIZE_COIN = 16; + crepe::vec2 size; + + GameObject shop_item_bullet = this->new_object("bullet", "shop_item", vec2(-100, 0)); + shop_item_bullet.add_component<Sprite>( + Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .angle_offset = 30, + .position_offset = {0, 0}, + } + ); + shop_item_bullet.add_component<Sprite>( + Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .angle_offset = 10, + .position_offset = {-10, -30}, + } + ); + shop_item_bullet.add_component<Sprite>( + Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .angle_offset = -10, + .position_offset = {-40, 30}, + } + ); + shop_item_bullet.add_component<Sprite>( + Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .angle_offset = 0, + .position_offset = {10, 15}, + } + ); + shop_item_bullet.add_component<Sprite>( + Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .angle_offset = -5, + .position_offset = {45, -5}, + } + ); + + const string BULLETS_STRING = "BULLETS"; + size + = {CHAR_SIZE * BULLETS_STRING.size(), + (CHAR_SIZE * BULLETS_STRING.size() / BULLETS_STRING.size()) * 2}; + + shop_item_bullet.add_component<Text>( + size, FONT, + Text::Data { + .world_space = true, + .text_color = Color::WHITE, + }, + vec2 {0, -75}, BULLETS_STRING + ); + shop_item_bullet.add_component<Sprite>( + Asset("asset/ui/buttonCoinsSmall.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 45}, + .position_offset = {25, 75}, + } + ); + + const string BULLETS_GOLD_STRING = "0"; + size + = {CHAR_SIZE_COIN * BULLETS_GOLD_STRING.size(), + (CHAR_SIZE_COIN * BULLETS_GOLD_STRING.size() / BULLETS_GOLD_STRING.size()) * 2}; + shop_item_bullet.add_component<Text>( + size, FONT, + Text::Data { + .world_space = true, + .text_color = Color::GOLD, + }, + vec2 {-5, 75}, BULLETS_GOLD_STRING + ); + + GameObject shop_item_bubble = this->new_object("bubble", "shop_item", vec2(100, 0)); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 10}, + .position_offset = {0, 0}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 10}, + .position_offset = {-50, -20}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .position_offset = {45, -40}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .position_offset = {-20, 40}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 15}, + .position_offset = {15, -25}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 10}, + .position_offset = {10, 5}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 10}, + .position_offset = {-5, -20}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .position_offset = {15, -40}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 20}, + .position_offset = {-20, 10}, + } + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/background/aquarium/bubble.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 15}, + .position_offset = {30, -25}, + } + ); + + const string BUBBLE_STRING = "BUBBLE"; + size + = {CHAR_SIZE * BUBBLE_STRING.size(), + (CHAR_SIZE * BUBBLE_STRING.size() / BUBBLE_STRING.size()) * 2}; + shop_item_bubble.add_component<Text>( + size, FONT, + Text::Data { + .world_space = true, + .text_color = Color::WHITE, + }, + vec2 {0, -75}, BUBBLE_STRING + ); + shop_item_bubble.add_component<Sprite>( + Asset("asset/ui/buttonCoinsSmall.png"), + Sprite::Data { + .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1, + .size = {0, 45}, + .position_offset = {45, 75}, + } + ); + + const string BUBBLE_GOLD_STRING = "1000"; + size + = {CHAR_SIZE_COIN * BUBBLE_GOLD_STRING.size(), + (CHAR_SIZE_COIN * BUBBLE_GOLD_STRING.size() / BUBBLE_GOLD_STRING.size()) * 2}; + shop_item_bubble.add_component<Text>( + size, FONT, + Text::Data { + .world_space = true, + .text_color = Color::GOLD, + }, + vec2 {-10, 75}, BUBBLE_GOLD_STRING + ); + + button.create( + *this, + ButtonSubScene::Data { + .text = "BUY", + .text_width = 100, + .position = vec2(-100, 120), + .script_type = ButtonSubScene::ScriptSelect::SHOP_BULLET, + .button_type = ButtonSubScene::ButtonSelect::LARGE, + .scale = 0.4, + .tag = BUY_BULLET, + .sorting_layer_offset = 20, + .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE + + } + ); + + button.create( + *this, + ButtonSubScene::Data { + .text = "BUY", + .text_width = 100, + .position = vec2(100, 120), + .script_type = ButtonSubScene::ScriptSelect::SHOP_BUBBLE, + .button_type = ButtonSubScene::ButtonSelect::LARGE, + .scale = 0.4, + .tag = BUY_BUBBLE, + .sorting_layer_offset = 20, + .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE + } + ); + + button.create( + *this, + ButtonSubScene::Data { + .text = "SELECT", + .text_width = 100, + .position = vec2(-100, 120), + .script_type = ButtonSubScene::ScriptSelect::SHOP_BULLET, + .button_type = ButtonSubScene::ButtonSelect::LARGE, + .scale = 0.4, + .tag = SELECT_BULLET, + .sorting_layer_offset = 20, + .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE + } + ); + + button.create( + *this, + ButtonSubScene::Data { + .text = "SELECT", + .text_width = 100, + .position = vec2(100, 120), + .script_type = ButtonSubScene::ScriptSelect::SHOP_BUBBLE, + .button_type = ButtonSubScene::ButtonSelect::LARGE, + .scale = 0.4, + .tag = SELECT_BUBBLE, + .sorting_layer_offset = 20, + .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE + } + ); +} + +string ShopMenuScene::get_name() const { return SHOP_SCENE; } diff --git a/game/menus/shop/ShopMenuScene.h b/game/menus/shop/ShopMenuScene.h new file mode 100644 index 0000000..34c05ff --- /dev/null +++ b/game/menus/shop/ShopMenuScene.h @@ -0,0 +1,12 @@ +#pragma once + +#include <string> + +#include <crepe/api/Scene.h> + +class ShopMenuScene : public crepe::Scene { +public: + void load_scene(); + + std::string get_name() const; +}; diff --git a/game/menus/shop/Shopconfig.h b/game/menus/shop/Shopconfig.h new file mode 100644 index 0000000..a686242 --- /dev/null +++ b/game/menus/shop/Shopconfig.h @@ -0,0 +1,14 @@ +#pragma once +#include "api/Event.h" + +//tags +static constexpr const char * BUY_BULLET = "BUY_BULLET"; +static constexpr const char * SELECT_BULLET = "SELECT_BULLET"; +static constexpr const char * BUY_BUBBLE = "BUY_BUBBLE"; +static constexpr const char * SELECT_BUBBLE = "SELECT_BUBBLE"; + +//save_data +static constexpr const char * BUY_BULLET_SAVE = "BUY_BULLET_SAVE"; +static constexpr const char * BUY_BUBBLE_SAVE = "BUY_BUBBLE_SAVE"; + +struct ShopUpdate : public crepe::Event {}; diff --git a/game/missile/AlertScript.cpp b/game/missile/AlertScript.cpp new file mode 100644 index 0000000..0e6f5c5 --- /dev/null +++ b/game/missile/AlertScript.cpp @@ -0,0 +1,38 @@ +#include "AlertScript.h" +#include "../Config.h" + +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> + +using namespace crepe; + +void AlertScript::fixed_update(crepe::duration_t dt) { + const auto & cam = this->get_components_by_name<Transform>("camera").front().get(); + //missile transform + const auto & this_transform = this->get_component<Transform>(); + auto missile_transforms = this->get_components_by_name<Transform>("missile"); + const auto & this_collider = this->get_component<CircleCollider>(); + + auto alert_sprites = this->get_components_by_name<Sprite>("missile_alert"); + auto alert_transforms = this->get_components_by_name<Transform>("missile_alert"); + + int idx = 0; + for (int i = 0; i < missile_transforms.size(); ++i) { + const auto & missile_transform = missile_transforms[i].get(); + if (this_transform.game_object_id == missile_transform.game_object_id) { + idx = i; + break; + } + } + + auto & alert_transform = alert_transforms[idx].get(); + alert_transform.position.x = cam.position.x + (VIEWPORT_X / 2 - 100); + alert_transform.position.y = this_transform.position.y; + + // check if transform is in camera view + if (this_transform.position.x > cam.position.x - (VIEWPORT_X / 2) + && this_transform.position.x < cam.position.x + (VIEWPORT_X / 2)) { + alert_sprites[idx].get().active = false; + } +} diff --git a/game/missile/AlertScript.h b/game/missile/AlertScript.h new file mode 100644 index 0000000..5b0b3a1 --- /dev/null +++ b/game/missile/AlertScript.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Script.h> + +class AlertScript : public crepe::Script { +private: + bool has_alert = false; + +public: + void fixed_update(crepe::duration_t dt); +}; diff --git a/game/missile/AlertSubScene.cpp b/game/missile/AlertSubScene.cpp new file mode 100644 index 0000000..5bce5ac --- /dev/null +++ b/game/missile/AlertSubScene.cpp @@ -0,0 +1,33 @@ +#include "AlertSubScene.h" +#include "../Config.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +using namespace crepe; + +MissileAlert::MissileAlert(Scene & scn) { + GameObject alert = scn.new_object("missile_alert", "missile_alert", {0, 0}, 0, 1); + + Asset missile_alert_ss {"asset/obstacles/missile/missileAlert.png"}; + + auto & missile_alert_sprite = alert.add_component<Sprite>( + missile_alert_ss, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .size = {0, 100}, + } + ); + + auto & missile_alert_anim = alert.add_component<Animator>( + missile_alert_sprite, ivec2 {64, 64}, uvec2 {4, 2}, + Animator::Data { + .fps = 15, + .looping = true, + } + ); + + missile_alert_anim.set_anim(1); + missile_alert_sprite.active = false; +} diff --git a/game/missile/AlertSubScene.h b/game/missile/AlertSubScene.h new file mode 100644 index 0000000..8ea288f --- /dev/null +++ b/game/missile/AlertSubScene.h @@ -0,0 +1,8 @@ +#pragma once + +#include <crepe/api/Scene.h> + +class MissileAlert { +public: + MissileAlert(crepe::Scene & scn); +}; diff --git a/game/missile/MissilePool.cpp b/game/missile/MissilePool.cpp new file mode 100644 index 0000000..23f03c9 --- /dev/null +++ b/game/missile/MissilePool.cpp @@ -0,0 +1,18 @@ +#include "MissilePool.h" +#include "MissileSubScene.h" +#include "missile/AlertSubScene.h" + +#include <crepe/api/Scene.h> + +using namespace std; +using namespace crepe; + +MissilePool::MissilePool(Scene & scn) { + int amount = 0; + MissileSubScene missile; + while (amount < this->MAX_MISSILE_COUNT) { + MissileAlert alert(scn); + missile.create(scn); + amount++; + } +} diff --git a/game/missile/MissilePool.h b/game/missile/MissilePool.h new file mode 100644 index 0000000..296701e --- /dev/null +++ b/game/missile/MissilePool.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Scene.h> + +class MissilePool { +public: + MissilePool(crepe::Scene & scn); + +private: + static constexpr unsigned int MAX_MISSILE_COUNT = 5; +}; diff --git a/game/missile/MissileScript.cpp b/game/missile/MissileScript.cpp new file mode 100644 index 0000000..3aa4eb6 --- /dev/null +++ b/game/missile/MissileScript.cpp @@ -0,0 +1,106 @@ +#include "MissileScript.h" +#include "../Config.h" +#include <cmath> + +#include <crepe/api/AI.h> +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/KeyCodes.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/types.h> + +using namespace std; +using namespace crepe; + +void MissileScript::init() { + subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { + return this->on_collision(ev); + }); + this->seeking_disabled = false; +} +void MissileScript::kill_missile() { + auto animations = this->get_components<Animator>(); + auto sprites = this->get_components<Sprite>(); + auto collider = this->get_component<CircleCollider>(); + auto & this_script = this->get_components<BehaviorScript>().front().get(); + + animations[0].get().active = false; + animations[1].get().active = false; + animations[2].get().active = true; + sprites[0].get().active = false; + sprites[1].get().active = false; + sprites[2].get().active = true; + + collider.active = false; + this_script.active = false; + this->seeking_disabled = false; +} +void MissileScript::activate() { + auto anim = this->get_components<Animator>(); + auto sprites = this->get_components<Sprite>(); + + sprites[0].get().active = true; + sprites[1].get().active = true; + sprites[2].get().active = false; + + anim[0].get().active = true; + anim[1].get().active = true; + anim[2].get().stop(); +} +bool MissileScript::on_collision(const CollisionEvent & ev) { + auto & explosion_sound = this->get_components<AudioSource>().back().get(); + + this->kill_missile(); + explosion_sound.play(); + + return false; +} + +bool MissileScript::is_in_x_range(const Transform & missile, const Transform & player) { + return fabs(missile.position.x - player.position.x) <= this->X_RANGE; +} + +void MissileScript::fixed_update(crepe::duration_t dt) { + auto & explosion_anim = this->get_components<Animator>().back().get(); + auto & missile = this->get_component<Transform>(); + auto & m_ai = this->get_component<AI>(); + + const auto & player = this->get_components_by_name<Transform>("player").front().get(); + const auto & cam = this->get_components_by_name<Transform>("camera").front().get(); + const auto & velocity = this->get_component<Rigidbody>().data.linear_velocity; + + // check if animation is at the end + if (explosion_anim.data.row == 7) { + this->activate(); + this->seeking_disabled = false; + return; + } + + if (missile.position.x < (cam.position.x - VIEWPORT_X / 1.8)) { + this->kill_missile(); + return; + } + + if (this->seeking_disabled) { + m_ai.seek_off(); + } else { + m_ai.seek_target = player.position; + m_ai.seek_on(); + + if (is_in_x_range(missile, player)) { + this->seeking_disabled = true; + m_ai.seek_off(); + } + } + + vec2 angle_pos = velocity; + float angle = atan2(angle_pos.y, angle_pos.x) * (180 / M_PI); + + missile.rotation = angle; + missile.position += velocity * dt.count(); +} diff --git a/game/missile/MissileScript.h b/game/missile/MissileScript.h new file mode 100644 index 0000000..a492e18 --- /dev/null +++ b/game/missile/MissileScript.h @@ -0,0 +1,20 @@ +#pragma once + +#include <crepe/api/Script.h> + +class MissileScript : public crepe::Script { +private: + bool on_collision(const crepe::CollisionEvent & ev); + + bool seeking_disabled; + + // will be used to calculate when ai will be stopped + static constexpr int X_RANGE = 90; + bool is_in_x_range(const crepe::Transform & missile, const crepe::Transform & player); + void kill_missile(); + void activate(); + +public: + void init(); + void fixed_update(crepe::duration_t dt); +}; diff --git a/game/missile/MissileSubScene.cpp b/game/missile/MissileSubScene.cpp new file mode 100644 index 0000000..d325050 --- /dev/null +++ b/game/missile/MissileSubScene.cpp @@ -0,0 +1,101 @@ +#include "MissileSubScene.h" +#include "../Config.h" +#include "../missile/MissileScript.h" +#include "Random.h" +#include "missile/AlertScript.h" + +#include <crepe/api/AI.h> +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> +#include <crepe/types.h> + +using namespace crepe; + +void MissileSubScene::create(crepe::Scene & scn) { + GameObject missle = scn.new_object("missile", "missile", {0, 0}, 0, 1); + + Asset missle_ss {"asset/obstacles/missile/missile.png"}; + Asset missle_thruster_ss {"asset/obstacles/missile/missileEffects.png"}; + Asset missile_explosion_ss {"asset/obstacles/missile/missileExplosion.png"}; + Asset explosion_sound {"asset/sfx/rocket_explode_1.ogg"}; + Asset missile_fire {"asset/sfx/missile_launch.ogg"}; + + missle.add_component<BehaviorScript>().set_script<MissileScript>().active = false; + missle.add_component<BehaviorScript>().set_script<AlertScript>(); + + auto & sound = missle.add_component<AudioSource>(missile_fire); + sound.volume = 0.5; + auto & sound2 = missle.add_component<AudioSource>(explosion_sound); + sound2.volume = 3; + + // sprites + auto & missle_sprite = missle.add_component<Sprite>( + missle_ss, + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .size = {0, 35}, + } + ); + + auto & missle_thruster_sprite = missle.add_component<Sprite>( + missle_thruster_ss, + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .size = {0, 35}, + .position_offset = {-20, 0}, + } + ); + + auto & missile_explosion_sprite = missle.add_component<Sprite>( + missile_explosion_ss, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .size = {0, 50}, + } + ); + + // Animations + missle.add_component<Animator>( + missle_sprite, ivec2 {32, 32}, uvec2 {4, 1}, + Animator::Data { + .fps = 15, + .looping = true, + } + ); + + missle.add_component<Animator>( + missle_thruster_sprite, ivec2 {64, 64}, uvec2 {4, 2}, + Animator::Data { + .fps = 15, + .looping = true, + } + ); + + auto & explosion_anim = missle.add_component<Animator>( + missile_explosion_sprite, ivec2 {64, 64}, uvec2 {8, 1}, + Animator::Data { + .fps = 10, + } + ); + + missile_explosion_sprite.active = false; + explosion_anim.active = false; + + missle.add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::KINEMATIC, + .max_linear_velocity = Random::f(250, 200), + .kinematic_collision = false, + .collision_layers = {COLL_LAY_PLAYER, COLL_LAY_BOT_TOP}, + .collision_layer = COLL_LAY_MISSILE, + }); + + missle.add_component<CircleCollider>(3).active = false; + + auto & missle_ai = missle.add_component<AI>(1000); +} diff --git a/game/missile/MissileSubScene.h b/game/missile/MissileSubScene.h new file mode 100644 index 0000000..9ea422a --- /dev/null +++ b/game/missile/MissileSubScene.h @@ -0,0 +1,12 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class MissileSubScene { +public: + MissileSubScene() = default; + + void create(crepe::Scene & scn); +}; diff --git a/game/missile/SpawnEvent.cpp b/game/missile/SpawnEvent.cpp new file mode 100644 index 0000000..6109686 --- /dev/null +++ b/game/missile/SpawnEvent.cpp @@ -0,0 +1,49 @@ +#include "SpawnEvent.h" +#include "Random.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Camera.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> + +#include <cstdlib> + +using namespace crepe; + +void MissileSpawnEventHandler::init() { + subscribe<MissileSpawnEvent>([this](const MissileSpawnEvent & ev) -> bool { + return this->on_event(ev); + }); +} + +bool MissileSpawnEventHandler::on_event(const MissileSpawnEvent & event) { + auto alert_sprites = this->get_components_by_name<Sprite>("missile_alert"); + + auto missile_transforms = this->get_components_by_name<Transform>("missile"); + auto colliders = this->get_components_by_name<CircleCollider>("missile"); + auto missile_behaviorscripts = this->get_components_by_name<BehaviorScript>("missile"); + auto missile_audiosources = this->get_components_by_name<AudioSource>("missile"); + + auto & camera_transform = this->get_components_by_name<Transform>("camera").front().get(); + + for (size_t i = 0; i < missile_transforms.size(); ++i) { + BehaviorScript & script = missile_behaviorscripts[i * 2].get(); + if (script.active) continue; + script.active = true; + colliders[i].get().active = true; + missile_audiosources[i * 2].get().play(); + + auto & transform = missile_transforms[i].get(); + transform.position.x = camera_transform.position.x + this->MISSILE_OFFSET; + transform.position.y = Random::i(this->MAX_RANGE, this->MIN_RANGE); + + auto & alert_sprite = alert_sprites[i].get(); + alert_sprite.active = true; + break; + } + + return false; +} diff --git a/game/missile/SpawnEvent.h b/game/missile/SpawnEvent.h new file mode 100644 index 0000000..84b1987 --- /dev/null +++ b/game/missile/SpawnEvent.h @@ -0,0 +1,19 @@ +#pragma once + +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> + +#include "../Config.h" + +struct MissileSpawnEvent : public crepe::Event {}; + +class MissileSpawnEventHandler : public crepe::Script { +private: + static constexpr int MISSILE_OFFSET = VIEWPORT_X; + static constexpr int MIN_RANGE = -150; + static constexpr int MAX_RANGE = 150; + +public: + void init(); + bool on_event(const MissileSpawnEvent & ev); +}; diff --git a/game/player/PlayerAudioScript.cpp b/game/player/PlayerAudioScript.cpp new file mode 100644 index 0000000..6b5f630 --- /dev/null +++ b/game/player/PlayerAudioScript.cpp @@ -0,0 +1,62 @@ +#include "PlayerAudioScript.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> + +using namespace crepe; +using namespace std; + +void PlayerAudioScript::fixed_update(crepe::duration_t dt) { + Animator & animator = this->get_components_by_name<Animator>("player").front(); + + if (animator.data.col == 0) { + if (animator.data.row != this->last_row) { + if (animator.data.row == 0) { + // right footstep + if (current_footstep == 0) { + AudioSource & audio + = this->get_components_by_name<AudioSource>("player_audio").at(0); + audio.play(); + } else if (current_footstep == 1) { + AudioSource & audio + = this->get_components_by_name<AudioSource>("player_audio").at(2); + audio.play(); + } else if (current_footstep == 2) { + AudioSource & audio + = this->get_components_by_name<AudioSource>("player_audio").at(4); + audio.play(); + } else if (current_footstep == 3) { + AudioSource & audio + = this->get_components_by_name<AudioSource>("player_audio").at(6); + audio.play(); + } + } else if (animator.data.row == 2) { + // left footstep + if (current_footstep == 0) { + AudioSource & audio + = this->get_components_by_name<AudioSource>("player_audio").at(1); + audio.play(); + current_footstep = 1; + } else if (current_footstep == 1) { + AudioSource & audio + = this->get_components_by_name<AudioSource>("player_audio").at(3); + audio.play(); + current_footstep = 2; + } else if (current_footstep == 2) { + AudioSource & audio + = this->get_components_by_name<AudioSource>("player_audio").at(5); + audio.play(); + current_footstep = 3; + } else if (current_footstep == 3) { + AudioSource & audio + = this->get_components_by_name<AudioSource>("player_audio").at(7); + audio.play(); + current_footstep = 0; + } + } + this->last_row = animator.data.row; + } + } else { + this->last_row = -1; + } +} diff --git a/game/player/PlayerAudioScript.h b/game/player/PlayerAudioScript.h new file mode 100644 index 0000000..764cb20 --- /dev/null +++ b/game/player/PlayerAudioScript.h @@ -0,0 +1,13 @@ +#pragma once + +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> + +class PlayerAudioScript : public crepe::Script { +public: + void fixed_update(crepe::duration_t dt); + +private: + int last_row = -1; + int current_footstep = 0; +}; diff --git a/game/player/PlayerBulletPool.cpp b/game/player/PlayerBulletPool.cpp new file mode 100644 index 0000000..5285ec8 --- /dev/null +++ b/game/player/PlayerBulletPool.cpp @@ -0,0 +1,11 @@ +#include "PlayerBulletPool.h" +#include "PlayerBulletSubScene.h" +using namespace std; + +void PlayerBulletPool::create_bullets(crepe::Scene & scn) { + PlayerBulletSubScene bullet; + int amount = 0; + while (amount < this->MAXIMUM_AMOUNT) { + amount = bullet.create(scn, amount); + } +} diff --git a/game/player/PlayerBulletPool.h b/game/player/PlayerBulletPool.h new file mode 100644 index 0000000..9618d54 --- /dev/null +++ b/game/player/PlayerBulletPool.h @@ -0,0 +1,11 @@ +#pragma once + +#include <crepe/api/Scene.h> + +class PlayerBulletPool { +public: + void create_bullets(crepe::Scene & scn); + +private: + static constexpr int MAXIMUM_AMOUNT = 20; +}; diff --git a/game/player/PlayerBulletScript.cpp b/game/player/PlayerBulletScript.cpp new file mode 100644 index 0000000..a823375 --- /dev/null +++ b/game/player/PlayerBulletScript.cpp @@ -0,0 +1,41 @@ + +#include <crepe/api/Camera.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Rigidbody.h> + +#include "PlayerBulletScript.h" + +using namespace crepe; +using namespace std; +void PlayerBulletScript::init() { + this->subscribe<CollisionEvent>([this](const CollisionEvent & e) -> bool { + return this->on_collide(e); + }); +} +void PlayerBulletScript::fixed_update(crepe::duration_t dt) { + Transform & transform = this->get_component<Transform>(); + Camera & camera = this->get_components_by_name<Camera>("camera").front(); + Transform & cam_transform = this->get_components_by_name<Transform>("camera").front(); + Rigidbody & bullet_body = this->get_component<Rigidbody>(); + transform.rotation += bullet_body.data.angular_velocity * dt.count(); + transform.position += bullet_body.data.linear_velocity * dt.count(); + vec2 half_screen = camera.viewport_size / 2; + float despawn_location = cam_transform.position.x + half_screen.x + 50; + if (transform.position.x > despawn_location) { + this->despawn_bullet(); + } +} + +void PlayerBulletScript::despawn_bullet() { + Transform & transform = this->get_component<Transform>(); + Rigidbody & bullet_body = this->get_component<Rigidbody>(); + bullet_body.active = false; + BehaviorScript & bullet_script = this->get_component<BehaviorScript>(); + bullet_script.active = false; + transform.position = {0, -850}; +} + +bool PlayerBulletScript::on_collide(const CollisionEvent & e) { + this->despawn_bullet(); + return false; +} diff --git a/game/player/PlayerBulletScript.h b/game/player/PlayerBulletScript.h new file mode 100644 index 0000000..0637790 --- /dev/null +++ b/game/player/PlayerBulletScript.h @@ -0,0 +1,11 @@ +#pragma once +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Script.h> + +class PlayerBulletScript : public crepe::Script { +public: + void init() override; + void fixed_update(crepe::duration_t dt) override; + bool on_collide(const crepe::CollisionEvent & e); + void despawn_bullet(); +}; diff --git a/game/player/PlayerBulletSubScene.cpp b/game/player/PlayerBulletSubScene.cpp new file mode 100644 index 0000000..82ce4a9 --- /dev/null +++ b/game/player/PlayerBulletSubScene.cpp @@ -0,0 +1,52 @@ +#include <string> + +#include "../Config.h" +#include <crepe/api/AI.h> +#include <crepe/api/Animator.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +#include "PlayerBulletScript.h" +#include "PlayerBulletSubScene.h" +#include "PlayerScript.h" +using namespace crepe; +using namespace std; +int PlayerBulletSubScene::create(Scene & scn, int counter) { + string unique_name = "player_bullet_" + to_string(counter++); + GameObject player_bullet + = scn.new_object(unique_name.c_str(), "player_bullet", vec2 {0, -850}, 0, 1); + + Rigidbody & player_bullet_body = player_bullet.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 0, + .body_type = Rigidbody::BodyType::KINEMATIC, + .linear_velocity = vec2 {450, 0}, + .angular_velocity = 300, + .kinematic_collision = false, + .collision_layers = {COLL_LAY_ENEMY, COLL_LAY_ZAPPER}, + + .collision_layer = COLL_LAY_PLAYER_BULLET, + + }); + player_bullet_body.active = false; + BoxCollider & player_bullet_collider + = player_bullet.add_component<BoxCollider>(vec2(30, 30)); + + Asset player_bullet_asset {"asset/other_effects/crepe.png"}; + Sprite & player_bullet_sprite = player_bullet.add_component<Sprite>( + player_bullet_asset, + Sprite::Data { + .flip = {true, false}, + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .order_in_layer = 1, + .size = vec2(30, 0), + } + ); + player_bullet.add_component<BehaviorScript>().set_script<PlayerBulletScript>().active + = false; + return counter; +} diff --git a/game/player/PlayerBulletSubScene.h b/game/player/PlayerBulletSubScene.h new file mode 100644 index 0000000..72eda62 --- /dev/null +++ b/game/player/PlayerBulletSubScene.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class PlayerBulletSubScene { +public: + int create(crepe::Scene & scn, int counter); +}; diff --git a/game/player/PlayerEndScript.cpp b/game/player/PlayerEndScript.cpp new file mode 100644 index 0000000..62fd350 --- /dev/null +++ b/game/player/PlayerEndScript.cpp @@ -0,0 +1,104 @@ +#include "PlayerEndScript.h" + +#include "../Config.h" +#include "../Events.h" +#include "manager/LoopTimerManager.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void PlayerEndScript::init() { + Rigidbody & rb_player = this->get_components_by_name<Rigidbody>("player").front(); + rb_player.data.elasticity_coefficient = 0.7; + + subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { + return this->on_collision(ev); + }); +} + +bool PlayerEndScript::on_collision(const crepe::CollisionEvent & ev) { + if (ev.info.other.metadata.name == "floor") { + Transform & transform_player + = this->get_components_by_name<Transform>("player").front(); + RefVector<Animator> anim_player = this->get_components_by_name<Animator>("player"); + Rigidbody & rb_player = this->get_components_by_name<Rigidbody>("player").front(); + Rigidbody & rb_camera = this->get_components_by_name<Rigidbody>("camera").front(); + + float dt = this->get_loop_timer().get_fixed_delta_time().count(); + + if (jump == 0) { + int random_number = rand() % 4; + for (Animator & anim : anim_player) { + anim.active = false; + anim.set_anim(6); + for (int i = 0; i < random_number; i++) { + anim.next_anim(); + } + } + } else if (jump == 1) { + for (Animator & anim : anim_player) { + anim.next_anim(); + } + } + + if (jump == 0) { + rb_player.data.angular_velocity = 16000 * dt; + rb_player.data.angular_velocity_coefficient = 0.7; + jump++; + } else if (jump == 1) { + jump++; + } else if (jump == 2) { + RefVector<Rigidbody> rb_back_forest + = this->get_components_by_tag<Rigidbody>("forest_background"); + for (Rigidbody & rb : rb_back_forest) { + rb.data.linear_velocity_coefficient = vec2(0.5, 0.5); + } + + rb_player.data.angular_velocity = 0; + rb_player.data.elasticity_coefficient = 0; + if (rb_player.data.linear_velocity.x != 0) { + rb_player.data.linear_velocity = vec2(PLAYER_SPEED * dt, 0); + } + rb_player.data.linear_velocity_coefficient = vec2(0.5, 0.5); + rb_camera.data.linear_velocity_coefficient = vec2(0.5, 0.5); + for (Animator & anim : anim_player) { + anim.active = false; + anim.set_anim(7); + } + if (transform_player.rotation > 0 && transform_player.rotation < 90) { + // Do not call next_anim() + } else if (transform_player.rotation > 90 && transform_player.rotation < 180) { + for (Animator & anim : anim_player) { + anim.next_anim(); + } + } else if (transform_player.rotation > 180 && transform_player.rotation < 270) { + for (Animator & anim : anim_player) { + anim.next_anim(); + anim.next_anim(); + } + } else { + for (Animator & anim : anim_player) { + anim.next_anim(); + anim.next_anim(); + anim.next_anim(); + } + } + jump++; + } + + if (rb_player.data.linear_velocity.x < 5 && jump == 3) { + this->trigger_event<EndGameEvent>(); + jump++; + } + + return false; + } + + return false; +} diff --git a/game/player/PlayerEndScript.h b/game/player/PlayerEndScript.h new file mode 100644 index 0000000..03ea8a9 --- /dev/null +++ b/game/player/PlayerEndScript.h @@ -0,0 +1,14 @@ +#pragma once + +#include <crepe/api/Script.h> + +class PlayerEndScript : public crepe::Script { +public: + void init(); + +private: + bool on_collision(const crepe::CollisionEvent & ev); + +private: + int jump = 0; +}; diff --git a/game/player/PlayerScript.cpp b/game/player/PlayerScript.cpp new file mode 100644 index 0000000..8cbe8dc --- /dev/null +++ b/game/player/PlayerScript.cpp @@ -0,0 +1,214 @@ +#include "PlayerScript.h" + +#include "../Config.h" +#include "../enemy/BattleScript.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void PlayerScript::init() { + subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { + return this->on_collision(ev); + }); + subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { + if (ev.repeat) return false; + return this->on_key_down(ev); + }); + subscribe<KeyReleaseEvent>([this](const KeyReleaseEvent & ev) -> bool { + return this->on_key_up(ev); + }); + this->last_fired = std::chrono::steady_clock::now(); + this->body = get_component<Rigidbody>(); +} + +bool PlayerScript::on_key_down(const KeyPressEvent & ev) { + if (ev.key == Keycode::SPACE) { + const vec2 UP = {0, -1}; + this->help_kick(UP); + } + return false; +} + +bool PlayerScript::on_key_up(const KeyReleaseEvent & ev) { + if (ev.key == Keycode::SPACE) { + const vec2 DOWN = {0, 1}; + this->help_kick(DOWN); + } + return false; +} + +void PlayerScript::help_kick(const vec2 & direction) { + // softly "kick" the player (at start/end of flight) + vec2 & velocity = this->body->data.linear_velocity; + float kick_amount = std::min( + velocity.length() * PLAYER_HELP_KICK_SCALE, engine_gravity * PLAYER_HELP_KICK_MAX + ); + velocity += direction * kick_amount; +} + +bool PlayerScript::on_collision(const CollisionEvent & ev) { + BehaviorScript & play_scr = this->get_components_by_name<BehaviorScript>("player").front(); + BehaviorScript & end_scr = this->get_components_by_name<BehaviorScript>("player").back(); + RefVector<Animator> animators = this->get_components_by_name<Animator>("player"); + RefVector<ParticleEmitter> emitters + = this->get_components_by_name<ParticleEmitter>("player"); + + if (ev.info.other.metadata.tag == "zapper") { + for (Animator & anim : animators) { + anim.active = true; + anim.set_anim(4); + anim.data.looping = true; + prev_anim = 0; + } + for (ParticleEmitter & emitter : emitters) { + emitter.data.emission_rate = 0; + } + play_scr.active = false; + end_scr.active = true; + + AudioSource & audio = this->get_components_by_name<AudioSource>("player").at(0); + audio.play(); + + return false; + } else if (ev.info.other.metadata.tag == "laser") { + for (Animator & anim : animators) { + anim.active = true; + anim.set_anim(4); + anim.data.looping = true; + prev_anim = 0; + } + for (ParticleEmitter & emitter : emitters) { + emitter.data.emission_rate = 0; + } + play_scr.active = false; + end_scr.active = true; + + AudioSource & audio = this->get_components_by_name<AudioSource>("player").at(1); + audio.play(); + + return false; + } else if (ev.info.other.metadata.tag == "missile" + || ev.info.other.metadata.tag == "enemy_bullet") { + for (Animator & anim : animators) { + anim.active = true; + anim.set_anim(5); + anim.data.looping = true; + prev_anim = 0; + } + for (ParticleEmitter & emitter : emitters) { + emitter.data.emission_rate = 0; + } + play_scr.active = false; + end_scr.active = true; + + AudioSource & audio = this->get_components_by_name<AudioSource>("player").at(2); + audio.play(); + + return false; + } + + return false; +} + +void PlayerScript::fixed_update(crepe::duration_t dt) { + RefVector<Animator> animators = this->get_components_by_name<Animator>("player"); + RefVector<ParticleEmitter> emitters + = this->get_components_by_name<ParticleEmitter>("player"); + Transform & transform = this->get_components_by_name<Transform>("player").front(); + + for (ParticleEmitter & emitter : emitters) { + emitter.data.boundary.offset = vec2(0, -transform.position.y); + } + + Rigidbody & rb = this->body; + if (this->get_key_state(Keycode::ENTER)) { + + auto now = std::chrono::steady_clock::now(); + std::chrono::duration<float> elapsed = now - last_fired; + if (elapsed > shot_delay) { + this->shoot(transform.position, 0); + last_fired = now; + } + } + if (this->get_key_state(Keycode::SPACE)) { + rb.add_force_linear( + vec2(0, -1) * (engine_gravity * PLAYER_GRAVITY_SCALE * dt.count()) + ); + + if (prev_anim != 1) { + for (Animator & anim : animators) { + anim.active = true; + anim.set_anim(1); + anim.data.looping = true; + prev_anim = 1; + } + for (ParticleEmitter & emitter : emitters) { + emitter.data.emission_rate = 30; + } + } + + AudioSource & audio = this->get_components_by_name<AudioSource>("player").at( + 3 + current_jetpack_sound + ); + audio.play(); + current_jetpack_sound++; + if (current_jetpack_sound > 7) { + current_jetpack_sound = 0; + } + } else if (transform.position.y == 200) { + Rigidbody & rb = this->body; + if (prev_anim != 0 && rb.data.linear_velocity.x != 0) { + for (Animator & anim : animators) { + anim.active = true; + anim.set_anim(0); + anim.data.looping = true; + prev_anim = 0; + } + for (ParticleEmitter & emitter : emitters) { + emitter.data.emission_rate = 0; + } + } + } else { + if (prev_anim != 2) { + for (Animator & anim : animators) { + anim.set_anim(2); + anim.data.looping = false; + prev_anim = 2; + } + for (ParticleEmitter & emitter : emitters) { + emitter.data.emission_rate = 0; + } + } + } +} + +void PlayerScript::shoot(const vec2 & location, float angle) { + RefVector<Transform> bullet_transforms + = this->get_components_by_tag<Transform>("player_bullet"); + + for (Transform & bullet_pos : bullet_transforms) { + if (bullet_pos.position.x == 0 && bullet_pos.position.y == -850) { + + bullet_pos.position = location; + bullet_pos.position.x += 20; + Rigidbody & bullet_body + = this->get_components_by_id<Rigidbody>(bullet_pos.game_object_id).front(); + BoxCollider bullet_collider + = this->get_components_by_id<BoxCollider>(bullet_pos.game_object_id).front(); + bullet_body.active = true; + BehaviorScript & bullet_script + = this->get_components_by_id<BehaviorScript>(bullet_pos.game_object_id) + .front(); + bullet_script.active = true; + return; + } + } +} diff --git a/game/player/PlayerScript.h b/game/player/PlayerScript.h new file mode 100644 index 0000000..6a7dedb --- /dev/null +++ b/game/player/PlayerScript.h @@ -0,0 +1,32 @@ +#pragma once + +#include "util/OptionalRef.h" +#include <chrono> +#include <crepe/api/Config.h> +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> + +class PlayerScript : public crepe::Script { +public: + void init(); + void fixed_update(crepe::duration_t dt); + +private: + bool on_collision(const crepe::CollisionEvent & ev); + bool on_key_down(const crepe::KeyPressEvent & ev); + bool on_key_up(const crepe::KeyReleaseEvent & ev); + // bool on_key_up(const crepe::KeyReleaseEvent& ev); + void shoot(const crepe::vec2 & location, float angle); + void help_kick(const crepe::vec2 & direction); + +private: + int prev_anim = 0; + std::chrono::time_point<std::chrono::steady_clock> last_fired; + std::chrono::time_point<std::chrono::steady_clock> last_switched; + std::chrono::duration<float> shot_delay = std::chrono::duration<float>(0.5); + std::chrono::duration<float> switch_delay = std::chrono::duration<float>(0.01); + int current_jetpack_sound = 0; + + float & engine_gravity = crepe::Config::get_instance().physics.gravity; + crepe::OptionalRef<crepe::Rigidbody> body; +}; diff --git a/game/player/PlayerSubScene.cpp b/game/player/PlayerSubScene.cpp new file mode 100644 index 0000000..d0142e0 --- /dev/null +++ b/game/player/PlayerSubScene.cpp @@ -0,0 +1,222 @@ +#include "PlayerSubScene.h" +#include "PlayerAudioScript.h" +#include "PlayerEndScript.h" +#include "PlayerScript.h" + +#include "../Config.h" +#include "../coins/CoinScript.h" + +#include <crepe/ValueBroker.h> +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/CircleCollider.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/manager/SaveManager.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +PlayerSubScene::PlayerSubScene(Scene & scn) { + GameObject player = scn.new_object("player", "player", vec2(-100, 200)); + + SaveManager & save = scn.get_save_manager(); + ValueBroker<int> particle_type = save.get<int>(JETPACK_PARTICLES, 0); + + string player_bullet_string; + string player_bullet_x2_string; + string player_shell_string; + if (particle_type.get() == 0) { + player_bullet_string = "asset/other_effects/effect_smgbullet.png"; + player_bullet_x2_string = "asset/other_effects/effect_smgbullet_x2.png"; + player_shell_string = "asset/other_effects/effect_rocketmgshell_TVOS.png"; + } else { + player_bullet_string = "asset/background/aquarium/bubble.png"; + player_bullet_x2_string = "asset/background/aquarium/bubble.png"; + player_shell_string = "asset/background/aquarium/bubble.png"; + } + + Asset player_bullet {player_bullet_string}; + Sprite & player_bullet_sprite = player.add_component<Sprite>( + player_bullet, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 3, + .size = vec2(0, 6), + } + ); + player.add_component<ParticleEmitter>(player_bullet_sprite, ParticleEmitter::Data{ + .offset = vec2(-15, 15), + .emission_rate = 0, + .min_speed = 300, + .max_speed = 500, + .min_angle = 85, + .max_angle = 100, + .boundary = ParticleEmitter::Boundary { + .height = 400, + .reset_on_exit = true, + }, + }); + Asset player_bullet_x2 {player_bullet_x2_string}; + Sprite & player_bullet_x2_sprite = player.add_component<Sprite>( + player_bullet_x2, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 3, + .size = vec2(0, 12), + } + ); + player.add_component<ParticleEmitter>(player_bullet_x2_sprite, ParticleEmitter::Data{ + .offset = vec2(-15, 15), + .emission_rate = 0, + .min_speed = 300, + .max_speed = 500, + .min_angle = 85, + .max_angle = 100, + .boundary = ParticleEmitter::Boundary { + .height = 400, + .reset_on_exit = true, + }, + }); + Asset player_shell {player_shell_string}; + Sprite & player_shell_sprite = player.add_component<Sprite>( + player_shell, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 3, + .size = vec2(0, 12), + .angle_offset = 90, + } + ); + player.add_component<ParticleEmitter>(player_shell_sprite, ParticleEmitter::Data{ + .offset = vec2(-15, 15), + .emission_rate = 0, + .min_speed = 200, + .max_speed = 500, + .min_angle = 110, + .max_angle = 120, + .force_over_time = vec2(0, 1000), + .boundary = ParticleEmitter::Boundary { + .height = 400, + .reset_on_exit = true, + }, + }); + + Asset player_body_asset {"asset/barry/defaultBody.png"}; + Sprite & player_body_sprite = player.add_component<Sprite>( + player_body_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 0, + .size = vec2(0, 50), + } + ); + player.add_component<Animator>( + player_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = 5, + .looping = true, + } + ); + player.add_component<BoxCollider>(vec2(50, 35)); + Asset player_head_asset {"asset/barry/defaultHead.png"}; + Sprite & player_head_sprite = player.add_component<Sprite>( + player_head_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 1, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + player.add_component<Animator>( + player_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = 5, + .looping = true, + } + ); + player.add_component<CircleCollider>(25, vec2(0, -20)); + Asset player_jetpack_asset {"asset/barry/jetpackDefault.png"}; + Sprite & player_jetpack_sprite = player.add_component<Sprite>( + player_jetpack_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 2, + .size = vec2(0, 60), + .position_offset = vec2(-20, 0), + } + ); + player_jetpack_sprite.active = false; + player.add_component<Animator>( + player_jetpack_sprite, ivec2(32, 44), uvec2(4, 4), + Animator::Data { + .fps = 5, + .looping = true, + } + ); + player.add_component<BoxCollider>(vec2(40, 50), vec2(-20, 0)); + player.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1.0, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(PLAYER_SPEED * 0.02, 0), + .collision_layers + = {COLL_LAY_BOT_TOP, COLL_LAY_ZAPPER, COLL_LAY_LASER, COLL_LAY_MISSILE, COLL_LAY_BULLET + }, + .collision_layer = COLL_LAY_PLAYER, + }); + + player.add_component<BehaviorScript>().set_script<PlayerScript>().active = false; + player.add_component<BehaviorScript>().set_script<CoinScript>(); + player.add_component<BehaviorScript>().set_script<PlayerEndScript>().active = false; + + player.add_component<AudioSource>(Asset("asset/sfx/dud_zapper_lp.ogg")); + player.add_component<AudioSource>(Asset("asset/sfx/dud_zapper_pop.ogg")); + player.add_component<AudioSource>(Asset("asset/sfx/dud_fire.ogg")); + player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_01.ogg")).volume + = 0.1; + player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_02.ogg")).volume + = 0.1; + player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_03.ogg")).volume + = 0.1; + player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_04.ogg")).volume + = 0.1; + player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_05.ogg")).volume + = 0.1; + player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_06.ogg")).volume + = 0.1; + player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_07.ogg")).volume + = 0.1; + player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_08.ogg")).volume + = 0.1; + + GameObject player_audio = scn.new_object("player_audio", "player_audio", vec2(0, 0)); + player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_left_1.ogg")).volume + = 3.0; + player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_right_1.ogg")) + .volume + = 3.0; + player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_left_2.ogg")).volume + = 3.0; + player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_right_2.ogg")) + .volume + = 3.0; + player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_left_3.ogg")).volume + = 3.0; + player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_right_3.ogg")) + .volume + = 3.0; + player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_left_4.ogg")).volume + = 3.0; + player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_right_4.ogg")) + .volume + = 3.0; + + player_audio.add_component<BehaviorScript>().set_script<PlayerAudioScript>().active + = false; +} diff --git a/game/player/PlayerSubScene.h b/game/player/PlayerSubScene.h new file mode 100644 index 0000000..bf94c32 --- /dev/null +++ b/game/player/PlayerSubScene.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class PlayerSubScene { +public: + PlayerSubScene(crepe::Scene & scn); +}; diff --git a/game/prefab/CMakeLists.txt b/game/prefab/CMakeLists.txt new file mode 100644 index 0000000..6c36ef2 --- /dev/null +++ b/game/prefab/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(main PUBLIC + ZapperObject.cpp + ZapperPoolSubScene.cpp + ZapperPoolScript.cpp +) + diff --git a/game/prefab/ZapperObject.cpp b/game/prefab/ZapperObject.cpp new file mode 100644 index 0000000..24bbbd2 --- /dev/null +++ b/game/prefab/ZapperObject.cpp @@ -0,0 +1,118 @@ +#include <crepe/api/Transform.h> + +#include "Config.h" +#include "ZapperObject.h" + +using namespace crepe; + +ZapperObject::ZapperObject(crepe::GameObject && base) + : GameObject(std::move(base)), + sprite { + .orb_start = add_component<Sprite>( + Asset {"asset/obstacles/zapper/orbAnim.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .order_in_layer = 1, + .size = vec2(0, 50) * SCALE, + } + ), + .orb_end = add_component<Sprite>( + sprite.orb_start.source, + Sprite::Data { + .flip = {true, true}, + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .order_in_layer = 1, + .size = vec2(0, 50) * SCALE, + } + ), + .glow_start = add_component<Sprite>( + Asset {"asset/obstacles/zapper/regular_zappers/glow.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .order_in_layer = -1, + .size = vec2(128, 128) * SCALE, + } + ), + .glow_end = add_component<Sprite>( + sprite.glow_start.source, + Sprite::Data { + .flip = {true, true}, + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .order_in_layer = -1, + .size = vec2(128, 128) * SCALE, + } + ), + .beam = add_component<Sprite>( + Asset {"asset/obstacles/zapper/regular_zappers/zapEffect.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_OBSTACLES, + .order_in_layer = 0, + .size = vec2(0, 40 * SCALE), + .angle_offset = 90, + } + ), + }, + animator { + .orb_start = add_component<Animator>( + sprite.orb_start, ivec2(62, 42), uvec2(4, 1), + Animator::Data { + .fps = 10, + .looping = true, + } + ), + .orb_end = add_component<Animator>( + sprite.orb_end, ivec2(62, 42), uvec2(4, 1), animator.orb_start.data + ), + .glow_start = add_component<Animator>( + sprite.glow_start, ivec2(128, 128), uvec2(16, 1), + Animator::Data { + .fps = 30, + .looping = true, + } + ), + .glow_end = add_component<Animator>( + sprite.glow_end, ivec2(128, 128), uvec2(16, 1), animator.glow_start.data + ), + }, + body {add_component<Rigidbody>(Rigidbody::Data { + .body_type = Rigidbody::BodyType::KINEMATIC, + .kinematic_collision = false, + .collision_layer = COLL_LAY_ZAPPER, + })}, + collider {add_component<BoxCollider>(vec2(0, 0))} { + this->set_active(false); +} + +void ZapperObject::place(const crepe::vec2 & position, float rotation, float length) { + this->transform.position = position; + this->transform.rotation = rotation; + + vec2 offset = vec2(0, 1) * length / 2; + + this->sprite.orb_start.data.position_offset = offset; + this->sprite.glow_start.data.position_offset = offset; + this->sprite.orb_end.data.position_offset = -offset; + this->sprite.glow_end.data.position_offset = -offset; + + this->sprite.beam.data.size.x = length; + + this->collider.dimensions = offset.rotate(rotation) * 2 + vec2(30, 30) * SCALE; +} + +void ZapperObject::set_active(bool active) { + this->sprite.orb_start.active = active; + this->sprite.orb_end.active = active; + this->sprite.glow_start.active = active; + this->sprite.glow_end.active = active; + this->sprite.beam.active = active; + + this->animator.orb_start.active = active; + this->animator.orb_end.active = active; + this->animator.glow_start.active = active; + this->animator.glow_end.active = active; + + this->body.active = active; + this->collider.active = active; + + this->active = active; +} diff --git a/game/prefab/ZapperObject.h b/game/prefab/ZapperObject.h new file mode 100644 index 0000000..42edc51 --- /dev/null +++ b/game/prefab/ZapperObject.h @@ -0,0 +1,40 @@ +#pragma once + +#include <crepe/api/Animator.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> + +class ZapperObject : public crepe::GameObject { +public: + ZapperObject(crepe::GameObject &&); + +public: + bool active = true; + + struct { + crepe::Sprite & orb_start; + crepe::Sprite & orb_end; + crepe::Sprite & glow_start; + crepe::Sprite & glow_end; + crepe::Sprite & beam; + } sprite; + + struct { + crepe::Animator & orb_start; + crepe::Animator & orb_end; + crepe::Animator & glow_start; + crepe::Animator & glow_end; + } animator; + + crepe::Rigidbody & body; + crepe::BoxCollider & collider; + +private: + static constexpr float SCALE = 0.8; + +public: + void place(const crepe::vec2 & position, float rotation, float length); + void set_active(bool active); +}; diff --git a/game/prefab/ZapperPoolScript.cpp b/game/prefab/ZapperPoolScript.cpp new file mode 100644 index 0000000..b832ddd --- /dev/null +++ b/game/prefab/ZapperPoolScript.cpp @@ -0,0 +1,70 @@ +#include <crepe/api/Camera.h> + +#include "../Config.h" +#include "../Random.h" + +#include "ZapperPoolScript.h" +#include "ZapperPoolSubScene.h" +#include "util/OptionalRef.h" + +using namespace crepe; +using namespace std; + +ZapperPoolScript::ZapperPoolScript(std::vector<ZapperObject> && pool) : pool(pool) {} + +void ZapperPoolScript::init() { + subscribe<CreateZapperEvent>([this](const CreateZapperEvent &) { + this->spawn_random(); + return true; + }); + + camera_transform = get_components_by_name<Transform>(CAMERA_NAME).back(); + camera_camera = get_components_by_name<Camera>(CAMERA_NAME).back(); +} + +void ZapperPoolScript::fixed_update(crepe::duration_t) { + float threshold + = camera_transform->position.x - camera_camera->viewport_size.x / 2 - OFFSCREEN_MARGIN; + for (ZapperObject & zapper : this->pool) { + if (!zapper.active) continue; + + if (zapper.transform.position.x < threshold) zapper.set_active(false); + } +} + +void ZapperPoolScript::spawn_random() { + OptionalRef<ZapperObject> zapper = this->get_next_zapper(); + if (!zapper) return; // pool exhausted + + bool horizontal = Random::b(); + vec2 pos; + float rotation, length; + pos.x + = camera_transform->position.x + camera_camera->viewport_size.x / 2 + OFFSCREEN_MARGIN; + + if (horizontal) { + rotation = 90; + length = Random::f(400, 200); + pos.y = Random::f(0.5, -0.5) * HALLWAY_HEIGHT; + // align zapper to right edge of camera + pos.x -= MAX_LENGTH - (length / 2); + } else { + rotation = 0; + length = Random::f(200, 75); + // ensure zapper doesn't crash into ceiling or floor + pos.y = Random::f(0.5, -0.5) * (HALLWAY_HEIGHT - length); + // align zapper to right edge of camera + pos.x -= MAX_LENGTH; + } + + zapper->place(pos, rotation, length); + zapper->set_active(true); +} + +OptionalRef<ZapperObject> ZapperPoolScript::get_next_zapper() { + for (ZapperObject & zapper : this->pool) { + if (zapper.active) continue; + return zapper; + } + return {}; +} diff --git a/game/prefab/ZapperPoolScript.h b/game/prefab/ZapperPoolScript.h new file mode 100644 index 0000000..2208c80 --- /dev/null +++ b/game/prefab/ZapperPoolScript.h @@ -0,0 +1,33 @@ +#pragma once + +#include <crepe/api/Script.h> + +#include "ZapperObject.h" +#include "util/OptionalRef.h" + +class ZapperPoolSubScene; + +class ZapperPoolScript : public crepe::Script { +public: + ZapperPoolScript(std::vector<ZapperObject> && pool); + + void init(); + void fixed_update(crepe::duration_t); + + unsigned i = 0; + +private: + std::vector<ZapperObject> pool; + +private: + crepe::OptionalRef<crepe::Transform> camera_transform; + crepe::OptionalRef<crepe::Camera> camera_camera; + crepe::OptionalRef<ZapperObject> get_next_zapper(); + +private: + void spawn_random(); + +private: + static constexpr float MAX_LENGTH = 400; + static constexpr float OFFSCREEN_MARGIN = 50 + MAX_LENGTH; +}; diff --git a/game/prefab/ZapperPoolSubScene.cpp b/game/prefab/ZapperPoolSubScene.cpp new file mode 100644 index 0000000..a52aa75 --- /dev/null +++ b/game/prefab/ZapperPoolSubScene.cpp @@ -0,0 +1,17 @@ +#include <crepe/api/BehaviorScript.h> + +#include "ZapperPoolScript.h" +#include "ZapperPoolSubScene.h" + +using namespace crepe; +using namespace std; + +ZapperPoolSubScene::ZapperPoolSubScene(Scene & scene) + : controller {scene.new_object("controller")} { + + vector<ZapperObject> pool; + for (size_t i = 0; i < this->POOL_SIZE; i++) + pool.emplace_back(scene.new_object("zapper", "zapper")); + BehaviorScript & behavior = this->controller.add_component<BehaviorScript>(); + behavior.set_script<ZapperPoolScript>(std::move(pool)); +} diff --git a/game/prefab/ZapperPoolSubScene.h b/game/prefab/ZapperPoolSubScene.h new file mode 100644 index 0000000..6f6e297 --- /dev/null +++ b/game/prefab/ZapperPoolSubScene.h @@ -0,0 +1,19 @@ +#pragma once + +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Scene.h> +#include <crepe/util/OptionalRef.h> + +class CreateZapperEvent : public crepe::Event {}; + +class ZapperPoolSubScene { +public: + ZapperPoolSubScene(crepe::Scene & scene); + +private: + crepe::GameObject controller; + +private: + static constexpr size_t POOL_SIZE = 4; +}; diff --git a/game/preview/NpcScript.cpp b/game/preview/NpcScript.cpp new file mode 100644 index 0000000..86117d4 --- /dev/null +++ b/game/preview/NpcScript.cpp @@ -0,0 +1,29 @@ +#include "NpcScript.h" + +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/SaveManager.h> + +using namespace std; +using namespace crepe; + +void NpcScript::fixed_update(duration_t dt) { + auto & rb = this->get_component<Rigidbody>(); + auto npc = this->get_components<Sprite>(); + auto & transform = this->get_component<Transform>(); + + if (transform.position.x < 200) { + rb.data.linear_velocity.x *= -1; + } + if (transform.position.x > 700) { + rb.data.linear_velocity.x *= -1; + } + + if (rb.data.linear_velocity.x < 0) { + npc.front().get().data.flip = {true, false}; + npc.back().get().data.flip = {true, false}; + } else { + npc.front().get().data.flip = {false, false}; + npc.back().get().data.flip = {false, false}; + } +} diff --git a/game/preview/NpcScript.h b/game/preview/NpcScript.h new file mode 100644 index 0000000..d278f83 --- /dev/null +++ b/game/preview/NpcScript.h @@ -0,0 +1,8 @@ +#pragma once + +#include <crepe/api/Script.h> + +class NpcScript : public crepe::Script { +public: + void fixed_update(crepe::duration_t dt); +}; diff --git a/game/preview/NpcSubScene.cpp b/game/preview/NpcSubScene.cpp new file mode 100644 index 0000000..5ededb6 --- /dev/null +++ b/game/preview/NpcSubScene.cpp @@ -0,0 +1,65 @@ + + +#include "NpcSubScene.h" + +#include "../Config.h" +#include "NpcScript.h" + +#include <crepe/ValueBroker.h> +#include <crepe/api/Animator.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> +#include <crepe/manager/SaveManager.h> + +using namespace crepe; + +NpcSubScene::NpcSubScene(Scene & scn) { + GameObject npc = scn.new_object("npc", "npc_tag", vec2 {500, 0}, 0, 1); + Asset npc_body {"asset/workers/worker1Body.png"}; + Asset npc_head {"asset/workers/worker1Head.png"}; + + auto & npc_body_sprite = npc.add_component<Sprite>( + npc_body, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .size = {0, 50}, + } + ); + auto & npc_head_sprite = npc.add_component<Sprite>( + npc_head, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .size = {0, 50}, + .position_offset = {0, -20}, + } + ); + + npc.add_component<Animator>( + npc_body_sprite, ivec2 {32, 32}, uvec2 {4, 8}, + Animator::Data { + .fps = 5, + .looping = true, + } + ); + npc.add_component<Animator>( + npc_head_sprite, ivec2 {32, 32}, uvec2 {4, 8}, + Animator::Data { + .fps = 5, + .looping = true, + } + ); + npc.add_component<BoxCollider>(vec2 {40, 50}); + + npc.add_component<Rigidbody>(Rigidbody::Data { + .mass = 10, + .gravity_scale = 1, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = {-50, 0}, + //.max_linear_velocity = 40, + .collision_layers = {COLL_LAY_BOT_TOP, 100}, + .collision_layer = COLL_LAY_PLAYER, + }); + + npc.add_component<BehaviorScript>().set_script<NpcScript>(); +} diff --git a/game/preview/NpcSubScene.h b/game/preview/NpcSubScene.h new file mode 100644 index 0000000..a226195 --- /dev/null +++ b/game/preview/NpcSubScene.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class NpcSubScene { +public: + NpcSubScene(crepe::Scene & scn); +}; diff --git a/game/preview/PrevPlayerScript.cpp b/game/preview/PrevPlayerScript.cpp new file mode 100644 index 0000000..ae25dad --- /dev/null +++ b/game/preview/PrevPlayerScript.cpp @@ -0,0 +1,157 @@ +#include "PrevPlayerScript.h" + +#include "../missile/SpawnEvent.h" + +#include <crepe/api/AudioSource.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/SaveManager.h> + +using namespace crepe; + +bool PrevPlayerScript::key_pressed(const KeyPressEvent & ev) { + switch (ev.key) { + case Keycode::A: + this->get_component<Rigidbody>().data.linear_velocity.x = -move_speed; + this->body->data.flip = {true, false}; + this->head->data.flip = {true, false}; + break; + case Keycode::D: + this->get_component<Rigidbody>().data.linear_velocity.x = move_speed; + this->body->data.flip = {false, false}; + this->head->data.flip = {false, false}; + break; + case Keycode::D0: + this->body_anim->set_anim(0); + this->head_anim->set_anim(0); + break; + case Keycode::D1: + this->body_anim->set_anim(1); + this->head_anim->set_anim(1); + break; + case Keycode::D2: + this->body_anim->set_anim(2); + this->head_anim->set_anim(2); + break; + case Keycode::D3: + this->body_anim->set_anim(3); + this->head_anim->set_anim(3); + break; + case Keycode::D4: + this->body_anim->set_anim(4); + this->head_anim->set_anim(4); + break; + case Keycode::D5: + this->body_anim->set_anim(5); + this->head_anim->set_anim(5); + break; + case Keycode::D6: + this->body_anim->set_anim(6); + this->head_anim->set_anim(6); + break; + case Keycode::D7: + this->body_anim->set_anim(7); + this->head_anim->set_anim(7); + break; + case Keycode::LEFT: + this->get_component<Transform>().rotation += 10; + break; + case Keycode::RIGHT: + this->get_component<Transform>().rotation -= 10; + break; + case Keycode::UP: + this->head->data.position_offset += 10; + break; + case Keycode::DOWN: + this->head->data.position_offset -= 10; + break; + case Keycode::P: + this->get_components_by_name<AudioSource>("background_music").front().get().active + = true; + break; + case Keycode::J: + this->get_components_by_name<Transform>("camera").front().get().position.x + -= move_speed; + break; + case Keycode::K: + this->get_components_by_name<Transform>("camera").front().get().position.y + -= move_speed; + break; + case Keycode::L: + this->get_components_by_name<Transform>("camera").front().get().position.x + += move_speed; + break; + case Keycode::I: + this->get_components_by_name<Transform>("camera").front().get().position.y + += move_speed; + break; + case Keycode::M: + trigger_event<MissileSpawnEvent>(MissileSpawnEvent {}); + break; + default: + break; + } + return false; +} + +void PrevPlayerScript::init() { + this->rb = get_component<Rigidbody>(); + + auto animations = this->get_components<Animator>(); + body_anim = animations[0]; + head_anim = animations[1]; + + auto sprites = this->get_components<Sprite>(); + body = sprites[0]; + head = sprites[1]; + + subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { + return this->key_pressed(ev); + }); + subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { + if (ev.repeat) return false; + return this->on_key_down(ev); + }); + subscribe<KeyReleaseEvent>([this](const KeyReleaseEvent & ev) -> bool { + return this->on_key_up(ev); + }); +}; + +void PrevPlayerScript::fixed_update(crepe::duration_t dt) { + if (this->get_key_state(Keycode::SPACE)) { + this->rb->add_force_linear( + vec2(0, -1) * (engine_gravity * PLAYER_GRAVITY_SCALE * dt.count()) + ); + } + + auto & savemgr = this->get_save_manager(); + const auto & pos = this->get_component<Transform>().position; + + savemgr.set("player_x", pos.x); + savemgr.set("player_y", pos.y); +}; + +bool PrevPlayerScript::on_key_down(const KeyPressEvent & ev) { + if (ev.key == Keycode::SPACE) { + const vec2 UP = {0, -1}; + this->help_kick(UP); + } + return false; +} + +bool PrevPlayerScript::on_key_up(const KeyReleaseEvent & ev) { + if (ev.key == Keycode::SPACE) { + const vec2 DOWN = {0, 1}; + this->help_kick(DOWN); + } + return false; +} + +void PrevPlayerScript::help_kick(const vec2 & direction) { + // softly "kick" the player (at start/end of flight) + vec2 & velocity = this->rb->data.linear_velocity; + float kick_amount = std::min( + velocity.length() * PLAYER_HELP_KICK_SCALE, engine_gravity * PLAYER_HELP_KICK_MAX + ); + velocity += direction * kick_amount; +} diff --git a/game/preview/PrevPlayerScript.h b/game/preview/PrevPlayerScript.h new file mode 100644 index 0000000..ae66449 --- /dev/null +++ b/game/preview/PrevPlayerScript.h @@ -0,0 +1,32 @@ +#include <crepe/api/Animator.h> +#include <crepe/api/Config.h> +#include <crepe/api/Event.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/util/OptionalRef.h> + +class PrevPlayerScript : public crepe::Script { +private: + crepe::OptionalRef<crepe::Animator> head_anim; + crepe::OptionalRef<crepe::Animator> body_anim; + crepe::OptionalRef<crepe::Sprite> head; + crepe::OptionalRef<crepe::Sprite> body; + +private: + float move_speed = 100; + +private: + void init(); + void fixed_update(crepe::duration_t dt); + bool key_pressed(const crepe::KeyPressEvent & ev); + +private: + bool on_key_down(const crepe::KeyPressEvent & ev); + bool on_key_up(const crepe::KeyReleaseEvent & ev); + void help_kick(const crepe::vec2 & direction); + +private: + float & engine_gravity = crepe::Config::get_instance().physics.gravity; + crepe::OptionalRef<crepe::Rigidbody> rb; +}; diff --git a/game/preview/PrevPlayerSubScene.cpp b/game/preview/PrevPlayerSubScene.cpp new file mode 100644 index 0000000..074cfb4 --- /dev/null +++ b/game/preview/PrevPlayerSubScene.cpp @@ -0,0 +1,87 @@ + +#include "PrevPlayerSubScene.h" + +#include "../Config.h" +#include "PrevPlayerScript.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/AudioSource.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +#include <crepe/ValueBroker.h> +#include <crepe/manager/SaveManager.h> + +using namespace crepe; + +PrevPlayerSubScene::PrevPlayerSubScene(Scene & scn) { + + GameObject player = scn.new_object("player", "player", vec2 {800, -100}, 0, 1); + Asset player_body_asset {"asset/barry/defaultBody.png"}; + Sprite & player_body_sprite = player.add_component<Sprite>( + player_body_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 0, + .size = vec2(0, 50), + } + ); + player.add_component<Animator>( + player_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = 5, + .looping = true, + } + ); + Asset player_head_asset {"asset/barry/defaultHead.png"}; + Sprite & player_head_sprite = player.add_component<Sprite>( + player_head_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 1, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + player.add_component<Animator>( + player_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = 5, + .looping = true, + } + ); + Asset player_jetpack_asset {"asset/barry/jetpackDefault.png"}; + Sprite & player_jetpack_sprite = player.add_component<Sprite>( + player_jetpack_asset, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PLAYER, + .order_in_layer = 2, + .size = vec2(0, 60), + .position_offset = vec2(-20, 0), + } + ); + player_jetpack_sprite.active = false; + player.add_component<Animator>( + player_jetpack_sprite, ivec2(32, 44), uvec2(4, 4), + Animator::Data { + .fps = 5, + .looping = true, + } + ); + player.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 1, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(100, 0), + .collision_layers = {COLL_LAY_BOT_TOP, 100}, + .collision_layer = COLL_LAY_PLAYER, + }); + player.add_component<BoxCollider>(vec2(40, 50)); + player.add_component<BehaviorScript>().set_script<PrevPlayerScript>(); + + GameObject music = scn.new_object("background_music", "background_music"); + AudioSource & audio = music.add_component<AudioSource>(Asset {"asset/music/level.ogg"}); + audio.loop = true; + audio.play_on_awake = true; + audio.active = false; +} diff --git a/game/preview/PrevPlayerSubScene.h b/game/preview/PrevPlayerSubScene.h new file mode 100644 index 0000000..a61f341 --- /dev/null +++ b/game/preview/PrevPlayerSubScene.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class PrevPlayerSubScene { +public: + PrevPlayerSubScene(crepe::Scene & scn); +}; diff --git a/game/preview/PreviewReplaySubScript.cpp b/game/preview/PreviewReplaySubScript.cpp new file mode 100644 index 0000000..580a608 --- /dev/null +++ b/game/preview/PreviewReplaySubScript.cpp @@ -0,0 +1,55 @@ +#include "PreviewReplaySubScript.h" +#include "Config.h" +#include "menus/ButtonReplaySubScript.h" +#include <crepe/api/AudioSource.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void PreviewReplaySubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); + this->subscribe<StopPreviewRecording>([this](const StopPreviewRecording & e) { + return this->stop_recording(); + }); + this->subscribe<DeleteRecordingEvent>([this](const DeleteRecordingEvent & e) { + return this->delete_recording(); + }); + this->subscribe<StartPreviewRecording>([this](const StartPreviewRecording & e) { + return this->start_recording(); + }); +} + +bool PreviewReplaySubScript::on_button_press(const ButtonPressEvent & e) { + if (DISABLE_REPLAY) return false; + replay.play(this->recording); + return false; +} +bool PreviewReplaySubScript::start_recording() { + if (DISABLE_REPLAY) return false; + if (record_saved) { + this->stop_recording(); + this->delete_recording(); + } + replay.record_start(); + this->record_started = true; + return false; +} + +bool PreviewReplaySubScript::stop_recording() { + if (DISABLE_REPLAY) return false; + if (this->record_started) this->recording = replay.record_end(); + this->record_saved = true; + return false; +} + +bool PreviewReplaySubScript::delete_recording() { + if (DISABLE_REPLAY) return false; + if (this->record_started) this->stop_recording(); + if (this->record_saved) replay.release(this->recording); + this->record_saved = false; + return false; +} diff --git a/game/preview/PreviewReplaySubScript.h b/game/preview/PreviewReplaySubScript.h new file mode 100644 index 0000000..59b78c3 --- /dev/null +++ b/game/preview/PreviewReplaySubScript.h @@ -0,0 +1,24 @@ +#pragma once + +#include "menus/IButtonScript.h" + +#include <crepe/api/Script.h> + +struct StartPreviewRecording : public crepe::Event {}; +struct StopPreviewRecording : public crepe::Event {}; + +class PreviewReplaySubScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); + +private: + crepe::recording_t recording = 0; + bool start_recording(); + bool stop_recording(); + bool delete_recording(); + +private: + bool record_saved = false; + bool record_started = false; +}; diff --git a/game/preview/PreviewStartRecSubScript.cpp b/game/preview/PreviewStartRecSubScript.cpp new file mode 100644 index 0000000..8a2f54c --- /dev/null +++ b/game/preview/PreviewStartRecSubScript.cpp @@ -0,0 +1,20 @@ +#include "PreviewStartRecSubScript.h" +#include "PreviewReplaySubScript.h" + +#include <crepe/api/AudioSource.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void PreviewStartRecSubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool PreviewStartRecSubScript::on_button_press(const ButtonPressEvent & e) { + this->trigger_event<StartPreviewRecording>(); + return false; +} diff --git a/game/preview/PreviewStartRecSubScript.h b/game/preview/PreviewStartRecSubScript.h new file mode 100644 index 0000000..a54a085 --- /dev/null +++ b/game/preview/PreviewStartRecSubScript.h @@ -0,0 +1,11 @@ +#pragma once + +#include "menus/IButtonScript.h" + +#include <crepe/api/Script.h> + +class PreviewStartRecSubScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); +}; diff --git a/game/preview/PreviewStopRecSubScript.cpp b/game/preview/PreviewStopRecSubScript.cpp new file mode 100644 index 0000000..a229da8 --- /dev/null +++ b/game/preview/PreviewStopRecSubScript.cpp @@ -0,0 +1,20 @@ +#include "PreviewStopRecSubScript.h" +#include "PreviewReplaySubScript.h" + +#include <crepe/api/AudioSource.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void PreviewStopRecSubScript::init() { + IButtonScript::init(); + this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) { + return this->on_button_press(e); + }); +} + +bool PreviewStopRecSubScript::on_button_press(const ButtonPressEvent & e) { + this->trigger_event<StopPreviewRecording>(); + return false; +} diff --git a/game/preview/PreviewStopRecSubScript.h b/game/preview/PreviewStopRecSubScript.h new file mode 100644 index 0000000..b2dd73b --- /dev/null +++ b/game/preview/PreviewStopRecSubScript.h @@ -0,0 +1,11 @@ +#pragma once + +#include "menus/IButtonScript.h" + +#include <crepe/api/Script.h> + +class PreviewStopRecSubScript : public IButtonScript { +public: + void init() override; + bool on_button_press(const crepe::ButtonPressEvent & e); +}; diff --git a/game/preview/SmokeSubScene.cpp b/game/preview/SmokeSubScene.cpp new file mode 100644 index 0000000..e363f95 --- /dev/null +++ b/game/preview/SmokeSubScene.cpp @@ -0,0 +1,37 @@ + +#include "SmokeSubScene.h" + +#include "../Config.h" + +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +using namespace crepe; + +SmokeSubScene::SmokeSubScene(Scene & scn) { + GameObject smoke = scn.new_object("smoke_particle", "TAG", vec2 {500, -210}, 0, 1); + + Asset smoke_ss {"asset/particles/smoke.png"}; + + auto & smoke_sprite = smoke.add_component<Sprite>( + smoke_ss, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_PARTICLES_FOREGROUND, + .size = {0, 30}, + } + ); + + smoke.add_component<ParticleEmitter>( + smoke_sprite, + ParticleEmitter::Data { + .offset = {0, -60}, + .max_particles = 10, + .emission_rate = 25, + .min_angle = 60, + .max_angle = 120, + .begin_lifespan = 1, + .end_lifespan = 2, + } + ); +} diff --git a/game/preview/SmokeSubScene.h b/game/preview/SmokeSubScene.h new file mode 100644 index 0000000..93d8a2d --- /dev/null +++ b/game/preview/SmokeSubScene.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class SmokeSubScene { +public: + SmokeSubScene(crepe::Scene & scn); +}; diff --git a/game/scheduler/ObjectsScheduler.cpp b/game/scheduler/ObjectsScheduler.cpp new file mode 100644 index 0000000..7f58c79 --- /dev/null +++ b/game/scheduler/ObjectsScheduler.cpp @@ -0,0 +1,120 @@ + + +#include "ObjectsScheduler.h" + +#include "../Config.h" +#include "../Random.h" +#include "../enemy/EnemyScript.h" +#include "../missile/SpawnEvent.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "enemy/BattleScript.h" +#include "prefab/ZapperPoolSubScene.h" + +using namespace crepe; + +void ObjectsScheduler::preset_0() { + for (int i = 0; i < this->amount_of_boss_fights; i++) { + this->trigger_event<MissileSpawnEvent>(MissileSpawnEvent {}); + } + if (this->amount_of_boss_fights >= 1) { + this->trigger_event<BattleStartEvent>(BattleStartEvent { + .num_enemies = Random::i(this->amount_of_boss_fights, 0), + .battle = false, + }); + } +} + +void ObjectsScheduler::preset_1() { + trigger_event<MissileSpawnEvent>(MissileSpawnEvent {}); + if (this->amount_of_boss_fights >= 3) { + this->trigger_event<BattleStartEvent>(BattleStartEvent { + .num_enemies = Random::i(1, 0), + .battle = false, + }); + } +} + +void ObjectsScheduler::preset_2() { + trigger_event<CreateZapperEvent>(CreateZapperEvent {}); + if (this->amount_of_boss_fights >= 2) { + this->trigger_event<BattleStartEvent>(BattleStartEvent { + .num_enemies = Random::i(2, 1), + .battle = false, + }); + } +} + +void ObjectsScheduler::preset_3() { trigger_event<CreateZapperEvent>(CreateZapperEvent {}); } + +void ObjectsScheduler::preset_4() {} + +void ObjectsScheduler::boss_fight_1() { + this->get_components_by_name<Rigidbody>("camera").front().get().data.linear_velocity.x = 0; + this->get_components_by_name<Rigidbody>("player").front().get().data.linear_velocity.x = 0; + + this->amount_of_boss_fights++; + this->trigger_event<BattleStartEvent>(BattleStartEvent { + .num_enemies = amount_of_boss_fights, + .battle = true, + }); + + RefVector<Rigidbody> rb_back_forest + = this->get_components_by_tag<Rigidbody>("forest_background"); + for (Rigidbody & rb : rb_back_forest) { + rb.data.linear_velocity.x = 0; + } +} + +bool ObjectsScheduler::boss_fight_1_event() { + this->get_components_by_name<Rigidbody>("camera").front().get().data.linear_velocity.x + = PLAYER_SPEED * 0.02; + this->get_components_by_name<Rigidbody>("player").front().get().data.linear_velocity.x + = PLAYER_SPEED * 0.02; + + bool first = true; + RefVector<Rigidbody> rb_back_forest + = this->get_components_by_tag<Rigidbody>("forest_background"); + for (Rigidbody & rb : rb_back_forest) { + if (first == true) { + rb.data.linear_velocity.x = 30; + first = false; + } else { + rb.data.linear_velocity.x = 40; + first = true; + } + } + + return false; +} + +void ObjectsScheduler::init() { + this->obstacles.push_back([this]() { preset_0(); }); + this->obstacles.push_back([this]() { preset_1(); }); + this->obstacles.push_back([this]() { preset_2(); }); + this->obstacles.push_back([this]() { preset_3(); }); + this->obstacles.push_back([this]() { preset_4(); }); + + this->obstacles.push_back([this]() { boss_fight_1(); }); + + // subscribe to battlewonevent + this->subscribe<BattleWonEvent>([this](const BattleWonEvent & ev) -> bool { + return this->boss_fight_1_event(); + }); +} + +void ObjectsScheduler::fixed_update(duration_t dt) { + int pos_x + = (int) this->get_components_by_name<Transform>("camera").front().get().position.x; + + int boss_check = (pos_x - this->start_offset) / this->boss_fight_interval; + if (boss_check > this->last_boss_check) { + this->obstacles.back()(); + this->last_boss_check = boss_check; + } + int obstacle_check = (pos_x - this->start_offset) / this->obstacle_interval; + if (obstacle_check > this->last_obstacle_check) { + this->obstacles[Random::i(this->obstacles.size() - 1, 0)](); + this->last_obstacle_check = obstacle_check; + } +} diff --git a/game/scheduler/ObjectsScheduler.h b/game/scheduler/ObjectsScheduler.h new file mode 100644 index 0000000..7ada8e1 --- /dev/null +++ b/game/scheduler/ObjectsScheduler.h @@ -0,0 +1,34 @@ +#pragma once + +#include "api/Script.h" +#include <functional> +#include <vector> + +class ObjectsScheduler : public crepe::Script { + +private: + std::vector<std::function<void()>> obstacles; + + int last_boss_check = 0; + int last_obstacle_check = 0; + + int boss_fight_interval = 5000; + int obstacle_interval = 350; + int start_offset = 1300; + + int amount_of_boss_fights = 0; + +private: + void preset_0(); + void preset_1(); + void preset_2(); + void preset_3(); + void preset_4(); + void boss_fight_1(); + + bool boss_fight_1_event(); + +public: + void init(); + void fixed_update(crepe::duration_t dt); +}; diff --git a/game/util/scrollgen b/game/util/scrollgen new file mode 100755 index 0000000..0389107 --- /dev/null +++ b/game/util/scrollgen @@ -0,0 +1,36 @@ +#!/bin/sh +INPUT="$1" +FRAMES="$2" + +die() { + echo "$@" + exit 1 +} +check_command() { + cmd="$1" + command -v "$cmd" > /dev/null || die "command '$cmd' not found" +} + +check_command magick +check_command identify +[ "$#" -eq 2 ] || die "usage: $0 <input image> <frame count>" +[ -e "$INPUT" ] || die "file not found: $INPUT" +[ "$FRAMES" -gt 0 ] || die "invalid frame count: $FRAMES" + +tile_width=$(identify -format "%w" "$INPUT") +tile_height=$(identify -format "%h" "$INPUT") + +OUTPUT="$INPUT.scroll.png" +magick -size "${tile_width}x$(( $tile_height * $FRAMES ))" 'xc:#ff00ff00' "$OUTPUT" + +for i in $(seq 0 $(( $FRAMES - 1 ))); do + offset_x=$(( $tile_width * $i / $FRAMES )) + offset_y=$(( i * $tile_height )) + + magick "$OUTPUT" "$INPUT" -geometry "+${offset_x}+${offset_y}" -composite "$OUTPUT" + magick "$OUTPUT" "$INPUT" -geometry "+$(( $offset_x - $tile_width ))+${offset_y}" -composite "$OUTPUT" + echo "+${offset_x}+${offset_y}" +done + +# magick -size ${total_width}x${sprite_height} xc:none canvas.png + diff --git a/game/workers/CollisionScript.cpp b/game/workers/CollisionScript.cpp new file mode 100644 index 0000000..372bfec --- /dev/null +++ b/game/workers/CollisionScript.cpp @@ -0,0 +1,69 @@ +#include "CollisionScript.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void CollisionScript::init() { + subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { + return this->on_collision(ev); + }); +} + +bool CollisionScript::on_collision(const CollisionEvent & ev) { + RefVector<Animator> animators = this->get_components<Animator>(); + RefVector<Sprite> sprites = this->get_components<Sprite>(); + Rigidbody & rb = this->get_component<Rigidbody>(); + Transform & tr = this->get_component<Transform>(); + BehaviorScript & bs_panic = this->get_components<BehaviorScript>().front(); + + if (ev.info.other.metadata.tag == "zapper") { + for (Animator & anim : animators) { + anim.active = false; + anim.set_anim(3); + } + for (Sprite & sprite : sprites) { + sprite.data.position_offset.x = 15; + } + rb.data.linear_velocity_coefficient = {0.5, 0.5}; + tr.rotation = 90; + bs_panic.active = false; + + return false; + } else if (ev.info.other.metadata.tag == "laser") { + for (Animator & anim : animators) { + anim.active = false; + anim.set_anim(3); + } + for (Sprite & sprite : sprites) { + sprite.data.position_offset.x = 15; + } + rb.data.linear_velocity_coefficient = {0.5, 0.5}; + tr.rotation = 90; + bs_panic.active = false; + + return false; + } else if (ev.info.other.metadata.tag == "missile" + || ev.info.other.metadata.tag == "enemy_bullet") { + for (Animator & anim : animators) { + anim.active = false; + anim.set_anim(3); + } + for (Sprite & sprite : sprites) { + sprite.data.position_offset.x = 15; + } + rb.data.linear_velocity_coefficient = {0.5, 0.5}; + tr.rotation = 90; + bs_panic.active = false; + + return false; + } + + return false; +} diff --git a/game/workers/CollisionScript.h b/game/workers/CollisionScript.h new file mode 100644 index 0000000..70c5fe1 --- /dev/null +++ b/game/workers/CollisionScript.h @@ -0,0 +1,12 @@ +#pragma once + +#include <crepe/api/Event.h> +#include <crepe/api/Script.h> + +class CollisionScript : public crepe::Script { +public: + void init(); + +private: + bool on_collision(const crepe::CollisionEvent & ev); +}; diff --git a/game/workers/PanicFromPlayerScript.cpp b/game/workers/PanicFromPlayerScript.cpp new file mode 100644 index 0000000..baa48df --- /dev/null +++ b/game/workers/PanicFromPlayerScript.cpp @@ -0,0 +1,55 @@ +#include "PanicFromPlayerScript.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +void PanicFromPlayerScript::fixed_update(duration_t dt) { + Animator & anim_player = this->get_components_by_name<Animator>("player").front(); + + if (anim_player.data.col == 1) { + Transform & trans_player = this->get_components_by_name<Transform>("player").back(); + Transform & trans_worker = this->get_components<Transform>().back(); + + float result_x = trans_player.position.x - trans_worker.position.x; + + if (result_x < 100 && result_x > -20) { + RefVector<Animator> anim_worker = this->get_components<Animator>(); + RefVector<Sprite> sprite_worker = this->get_components<Sprite>(); + Rigidbody & rb_worker = this->get_components<Rigidbody>().back(); + + if (anim_worker.front().get().data.col != 1) { + anim_worker.front().get().set_anim(1); + anim_worker.back().get().set_anim(1); + + anim_worker.front().get().data.fps = 10; + anim_worker.back().get().data.fps = 10; + } + + if (result_x < 0) { + float min_value = 8000; + float max_value = 10000; + float value = min_value + + static_cast<float>(rand()) + / (static_cast<float>(RAND_MAX / (max_value - min_value))); + rb_worker.data.linear_velocity.x = value * dt.count(); + sprite_worker.front().get().data.flip.flip_x = false; + sprite_worker.back().get().data.flip.flip_x = false; + } else { + float min_value = -4000; + float max_value = -5000; + float value = min_value + + static_cast<float>(rand()) + / (static_cast<float>(RAND_MAX / (max_value - min_value))); + rb_worker.data.linear_velocity.x = value * dt.count(); + sprite_worker.front().get().data.flip.flip_x = true; + sprite_worker.back().get().data.flip.flip_x = true; + } + } + } +}; diff --git a/game/workers/PanicFromPlayerScript.h b/game/workers/PanicFromPlayerScript.h new file mode 100644 index 0000000..d173e89 --- /dev/null +++ b/game/workers/PanicFromPlayerScript.h @@ -0,0 +1,8 @@ +#pragma once + +#include <crepe/api/Script.h> + +class PanicFromPlayerScript : public crepe::Script { +public: + void fixed_update(crepe::duration_t dt); +}; diff --git a/game/workers/WorkerScript.cpp b/game/workers/WorkerScript.cpp new file mode 100644 index 0000000..b0bfc4e --- /dev/null +++ b/game/workers/WorkerScript.cpp @@ -0,0 +1,145 @@ +#include "WorkerScript.h" + +#include "../Config.h" +#include "api/BehaviorScript.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/types.h> +#include <cstdlib> + +using namespace crepe; +using namespace std; + +void WorkerScript::fixed_update(duration_t dt) { + RefVector<Rigidbody> rb_workers = this->get_components_by_tag<Rigidbody>("worker"); + RefVector<Transform> trans_workers = this->get_components_by_tag<Transform>("worker"); + + Rigidbody & rb_cam = this->get_components_by_name<Rigidbody>("camera").back(); + Transform & trans_cam = this->get_components_by_name<Transform>("camera").back(); + + int counter = 0; + for (Rigidbody & rb_worker : rb_workers) { + Transform & trans_worker = trans_workers.at(counter); + + float result_x = rb_cam.data.linear_velocity.x - rb_worker.data.linear_velocity.x; + + if (result_x > 0) { + float left_cam_pos_x = trans_cam.position.x - VIEWPORT_X / 2; + if (trans_worker.position.x < left_cam_pos_x - 1000) { + trans_worker.position.x = left_cam_pos_x + VIEWPORT_X + 1000; + + do { + float min_value = -2500 * dt.count(); + float max_value = 2500 * dt.count(); + rb_worker.data.linear_velocity.x + = min_value + + static_cast<float>(rand()) + / (static_cast<float>(RAND_MAX / (max_value - min_value))); + } while (rb_worker.data.linear_velocity.x < 500 * dt.count() + && rb_worker.data.linear_velocity.x > -500 * dt.count()); + + RefVector<Sprite> sprite_worker + = this->get_components_by_id<Sprite>(trans_worker.game_object_id); + RefVector<Animator> animator_worker + = this->get_components_by_id<Animator>(trans_worker.game_object_id); + BehaviorScript & bs_panic + = this->get_components_by_id<BehaviorScript>(trans_worker.game_object_id) + .front(); + + if (rb_worker.data.linear_velocity.x < 0) { + sprite_worker.front().get().data.flip.flip_x = true; + sprite_worker.back().get().data.flip.flip_x = true; + + animator_worker.front().get().data.fps + = -rb_worker.data.linear_velocity.x / 5; + animator_worker.back().get().data.fps + = -rb_worker.data.linear_velocity.x / 5; + animator_worker.front().get().set_anim(0); + animator_worker.back().get().set_anim(0); + animator_worker.front().get().active = true; + animator_worker.back().get().active = true; + } else { + sprite_worker.front().get().data.flip.flip_x = false; + sprite_worker.back().get().data.flip.flip_x = false; + + animator_worker.front().get().data.fps + = rb_worker.data.linear_velocity.x / 5; + animator_worker.back().get().data.fps + = rb_worker.data.linear_velocity.x / 5; + animator_worker.front().get().set_anim(0); + animator_worker.back().get().set_anim(0); + animator_worker.front().get().active = true; + animator_worker.back().get().active = true; + } + + trans_worker.rotation = 0; + bs_panic.active = true; + rb_worker.data.linear_velocity_coefficient = {1, 1}; + for (Sprite & sprite : sprite_worker) { + sprite.data.position_offset.x = 0; + } + } + } else { + float right_cam_pos_x = trans_cam.position.x + VIEWPORT_X / 2; + if (trans_worker.position.x > right_cam_pos_x + 1000) { + do { + float min_value = -2500 * dt.count(); + float max_value = 2500 * dt.count(); + rb_worker.data.linear_velocity.x + = min_value + + static_cast<float>(rand()) + / (static_cast<float>(RAND_MAX / (max_value - min_value))); + } while (rb_worker.data.linear_velocity.x < 500 * dt.count() + && rb_worker.data.linear_velocity.x > -500 * dt.count()); + + RefVector<Sprite> sprite_worker + = this->get_components_by_id<Sprite>(trans_worker.game_object_id); + RefVector<Animator> animator_worker + = this->get_components_by_id<Animator>(trans_worker.game_object_id); + BehaviorScript & bs_panic + = this->get_components_by_id<BehaviorScript>(trans_worker.game_object_id) + .front(); + + if (rb_worker.data.linear_velocity.x < 0) { + sprite_worker.front().get().data.flip.flip_x = true; + sprite_worker.back().get().data.flip.flip_x = true; + + animator_worker.front().get().data.fps + = -rb_worker.data.linear_velocity.x / 5; + animator_worker.back().get().data.fps + = -rb_worker.data.linear_velocity.x / 5; + + animator_worker.front().get().set_anim(0); + animator_worker.back().get().set_anim(0); + animator_worker.front().get().active = true; + animator_worker.back().get().active = true; + } else { + sprite_worker.front().get().data.flip.flip_x = false; + sprite_worker.back().get().data.flip.flip_x = false; + + animator_worker.front().get().data.fps + = rb_worker.data.linear_velocity.x / 5; + animator_worker.back().get().data.fps + = rb_worker.data.linear_velocity.x / 5; + + animator_worker.front().get().set_anim(0); + animator_worker.back().get().set_anim(0); + animator_worker.front().get().active = true; + animator_worker.back().get().active = true; + } + + trans_worker.rotation = 0; + bs_panic.active = true; + rb_worker.data.linear_velocity_coefficient = {1, 1}; + for (Sprite & sprite : sprite_worker) { + sprite.data.position_offset.x = 0; + } + } + } + + counter++; + } +} diff --git a/game/workers/WorkerScript.h b/game/workers/WorkerScript.h new file mode 100644 index 0000000..ae4a6c7 --- /dev/null +++ b/game/workers/WorkerScript.h @@ -0,0 +1,8 @@ +#pragma once + +#include <crepe/api/Script.h> + +class WorkerScript : public crepe::Script { +public: + void fixed_update(crepe::duration_t dt); +}; diff --git a/game/workers/WorkersSubScene.cpp b/game/workers/WorkersSubScene.cpp new file mode 100644 index 0000000..54996d1 --- /dev/null +++ b/game/workers/WorkersSubScene.cpp @@ -0,0 +1,423 @@ +#include "WorkersSubScene.h" +#include "CollisionScript.h" +#include "PanicFromPlayerScript.h" +#include "WorkerScript.h" + +#include "../Config.h" +#include "api/GameObject.h" + +#include <crepe/api/Animator.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/BoxCollider.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Sprite.h> + +using namespace crepe; +using namespace std; + +WorkersSubScene::WorkersSubScene(Scene & scn) { + this->worker1(scn, 1200, -50); + this->worker2(scn, 1300, 20); + this->worker3(scn, 1400, -40); + this->worker4(scn, 7250, 50); + this->worker5(scn, 3400, -20); + this->worker6(scn, 2000, 30); + this->worker7(scn, 3725, 35); + this->worker8(scn, 2200, -15); + + GameObject script = scn.new_object("workers_script"); + script.add_component<BehaviorScript>().set_script<WorkerScript>(); +} + +void WorkersSubScene::worker1(crepe::Scene & scn, float start_x, float init_speed) { + GameObject worker_1 = scn.new_object("worker_1", "worker", vec2(start_x, 200)); + Sprite & worker_1_body_sprite = worker_1.add_component<Sprite>( + Asset {"asset/workers/worker1Body.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK, + .order_in_layer = 0, + .size = vec2(0, 50), + } + ); + worker_1.add_component<Animator>( + worker_1_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + Sprite & worker_1_head_sprite = worker_1.add_component<Sprite>( + Asset {"asset/workers/worker1Head.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK, + .order_in_layer = 1, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + worker_1.add_component<Animator>( + worker_1_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + worker_1.add_component<BoxCollider>(vec2(50, 50)); + worker_1.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 20, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(init_speed, 0), + .collision_layers = {COLL_LAY_BOT_TOP}, + }); + worker_1.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>(); + worker_1.add_component<BehaviorScript>().set_script<CollisionScript>(); + + if (init_speed < 0) { + worker_1_body_sprite.data.flip = Sprite::FlipSettings {true, false}; + worker_1_head_sprite.data.flip = Sprite::FlipSettings {true, false}; + } +} + +void WorkersSubScene::worker2(crepe::Scene & scn, float start_x, float init_speed) { + GameObject worker_2 = scn.new_object("worker_2", "worker", vec2(start_x, 200)); + Sprite & worker_2_body_sprite = worker_2.add_component<Sprite>( + Asset {"asset/workers/worker2Body.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 2, + .size = vec2(0, 50), + } + ); + worker_2.add_component<Animator>( + worker_2_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + Sprite & worker_2_head_sprite = worker_2.add_component<Sprite>( + Asset {"asset/workers/worker1Head.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 3, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + worker_2.add_component<Animator>( + worker_2_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + worker_2.add_component<BoxCollider>(vec2(50, 50)); + worker_2.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 20, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(init_speed, 0), + .collision_layers = {COLL_LAY_BOT_TOP}, + }); + worker_2.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>(); + worker_2.add_component<BehaviorScript>().set_script<CollisionScript>(); + + if (init_speed < 0) { + worker_2_body_sprite.data.flip = Sprite::FlipSettings {true, false}; + worker_2_head_sprite.data.flip = Sprite::FlipSettings {true, false}; + } +} + +void WorkersSubScene::worker3(crepe::Scene & scn, float start_x, float init_speed) { + GameObject worker_3 = scn.new_object("worker_3", "worker", vec2(start_x, 200)); + Sprite & worker_3_body_sprite = worker_3.add_component<Sprite>( + Asset {"asset/workers/worker1Body.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK, + .order_in_layer = 4, + .size = vec2(0, 50), + } + ); + worker_3.add_component<Animator>( + worker_3_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + Sprite & worker_3_head_sprite = worker_3.add_component<Sprite>( + Asset {"asset/workers/worker2Head.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK, + .order_in_layer = 5, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + worker_3.add_component<Animator>( + worker_3_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + worker_3.add_component<BoxCollider>(vec2(50, 50)); + worker_3.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 20, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(init_speed, 0), + .collision_layers = {COLL_LAY_BOT_TOP}, + }); + worker_3.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>(); + worker_3.add_component<BehaviorScript>().set_script<CollisionScript>(); + + if (init_speed < 0) { + worker_3_body_sprite.data.flip = Sprite::FlipSettings {true, false}; + worker_3_head_sprite.data.flip = Sprite::FlipSettings {true, false}; + } +} + +void WorkersSubScene::worker4(crepe::Scene & scn, float start_x, float init_speed) { + GameObject worker_4 = scn.new_object("worker_4", "worker", vec2(start_x, 200)); + Sprite & worker_4_body_sprite = worker_4.add_component<Sprite>( + Asset {"asset/workers/worker2Body.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 6, + .size = vec2(0, 50), + } + ); + worker_4.add_component<Animator>( + worker_4_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + Sprite & worker_4_head_sprite = worker_4.add_component<Sprite>( + Asset {"asset/workers/worker2Head.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 7, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + worker_4.add_component<Animator>( + worker_4_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + worker_4.add_component<BoxCollider>(vec2(50, 50)); + worker_4.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 20, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(init_speed, 0), + .collision_layers = {COLL_LAY_BOT_HIGH}, + }); + worker_4.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>(); + worker_4.add_component<BehaviorScript>().set_script<CollisionScript>(); + + if (init_speed < 0) { + worker_4_body_sprite.data.flip = Sprite::FlipSettings {true, false}; + worker_4_head_sprite.data.flip = Sprite::FlipSettings {true, false}; + } +} + +void WorkersSubScene::worker5(crepe::Scene & scn, float start_x, float init_speed) { + GameObject worker_5 = scn.new_object("worker_5", "worker", vec2(start_x, 200)); + Sprite & worker_5_body_sprite = worker_5.add_component<Sprite>( + Asset {"asset/workers/workerFatBody.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK, + .order_in_layer = 8, + .size = vec2(0, 50), + } + ); + worker_5.add_component<Animator>( + worker_5_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + Sprite & worker_5_head_sprite = worker_5.add_component<Sprite>( + Asset {"asset/workers/worker1Head.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK, + .order_in_layer = 9, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + worker_5.add_component<Animator>( + worker_5_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + worker_5.add_component<BoxCollider>(vec2(50, 50)); + worker_5.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 20, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(init_speed, 0), + .collision_layers = {COLL_LAY_BOT_HIGH}, + }); + worker_5.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>(); + worker_5.add_component<BehaviorScript>().set_script<CollisionScript>(); + + if (init_speed < 0) { + worker_5_body_sprite.data.flip = Sprite::FlipSettings {true, false}; + worker_5_head_sprite.data.flip = Sprite::FlipSettings {true, false}; + } +} + +void WorkersSubScene::worker6(crepe::Scene & scn, float start_x, float init_speed) { + GameObject worker_6 = scn.new_object("worker_6", "worker", vec2(start_x, 200)); + Sprite & worker_6_body_sprite = worker_6.add_component<Sprite>( + Asset {"asset/workers/workerFatBody.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 10, + .size = vec2(0, 50), + } + ); + worker_6.add_component<Animator>( + worker_6_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + Sprite & worker_6_head_sprite = worker_6.add_component<Sprite>( + Asset {"asset/workers/worker2Head.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 11, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + worker_6.add_component<Animator>( + worker_6_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + worker_6.add_component<BoxCollider>(vec2(50, 50)); + worker_6.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 20, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(init_speed, 0), + .collision_layers = {COLL_LAY_BOT_LOW}, + }); + worker_6.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>(); + worker_6.add_component<BehaviorScript>().set_script<CollisionScript>(); + + if (init_speed < 0) { + worker_6_body_sprite.data.flip = Sprite::FlipSettings {true, false}; + worker_6_head_sprite.data.flip = Sprite::FlipSettings {true, false}; + } +} + +void WorkersSubScene::worker7(crepe::Scene & scn, float start_x, float init_speed) { + GameObject worker_7 = scn.new_object("worker_7", "worker", vec2(start_x, 200)); + Sprite & worker_7_body_sprite = worker_7.add_component<Sprite>( + Asset {"asset/workers/workerTallBody.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK, + .order_in_layer = 12, + .size = vec2(0, 50), + } + ); + worker_7.add_component<Animator>( + worker_7_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + Sprite & worker_7_head_sprite = worker_7.add_component<Sprite>( + Asset {"asset/workers/worker1Head.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK, + .order_in_layer = 13, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + worker_7.add_component<Animator>( + worker_7_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + worker_7.add_component<BoxCollider>(vec2(50, 50)); + worker_7.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 20, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(init_speed, 0), + .collision_layers = {COLL_LAY_BOT_LOW}, + }); + worker_7.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>(); + worker_7.add_component<BehaviorScript>().set_script<CollisionScript>(); + + if (init_speed < 0) { + worker_7_body_sprite.data.flip = Sprite::FlipSettings {true, false}; + worker_7_head_sprite.data.flip = Sprite::FlipSettings {true, false}; + } +} + +void WorkersSubScene::worker8(crepe::Scene & scn, float start_x, float init_speed) { + GameObject worker_8 = scn.new_object("worker_8", "worker", vec2(start_x, 200)); + Sprite & worker_8_body_sprite = worker_8.add_component<Sprite>( + Asset {"asset/workers/workerTallBody.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 14, + .size = vec2(0, 50), + } + ); + worker_8.add_component<Animator>( + worker_8_body_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + Sprite & worker_8_head_sprite = worker_8.add_component<Sprite>( + Asset {"asset/workers/worker2Head.png"}, + Sprite::Data { + .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT, + .order_in_layer = 15, + .size = vec2(0, 50), + .position_offset = vec2(0, -20), + } + ); + worker_8.add_component<Animator>( + worker_8_head_sprite, ivec2(32, 32), uvec2(4, 8), + Animator::Data { + .fps = static_cast<unsigned int>(abs(init_speed) / 5), + .looping = true, + } + ); + worker_8.add_component<BoxCollider>(vec2(50, 50)); + worker_8.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 20, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = vec2(init_speed, 0), + .collision_layers = {COLL_LAY_BOT_LOW}, + }); + worker_8.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>(); + worker_8.add_component<BehaviorScript>().set_script<CollisionScript>(); + + if (init_speed < 0) { + worker_8_body_sprite.data.flip = Sprite::FlipSettings {true, false}; + worker_8_head_sprite.data.flip = Sprite::FlipSettings {true, false}; + } +} diff --git a/game/workers/WorkersSubScene.h b/game/workers/WorkersSubScene.h new file mode 100644 index 0000000..6692d87 --- /dev/null +++ b/game/workers/WorkersSubScene.h @@ -0,0 +1,20 @@ +#pragma once + +namespace crepe { +class Scene; +} + +class WorkersSubScene { +public: + WorkersSubScene(crepe::Scene & scn); + +private: + void worker1(crepe::Scene & scn, float start_x, float init_speed); + void worker2(crepe::Scene & scn, float start_x, float init_speed); + void worker3(crepe::Scene & scn, float start_x, float init_speed); + void worker4(crepe::Scene & scn, float start_x, float init_speed); + void worker5(crepe::Scene & scn, float start_x, float init_speed); + void worker6(crepe::Scene & scn, float start_x, float init_speed); + void worker7(crepe::Scene & scn, float start_x, float init_speed); + void worker8(crepe::Scene & scn, float start_x, float init_speed); +}; diff --git a/lib/fontconfig b/lib/fontconfig new file mode 160000 +Subproject 72b9a48f57de6204d99ce1c217b5609ee92ece9 diff --git a/lib/sdl2 b/lib/sdl2 -Subproject c98c4fbff6d8f3016a3ce6685bf8f43433c3efc +Subproject 9519b9916cd29a14587af0507292f2bd31dd575 diff --git a/lib/sdl_image b/lib/sdl_image -Subproject 56560190a6c5b5740e5afdce747e2a2bfbf16db +Subproject abcf63aa71b4e3ac32120fa9870a6500ddcdcc8 diff --git a/lib/sdl_ttf b/lib/sdl_ttf -Subproject a3d0895c1b60c41ff9e85d9203ddd7485c014da +Subproject 4a318f8dfaa1bb6f10e0c5e54052e25d3c7f344 diff --git a/mwe/ecs-homemade/inc/ComponentManager.hpp b/mwe/ecs-homemade/inc/ComponentManager.hpp index af9c3a1..7d26df4 100644 --- a/mwe/ecs-homemade/inc/ComponentManager.hpp +++ b/mwe/ecs-homemade/inc/ComponentManager.hpp @@ -54,8 +54,8 @@ void ComponentManager::DeleteComponents() { } template <typename T> -std::vector<std::reference_wrapper<T>> -ComponentManager::GetComponentsByID(std::uint32_t id) const { +std::vector<std::reference_wrapper<T>> ComponentManager::GetComponentsByID(std::uint32_t id +) const { //Determine the type of T (this is used as the key of the unordered_map<>) std::type_index type = typeid(T); diff --git a/mwe/ecs-homemade/src/ComponentManager.cpp b/mwe/ecs-homemade/src/ComponentManager.cpp index 33ba12e..835bdda 100644 --- a/mwe/ecs-homemade/src/ComponentManager.cpp +++ b/mwe/ecs-homemade/src/ComponentManager.cpp @@ -9,9 +9,8 @@ ComponentManager::ComponentManager() {} void ComponentManager::DeleteAllComponentsOfId(std::uint32_t id) { for (auto & [type, componentArray] : mComponents) { //Loop through all the types (in the unordered_map<>) - if (id - < componentArray - .size()) { //Make sure that the id (that we are looking for) is within the boundaries of the vector<> + if (id < componentArray.size( + )) { //Make sure that the id (that we are looking for) is within the boundaries of the vector<> componentArray[id].clear(); //Clear the components at this specific id } } diff --git a/mwe/events/include/event.h b/mwe/events/include/event.h index ee1bf52..6df98ee 100644 --- a/mwe/events/include/event.h +++ b/mwe/events/include/event.h @@ -27,8 +27,8 @@ public: virtual ~Event() = default; virtual std::uint32_t getEventType() const = 0; virtual std::string toString() const; - void addArgument(const std::string & key, - const std::variant<int, std::string, float> & value); + void + addArgument(const std::string & key, const std::variant<int, std::string, float> & value); std::variant<int, std::string, float> getArgument(const std::string & key) const; diff --git a/mwe/events/include/eventHandler.h b/mwe/events/include/eventHandler.h index 3a83b15..8598cee 100644 --- a/mwe/events/include/eventHandler.h +++ b/mwe/events/include/eventHandler.h @@ -22,8 +22,9 @@ private: template <typename EventType> class EventHandlerWrapper : public IEventHandlerWrapper { public: - explicit EventHandlerWrapper(const EventHandler<EventType> & handler, - const bool destroyOnSuccess = false) + explicit EventHandlerWrapper( + const EventHandler<EventType> & handler, const bool destroyOnSuccess = false + ) : m_handler(handler), m_handlerType(m_handler.target_type().name()), m_destroyOnSuccess(destroyOnSuccess) { @@ -42,5 +43,5 @@ private: EventHandler<EventType> m_handler; const std::string m_handlerType; - bool m_destroyOnSuccess{false}; + bool m_destroyOnSuccess {false}; }; diff --git a/mwe/events/include/eventManager.h b/mwe/events/include/eventManager.h index 30e927f..43906e8 100644 --- a/mwe/events/include/eventManager.h +++ b/mwe/events/include/eventManager.h @@ -18,8 +18,8 @@ public: } void shutdown(); - void subscribe(int eventType, std::unique_ptr<IEventHandlerWrapper> && handler, - int eventId); + void + subscribe(int eventType, std::unique_ptr<IEventHandlerWrapper> && handler, int eventId); void unsubscribe(int eventType, const std::string & handlerName, int eventId); void triggerEvent(const Event & event_, int eventId); void queueEvent(std::unique_ptr<Event> && event_, int eventId); @@ -35,19 +35,23 @@ private: }; template <typename EventType> -inline void subscribe(const EventHandler<EventType> & callback, int eventId = 0, - const bool unsubscribeOnSuccess = false) { +inline void subscribe( + const EventHandler<EventType> & callback, int eventId = 0, + const bool unsubscribeOnSuccess = false +) { std::unique_ptr<IEventHandlerWrapper> handler = std::make_unique<EventHandlerWrapper<EventType>>(callback, unsubscribeOnSuccess); - EventManager::getInstance().subscribe(EventType::getStaticEventType(), std::move(handler), - eventId); + EventManager::getInstance().subscribe( + EventType::getStaticEventType(), std::move(handler), eventId + ); } template <typename EventType> inline void unsubscribe(const EventHandler<EventType> & callback, int eventId = 0) { const std::string handlerName = callback.target_type().name(); - EventManager::getInstance().unsubscribe(EventType::getStaticEventType(), handlerName, - eventId); + EventManager::getInstance().unsubscribe( + EventType::getStaticEventType(), handlerName, eventId + ); } inline void triggerEvent(const Event & triggeredEvent, int eventId = 0) { @@ -55,6 +59,7 @@ inline void triggerEvent(const Event & triggeredEvent, int eventId = 0) { } inline void queueEvent(std::unique_ptr<Event> && queuedEvent, int eventId = 0) { - EventManager::getInstance().queueEvent(std::forward<std::unique_ptr<Event>>(queuedEvent), - eventId); + EventManager::getInstance().queueEvent( + std::forward<std::unique_ptr<Event>>(queuedEvent), eventId + ); } diff --git a/mwe/events/src/event.cpp b/mwe/events/src/event.cpp index 0040c73..5177199 100644 --- a/mwe/events/src/event.cpp +++ b/mwe/events/src/event.cpp @@ -3,8 +3,9 @@ // Event class methods Event::Event(std::string eventType) { eventData["eventType"] = eventType; } -void Event::addArgument(const std::string & key, - const std::variant<int, std::string, float> & value) { +void Event::addArgument( + const std::string & key, const std::variant<int, std::string, float> & value +) { eventData[key] = value; } diff --git a/mwe/events/src/eventManager.cpp b/mwe/events/src/eventManager.cpp index 9e7d880..b77a0a3 100644 --- a/mwe/events/src/eventManager.cpp +++ b/mwe/events/src/eventManager.cpp @@ -2,8 +2,9 @@ void EventManager::shutdown() { subscribers.clear(); } -void EventManager::subscribe(int eventType, std::unique_ptr<IEventHandlerWrapper> && handler, - int eventId) { +void EventManager::subscribe( + int eventType, std::unique_ptr<IEventHandlerWrapper> && handler, int eventId +) { if (eventId) { std::unordered_map< int, std::unordered_map<int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>>:: diff --git a/mwe/events/src/loopManager.cpp b/mwe/events/src/loopManager.cpp index c58a5e7..7be10df 100644 --- a/mwe/events/src/loopManager.cpp +++ b/mwe/events/src/loopManager.cpp @@ -51,8 +51,10 @@ void onKey(const KeyPressedEvent & e) { std::cout << "keycode pressed: " << keyCode << std::endl; } void onMouse(const MousePressedEvent & e) { - fprintf(stderr, "mouse Position X: %d Y: %d\n", e.getMousePosition().first, - e.getMousePosition().second); + fprintf( + stderr, "mouse Position X: %d Y: %d\n", e.getMousePosition().first, + e.getMousePosition().second + ); } void LoopManager::setup() { gameRunning = window.initWindow(); diff --git a/mwe/events/src/uiObject.cpp b/mwe/events/src/uiObject.cpp index 947d1a2..6b5b326 100644 --- a/mwe/events/src/uiObject.cpp +++ b/mwe/events/src/uiObject.cpp @@ -10,7 +10,7 @@ Text::Text(int width, int height) : UIObject(width, height), size(12), font(nullptr), - color{255, 255, 255} { // Default size and color + color {255, 255, 255} { // Default size and color alignment.horizontal = Alignment::Horizontal::CENTER; alignment.vertical = Alignment::Vertical::MIDDLE; alignment.mode = Alignment::PositioningMode::RELATIVE; @@ -21,8 +21,8 @@ TextInput::TextInput(int width, int height) textBuffer(""), placeholder(""), isActive(false), - textColor{255, 255, 255}, - backgroundColor{0, 0, 0}, + textColor {255, 255, 255}, + backgroundColor {0, 0, 0}, maxLength(100), font(nullptr) { alignment.horizontal = Alignment::Horizontal::LEFT; diff --git a/mwe/events/src/window.cpp b/mwe/events/src/window.cpp index af2b627..5cdd425 100644 --- a/mwe/events/src/window.cpp +++ b/mwe/events/src/window.cpp @@ -11,8 +11,10 @@ bool WindowManager::initWindow() { return false; } - window = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); + window = SDL_CreateWindow( + "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, + SCREEN_HEIGHT, SDL_WINDOW_SHOWN + ); if (!window) { std::cerr << "Error creating SDL Window.\n"; return false; diff --git a/mwe/gameloop/include/gameObject.h b/mwe/gameloop/include/gameObject.h index 2764215..1955b59 100644 --- a/mwe/gameloop/include/gameObject.h +++ b/mwe/gameloop/include/gameObject.h @@ -3,8 +3,9 @@ class GameObject { public: GameObject(); - GameObject(std::string name, float x, float y, float width, float height, float velX, - float velY); + GameObject( + std::string name, float x, float y, float width, float height, float velX, float velY + ); std::string getName() const; float getX() const; float getY() const; diff --git a/mwe/gameloop/src/gameObject.cpp b/mwe/gameloop/src/gameObject.cpp index 809e25f..31503d1 100644 --- a/mwe/gameloop/src/gameObject.cpp +++ b/mwe/gameloop/src/gameObject.cpp @@ -24,8 +24,9 @@ void GameObject::setVelX(float value) { velX = value; } void GameObject::setVelY(float value) { velY = value; } -GameObject::GameObject(std::string name, float x, float y, float width, float height, - float velX, float velY) +GameObject::GameObject( + std::string name, float x, float y, float width, float height, float velX, float velY +) : name(name), x(x), y(y), diff --git a/mwe/gameloop/src/loopManager.cpp b/mwe/gameloop/src/loopManager.cpp index fb06995..70cea4c 100644 --- a/mwe/gameloop/src/loopManager.cpp +++ b/mwe/gameloop/src/loopManager.cpp @@ -18,11 +18,13 @@ void LoopManager::processInput() { if (event.key.keysym.sym == SDLK_ESCAPE) { gameRunning = false; } else if (event.key.keysym.sym == SDLK_i) { - LoopTimer::getInstance().setGameScale(LoopTimer::getInstance().getGameScale() - + 0.1); + LoopTimer::getInstance().setGameScale( + LoopTimer::getInstance().getGameScale() + 0.1 + ); } else if (event.key.keysym.sym == SDLK_k) { - LoopTimer::getInstance().setGameScale(LoopTimer::getInstance().getGameScale() - - 0.1); + LoopTimer::getInstance().setGameScale( + LoopTimer::getInstance().getGameScale() - 0.1 + ); } break; diff --git a/mwe/gameloop/src/window.cpp b/mwe/gameloop/src/window.cpp index 8f802e1..df17773 100644 --- a/mwe/gameloop/src/window.cpp +++ b/mwe/gameloop/src/window.cpp @@ -36,8 +36,10 @@ bool WindowManager::initWindow() { fprintf(stderr, "Error inititalising SDL.\n"); return false; } - window = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); + window = SDL_CreateWindow( + "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, + SCREEN_HEIGHT, SDL_WINDOW_SHOWN + ); if (!window) { fprintf(stderr, "Error creating SDL Window. \n"); return false; diff --git a/mwe/resource-manager/main.cpp b/mwe/resource-manager/main.cpp index 1910af8..e83d35f 100644 --- a/mwe/resource-manager/main.cpp +++ b/mwe/resource-manager/main.cpp @@ -23,8 +23,9 @@ int main() { SDL_Event event; - SDL_Window * window = SDL_CreateWindow("Tessting resources", SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, 640, 480, 0); + SDL_Window * window = SDL_CreateWindow( + "Tessting resources", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0 + ); SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0); diff --git a/mwe/resource-manager/map_layer.cpp b/mwe/resource-manager/map_layer.cpp index 17792a6..82f8f24 100644 --- a/mwe/resource-manager/map_layer.cpp +++ b/mwe/resource-manager/map_layer.cpp @@ -9,8 +9,9 @@ MapLayer::MapLayer() {} MapLayer::~MapLayer() { m_subsets.clear(); } -bool MapLayer::create(const tmx::Map & map, std::uint32_t layerIndex, - const std::vector<TextureMap *> & textures) { +bool MapLayer::create( + const tmx::Map & map, std::uint32_t layerIndex, const std::vector<TextureMap *> & textures +) { const auto & layers = map.getLayers(); assert(layers[layerIndex]->getType() == tmx::Layer::Type::Tile); @@ -68,9 +69,10 @@ bool MapLayer::create(const tmx::Map & map, std::uint32_t layerIndex, verts.emplace_back(vert); vert = {{tilePosX + mapTileSize.x, tilePosY}, vertColour, {u + uNorm, v}}; verts.emplace_back(vert); - vert = {{tilePosX + mapTileSize.x, tilePosY + mapTileSize.y}, - vertColour, - {u + uNorm, v + vNorm}}; + vert + = {{tilePosX + mapTileSize.x, tilePosY + mapTileSize.y}, + vertColour, + {u + uNorm, v + vNorm}}; verts.emplace_back(vert); } } @@ -89,7 +91,9 @@ bool MapLayer::create(const tmx::Map & map, std::uint32_t layerIndex, void MapLayer::draw(SDL_Renderer * renderer) const { assert(renderer); for (const auto & s : m_subsets) { - SDL_RenderGeometry(renderer, s.texture, s.vertexData.data(), - static_cast<std::int32_t>(s.vertexData.size()), nullptr, 0); + SDL_RenderGeometry( + renderer, s.texture, s.vertexData.data(), + static_cast<std::int32_t>(s.vertexData.size()), nullptr, 0 + ); } } diff --git a/mwe/resource-manager/map_layer.h b/mwe/resource-manager/map_layer.h index fb656ed..2adbc0f 100644 --- a/mwe/resource-manager/map_layer.h +++ b/mwe/resource-manager/map_layer.h @@ -10,8 +10,8 @@ public: explicit MapLayer(); ~MapLayer(); - bool create(const tmx::Map &, std::uint32_t index, - const std::vector<TextureMap *> & textures); + bool + create(const tmx::Map &, std::uint32_t index, const std::vector<TextureMap *> & textures); void draw(SDL_Renderer *) const; private: diff --git a/mwe/resource-manager/stb_image.h b/mwe/resource-manager/stb_image.h index 3462f3a..43fa6a6 100644 --- a/mwe/resource-manager/stb_image.h +++ b/mwe/resource-manager/stb_image.h @@ -409,9 +409,12 @@ extern "C" { typedef struct { int (*read)( void * user, char * data, - int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip)(void * user, - int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int size + ); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip)( + void * user, + int n + ); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative int (*eof)(void * user); // returns nonzero if we are at end of file/data } stbi_io_callbacks; @@ -420,24 +423,29 @@ typedef struct { // 8-bits-per-channel interface // -STBIDEF stbi_uc * stbi_load_from_memory(stbi_uc const * buffer, int len, int * x, int * y, - int * channels_in_file, int desired_channels); -STBIDEF stbi_uc * stbi_load_from_callbacks(stbi_io_callbacks const * clbk, void * user, - int * x, int * y, int * channels_in_file, - int desired_channels); +STBIDEF stbi_uc * stbi_load_from_memory( + stbi_uc const * buffer, int len, int * x, int * y, int * channels_in_file, + int desired_channels +); +STBIDEF stbi_uc * stbi_load_from_callbacks( + stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * channels_in_file, + int desired_channels +); #ifndef STBI_NO_STDIO -STBIDEF stbi_uc * stbi_load(char const * filename, int * x, int * y, int * channels_in_file, - int desired_channels); -STBIDEF stbi_uc * stbi_load_from_file(FILE * f, int * x, int * y, int * channels_in_file, - int desired_channels); +STBIDEF stbi_uc * stbi_load( + char const * filename, int * x, int * y, int * channels_in_file, int desired_channels +); +STBIDEF stbi_uc * +stbi_load_from_file(FILE * f, int * x, int * y, int * channels_in_file, int desired_channels); // for stbi_load_from_file, file pointer is left pointing immediately after image #endif #ifndef STBI_NO_GIF -STBIDEF stbi_uc * stbi_load_gif_from_memory(stbi_uc const * buffer, int len, int ** delays, - int * x, int * y, int * z, int * comp, - int req_comp); +STBIDEF stbi_uc * stbi_load_gif_from_memory( + stbi_uc const * buffer, int len, int ** delays, int * x, int * y, int * z, int * comp, + int req_comp +); #endif #ifdef STBI_WINDOWS_UTF8 @@ -449,17 +457,22 @@ STBIDEF int stbi_convert_wchar_to_utf8(char * buffer, size_t bufferlen, const wc // 16-bits-per-channel interface // -STBIDEF stbi_us * stbi_load_16_from_memory(stbi_uc const * buffer, int len, int * x, int * y, - int * channels_in_file, int desired_channels); -STBIDEF stbi_us * stbi_load_16_from_callbacks(stbi_io_callbacks const * clbk, void * user, - int * x, int * y, int * channels_in_file, - int desired_channels); +STBIDEF stbi_us * stbi_load_16_from_memory( + stbi_uc const * buffer, int len, int * x, int * y, int * channels_in_file, + int desired_channels +); +STBIDEF stbi_us * stbi_load_16_from_callbacks( + stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * channels_in_file, + int desired_channels +); #ifndef STBI_NO_STDIO -STBIDEF stbi_us * stbi_load_16(char const * filename, int * x, int * y, int * channels_in_file, - int desired_channels); -STBIDEF stbi_us * stbi_load_from_file_16(FILE * f, int * x, int * y, int * channels_in_file, - int desired_channels); +STBIDEF stbi_us * stbi_load_16( + char const * filename, int * x, int * y, int * channels_in_file, int desired_channels +); +STBIDEF stbi_us * stbi_load_from_file_16( + FILE * f, int * x, int * y, int * channels_in_file, int desired_channels +); #endif //////////////////////////////////// @@ -467,17 +480,21 @@ STBIDEF stbi_us * stbi_load_from_file_16(FILE * f, int * x, int * y, int * chann // float-per-channel interface // #ifndef STBI_NO_LINEAR -STBIDEF float * stbi_loadf_from_memory(stbi_uc const * buffer, int len, int * x, int * y, - int * channels_in_file, int desired_channels); -STBIDEF float * stbi_loadf_from_callbacks(stbi_io_callbacks const * clbk, void * user, int * x, - int * y, int * channels_in_file, - int desired_channels); +STBIDEF float * stbi_loadf_from_memory( + stbi_uc const * buffer, int len, int * x, int * y, int * channels_in_file, + int desired_channels +); +STBIDEF float * stbi_loadf_from_callbacks( + stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * channels_in_file, + int desired_channels +); #ifndef STBI_NO_STDIO -STBIDEF float * stbi_loadf(char const * filename, int * x, int * y, int * channels_in_file, - int desired_channels); -STBIDEF float * stbi_loadf_from_file(FILE * f, int * x, int * y, int * channels_in_file, - int desired_channels); +STBIDEF float * stbi_loadf( + char const * filename, int * x, int * y, int * channels_in_file, int desired_channels +); +STBIDEF float * +stbi_loadf_from_file(FILE * f, int * x, int * y, int * channels_in_file, int desired_channels); #endif #endif @@ -507,10 +524,11 @@ STBIDEF const char * stbi_failure_reason(void); STBIDEF void stbi_image_free(void * retval_from_stbi_load); // get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const * buffer, int len, int * x, int * y, - int * comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const * clbk, void * user, int * x, - int * y, int * comp); +STBIDEF int +stbi_info_from_memory(stbi_uc const * buffer, int len, int * x, int * y, int * comp); +STBIDEF int stbi_info_from_callbacks( + stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * comp +); STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const * buffer, int len); STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const * clbk, void * user); @@ -542,17 +560,18 @@ STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_fli // ZLIB client - used by PNG, available for other purposes -STBIDEF char * stbi_zlib_decode_malloc_guesssize(const char * buffer, int len, - int initial_size, int * outlen); -STBIDEF char * stbi_zlib_decode_malloc_guesssize_headerflag(const char * buffer, int len, - int initial_size, int * outlen, - int parse_header); +STBIDEF char * stbi_zlib_decode_malloc_guesssize( + const char * buffer, int len, int initial_size, int * outlen +); +STBIDEF char * stbi_zlib_decode_malloc_guesssize_headerflag( + const char * buffer, int len, int initial_size, int * outlen, int parse_header +); STBIDEF char * stbi_zlib_decode_malloc(const char * buffer, int len, int * outlen); STBIDEF int stbi_zlib_decode_buffer(char * obuffer, int olen, const char * ibuffer, int ilen); STBIDEF char * stbi_zlib_decode_noheader_malloc(const char * buffer, int len, int * outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char * obuffer, int olen, const char * ibuffer, - int ilen); +STBIDEF int +stbi_zlib_decode_noheader_buffer(char * obuffer, int olen, const char * ibuffer, int ilen); #ifdef __cplusplus } @@ -910,68 +929,79 @@ typedef struct { #ifndef STBI_NO_JPEG static int stbi__jpeg_test(stbi__context * s); -static void * stbi__jpeg_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri); +static void * stbi__jpeg_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +); static int stbi__jpeg_info(stbi__context * s, int * x, int * y, int * comp); #endif #ifndef STBI_NO_PNG static int stbi__png_test(stbi__context * s); -static void * stbi__png_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri); +static void * stbi__png_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +); static int stbi__png_info(stbi__context * s, int * x, int * y, int * comp); static int stbi__png_is16(stbi__context * s); #endif #ifndef STBI_NO_BMP static int stbi__bmp_test(stbi__context * s); -static void * stbi__bmp_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri); +static void * stbi__bmp_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +); static int stbi__bmp_info(stbi__context * s, int * x, int * y, int * comp); #endif #ifndef STBI_NO_TGA static int stbi__tga_test(stbi__context * s); -static void * stbi__tga_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri); +static void * stbi__tga_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +); static int stbi__tga_info(stbi__context * s, int * x, int * y, int * comp); #endif #ifndef STBI_NO_PSD static int stbi__psd_test(stbi__context * s); -static void * stbi__psd_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri, int bpc); +static void * stbi__psd_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri, + int bpc +); static int stbi__psd_info(stbi__context * s, int * x, int * y, int * comp); static int stbi__psd_is16(stbi__context * s); #endif #ifndef STBI_NO_HDR static int stbi__hdr_test(stbi__context * s); -static float * stbi__hdr_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri); +static float * stbi__hdr_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +); static int stbi__hdr_info(stbi__context * s, int * x, int * y, int * comp); #endif #ifndef STBI_NO_PIC static int stbi__pic_test(stbi__context * s); -static void * stbi__pic_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri); +static void * stbi__pic_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +); static int stbi__pic_info(stbi__context * s, int * x, int * y, int * comp); #endif #ifndef STBI_NO_GIF static int stbi__gif_test(stbi__context * s); -static void * stbi__gif_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri); -static void * stbi__load_gif_main(stbi__context * s, int ** delays, int * x, int * y, int * z, - int * comp, int req_comp); +static void * stbi__gif_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +); +static void * stbi__load_gif_main( + stbi__context * s, int ** delays, int * x, int * y, int * z, int * comp, int req_comp +); static int stbi__gif_info(stbi__context * s, int * x, int * y, int * comp); #endif #ifndef STBI_NO_PNM static int stbi__pnm_test(stbi__context * s); -static void * stbi__pnm_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri); +static void * stbi__pnm_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +); static int stbi__pnm_info(stbi__context * s, int * x, int * y, int * comp); static int stbi__pnm_is16(stbi__context * s); #endif @@ -1135,8 +1165,10 @@ STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_fli : stbi__vertically_flip_on_load_global) #endif // STBI_THREAD_LOCAL -static void * stbi__load_main(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri, int bpc) { +static void * stbi__load_main( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri, + int bpc +) { memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed @@ -1198,9 +1230,8 @@ static stbi_uc * stbi__convert_16_to_8(stbi__uint16 * orig, int w, int h, int ch if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); for (i = 0; i < img_len; ++i) - reduced[i] - = (stbi_uc) ((orig[i] >> 8) - & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + reduced[i] = (stbi_uc) ((orig[i] >> 8) & 0xFF + ); // top half of each byte is sufficient approx of 16->8 bit scaling STBI_FREE(orig); return reduced; @@ -1215,10 +1246,8 @@ static stbi__uint16 * stbi__convert_8_to_16(stbi_uc * orig, int w, int h, int ch if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); for (i = 0; i < img_len; ++i) - enlarged[i] - = (stbi__uint16) ((orig[i] << 8) - + orig - [i]); // replicate to high and low byte, maps 0->0, 255->0xffff + enlarged[i] = (stbi__uint16) ((orig[i] << 8) + orig[i] + ); // replicate to high and low byte, maps 0->0, 255->0xffff STBI_FREE(orig); return enlarged; @@ -1248,8 +1277,8 @@ static void stbi__vertical_flip(void * image, int w, int h, int bytes_per_pixel) } #ifndef STBI_NO_GIF -static void stbi__vertical_flip_slices(void * image, int w, int h, int z, - int bytes_per_pixel) { +static void +stbi__vertical_flip_slices(void * image, int w, int h, int z, int bytes_per_pixel) { int slice; int slice_size = w * h * bytes_per_pixel; @@ -1261,8 +1290,9 @@ static void stbi__vertical_flip_slices(void * image, int w, int h, int z, } #endif -static unsigned char * stbi__load_and_postprocess_8bit(stbi__context * s, int * x, int * y, - int * comp, int req_comp) { +static unsigned char * stbi__load_and_postprocess_8bit( + stbi__context * s, int * x, int * y, int * comp, int req_comp +) { stbi__result_info ri; void * result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); @@ -1272,8 +1302,9 @@ static unsigned char * stbi__load_and_postprocess_8bit(stbi__context * s, int * STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); if (ri.bits_per_channel != 8) { - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, - req_comp == 0 ? *comp : req_comp); + result = stbi__convert_16_to_8( + (stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp + ); ri.bits_per_channel = 8; } @@ -1287,8 +1318,9 @@ static unsigned char * stbi__load_and_postprocess_8bit(stbi__context * s, int * return (unsigned char *) result; } -static stbi__uint16 * stbi__load_and_postprocess_16bit(stbi__context * s, int * x, int * y, - int * comp, int req_comp) { +static stbi__uint16 * stbi__load_and_postprocess_16bit( + stbi__context * s, int * x, int * y, int * comp, int req_comp +) { stbi__result_info ri; void * result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); @@ -1298,8 +1330,9 @@ static stbi__uint16 * stbi__load_and_postprocess_16bit(stbi__context * s, int * STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); if (ri.bits_per_channel != 16) { - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, - req_comp == 0 ? *comp : req_comp); + result = stbi__convert_8_to_16( + (stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp + ); ri.bits_per_channel = 16; } @@ -1315,8 +1348,8 @@ static stbi__uint16 * stbi__load_and_postprocess_16bit(stbi__context * s, int * } #if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) -static void stbi__float_postprocess(float * result, int * x, int * y, int * comp, - int req_comp) { +static void +stbi__float_postprocess(float * result, int * x, int * y, int * comp, int req_comp) { if (stbi__vertically_flip_on_load && result != NULL) { int channels = req_comp ? req_comp : *comp; stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); @@ -1328,19 +1361,22 @@ static void stbi__float_postprocess(float * result, int * x, int * y, int * comp #if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) STBI_EXTERN -__declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, - const char * str, int cbmb, - wchar_t * widestr, int cchwide); +__declspec(dllimport) int __stdcall MultiByteToWideChar( + unsigned int cp, unsigned long flags, const char * str, int cbmb, wchar_t * widestr, + int cchwide +); STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte( unsigned int cp, unsigned long flags, const wchar_t * widestr, int cchwide, char * str, - int cbmb, const char * defchar, int * used_default); + int cbmb, const char * defchar, int * used_default +); #endif #if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) -STBIDEF int stbi_convert_wchar_to_utf8(char * buffer, size_t bufferlen, - const wchar_t * input) { - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, - NULL); +STBIDEF int +stbi_convert_wchar_to_utf8(char * buffer, size_t bufferlen, const wchar_t * input) { + return WideCharToMultiByte( + 65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL + ); } #endif @@ -1350,13 +1386,16 @@ static FILE * stbi__fopen(char const * filename, char const * mode) { wchar_t wMode[64]; wchar_t wFilename[1024]; if (0 - == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, - sizeof(wFilename) / sizeof(*wFilename))) + == MultiByteToWideChar( + 65001 /* UTF8 */, 0, filename, -1, wFilename, + sizeof(wFilename) / sizeof(*wFilename) + )) return 0; if (0 - == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, - sizeof(wMode) / sizeof(*wMode))) + == MultiByteToWideChar( + 65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode) / sizeof(*wMode) + )) return 0; #if defined(_MSC_VER) && _MSC_VER >= 1400 @@ -1373,8 +1412,8 @@ static FILE * stbi__fopen(char const * filename, char const * mode) { return f; } -STBIDEF stbi_uc * stbi_load(char const * filename, int * x, int * y, int * comp, - int req_comp) { +STBIDEF stbi_uc * +stbi_load(char const * filename, int * x, int * y, int * comp, int req_comp) { FILE * f = stbi__fopen(filename, "rb"); unsigned char * result; if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); @@ -1395,8 +1434,8 @@ STBIDEF stbi_uc * stbi_load_from_file(FILE * f, int * x, int * y, int * comp, in return result; } -STBIDEF stbi__uint16 * stbi_load_from_file_16(FILE * f, int * x, int * y, int * comp, - int req_comp) { +STBIDEF stbi__uint16 * +stbi_load_from_file_16(FILE * f, int * x, int * y, int * comp, int req_comp) { stbi__uint16 * result; stbi__context s; stbi__start_file(&s, f); @@ -1408,8 +1447,8 @@ STBIDEF stbi__uint16 * stbi_load_from_file_16(FILE * f, int * x, int * y, int * return result; } -STBIDEF stbi_us * stbi_load_16(char const * filename, int * x, int * y, int * comp, - int req_comp) { +STBIDEF stbi_us * +stbi_load_16(char const * filename, int * x, int * y, int * comp, int req_comp) { FILE * f = stbi__fopen(filename, "rb"); stbi__uint16 * result; if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); @@ -1420,39 +1459,45 @@ STBIDEF stbi_us * stbi_load_16(char const * filename, int * x, int * y, int * co #endif //!STBI_NO_STDIO -STBIDEF stbi_us * stbi_load_16_from_memory(stbi_uc const * buffer, int len, int * x, int * y, - int * channels_in_file, int desired_channels) { +STBIDEF stbi_us * stbi_load_16_from_memory( + stbi_uc const * buffer, int len, int * x, int * y, int * channels_in_file, + int desired_channels +) { stbi__context s; stbi__start_mem(&s, buffer, len); return stbi__load_and_postprocess_16bit(&s, x, y, channels_in_file, desired_channels); } -STBIDEF stbi_us * stbi_load_16_from_callbacks(stbi_io_callbacks const * clbk, void * user, - int * x, int * y, int * channels_in_file, - int desired_channels) { +STBIDEF stbi_us * stbi_load_16_from_callbacks( + stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * channels_in_file, + int desired_channels +) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__load_and_postprocess_16bit(&s, x, y, channels_in_file, desired_channels); } -STBIDEF stbi_uc * stbi_load_from_memory(stbi_uc const * buffer, int len, int * x, int * y, - int * comp, int req_comp) { +STBIDEF stbi_uc * stbi_load_from_memory( + stbi_uc const * buffer, int len, int * x, int * y, int * comp, int req_comp +) { stbi__context s; stbi__start_mem(&s, buffer, len); return stbi__load_and_postprocess_8bit(&s, x, y, comp, req_comp); } -STBIDEF stbi_uc * stbi_load_from_callbacks(stbi_io_callbacks const * clbk, void * user, - int * x, int * y, int * comp, int req_comp) { +STBIDEF stbi_uc * stbi_load_from_callbacks( + stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * comp, int req_comp +) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__load_and_postprocess_8bit(&s, x, y, comp, req_comp); } #ifndef STBI_NO_GIF -STBIDEF stbi_uc * stbi_load_gif_from_memory(stbi_uc const * buffer, int len, int ** delays, - int * x, int * y, int * z, int * comp, - int req_comp) { +STBIDEF stbi_uc * stbi_load_gif_from_memory( + stbi_uc const * buffer, int len, int ** delays, int * x, int * y, int * z, int * comp, + int req_comp +) { unsigned char * result; stbi__context s; stbi__start_mem(&s, buffer, len); @@ -1467,8 +1512,8 @@ STBIDEF stbi_uc * stbi_load_gif_from_memory(stbi_uc const * buffer, int len, int #endif #ifndef STBI_NO_LINEAR -static float * stbi__loadf_main(stbi__context * s, int * x, int * y, int * comp, - int req_comp) { +static float * +stbi__loadf_main(stbi__context * s, int * x, int * y, int * comp, int req_comp) { unsigned char * data; #ifndef STBI_NO_HDR if (stbi__hdr_test(s)) { @@ -1483,15 +1528,17 @@ static float * stbi__loadf_main(stbi__context * s, int * x, int * y, int * comp, return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); } -STBIDEF float * stbi_loadf_from_memory(stbi_uc const * buffer, int len, int * x, int * y, - int * comp, int req_comp) { +STBIDEF float * stbi_loadf_from_memory( + stbi_uc const * buffer, int len, int * x, int * y, int * comp, int req_comp +) { stbi__context s; stbi__start_mem(&s, buffer, len); return stbi__loadf_main(&s, x, y, comp, req_comp); } -STBIDEF float * stbi_loadf_from_callbacks(stbi_io_callbacks const * clbk, void * user, int * x, - int * y, int * comp, int req_comp) { +STBIDEF float * stbi_loadf_from_callbacks( + stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * comp, int req_comp +) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__loadf_main(&s, x, y, comp, req_comp); @@ -1745,8 +1792,9 @@ static stbi_uc stbi__compute_y(int r, int g, int b) { && defined(STBI_NO_PNM) // nothing #else -static unsigned char * stbi__convert_format(unsigned char * data, int img_n, int req_comp, - unsigned int x, unsigned int y) { +static unsigned char * stbi__convert_format( + unsigned char * data, int img_n, int req_comp, unsigned int x, unsigned int y +) { int i, j; unsigned char * good; @@ -1843,8 +1891,9 @@ static stbi__uint16 stbi__compute_y_16(int r, int g, int b) { #if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) // nothing #else -static stbi__uint16 * stbi__convert_format16(stbi__uint16 * data, int img_n, int req_comp, - unsigned int x, unsigned int y) { +static stbi__uint16 * stbi__convert_format16( + stbi__uint16 * data, int img_n, int req_comp, unsigned int x, unsigned int y +) { int i, j; stbi__uint16 * good; @@ -1920,8 +1969,9 @@ static stbi__uint16 * stbi__convert_format16(stbi__uint16 * data, int img_n, int STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); - return (stbi__uint16 *) stbi__errpuc("unsupported", - "Unsupported format conversion"); + return (stbi__uint16 *) stbi__errpuc( + "unsupported", "Unsupported format conversion" + ); } #undef STBI__CASE } @@ -2079,10 +2129,13 @@ typedef struct { // kernels void (*idct_block_kernel)(stbi_uc * out, int out_stride, short data[64]); - void (*YCbCr_to_RGB_kernel)(stbi_uc * out, const stbi_uc * y, const stbi_uc * pcb, - const stbi_uc * pcr, int count, int step); - stbi_uc * (*resample_row_hv_2_kernel)(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, - int w, int hs); + void (*YCbCr_to_RGB_kernel)( + stbi_uc * out, const stbi_uc * y, const stbi_uc * pcb, const stbi_uc * pcr, int count, + int step + ); + stbi_uc * (*resample_row_hv_2_kernel)( + stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs + ); } stbi__jpeg; static int stbi__build_huffman(stbi__huffman * h, int * count) { @@ -2215,8 +2268,9 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg * j, stbi__huffman * h) c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; if (c < 0 || c >= 256) // symbol id out of bounds! return -1; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) - == h->code[c]); + STBI_ASSERT( + (((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c] + ); // convert the id to a symbol j->code_bits -= k; @@ -2280,9 +2334,10 @@ static const stbi_uc stbi__jpeg_dezigzag[64 + 15] 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63}; // decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg * j, short data[64], stbi__huffman * hdc, - stbi__huffman * hac, stbi__int16 * fac, int b, - stbi__uint16 * dequant) { +static int stbi__jpeg_decode_block( + stbi__jpeg * j, short data[64], stbi__huffman * hdc, stbi__huffman * hac, + stbi__int16 * fac, int b, stbi__uint16 * dequant +) { int diff, dc, k; int t; @@ -2314,8 +2369,9 @@ static int stbi__jpeg_decode_block(stbi__jpeg * j, short data[64], stbi__huffman k += (r >> 4) & 15; // run s = r & 15; // combined length if (s > j->code_bits) - return stbi__err("bad huffman code", - "Combined length longer than code bits available"); + return stbi__err( + "bad huffman code", "Combined length longer than code bits available" + ); j->code_buffer <<= s; j->code_bits -= s; // decode into unzigzag'd location @@ -2340,8 +2396,8 @@ static int stbi__jpeg_decode_block(stbi__jpeg * j, short data[64], stbi__huffman return 1; } -static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg * j, short data[64], stbi__huffman * hdc, - int b) { +static int +stbi__jpeg_decode_block_prog_dc(stbi__jpeg * j, short data[64], stbi__huffman * hdc, int b) { int diff, dc; int t; if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); @@ -2371,8 +2427,9 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg * j, short data[64], stbi_ // @OPTIMIZE: store non-zigzagged during the decode passes, // and only de-zigzag when dequantizing -static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg * j, short data[64], stbi__huffman * hac, - stbi__int16 * fac) { +static int stbi__jpeg_decode_block_prog_ac( + stbi__jpeg * j, short data[64], stbi__huffman * hac, stbi__int16 * fac +) { int k; if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); @@ -2395,8 +2452,9 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg * j, short data[64], stbi_ k += (r >> 4) & 15; // run s = r & 15; // combined length if (s > j->code_bits) - return stbi__err("bad huffman code", - "Combined length longer than code bits available"); + return stbi__err( + "bad huffman code", "Combined length longer than code bits available" + ); j->code_buffer <<= s; j->code_bits -= s; zig = stbi__jpeg_dezigzag[k++]; @@ -2443,7 +2501,8 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg * j, short data[64], stbi_ int r, s; int rs = stbi__jpeg_huff_decode( j, - hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + hac + ); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh if (rs < 0) return stbi__err("bad huffman code", "Corrupt JPEG"); s = rs & 15; r = rs >> 4; @@ -2691,18 +2750,24 @@ static void stbi__idct_simd(stbi_uc * out, int out_stride, short data[64]) { = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f(0.765366865f), stbi__f2f(0.5411961f)); - __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), - stbi__f2f(1.175875602f)); - __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), - stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); - __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f(0.298631336f), - stbi__f2f(-1.961570560f)); - __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), - stbi__f2f(-1.961570560f) + stbi__f2f(3.072711026f)); - __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f(2.053119869f), - stbi__f2f(-0.390180644f)); - __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), - stbi__f2f(-0.390180644f) + stbi__f2f(1.501321110f)); + __m128i rot1_0 = dct_const( + stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f) + ); + __m128i rot1_1 = dct_const( + stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f) + ); + __m128i rot2_0 = dct_const( + stbi__f2f(-1.961570560f) + stbi__f2f(0.298631336f), stbi__f2f(-1.961570560f) + ); + __m128i rot2_1 = dct_const( + stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f(3.072711026f) + ); + __m128i rot3_0 = dct_const( + stbi__f2f(-0.390180644f) + stbi__f2f(2.053119869f), stbi__f2f(-0.390180644f) + ); + __m128i rot3_1 = dct_const( + stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f(1.501321110f) + ); // rounding biases in column/row passes, see stbi__idct_block for explanation. __m128i bias_0 = _mm_set1_epi32(512); @@ -3091,13 +3156,15 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg * z) { for (j = 0; j < h; ++j) { for (i = 0; i < w; ++i) { int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc + z->img_comp[n].hd, - z->huff_ac + ha, z->fast_ac[ha], n, - z->dequant[z->img_comp[n].tq])) + if (!stbi__jpeg_decode_block( + z, data, z->huff_dc + z->img_comp[n].hd, z->huff_ac + ha, + z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq] + )) return 0; - z->idct_block_kernel(z->img_comp[n].data + z->img_comp[n].w2 * j * 8 - + i * 8, - z->img_comp[n].w2, data); + z->idct_block_kernel( + z->img_comp[n].data + z->img_comp[n].w2 * j * 8 + i * 8, + z->img_comp[n].w2, data + ); // every data block is an MCU, so countdown the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); @@ -3124,14 +3191,16 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg * z) { int x2 = (i * z->img_comp[n].h + x) * 8; int y2 = (j * z->img_comp[n].v + y) * 8; int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, - z->huff_dc + z->img_comp[n].hd, - z->huff_ac + ha, z->fast_ac[ha], - n, z->dequant[z->img_comp[n].tq])) + if (!stbi__jpeg_decode_block( + z, data, z->huff_dc + z->img_comp[n].hd, + z->huff_ac + ha, z->fast_ac[ha], n, + z->dequant[z->img_comp[n].tq] + )) return 0; - z->idct_block_kernel(z->img_comp[n].data - + z->img_comp[n].w2 * y2 + x2, - z->img_comp[n].w2, data); + z->idct_block_kernel( + z->img_comp[n].data + z->img_comp[n].w2 * y2 + x2, + z->img_comp[n].w2, data + ); } } } @@ -3162,12 +3231,14 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg * z) { = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); if (z->spec_start == 0) { if (!stbi__jpeg_decode_block_prog_dc( - z, data, &z->huff_dc[z->img_comp[n].hd], n)) + z, data, &z->huff_dc[z->img_comp[n].hd], n + )) return 0; } else { int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], - z->fast_ac[ha])) + if (!stbi__jpeg_decode_block_prog_ac( + z, data, &z->huff_ac[ha], z->fast_ac[ha] + )) return 0; } // every data block is an MCU, so countdown the restart interval @@ -3195,7 +3266,8 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg * z) { short * data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); if (!stbi__jpeg_decode_block_prog_dc( - z, data, &z->huff_dc[z->img_comp[n].hd], n)) + z, data, &z->huff_dc[z->img_comp[n].hd], n + )) return 0; } } @@ -3231,9 +3303,10 @@ static void stbi__jpeg_finish(stbi__jpeg * z) { short * data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); - z->idct_block_kernel(z->img_comp[n].data + z->img_comp[n].w2 * j * 8 - + i * 8, - z->img_comp[n].w2, data); + z->idct_block_kernel( + z->img_comp[n].data + z->img_comp[n].w2 * j * 8 + i * 8, + z->img_comp[n].w2, data + ); } } } @@ -3283,7 +3356,8 @@ static int stbi__process_marker(stbi__jpeg * z, int m) { if (n > 256) return stbi__err( "bad DHT header", - "Corrupt JPEG"); // Loop over i < n would write past end of values! + "Corrupt JPEG" + ); // Loop over i < n would write past end of values! L -= 17; if (tc == 0) { if (!stbi__build_huffman(z->huff_dc + th, sizes)) return 0; @@ -3410,13 +3484,16 @@ static int stbi__process_frame_header(stbi__jpeg * z, int scan) { if (Lf < 11) return stbi__err("bad SOF len", "Corrupt JPEG"); // JPEG p = stbi__get8(s); if (p != 8) - return stbi__err("only 8-bit", - "JPEG format not supported: 8-bit only"); // JPEG baseline + return stbi__err( + "only 8-bit", + "JPEG format not supported: 8-bit only" + ); // JPEG baseline s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err( "no header height", - "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + "JPEG format not supported: delayed height" + ); // Legal, but we don't handle it--but neither does IJG s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width", "Corrupt JPEG"); // JPEG requires if (s->img_y > STBI_MAX_DIMENSIONS) @@ -3493,8 +3570,9 @@ static int stbi__process_frame_header(stbi__jpeg * z, int scan) { z->img_comp[i].linebuf = NULL; z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); if (z->img_comp[i].raw_data == NULL) - return stbi__free_jpeg_components(z, i + 1, - stbi__err("outofmem", "Out of memory")); + return stbi__free_jpeg_components( + z, i + 1, stbi__err("outofmem", "Out of memory") + ); // align blocks for idct using mmx/sse z->img_comp[i].data = (stbi_uc *) (((size_t) z->img_comp[i].raw_data + 15) & ~15); if (z->progressive) { @@ -3504,8 +3582,9 @@ static int stbi__process_frame_header(stbi__jpeg * z, int scan) { z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); if (z->img_comp[i].raw_coeff == NULL) - return stbi__free_jpeg_components(z, i + 1, - stbi__err("outofmem", "Out of memory")); + return stbi__free_jpeg_components( + z, i + 1, stbi__err("outofmem", "Out of memory") + ); z->img_comp[i].coeff = (short *) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); } } @@ -3603,13 +3682,14 @@ static int stbi__decode_jpeg_image(stbi__jpeg * j) { // static jfif-centered resampling (across block boundaries) -typedef stbi_uc * (*resample_row_func)(stbi_uc * out, stbi_uc * in0, stbi_uc * in1, int w, - int hs); +typedef stbi_uc * (*resample_row_func)( + stbi_uc * out, stbi_uc * in0, stbi_uc * in1, int w, int hs +); #define stbi__div4(x) ((stbi_uc) ((x) >> 2)) -static stbi_uc * resample_row_1(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, - int hs) { +static stbi_uc * +resample_row_1(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) { STBI_NOTUSED(out); STBI_NOTUSED(in_far); STBI_NOTUSED(w); @@ -3617,8 +3697,8 @@ static stbi_uc * resample_row_1(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_f return in_near; } -static stbi_uc * stbi__resample_row_v_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, - int w, int hs) { +static stbi_uc * +stbi__resample_row_v_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) { // need to generate two samples vertically for every one in input int i; STBI_NOTUSED(hs); @@ -3626,8 +3706,8 @@ static stbi_uc * stbi__resample_row_v_2(stbi_uc * out, stbi_uc * in_near, stbi_u return out; } -static stbi_uc * stbi__resample_row_h_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, - int w, int hs) { +static stbi_uc * +stbi__resample_row_h_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) { // need to generate two samples horizontally for every one in input int i; stbi_uc * input = in_near; @@ -3656,8 +3736,8 @@ static stbi_uc * stbi__resample_row_h_2(stbi_uc * out, stbi_uc * in_near, stbi_u #define stbi__div16(x) ((stbi_uc) ((x) >> 4)) -static stbi_uc * stbi__resample_row_hv_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, - int w, int hs) { +static stbi_uc * +stbi__resample_row_hv_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) { // need to generate 2x2 samples for every one in input int i, t0, t1; if (w == 1) { @@ -3681,8 +3761,9 @@ static stbi_uc * stbi__resample_row_hv_2(stbi_uc * out, stbi_uc * in_near, stbi_ } #if defined(STBI_SSE2) || defined(STBI_NEON) -static stbi_uc * stbi__resample_row_hv_2_simd(stbi_uc * out, stbi_uc * in_near, - stbi_uc * in_far, int w, int hs) { +static stbi_uc * stbi__resample_row_hv_2_simd( + stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs +) { // need to generate 2x2 samples for every one in input int i = 0, t0, t1; @@ -3797,8 +3878,8 @@ static stbi_uc * stbi__resample_row_hv_2_simd(stbi_uc * out, stbi_uc * in_near, } #endif -static stbi_uc * stbi__resample_row_generic(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, - int w, int hs) { +static stbi_uc * +stbi__resample_row_generic(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) { // resample with nearest-neighbor int i, j; STBI_NOTUSED(in_far); @@ -3810,8 +3891,10 @@ static stbi_uc * stbi__resample_row_generic(stbi_uc * out, stbi_uc * in_near, st // this is a reduced-precision calculation of YCbCr-to-RGB introduced // to make sure the code produces the same results in both SIMD and scalar #define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) -static void stbi__YCbCr_to_RGB_row(stbi_uc * out, const stbi_uc * y, const stbi_uc * pcb, - const stbi_uc * pcr, int count, int step) { +static void stbi__YCbCr_to_RGB_row( + stbi_uc * out, const stbi_uc * y, const stbi_uc * pcb, const stbi_uc * pcr, int count, + int step +) { int i; for (i = 0; i < count; ++i) { int y_fixed = (y[i] << 20) + (1 << 19); // rounding @@ -3846,8 +3929,10 @@ static void stbi__YCbCr_to_RGB_row(stbi_uc * out, const stbi_uc * y, const stbi_ } #if defined(STBI_SSE2) || defined(STBI_NEON) -static void stbi__YCbCr_to_RGB_simd(stbi_uc * out, stbi_uc const * y, stbi_uc const * pcb, - stbi_uc const * pcr, int count, int step) { +static void stbi__YCbCr_to_RGB_simd( + stbi_uc * out, stbi_uc const * y, stbi_uc const * pcb, stbi_uc const * pcr, int count, + int step +) { int i = 0; #ifdef STBI_SSE2 @@ -4031,8 +4116,8 @@ static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) { return (stbi_uc) ((t + (t >> 8)) >> 8); } -static stbi_uc * load_jpeg_image(stbi__jpeg * z, int * out_x, int * out_y, int * comp, - int req_comp) { +static stbi_uc * +load_jpeg_image(stbi__jpeg * z, int * out_x, int * out_y, int * comp, int req_comp) { int n, decode_n, is_rgb; z->s->img_n = 0; // make stbi__cleanup_jpeg safe @@ -4107,8 +4192,10 @@ static stbi_uc * load_jpeg_image(stbi__jpeg * z, int * out_x, int * out_y, int * for (k = 0; k < decode_n; ++k) { stbi__resample * r = &res_comp[k]; int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, r->w_lores, r->hs); + coutput[k] = r->resample( + z->img_comp[k].linebuf, y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, r->w_lores, r->hs + ); if (++r->ystep >= r->vs) { r->ystep = 0; r->line0 = r->line1; @@ -4206,8 +4293,9 @@ static stbi_uc * load_jpeg_image(stbi__jpeg * z, int * out_x, int * out_y, int * } } -static void * stbi__jpeg_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri) { +static void * stbi__jpeg_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +) { unsigned char * result; stbi__jpeg * j = (stbi__jpeg *) stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__errpuc("outofmem", "Out of memory"); @@ -4432,8 +4520,10 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf * a, stbi__zhuffman * z) return stbi__zhuffman_decode_slowpath(a, z); } -static int stbi__zexpand(stbi__zbuf * z, char * zout, - int n) // need to make room for n bytes +static int stbi__zexpand( + stbi__zbuf * z, char * zout, + int n +) // need to make room for n bytes { char * q; unsigned int cur, limit, old_limit; @@ -4500,7 +4590,8 @@ static int stbi__parse_huffman_block(stbi__zbuf * a) { if (z >= 286) return stbi__err( "bad huffman code", - "Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + "Corrupt PNG" + ); // per DEFLATE, length codes 286 and 287 must not appear in compressed data z -= 257; len = stbi__zlength_base[z]; if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); @@ -4508,7 +4599,8 @@ static int stbi__parse_huffman_block(stbi__zbuf * a) { if (z < 0 || z >= 30) return stbi__err( "bad huffman code", - "Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + "Corrupt PNG" + ); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist", "Corrupt PNG"); @@ -4617,8 +4709,10 @@ static int stbi__parse_zlib_header(stbi__zbuf * a) { if ((cmf * 256 + flg) % 31 != 0) return stbi__err("bad zlib header", "Corrupt PNG"); // zlib spec if (flg & 32) - return stbi__err("no preset dict", - "Corrupt PNG"); // preset dictionary not allowed in png + return stbi__err( + "no preset dict", + "Corrupt PNG" + ); // preset dictionary not allowed in png if (cm != 8) return stbi__err("bad compression", "Corrupt PNG"); // DEFLATE required for png @@ -4692,8 +4786,9 @@ static int stbi__do_zlib(stbi__zbuf * a, char * obuf, int olen, int exp, int par return stbi__parse_zlib(a, parse_header); } -STBIDEF char * stbi_zlib_decode_malloc_guesssize(const char * buffer, int len, - int initial_size, int * outlen) { +STBIDEF char * stbi_zlib_decode_malloc_guesssize( + const char * buffer, int len, int initial_size, int * outlen +) { stbi__zbuf a; char * p = (char *) stbi__malloc(initial_size); if (p == NULL) return NULL; @@ -4712,9 +4807,9 @@ STBIDEF char * stbi_zlib_decode_malloc(char const * buffer, int len, int * outle return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); } -STBIDEF char * stbi_zlib_decode_malloc_guesssize_headerflag(const char * buffer, int len, - int initial_size, int * outlen, - int parse_header) { +STBIDEF char * stbi_zlib_decode_malloc_guesssize_headerflag( + const char * buffer, int len, int initial_size, int * outlen, int parse_header +) { stbi__zbuf a; char * p = (char *) stbi__malloc(initial_size); if (p == NULL) return NULL; @@ -4752,8 +4847,8 @@ STBIDEF char * stbi_zlib_decode_noheader_malloc(char const * buffer, int len, in } } -STBIDEF int stbi_zlib_decode_noheader_buffer(char * obuffer, int olen, const char * ibuffer, - int ilen) { +STBIDEF int +stbi_zlib_decode_noheader_buffer(char * obuffer, int olen, const char * ibuffer, int ilen) { stbi__zbuf a; a.zbuffer = (stbi_uc *) ibuffer; a.zbuffer_end = (stbi_uc *) ibuffer + ilen; @@ -4831,8 +4926,8 @@ static const stbi_uc stbi__depth_scale_table[9] = {0, 0xff, 0x55, 0, 0x11, 0, 0, // adds an extra all-255 alpha channel // dest == src is legal // img_n must be 1 or 3 -static void stbi__create_png_alpha_expand8(stbi_uc * dest, stbi_uc * src, stbi__uint32 x, - int img_n) { +static void +stbi__create_png_alpha_expand8(stbi_uc * dest, stbi_uc * src, stbi__uint32 x, int img_n) { int i; // must process data backwards since we allow dest==src if (img_n == 1) { @@ -4852,9 +4947,10 @@ static void stbi__create_png_alpha_expand8(stbi_uc * dest, stbi_uc * src, stbi__ } // create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png * a, stbi_uc * raw, stbi__uint32 raw_len, - int out_n, stbi__uint32 x, stbi__uint32 y, int depth, - int color) { +static int stbi__create_png_image_raw( + stbi__png * a, stbi_uc * raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, + stbi__uint32 y, int depth, int color +) { int bytes = (depth == 16 ? 2 : 1); stbi__context * s = a->s; stbi__uint32 i, j, stride = x * out_n * bytes; @@ -4869,8 +4965,10 @@ static int stbi__create_png_image_raw(stbi__png * a, stbi_uc * raw, stbi__uint32 int width = x; STBI_ASSERT(out_n == s->img_n || out_n == s->img_n + 1); - a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, - 0); // extra bytes to write off the end into + a->out = (stbi_uc *) stbi__malloc_mad3( + x, y, output_bytes, + 0 + ); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); // note: error exits here don't need to clean up a->out individually, @@ -4937,11 +5035,13 @@ static int stbi__create_png_image_raw(stbi__png * a, stbi_uc * raw, stbi__uint32 case STBI__F_paeth: for (k = 0; k < filter_bytes; ++k) cur[k] = STBI__BYTECAST( - raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + raw[k] + prior[k] + ); // prior[k] == stbi__paeth(0,prior[k],0) for (k = filter_bytes; k < nk; ++k) - cur[k] = STBI__BYTECAST(raw[k] - + stbi__paeth(cur[k - filter_bytes], prior[k], - prior[k - filter_bytes])); + cur[k] = STBI__BYTECAST( + raw[k] + + stbi__paeth(cur[k - filter_bytes], prior[k], prior[k - filter_bytes]) + ); break; case STBI__F_avg_first: memcpy(cur, raw, filter_bytes); @@ -5022,16 +5122,18 @@ static int stbi__create_png_image_raw(stbi__png * a, stbi_uc * raw, stbi__uint32 return 1; } -static int stbi__create_png_image(stbi__png * a, stbi_uc * image_data, - stbi__uint32 image_data_len, int out_n, int depth, int color, - int interlaced) { +static int stbi__create_png_image( + stbi__png * a, stbi_uc * image_data, stbi__uint32 image_data_len, int out_n, int depth, + int color, int interlaced +) { int bytes = (depth == 16 ? 2 : 1); int out_bytes = out_n * bytes; stbi_uc * final; int p; if (!interlaced) - return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, - a->s->img_y, depth, color); + return stbi__create_png_image_raw( + a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color + ); // de-interlacing final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); @@ -5047,8 +5149,9 @@ static int stbi__create_png_image(stbi__png * a, stbi_uc * image_data, y = (a->s->img_y - yorig[p] + yspc[p] - 1) / yspc[p]; if (x && y) { stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, - color)) { + if (!stbi__create_png_image_raw( + a, image_data, image_data_len, out_n, x, y, depth, color + )) { STBI_FREE(final); return 0; } @@ -5056,8 +5159,10 @@ static int stbi__create_png_image(stbi__png * a, stbi_uc * image_data, for (i = 0; i < x; ++i) { int out_y = j * yspc[p] + yorig[p]; int out_x = i * xspc[p] + xorig[p]; - memcpy(final + out_y * a->s->img_x * out_bytes + out_x * out_bytes, - a->out + (j * x + i) * out_bytes, out_bytes); + memcpy( + final + out_y * a->s->img_x * out_bytes + out_x * out_bytes, + a->out + (j * x + i) * out_bytes, out_bytes + ); } } STBI_FREE(a->out); @@ -5270,8 +5375,9 @@ static int stbi__parse_png_file(stbi__png * z, int scan, int req_comp) { z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) - return stbi__err("1/2/4/8/16-bit only", - "PNG not supported: 1/2/4/8/16-bit only"); + return stbi__err( + "1/2/4/8/16-bit only", "PNG not supported: 1/2/4/8/16-bit only" + ); color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype", "Corrupt PNG"); if (color == 3 && z->depth == 16) return stbi__err("bad ctype", "Corrupt PNG"); @@ -5385,15 +5491,17 @@ static int stbi__parse_png_file(stbi__png * z, int scan, int req_comp) { raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag( - (char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + (char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone + ); if (z->expanded == NULL) return 0; // zlib should set error STBI_FREE(z->idata); z->idata = NULL; if ((req_comp == s->img_n + 1 && req_comp != 3 && !pal_img_n) || has_trans) s->img_out_n = s->img_n + 1; else s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, - color, interlace)) + if (!stbi__create_png_image( + z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace + )) return 0; if (has_trans) { if (z->depth == 16) { @@ -5432,8 +5540,9 @@ static int stbi__parse_png_file(stbi__png * z, int scan, int req_comp) { invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); #endif - return stbi__err(invalid_chunk, - "PNG not supported: unknown PNG chunk type"); + return stbi__err( + invalid_chunk, "PNG not supported: unknown PNG chunk type" + ); } stbi__skip(s, c.length); break; @@ -5443,25 +5552,30 @@ static int stbi__parse_png_file(stbi__png * z, int scan, int req_comp) { } } -static void * stbi__do_png(stbi__png * p, int * x, int * y, int * n, int req_comp, - stbi__result_info * ri) { +static void * +stbi__do_png(stbi__png * p, int * x, int * y, int * n, int req_comp, stbi__result_info * ri) { void * result = NULL; if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { if (p->depth <= 8) ri->bits_per_channel = 8; else if (p->depth == 16) ri->bits_per_channel = 16; else - return stbi__errpuc("bad bits_per_channel", - "PNG not supported: unsupported color depth"); + return stbi__errpuc( + "bad bits_per_channel", "PNG not supported: unsupported color depth" + ); result = p->out; p->out = NULL; if (req_comp && req_comp != p->s->img_out_n) { if (ri->bits_per_channel == 8) - result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, - req_comp, p->s->img_x, p->s->img_y); + result = stbi__convert_format( + (unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, + p->s->img_y + ); else - result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, - req_comp, p->s->img_x, p->s->img_y); + result = stbi__convert_format16( + (stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, + p->s->img_y + ); p->s->img_out_n = req_comp; if (result == NULL) return result; } @@ -5479,8 +5593,9 @@ static void * stbi__do_png(stbi__png * p, int * x, int * y, int * n, int req_com return result; } -static void * stbi__png_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri) { +static void * stbi__png_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +) { stbi__png p; p.s = s; return stbi__do_png(&p, x, y, comp, req_comp, ri); @@ -5667,12 +5782,16 @@ static void * stbi__bmp_parse_header(stbi__context * s, stbi__bmp_data * info) { if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); if (compress >= 4) - return stbi__errpuc("BMP JPEG/PNG", - "BMP type not supported: unsupported " - "compression"); // this includes PNG/JPEG modes + return stbi__errpuc( + "BMP JPEG/PNG", + "BMP type not supported: unsupported " + "compression" + ); // this includes PNG/JPEG modes if (compress == 3 && info->bpp != 16 && info->bpp != 32) - return stbi__errpuc("bad BMP", - "bad BMP"); // bitfields requires 16 or 32 bits/pixel + return stbi__errpuc( + "bad BMP", + "bad BMP" + ); // bitfields requires 16 or 32 bits/pixel stbi__get32le(s); // discard sizeof stbi__get32le(s); // discard hres stbi__get32le(s); // discard vres @@ -5723,8 +5842,9 @@ static void * stbi__bmp_parse_header(stbi__context * s, stbi__bmp_data * info) { return (void *) 1; } -static void * stbi__bmp_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri) { +static void * stbi__bmp_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +) { stbi_uc * out; unsigned int mr = 0, mg = 0, mb = 0, ma = 0, all_a; stbi_uc pal[256][4]; @@ -5804,8 +5924,9 @@ static void * stbi__bmp_load(stbi__context * s, int * x, int * y, int * comp, in if (info.hsz != 12) stbi__get8(s); pal[i][3] = 255; } - stbi__skip(s, info.offset - info.extra_read - info.hsz - - psize * (info.hsz == 12 ? 3 : 4)); + stbi__skip( + s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4) + ); if (info.bpp == 1) width = (s->img_x + 7) >> 3; else if (info.bpp == 4) width = (s->img_x + 1) >> 1; else if (info.bpp == 8) width = s->img_x; @@ -6023,8 +6144,9 @@ static int stbi__tga_info(stbi__context * s, int * x, int * y, int * comp) { } tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); } else { - tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, - (tga_image_type == 3) || (tga_image_type == 11), NULL); + tga_comp = stbi__tga_get_comp( + tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL + ); } if (!tga_comp) { stbi__rewind(s); @@ -6087,8 +6209,9 @@ static void stbi__tga_read_rgb16(stbi__context * s, stbi_uc * out) { // so let's treat all 15 and 16bit TGAs as RGB with no alpha. } -static void * stbi__tga_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri) { +static void * stbi__tga_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +) { // read in the TGA header stuff int tga_offset = stbi__get8(s); int tga_indexed = stbi__get8(s); @@ -6325,8 +6448,10 @@ static int stbi__psd_decode_rle(stbi__context * s, stbi_uc * p, int pixelCount) return 1; } -static void * stbi__psd_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri, int bpc) { +static void * stbi__psd_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri, + int bpc +) { int pixelCount; int channelCount, compression; int channel, i; @@ -6349,8 +6474,9 @@ static void * stbi__psd_load(stbi__context * s, int * x, int * y, int * comp, in // Read the number of channels (R, G, B, A, etc). channelCount = stbi__get16be(s); if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", - "Unsupported number of channels in PSD image"); + return stbi__errpuc( + "wrong channel count", "Unsupported number of channels in PSD image" + ); // Read the rows and columns of the image. h = stbi__get32be(s); @@ -6575,8 +6701,8 @@ static void stbi__copyval(int channel, stbi_uc * dest, const stbi_uc * src) { if (channel & mask) dest[i] = src[i]; } -static stbi_uc * stbi__pic_load_core(stbi__context * s, int width, int height, int * comp, - stbi_uc * result) { +static stbi_uc * +stbi__pic_load_core(stbi__context * s, int width, int height, int * comp, stbi_uc * result) { int act_comp = 0, num_packets = 0, y, chained; stbi__pic_packet packets[10]; @@ -6632,8 +6758,9 @@ static stbi_uc * stbi__pic_load_core(stbi__context * s, int width, int height, i count = stbi__get8(s); if (stbi__at_eof(s)) - return stbi__errpuc("bad file", - "file too short (pure read count)"); + return stbi__errpuc( + "bad file", "file too short (pure read count)" + ); if (count > left) count = (stbi_uc) left; @@ -6650,8 +6777,9 @@ static stbi_uc * stbi__pic_load_core(stbi__context * s, int width, int height, i while (left > 0) { int count = stbi__get8(s), i; if (stbi__at_eof(s)) - return stbi__errpuc("bad file", - "file too short (mixed read count)"); + return stbi__errpuc( + "bad file", "file too short (mixed read count)" + ); if (count >= 128) { // Repeated stbi_uc value[4]; @@ -6684,8 +6812,9 @@ static stbi_uc * stbi__pic_load_core(stbi__context * s, int width, int height, i return result; } -static void * stbi__pic_load(stbi__context * s, int * px, int * py, int * comp, int req_comp, - stbi__result_info * ri) { +static void * stbi__pic_load( + stbi__context * s, int * px, int * py, int * comp, int req_comp, stbi__result_info * ri +) { stbi_uc * result; int i, x, y, internal_comp; STBI_NOTUSED(ri); @@ -6780,8 +6909,9 @@ static int stbi__gif_test(stbi__context * s) { return r; } -static void stbi__gif_parse_colortable(stbi__context * s, stbi_uc pal[256][4], int num_entries, - int transp) { +static void stbi__gif_parse_colortable( + stbi__context * s, stbi_uc pal[256][4], int num_entries, int transp +) { int i; for (i = 0; i < num_entries; ++i) { pal[i][2] = stbi__get8(s); @@ -6957,8 +7087,9 @@ static stbi_uc * stbi__process_gif_raster(stbi__context * s, stbi__gif * g) { // this function is designed to support animated gifs, although stb_image doesn't support it // two back is the image from two frames ago, used for a very specific disposal format -static stbi_uc * stbi__gif_load_next(stbi__context * s, stbi__gif * g, int * comp, - int req_comp, stbi_uc * two_back) { +static stbi_uc * stbi__gif_load_next( + stbi__context * s, stbi__gif * g, int * comp, int req_comp, stbi_uc * two_back +) { int dispose; int first_frame; int pi; @@ -6983,8 +7114,10 @@ static stbi_uc * stbi__gif_load_next(stbi__context * s, stbi__gif * g, int * com // background colour is only used for pixels that are not rendered first frame, after that "background" // color refers to the color that was there the previous frame. memset(g->out, 0x00, 4 * pcount); - memset(g->background, 0x00, - 4 * pcount); // state of the background (starts transparent) + memset( + g->background, 0x00, + 4 * pcount + ); // state of the background (starts transparent) memset(g->history, 0x00, pcount); // pixels that were affected previous frame first_frame = 1; @@ -7066,8 +7199,10 @@ static stbi_uc * stbi__gif_load_next(stbi__context * s, stbi__gif * g, int * com } if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s, g->lpal, 2 << (g->lflags & 7), - g->eflags & 0x01 ? g->transparent : -1); + stbi__gif_parse_colortable( + s, g->lpal, 2 << (g->lflags & 7), + g->eflags & 0x01 ? g->transparent : -1 + ); g->color_table = (stbi_uc *) g->lpal; } else if (g->flags & 0x80) { g->color_table = (stbi_uc *) g->pal; @@ -7101,8 +7236,8 @@ static stbi_uc * stbi__gif_load_next(stbi__context * s, stbi__gif * g, int * com if (len == 4) { g->eflags = stbi__get8(s); g->delay = 10 - * stbi__get16le( - s); // delay - 1/100th of a second, saving as 1/1000ths. + * stbi__get16le(s + ); // delay - 1/100th of a second, saving as 1/1000ths. // unset old transparent if (g->transparent >= 0) { @@ -7148,8 +7283,9 @@ static void * stbi__load_gif_main_outofmem(stbi__gif * g, stbi_uc * out, int ** return stbi__errpuc("outofmem", "Out of memory"); } -static void * stbi__load_gif_main(stbi__context * s, int ** delays, int * x, int * y, int * z, - int * comp, int req_comp) { +static void * stbi__load_gif_main( + stbi__context * s, int ** delays, int * x, int * y, int * z, int * comp, int req_comp +) { if (stbi__gif_test(s)) { int layers = 0; stbi_uc * u = 0; @@ -7188,8 +7324,9 @@ static void * stbi__load_gif_main(stbi__context * s, int ** delays, int * x, int } if (delays) { - int * new_delays = (int *) STBI_REALLOC_SIZED(*delays, delays_size, - sizeof(int) * layers); + int * new_delays = (int *) STBI_REALLOC_SIZED( + *delays, delays_size, sizeof(int) * layers + ); if (!new_delays) return stbi__load_gif_main_outofmem(&g, out, delays); *delays = new_delays; delays_size = layers * sizeof(int); @@ -7231,8 +7368,9 @@ static void * stbi__load_gif_main(stbi__context * s, int ** delays, int * x, int } } -static void * stbi__gif_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri) { +static void * stbi__gif_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +) { stbi_uc * u = 0; stbi__gif g; memset(&g, 0, sizeof(g)); @@ -7336,8 +7474,9 @@ static void stbi__hdr_convert(float * output, stbi_uc * input, int req_comp) { } } -static float * stbi__hdr_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri) { +static float * stbi__hdr_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +) { char buffer[STBI__HDR_BUFLEN]; char * token; int valid = 0; @@ -7404,8 +7543,9 @@ static float * stbi__hdr_load(stbi__context * s, int * x, int * y, int * comp, i stbi_uc rgbe[4]; main_decode_loop: stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, - req_comp); + stbi__hdr_convert( + hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp + ); } } } else { @@ -7472,8 +7612,9 @@ static float * stbi__hdr_load(stbi__context * s, int * x, int * y, int * comp, i } } for (i = 0; i < width; ++i) - stbi__hdr_convert(hdr_data + (j * width + i) * req_comp, scanline + i * 4, - req_comp); + stbi__hdr_convert( + hdr_data + (j * width + i) * req_comp, scanline + i * 4, req_comp + ); } if (scanline) STBI_FREE(scanline); } @@ -7689,8 +7830,9 @@ static int stbi__pnm_test(stbi__context * s) { return 1; } -static void * stbi__pnm_load(stbi__context * s, int * x, int * y, int * comp, int req_comp, - stbi__result_info * ri) { +static void * stbi__pnm_load( + stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri +) { stbi_uc * out; STBI_NOTUSED(ri); @@ -7710,8 +7852,9 @@ static void * stbi__pnm_load(stbi__context * s, int * x, int * y, int * comp, in if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) return stbi__errpuc("too large", "PNM too large"); - out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, - 0); + out = (stbi_uc *) stbi__malloc_mad4( + s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0 + ); if (!out) return stbi__errpuc("outofmem", "Out of memory"); if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { STBI_FREE(out); @@ -7720,8 +7863,9 @@ static void * stbi__pnm_load(stbi__context * s, int * x, int * y, int * comp, in if (req_comp && req_comp != s->img_n) { if (ri->bits_per_channel == 16) { - out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, - s->img_x, s->img_y); + out = (stbi_uc *) stbi__convert_format16( + (stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y + ); } else { out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); } @@ -7753,8 +7897,10 @@ static int stbi__pnm_getinteger(stbi__context * s, char * c) { value = value * 10 + (*c - '0'); *c = (char) stbi__get8(s); if ((value > 214748364) || (value == 214748364 && *c > '7')) - return stbi__err("integer parse overflow", - "Parsing an integer in the PPM header overflowed a 32-bit int"); + return stbi__err( + "integer parse overflow", + "Parsing an integer in the PPM header overflowed a 32-bit int" + ); } return value; @@ -7795,8 +7941,9 @@ static int stbi__pnm_info(stbi__context * s, int * x, int * y, int * comp) { maxv = stbi__pnm_getinteger(s, &c); // read max value if (maxv > 65535) - return stbi__err("max value > 65535", - "PPM image supports only 8-bit and 16-bit images"); + return stbi__err( + "max value > 65535", "PPM image supports only 8-bit and 16-bit images" + ); else if (maxv > 255) return 16; else return 8; } @@ -7902,15 +8049,16 @@ STBIDEF int stbi_is_16_bit_from_file(FILE * f) { } #endif // !STBI_NO_STDIO -STBIDEF int stbi_info_from_memory(stbi_uc const * buffer, int len, int * x, int * y, - int * comp) { +STBIDEF int +stbi_info_from_memory(stbi_uc const * buffer, int len, int * x, int * y, int * comp) { stbi__context s; stbi__start_mem(&s, buffer, len); return stbi__info_main(&s, x, y, comp); } -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const * c, void * user, int * x, - int * y, int * comp) { +STBIDEF int stbi_info_from_callbacks( + stbi_io_callbacks const * c, void * user, int * x, int * y, int * comp +) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); return stbi__info_main(&s, x, y, comp); @@ -33,6 +33,7 @@ This project uses the following libraries |Google Test (`GTest`)|1.15.2| |Berkeley DB (`libdb`)|5.3.21| |Where Am I?|(latest git `master` version) +|fontconfig|2.15.0| > [!NOTE] > Most of these libraries are likely available from your package manager if you diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3f29da..696856c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,10 +9,12 @@ project(crepe C CXX) find_package(SDL2 REQUIRED) find_package(SDL2_image REQUIRED) +find_package(SDL2_ttf REQUIRED) find_package(SoLoud REQUIRED) find_package(GTest REQUIRED) find_package(whereami REQUIRED) find_library(BERKELEY_DB db) +find_library(FONTCONFIG_LIB fontconfig) add_library(crepe SHARED) add_executable(test_main EXCLUDE_FROM_ALL) @@ -24,9 +26,11 @@ target_include_directories(crepe target_link_libraries(crepe PRIVATE soloud PUBLIC SDL2 + PUBLIC SDL2_ttf PUBLIC SDL2_image PUBLIC ${BERKELEY_DB} PUBLIC whereami + PUBLIC ${FONTCONFIG_LIB} ) add_subdirectory(crepe) @@ -40,5 +44,6 @@ install( target_link_libraries(test_main PRIVATE gtest + PRIVATE gmock PUBLIC crepe ) diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index 7e176e7..6cbb9fe 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -1,21 +1,21 @@ target_sources(crepe PUBLIC Particle.cpp - ComponentManager.cpp Component.cpp Collider.cpp + Resource.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - ComponentManager.h - ComponentManager.hpp Component.h Collider.h ValueBroker.h ValueBroker.hpp + Resource.h ) add_subdirectory(api) add_subdirectory(facade) +add_subdirectory(manager) add_subdirectory(system) add_subdirectory(util) diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp index bbec488..77e11c8 100644 --- a/src/crepe/Collider.cpp +++ b/src/crepe/Collider.cpp @@ -2,4 +2,4 @@ using namespace crepe; -Collider::Collider(game_object_id_t id) : Component(id) {} +Collider::Collider(game_object_id_t id, const vec2 & offset) : Component(id), offset(offset) {} diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h index 827f83d..4344f15 100644 --- a/src/crepe/Collider.h +++ b/src/crepe/Collider.h @@ -1,14 +1,26 @@ #pragma once #include "Component.h" +#include "types.h" namespace crepe { +/** + * \brief Base collider class + */ class Collider : public Component { public: - Collider(game_object_id_t id); + Collider(game_object_id_t id, const vec2 & offset); - int size; +public: + /** + * \brief Offset of the collider relative to the rigidbody position. + * + * The `offset` defines the positional shift applied to the collider relative to the position of the rigidbody it is attached to. + * This allows the collider to be placed at a different position than the rigidbody. + * + */ + vec2 offset; }; } // namespace crepe diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp index acfd35c..ae76e65 100644 --- a/src/crepe/Component.cpp +++ b/src/crepe/Component.cpp @@ -1,5 +1,17 @@ #include "Component.h" using namespace crepe; +using namespace std; Component::Component(game_object_id_t id) : game_object_id(id) {} + +Component & Component::operator=(const Component & other) { + this->active = other.active; + return *this; +} + +unique_ptr<Component> Component::save() const { + return unique_ptr<Component>(new Component(*this)); +} + +void Component::restore(const Component & snapshot) { *this = snapshot; } diff --git a/src/crepe/Component.h b/src/crepe/Component.h index dc17721..52e06d5 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -1,5 +1,7 @@ #pragma once +#include <memory> + #include "types.h" namespace crepe { @@ -8,7 +10,7 @@ class ComponentManager; /** * \brief Base class for all components - * + * * This class is the base class for all components. It provides a common interface for all * components. */ @@ -16,7 +18,12 @@ class Component { public: //! Whether the component is active bool active = true; - //! The id of the GameObject this component belongs to + /** + * \brief The id of the GameObject this component belongs to + * + * \note Only systems are supposed to use this member, but since friend + * relations aren't inherited this needs to be public. + */ const game_object_id_t game_object_id; protected: @@ -24,14 +31,36 @@ protected: * \param id The id of the GameObject this component belongs to */ Component(game_object_id_t id); - //! Only the ComponentManager can create components + //! Only ComponentManager can create components friend class ComponentManager; - Component(const Component &) = delete; + // components are never moved Component(Component &&) = delete; - virtual Component & operator=(const Component &) = delete; virtual Component & operator=(Component &&) = delete; +protected: + /** + * \name ReplayManager (Memento) functions + * \{ + */ + /** + * \brief Save a snapshot of this component's state + * \note This function should only be implemented on components that should be saved/restored + * by ReplayManager. + * \returns Unique pointer to a deep copy of this component + */ + virtual std::unique_ptr<Component> save() const; + //! Copy constructor (used by \c save()) + Component(const Component &) = default; + /** + * \brief Restore this component from a snapshot + * \param snapshot Data to fill this component with (as returned by \c save()) + */ + virtual void restore(const Component & snapshot); + //! Copy assignment operator (used by \c restore()) + virtual Component & operator=(const Component &); + //! \} + public: virtual ~Component() = default; diff --git a/src/crepe/ComponentManager.cpp b/src/crepe/ComponentManager.cpp deleted file mode 100644 index e310577..0000000 --- a/src/crepe/ComponentManager.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "api/GameObject.h" -#include "util/Log.h" - -#include "ComponentManager.h" - -using namespace crepe; -using namespace std; - -ComponentManager::ComponentManager() { dbg_trace(); } -ComponentManager::~ComponentManager() { dbg_trace(); } - -void ComponentManager::delete_all_components_of_id(game_object_id_t id) { - // Loop through all the types (in the unordered_map<>) - for (auto & [type, componentArray] : this->components) { - // Make sure that the id (that we are looking for) is within the boundaries of the vector<> - if (id < componentArray.size()) { - // Clear the components at this specific id - componentArray[id].clear(); - } - } -} - -void ComponentManager::delete_all_components() { - this->components.clear(); - this->next_id = 0; -} - -GameObject ComponentManager::new_object(const string & name, const string & tag, - const Vector2 & position, double rotation, - double scale) { - GameObject object{*this, this->next_id, name, tag, position, rotation, scale}; - this->next_id++; - return object; -} diff --git a/src/crepe/ComponentManager.h b/src/crepe/ComponentManager.h deleted file mode 100644 index 0956d1e..0000000 --- a/src/crepe/ComponentManager.h +++ /dev/null @@ -1,148 +0,0 @@ -#pragma once - -#include <memory> -#include <typeindex> -#include <unordered_map> -#include <vector> - -#include "api/Vector2.h" - -#include "Component.h" -#include "types.h" - -namespace crepe { - -class GameObject; - -/** - * \brief Manages all components - * - * This class manages all components. It provides methods to add, delete and get components. - */ -class ComponentManager { - // TODO: This relation should be removed! I (loek) believe that the scene manager should - // create/destroy components because the GameObject's are stored in concrete Scene classes, - // which will in turn call GameObject's destructor, which will in turn call - // ComponentManager::delete_components_by_id or something. This is a pretty major change, so - // here is a comment and temporary fix instead :tada: - friend class SceneManager; - -public: - ComponentManager(); // dbg_trace - ~ComponentManager(); // dbg_trace - - /** - * \brief Create a new game object using the component manager - * - * \param name Metadata::name (required) - * \param tag Metadata::tag (optional, empty by default) - * \param position Transform::position (optional, origin by default) - * \param rotation Transform::rotation (optional, 0 by default) - * \param scale Transform::scale (optional, 1 by default) - * - * \returns GameObject interface - * - * \note This method automatically assigns a new entity ID - */ - GameObject new_object(const std::string & name, const std::string & tag = "", - const Vector2 & position = {0, 0}, double rotation = 0, - double scale = 1); - -protected: - /** - * GameObject is used as an interface to add/remove components, and the game programmer is - * supposed to use it instead of interfacing with the component manager directly. - */ - friend class GameObject; - /** - * \brief Add a component to the ComponentManager - * - * This method adds a component to the ComponentManager. The component is created with the - * given arguments and added to the ComponentManager. - * - * \tparam T The type of the component - * \tparam Args The types of the arguments - * \param id The id of the GameObject this component belongs to - * \param args The arguments to create the component - * \return The created component - */ - template <typename T, typename... Args> - T & add_component(game_object_id_t id, Args &&... args); - /** - * \brief Delete all components of a specific type and id - * - * This method deletes all components of a specific type and id. - * - * \tparam T The type of the component - * \param id The id of the GameObject this component belongs to - */ - template <typename T> - void delete_components_by_id(game_object_id_t id); - /** - * \brief Delete all components of a specific type - * - * This method deletes all components of a specific type. - * - * \tparam T The type of the component - */ - template <typename T> - void delete_components(); - /** - * \brief Delete all components of a specific id - * - * This method deletes all components of a specific id. - * - * \param id The id of the GameObject this component belongs to - */ - void delete_all_components_of_id(game_object_id_t id); - /** - * \brief Delete all components - * - * This method deletes all components. - */ - void delete_all_components(); - -public: - /** - * \brief Get all components of a specific type and id - * - * This method gets all components of a specific type and id. - * - * \tparam T The type of the component - * \param id The id of the GameObject this component belongs to - * \return A vector of all components of the specific type and id - */ - template <typename T> - RefVector<T> get_components_by_id(game_object_id_t id) const; - /** - * \brief Get all components of a specific type - * - * This method gets all components of a specific type. - * - * \tparam T The type of the component - * \return A vector of all components of the specific type - */ - template <typename T> - RefVector<T> get_components_by_type() const; - -private: - /** - * \brief The components - * - * This unordered_map stores all components. The key is the type of the component and the - * value is a vector of vectors of unique pointers to the components. - * - * Every component type has its own vector of vectors of unique pointers to the components. - * The first vector is for the ids of the GameObjects and the second vector is for the - * components (because a GameObject might have multiple components). - */ - std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> - components; - - //! ID of next GameObject allocated by \c ComponentManager::new_object - game_object_id_t next_id = 0; -}; - -} // namespace crepe - -#include "ComponentManager.hpp" diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index 1068cbf..27fa97f 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -2,8 +2,9 @@ using namespace crepe; -void Particle::reset(uint32_t lifespan, const Vector2 & position, const Vector2 & velocity, - double angle) { +void Particle::reset( + unsigned int lifespan, const vec2 & position, const vec2 & velocity, float angle +) { // Initialize the particle state this->time_in_life = 0; this->lifespan = lifespan; @@ -15,16 +16,17 @@ void Particle::reset(uint32_t lifespan, const Vector2 & position, const Vector2 this->force_over_time = {0, 0}; } -void Particle::update() { +void Particle::update(double dt) { // Deactivate particle if it has exceeded its lifespan - if (++time_in_life >= lifespan) { + time_in_life += dt; + if (time_in_life >= lifespan) { this->active = false; return; } // Update velocity based on accumulated force and update position - this->velocity += force_over_time; - this->position += velocity; + this->velocity += force_over_time * dt; + this->position += velocity * dt; } void Particle::stop_movement() { diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index 19859fe..c013de5 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -2,7 +2,7 @@ #include <cstdint> -#include "api/Vector2.h" +#include "types.h" namespace crepe { @@ -14,23 +14,21 @@ namespace crepe { * can also be reset or stopped. */ class Particle { - // TODO: add friend particleSsytem and rendersystem. Unit test will fail. - public: //! Position of the particle in 2D space. - Vector2 position; + vec2 position; //! Velocity vector indicating the speed and direction of the particle. - Vector2 velocity; + vec2 velocity; //! Accumulated force affecting the particle over time. - Vector2 force_over_time; + vec2 force_over_time; //! Total lifespan of the particle in milliseconds. - uint32_t lifespan; + float lifespan; //! Active state of the particle; true if it is in use, false otherwise. bool active = false; //! The time the particle has been alive, in milliseconds. - uint32_t time_in_life = 0; + float time_in_life = 0; //! The angle at which the particle is oriented or moving. - double angle = 0; + float angle = 0; /** * \brief Resets the particle with new properties. @@ -43,15 +41,16 @@ public: * \param velocity The initial velocity of the particle. * \param angle The angle of the particle's trajectory or orientation. */ - void reset(uint32_t lifespan, const Vector2 & position, const Vector2 & velocity, - double angle); + void + reset(unsigned int lifespan, const vec2 & position, const vec2 & velocity, float angle); /** * \brief Updates the particle's state. * * Advances the particle's position based on its velocity and applies accumulated forces. * Deactivates the particle if its lifespan has expired. + * \param dt The amount of fixed delta time that has passed. */ - void update(); + void update(double dt); /** * \brief Stops the particle's movement. * diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp new file mode 100644 index 0000000..85913ed --- /dev/null +++ b/src/crepe/Resource.cpp @@ -0,0 +1,6 @@ +#include "Resource.h" +#include "manager/Mediator.h" + +using namespace crepe; + +Resource::Resource(const Asset & asset, Mediator & mediator) {} diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h new file mode 100644 index 0000000..d65206b --- /dev/null +++ b/src/crepe/Resource.h @@ -0,0 +1,30 @@ +#pragma once + +namespace crepe { + +class ResourceManager; +class Asset; +class Mediator; + +/** + * \brief Resource interface + * + * Resource is an interface class used to represent a (deserialized) game resource (e.g. + * textures, sounds). Resources are always created from \ref Asset "assets" by ResourceManager. + * + * The game programmer has the ability to use the ResourceManager to keep instances of concrete + * resources between scenes, preventing them from being reinstantiated during a scene + * transition. + */ +class Resource { +public: + Resource(const Asset & src, Mediator & mediator); + virtual ~Resource() = default; + + Resource(const Resource &) = delete; + Resource(Resource &&) = delete; + Resource & operator=(const Resource &) = delete; + Resource & operator=(Resource &&) = delete; +}; + +} // namespace crepe diff --git a/src/crepe/api/AI.cpp b/src/crepe/api/AI.cpp new file mode 100644 index 0000000..2fedaf4 --- /dev/null +++ b/src/crepe/api/AI.cpp @@ -0,0 +1,98 @@ +#include <stdexcept> +#include <type_traits> + +#include "AI.h" +#include "types.h" + +namespace crepe { + +AI::AI(game_object_id_t id, float max_force) : Component(id), max_force(max_force) {} + +void AI::make_circle_path( + float radius, const vec2 & center, float start_angle, bool clockwise +) { + if (radius <= 0) { + throw std::runtime_error("Radius must be greater than 0"); + } + + // The step size is determined by the radius (step size is in radians) + float step = RADIUS_TO_STEP / radius; + // Force at least MIN_STEP steps (in case of a small radius) + if (step > 2 * M_PI / MIN_STEP) { + step = 2 * M_PI / MIN_STEP; + } + // The path node distance is determined by the step size and the radius + this->path_node_distance = radius * step * PATH_NODE_DISTANCE_FACTOR; + + if (clockwise) { + for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) { + path.push_back(vec2 { + static_cast<float>(center.x + radius * cos(i)), + static_cast<float>(center.y + radius * sin(i)) + }); + } + } else { + for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) { + path.push_back(vec2 { + static_cast<float>(center.x + radius * cos(i)), + static_cast<float>(center.y + radius * sin(i)) + }); + } + } +} + +void AI::make_oval_path( + float radius_x, float radius_y, const vec2 & center, float start_angle, bool clockwise, + float rotation +) { + if (radius_x <= 0 && radius_y <= 0) { + throw std::runtime_error("Radius must be greater than 0"); + } + + float max_radius = std::max(radius_x, radius_y); + // The step size is determined by the radius (step size is in radians) + float step = RADIUS_TO_STEP / max_radius; + // Force at least MIN_STEP steps (in case of a small radius) + if (step > 2 * M_PI / MIN_STEP) { + step = 2 * M_PI / MIN_STEP; + } + // The path node distance is determined by the step size and the radius + this->path_node_distance = max_radius * step * PATH_NODE_DISTANCE_FACTOR; + + std::function<vec2(vec2, vec2)> rotate_point = [rotation](vec2 point, vec2 center) { + float s = sin(rotation); + float c = cos(rotation); + + // Translate point back to origin + point.x -= center.x; + point.y -= center.y; + + // Rotate point + float xnew = point.x * c - point.y * s; + float ynew = point.x * s + point.y * c; + + // Translate point back + point.x = xnew + center.x; + point.y = ynew + center.y; + + return point; + }; + + if (clockwise) { + for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) { + vec2 point + = {static_cast<float>(center.x + radius_x * cos(i)), + static_cast<float>(center.y + radius_y * sin(i))}; + path.push_back(rotate_point(point, center)); + } + } else { + for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) { + vec2 point + = {static_cast<float>(center.x + radius_x * cos(i)), + static_cast<float>(center.y + radius_y * sin(i))}; + path.push_back(rotate_point(point, center)); + } + } +} + +} // namespace crepe diff --git a/src/crepe/api/AI.h b/src/crepe/api/AI.h new file mode 100644 index 0000000..bee11b3 --- /dev/null +++ b/src/crepe/api/AI.h @@ -0,0 +1,132 @@ +#pragma once + +#include "Component.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AI component is used to control the movement of an entity using AI. + * + * The AI component can be used to control the movement of an entity. The AI component can be used + * to implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AI : public Component { +public: + //! The different types of behaviors that can be used + enum BehaviorTypeMask { + SEEK = 0x00002, + FLEE = 0x00004, + ARRIVE = 0x00008, + PATH_FOLLOW = 0x00010, + }; + +public: + /** + * \param id The id of the game object + * \param max_force The maximum force that can be applied to the entity + */ + AI(game_object_id_t id, float max_force); + + /** + * \brief Check if a behavior is on (aka activated) + * + * \param behavior The behavior to check + * \return true if the behavior is on, false otherwise + */ + bool on(BehaviorTypeMask behavior) const { return (flags & behavior); } + //! Turn on the seek behavior + void seek_on() { flags |= SEEK; } + //! Turn off the seek behavior + void seek_off() { flags &= ~SEEK; } + //! Turn on the flee behavior + void flee_on() { flags |= FLEE; } + //! Turn off the flee behavior + void flee_off() { flags &= ~FLEE; } + //! Turn on the arrive behavior + void arrive_on() { flags |= ARRIVE; } + //! Turn off the arrive behavior + void arrive_off() { flags &= ~ARRIVE; } + //! Turn on the path follow behavior + void path_follow_on() { flags |= PATH_FOLLOW; } + //! Turn off the path follow behavior + void path_follow_off() { flags &= ~PATH_FOLLOW; } + + /** + * \brief Add a path node (for the path following behavior) + * + * \note The path is not relative to the entity's position (it is an absolute path) + * + * \param node The path node to add + */ + void add_path_node(const vec2 & node) { path.push_back(node); } + /** + * \brief Make a circle path (for the path following behavior) + * + * \note The path is not relative to the entity's position (it is an absolute path) + * + * \param radius The radius of the circle (in game units) + * \param center The center of the circle (in game units) + * \param start_angle The start angle of the circle (in radians) + * \param clockwise The direction of the circle + */ + void make_circle_path( + float radius, const vec2 & center = {0, 0}, float start_angle = 0, + bool clockwise = true + ); + /** + * \brief Make an oval path (for the path following behavior) + * + * \note The path is not relative to the entity's position (it is an absolute path) + * + * \param radius_x The x radius of the oval (in game units) + * \param radius_y The y radius of the oval (in game units) + * \param center The center of the oval (in game units) + * \param start_angle The start angle of the oval (in radians) + * \param clockwise The direction of the oval + * \param rotation The rotation of the oval (in radians) + */ + void make_oval_path( + float radius_x, float radius_y, const vec2 & center = {0, 0}, float start_angle = 0, + bool clockwise = true, float rotation = 0 + ); + +public: + //! The maximum force that can be applied to the entity (higher values will make the entity adjust faster) + float max_force; + + //! The target to seek at + vec2 seek_target; + //! The target to arrive at + vec2 arrive_target; + //! The target to flee from + vec2 flee_target; + //! The distance at which the entity will start to flee from the target + float square_flee_panic_distance = 200.0f * 200.0f; + //! The deceleration rate for the arrive behavior (higher values will make the entity decelerate faster (less overshoot)) + float arrive_deceleration = 40.0f; + //! The path to follow (for the path following behavior) + std::vector<vec2> path; + //! The distance from the path node at which the entity will move to the next node (automatically set by make_circle_path()) + float path_node_distance = 400.0f; + //! Looping behavior for the path + bool path_loop = true; + +private: + //! The flags for the behaviors + int flags = 0; + //! The current path index + size_t path_index = 0; + + //! The AISystem is the only class that should access the flags and path_index variables + friend class AISystem; + + //! The minimum amount of steps for the path following behavior + static constexpr int MIN_STEP = 16; + //! The radius to step size ratio for the path following behavior + static constexpr float RADIUS_TO_STEP = 400.0f; + //! The path node distance factor for the path following behavior + static constexpr float PATH_NODE_DISTANCE_FACTOR = 0.75f; +}; + +} // namespace crepe diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp index 464b0fd..c558d86 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -1,5 +1,5 @@ -#include "util/Log.h" +#include "util/dbg.h" #include "Animator.h" #include "Component.h" @@ -7,18 +7,53 @@ using namespace crepe; -Animator::Animator(game_object_id_t id, Sprite & ss, int row, int col, int col_animator) +Animator::Animator( + game_object_id_t id, Sprite & spritesheet, const ivec2 & single_frame_size, + const uvec2 & grid_size, const Animator::Data & data +) : Component(id), - spritesheet(ss), - row(row), - col(col) { + spritesheet(spritesheet), + grid_size(grid_size), + data(data) { dbg_trace(); - animator_rect = spritesheet.sprite_rect; - animator_rect.h /= col; - animator_rect.w /= row; - animator_rect.x = 0; - animator_rect.y = col_animator * animator_rect.h; - this->active = false; + this->spritesheet.mask.w = single_frame_size.x; + this->spritesheet.mask.h = single_frame_size.y; + this->spritesheet.mask.x = 0; + this->spritesheet.mask.y = 0; + + this->spritesheet.aspect_ratio + = static_cast<float>(single_frame_size.x) / single_frame_size.y; } + Animator::~Animator() { dbg_trace(); } + +void Animator::loop() { this->data.looping = true; } + +void Animator::play() { this->active = true; } + +void Animator::pause() { this->active = false; } + +void Animator::stop() { + this->active = false; + this->data.col = 0; + this->data.row = 0; +} +void Animator::set_fps(int fps) { this->data.fps = fps; } + +void Animator::set_cycle_range(int start, int end) { + this->data.cycle_start = start, this->data.cycle_end = end; +} + +void Animator::set_anim(int col) { + Animator::Data & ctx = this->data; + this->spritesheet.mask.x = ctx.row = 0; + ctx.col = col; + this->spritesheet.mask.y = ctx.col * this->spritesheet.mask.h; +} + +void Animator::next_anim() { + Animator::Data & ctx = this->data; + ctx.row = ++ctx.row % this->grid_size.x; + this->spritesheet.mask.x = ctx.row * this->spritesheet.mask.w; +} diff --git a/src/crepe/api/Animator.h b/src/crepe/api/Animator.h index 53f4b91..95539d3 100644 --- a/src/crepe/api/Animator.h +++ b/src/crepe/api/Animator.h @@ -1,5 +1,8 @@ #pragma once +#include "../manager/LoopTimerManager.h" +#include "../types.h" + #include "Component.h" #include "Sprite.h" @@ -16,62 +19,95 @@ class SDLContext; * sheet. It can be used to play animations, loop them, or stop them. */ class Animator : public Component { +public: + struct Data { + //! frames per second for animation + unsigned int fps = 1; + //! The current col being animated. + unsigned int col = 0; + //! The current row being animated. + unsigned int row = 0; + //! should the animation loop + bool looping = false; + //! starting frame for cycling + unsigned int cycle_start = 0; + //! end frame for cycling (-1 = use last frame) + int cycle_end = -1; + }; public: - //TODO: need to implement this + //! Animator will repeat the animation void loop(); + //! starts the animation + void play(); + //! pauses the animation + void pause(); + /** + * \brief stops the animation + * + * sets the active on false and resets all the current rows and columns + */ void stop(); + /** + * \brief set frames per second + * + * \param fps frames per second + */ + void set_fps(int fps); + /** + * \brief set the range in the row + * + * \param start of row animation + * \param end of row animation + */ + void set_cycle_range(int start, int end); + /** + * \brief select which column to animate from + * + * \param col animation column + */ + void set_anim(int col); + //! will go to the next animaiton of current row + void next_anim(); public: /** * \brief Constructs an Animator object that will control animations for a sprite sheet. * * \param id The unique identifier for the component, typically assigned automatically. - * \param spritesheet A reference to the Sprite object which holds the sprite sheet for - * animation. - * \param row The maximum number of rows in the sprite sheet. - * \param col The maximum number of columns in the sprite sheet. - * \param col_animate The specific col index of the sprite sheet to animate. This allows - * selecting which col to animate from multiple col in the sheet. + * \param spritesheet the reference to the spritesheet + * \param single_frame_size the width and height in pixels of a single frame inside the + * spritesheet + * \param grid_size the max rows and columns inside the given spritesheet + * \param data extra animation data for more control * * This constructor sets up the Animator with the given parameters, and initializes the * animation system. */ - Animator(uint32_t id, Sprite & spritesheet, int row, int col, int col_animate); - + Animator( + game_object_id_t id, Sprite & spritesheet, const ivec2 & single_frame_size, + const uvec2 & grid_size, const Animator::Data & data + ); ~Animator(); // dbg_trace - Animator(const Animator &) = delete; - Animator(Animator &&) = delete; - Animator & operator=(const Animator &) = delete; - Animator & operator=(Animator &&) = delete; + +public: + Animator::Data data; private: - //! A reference to the Sprite sheet containing the animation frames. + //! A reference to the Sprite sheet containing. Sprite & spritesheet; - //! The maximum number of columns in the sprite sheet. - const int col; - - //! The maximum number of rows in the sprite sheet. - const int row; + //! The maximum number of rows and columns inside the spritesheet + const uvec2 grid_size; - //! The current col being animated. - int curr_col = 0; + // the time elapsed from a frame duration + duration_t elapsed_time = {}; - //! The current row being animated. - int curr_row = 0; + // frame counter + unsigned int frame = 0; - Rect animator_rect; - - //TODO: Is this necessary? - //int fps; - -private: - //! AnimatorSystem adjust the private member parameters of Animator; - friend class AnimatorSystem; - - //! SDLContext reads the Animator member var's - friend class SDLContext; + //! Uses the spritesheet + friend AnimatorSystem; }; + } // namespace crepe -// diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp index e148367..bab82e7 100644 --- a/src/crepe/api/Asset.cpp +++ b/src/crepe/api/Asset.cpp @@ -50,5 +50,5 @@ string Asset::whereami() const noexcept { bool Asset::operator==(const Asset & other) const noexcept { return this->src == other.src; } size_t std::hash<const Asset>::operator()(const Asset & asset) const noexcept { - return std::hash<string>{}(asset.get_path()); + return std::hash<string> {}(asset.get_path()); }; diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h index bfd0ac7..d802e83 100644 --- a/src/crepe/api/Asset.h +++ b/src/crepe/api/Asset.h @@ -43,13 +43,13 @@ private: /** * \brief Locate asset path, or throw exception if it cannot be found * - * This function resolves asset locations relative to crepe::Config::root_pattern if it is + * This function resolves asset locations relative to Config::asset::root_pattern if it is * set and \p src is a relative path. If \p src is an absolute path, it is canonicalized. * This function only returns if the file can be found. * * \param src Arbitrary path to resource file * - * \returns \p src if crepe::Config::root_pattern is empty + * \returns \p src if Config::asset::root_pattern is empty * \returns Canonical path to \p src * * \throws std::runtime_error if root_pattern cannot be found diff --git a/src/crepe/api/AssetManager.cpp b/src/crepe/api/AssetManager.cpp deleted file mode 100644 index 3925758..0000000 --- a/src/crepe/api/AssetManager.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "util/Log.h" - -#include "AssetManager.h" - -using namespace crepe; - -AssetManager & AssetManager::get_instance() { - static AssetManager instance; - return instance; -} - -AssetManager::~AssetManager() { - dbg_trace(); - this->asset_cache.clear(); -} - -AssetManager::AssetManager() { dbg_trace(); } diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h deleted file mode 100644 index fee6780..0000000 --- a/src/crepe/api/AssetManager.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include <any> -#include <memory> -#include <string> -#include <unordered_map> - -namespace crepe { - -/** - * \brief The AssetManager is responsible for storing and managing assets over multiple scenes. - * - * The AssetManager ensures that assets are loaded once and can be accessed across different - * scenes. It caches assets to avoid reloading them every time a scene is loaded. Assets are - * retained in memory until the AssetManager is destroyed, at which point the cached assets are - * cleared. - */ -class AssetManager { - -private: - //! A cache that holds all the assets, accessible by their file path, over multiple scenes. - std::unordered_map<std::string, std::any> asset_cache; - -private: - AssetManager(); - virtual ~AssetManager(); - -public: - AssetManager(const AssetManager &) = delete; - AssetManager(AssetManager &&) = delete; - AssetManager & operator=(const AssetManager &) = delete; - AssetManager & operator=(AssetManager &&) = delete; - - /** - * \brief Retrieves the singleton instance of the AssetManager. - * - * \return A reference to the single instance of the AssetManager. - */ - static AssetManager & get_instance(); - -public: - /** - * \brief Caches an asset by loading it from the given file path. - * - * \param file_path The path to the asset file to load. - * \param reload If true, the asset will be reloaded from the file, even if it is already - * cached. - * \tparam T The type of asset to cache (e.g., texture, sound, etc.). - * - * \return A shared pointer to the cached asset. - * - * This template function caches the asset at the given file path. If the asset is already - * cached and `reload` is false, the existing cached version will be returned. Otherwise, the - * asset will be reloaded and added to the cache. - */ - template <typename T> - std::shared_ptr<T> cache(const std::string & file_path, bool reload = false); -}; - -} // namespace crepe - -#include "AssetManager.hpp" diff --git a/src/crepe/api/AssetManager.hpp b/src/crepe/api/AssetManager.hpp deleted file mode 100644 index 1c0e978..0000000 --- a/src/crepe/api/AssetManager.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "AssetManager.h" - -namespace crepe { - -template <typename asset> -std::shared_ptr<asset> AssetManager::cache(const std::string & file_path, bool reload) { - auto it = asset_cache.find(file_path); - - if (!reload && it != asset_cache.end()) { - return std::any_cast<std::shared_ptr<asset>>(it->second); - } - - std::shared_ptr<asset> new_asset = std::make_shared<asset>(file_path.c_str()); - - asset_cache[file_path] = new_asset; - - return new_asset; -} - -} // namespace crepe diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp new file mode 100644 index 0000000..7b05cb1 --- /dev/null +++ b/src/crepe/api/AudioSource.cpp @@ -0,0 +1,15 @@ +#include "AudioSource.h" + +using namespace crepe; +using namespace std; + +AudioSource::AudioSource(game_object_id_t id, const Asset & src) + : Component(id), + source(src) {} + +void AudioSource::play(bool looping) { + this->loop = looping; + this->oneshot_play = true; +} + +void AudioSource::stop() { this->oneshot_stop = true; } diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h new file mode 100644 index 0000000..eaa56e8 --- /dev/null +++ b/src/crepe/api/AudioSource.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../Component.h" +#include "../facade/SoundHandle.h" +#include "../types.h" + +#include "Asset.h" +#include "GameObject.h" + +namespace crepe { + +class AudioSystem; + +//! Audio source component +class AudioSource : public Component { + //! AudioSource components are handled by AudioSystem + friend class AudioSystem; + +protected: + /** + * \param source Sound sample to load + */ + AudioSource(game_object_id_t id, const Asset & source); + //! Only ComponentManager creates components + friend class ComponentManager; + +public: + // std::unique_ptr needs to be able to destoy this component + virtual ~AudioSource() = default; + +public: + //! Start this audio source + void play(bool looping = false); + //! Stop this audio source + void stop(); + +public: + //! Play when this component becomes active + bool play_on_awake = false; + //! Repeat the current audio clip during playback + bool loop = false; + //! Normalized volume (0.0 - 1.0) + float volume = 1.0; + +private: + //! This audio source's clip + const Asset source; + + /** + * \name One-shot state variables + * + * These variables trigger function calls when set to true, and are unconditionally reset on + * every system update. + * + * \{ + */ + //! Play this sample + bool oneshot_play = false; + //! Stop this sample + bool oneshot_stop = false; + //! \} + /** + * \name State diffing variables + * \{ + */ + typeof(active) last_active = false; + typeof(volume) last_volume = volume; + typeof(loop) last_loop = loop; + //! \} + //! This source's voice handle + SoundHandle voice {}; +}; + +} // namespace crepe diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp index 7bbace0..af7572c 100644 --- a/src/crepe/api/BehaviorScript.cpp +++ b/src/crepe/api/BehaviorScript.cpp @@ -4,12 +4,12 @@ using namespace crepe; -BehaviorScript::BehaviorScript(game_object_id_t id, ComponentManager & mgr) +BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator) : Component(id), - component_manager(mgr) {} + mediator(mediator) {} template <> BehaviorScript & GameObject::add_component<BehaviorScript>() { - ComponentManager & mgr = this->component_manager; - return mgr.add_component<BehaviorScript>(this->id, mgr); + ComponentManager & mgr = this->mediator.component_manager; + return mgr.add_component<BehaviorScript>(this->id, this->mediator); } diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h index 9d85d4c..3909b96 100644 --- a/src/crepe/api/BehaviorScript.h +++ b/src/crepe/api/BehaviorScript.h @@ -23,14 +23,13 @@ class BehaviorScript : public Component { protected: /** * \param id Parent \c GameObject id - * \param component_manager Reference to component manager (passed through to \c Script - * instance) + * \param mediator Mediator reference * * \note Calls to this constructor (should) always pass through \c GameObject::add_component, * which has an exception for this specific component type. This was done so the user does * not have to pass references used within \c Script to each \c BehaviorScript instance. */ - BehaviorScript(game_object_id_t id, ComponentManager & component_manager); + BehaviorScript(game_object_id_t id, Mediator & mediator); //! Only ComponentManager is allowed to instantiate BehaviorScript friend class ComponentManager; @@ -39,11 +38,14 @@ public: * \brief Set the concrete script of this component * * \tparam T Concrete script type (derived from \c crepe::Script) + * \tparam Args Arguments for concrete script constructor + * + * \param args Arguments for concrete script constructor (forwarded using perfect forwarding) * * \returns Reference to BehaviorScript component (`*this`) */ - template <class T> - BehaviorScript & set_script(); + template <class T, typename... Args> + BehaviorScript & set_script(Args &&... args); protected: //! Script instance @@ -52,8 +54,8 @@ protected: friend class ScriptSystem; protected: - //! Reference to component manager (passed to Script) - ComponentManager & component_manager; + //! Reference mediator + Mediator & mediator; }; /** diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index d80321d..353d5e2 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -2,21 +2,20 @@ #include <type_traits> -#include "../util/Log.h" - #include "BehaviorScript.h" #include "Script.h" namespace crepe { -template <class T> -BehaviorScript & BehaviorScript::set_script() { - dbg_trace(); +template <class T, typename... Args> +BehaviorScript & BehaviorScript::set_script(Args &&... args) { static_assert(std::is_base_of<Script, T>::value); - Script * s = new T(); - s->game_object_id = this->game_object_id; - s->component_manager_ref = &this->component_manager; - this->script = std::unique_ptr<Script>(s); + this->script = std::unique_ptr<Script>(new T(std::forward<Args>(args)...)); + + this->script->game_object_id = this->game_object_id; + this->script->active = this->active; + this->script->mediator = this->mediator; + return *this; } diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp new file mode 100644 index 0000000..f6b358d --- /dev/null +++ b/src/crepe/api/BoxCollider.cpp @@ -0,0 +1,11 @@ +#include "BoxCollider.h" + +#include "../Collider.h" + +using namespace crepe; + +BoxCollider::BoxCollider( + game_object_id_t game_object_id, const vec2 & dimensions, const vec2 & offset +) + : Collider(game_object_id, offset), + dimensions(dimensions) {} diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h new file mode 100644 index 0000000..229b90f --- /dev/null +++ b/src/crepe/api/BoxCollider.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../Collider.h" +#include "Vector2.h" +#include "types.h" + +namespace crepe { + +/** + * \brief A class representing a box-shaped collider. + * + * This class is used for collision detection with other colliders (e.g., CircleCollider). + */ +class BoxCollider : public Collider { +public: + BoxCollider( + game_object_id_t game_object_id, const vec2 & dimensions, const vec2 & offset = {0, 0} + ); + + //! Width and height of the box collider + vec2 dimensions; +}; + +} // namespace crepe diff --git a/src/crepe/api/Button.cpp b/src/crepe/api/Button.cpp new file mode 100644 index 0000000..8eadd89 --- /dev/null +++ b/src/crepe/api/Button.cpp @@ -0,0 +1,11 @@ +#include "Button.h" + +namespace crepe { + +Button::Button( + game_object_id_t id, const vec2 & dimensions, const Data & data, const vec2 & offset +) + : UIObject(id, dimensions, offset), + data(data) {} + +} // namespace crepe diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h new file mode 100644 index 0000000..e986c04 --- /dev/null +++ b/src/crepe/api/Button.h @@ -0,0 +1,60 @@ +#pragma once + +#include "../types.h" + +#include "UIObject.h" + +namespace crepe { + +/** + * \brief Button component. + * + * This component creates a clickable surface at the transform location with the specified width and height. + * + * The Button can be used in scripts by subscribing a EventHandler to the following events: + * - ButtonPressEvent + * - ButtonEnterEvent + * - ButtonExitEvent + * \see EventManager + * + */ +class Button : public UIObject { +public: + struct Data { + //! variable indicating if transform is relative to camera(false) or world(true) + bool world_space = false; + }; + +public: + /** + * \brief Constructs a Button with the specified game object ID and dimensions. + * + * \param id The unique ID of the game object associated with this button. + * \param dimensions The width and height of the UIObject + * \param offset The offset relative this GameObjects Transform + * \param data additional data the button has + */ + Button( + game_object_id_t id, const vec2 & dimensions, const Data & data, + const vec2 & offset = {0, 0} + ); + /** + * \brief Get the maximum number of instances for this component + * + * Since the button Event transfers the GameObject Metadata it will be the same for each button so only one button is allowed per GameObject + * + * \return 1 + */ + virtual int get_instances_max() const { return 1; } + +public: + Data data; + +private: + //! friend relation hover variable + friend class InputSystem; + //! Indicates whether the mouse is currently hovering over the button + bool hover = false; +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index d6b6801..2bee3fb 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -1,33 +1,31 @@ target_sources(crepe PUBLIC - # AudioSource.cpp + AudioSource.cpp BehaviorScript.cpp GameObject.cpp Rigidbody.cpp ParticleEmitter.cpp Transform.cpp Color.cpp - Texture.cpp - AssetManager.cpp Sprite.cpp - SaveManager.cpp Config.cpp Metadata.cpp - Scene.cpp - SceneManager.cpp - Vector2.cpp Camera.cpp Animator.cpp - EventManager.cpp - IKeyListener.cpp - IMouseListener.cpp - LoopManager.cpp - LoopTimer.cpp + BoxCollider.cpp + CircleCollider.cpp + Engine.cpp Asset.cpp EventHandler.cpp + Script.cpp + Button.cpp + UIObject.cpp + AI.cpp + Text.cpp + Scene.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - # AudioSource.h + AudioSource.h BehaviorScript.h Config.h Script.h @@ -37,25 +35,23 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Rigidbody.h Sprite.h Vector2.h + Vector2.hpp Color.h - Texture.h - AssetManager.h - AssetManager.hpp - SaveManager.h Scene.h + Scene.hpp Metadata.h - SceneManager.h - SceneManager.hpp Camera.h Animator.h - EventManager.h - EventManager.hpp + BoxCollider.h + CircleCollider.h EventHandler.h EventHandler.hpp Event.h - IKeyListener.h - IMouseListener.h - LoopManager.h - LoopTimer.h + Engine.h + Engine.hpp Asset.h + Button.h + UIObject.h + AI.h + Text.h ) diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp index 5835bdd..b1466b5 100644 --- a/src/crepe/api/Camera.cpp +++ b/src/crepe/api/Camera.cpp @@ -1,14 +1,18 @@ -#include "util/Log.h" +#include "util/dbg.h" #include "Camera.h" -#include "Color.h" #include "Component.h" +#include "types.h" using namespace crepe; -Camera::Camera(game_object_id_t id, const Color & bg_color) +Camera::Camera( + game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, const Data & data +) : Component(id), - bg_color(bg_color) { + screen(screen), + viewport_size(viewport_size), + data(data) { dbg_trace(); } diff --git a/src/crepe/api/Camera.h b/src/crepe/api/Camera.h index e0cda34..3191b04 100644 --- a/src/crepe/api/Camera.h +++ b/src/crepe/api/Camera.h @@ -2,6 +2,7 @@ #include "Color.h" #include "Component.h" +#include "types.h" namespace crepe { @@ -13,40 +14,56 @@ namespace crepe { * position, and zoom level. It controls what part of the game world is visible on the screen. */ class Camera : public Component { +public: + struct Data { + /** + * \bg_color background color of the game + * + * This will make the background the same color as the given value. + */ + const Color bg_color = Color::BLACK; + + /** + * \zoom Zooming level of the game + * + * zoom = 1 --> no zoom. + * zoom < 1 --> zoom out + * zoom > 1 --> zoom in + */ + double zoom = 1; + + //! offset postion from the game object transform component + vec2 postion_offset; + }; public: /** * \brief Constructs a Camera with the specified ID and background color. * \param id Unique identifier for the camera component. - * \param bg_color Background color for the camera view. + * \param screen is the actual screen size in pixels + * \param viewport_size is the view of the world in game units + * \param data the camera component data */ - Camera(game_object_id_t id, const Color & bg_color); + Camera( + game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, + const Camera::Data & data + ); ~Camera(); // dbg_trace only public: - //! Background color of the camera view. - Color bg_color; - - //! Aspect ratio height for the camera. - double aspect_height = 480; - - //! Aspect ratio width for the camera. - double aspect_width = 640; - - //! X-coordinate of the camera position. - double x = 0.0; + Camera::Data data; - //! Y-coordinate of the camera position. - double y = 0.0; + //! screen the display size in pixels ( output resolution ) + const ivec2 screen; - //! Zoom level of the camera view. - double zoom = 1.0; + //! viewport is the area of the world visible through the camera (in world units) + const vec2 viewport_size; public: /** * \brief Gets the maximum number of camera instances allowed. * \return Maximum instance count as an integer. */ - virtual int get_instances_max() const { return 10; } + virtual int get_instances_max() const { return 1; } }; } // namespace crepe diff --git a/src/crepe/api/CircleCollider.cpp b/src/crepe/api/CircleCollider.cpp new file mode 100644 index 0000000..e72800c --- /dev/null +++ b/src/crepe/api/CircleCollider.cpp @@ -0,0 +1,9 @@ +#include "CircleCollider.h" + +using namespace crepe; + +CircleCollider::CircleCollider( + game_object_id_t game_object_id, float radius, const vec2 & offset +) + : Collider(game_object_id, offset), + radius(radius) {} diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h index e77a592..e6ad4fa 100644 --- a/src/crepe/api/CircleCollider.h +++ b/src/crepe/api/CircleCollider.h @@ -1,14 +1,24 @@ #pragma once + +#include "Vector2.h" + #include "../Collider.h" namespace crepe { +/** + * \brief A class representing a circle-shaped collider. + * + * This class is used for collision detection with other colliders (e.g., BoxCollider). + */ class CircleCollider : public Collider { public: - CircleCollider(game_object_id_t game_object_id, int radius) - : Collider(game_object_id), - radius(radius) {} - int radius; + CircleCollider( + game_object_id_t game_object_id, float radius, const vec2 & offset = {0, 0} + ); + + //! Radius of the circle collider. + float radius; }; } // namespace crepe diff --git a/src/crepe/api/Color.cpp b/src/crepe/api/Color.cpp index 29bd77a..d0e3b35 100644 --- a/src/crepe/api/Color.cpp +++ b/src/crepe/api/Color.cpp @@ -2,11 +2,13 @@ using namespace crepe; -const Color Color::WHITE{0xff, 0xff, 0xff}; -const Color Color::RED{0xff, 0x00, 0x00}; -const Color Color::GREEN{0x00, 0xff, 0x00}; -const Color Color::BLUE{0x00, 0x00, 0xff}; -const Color Color::BLACK{0x00, 0x00, 0x00}; -const Color Color::CYAN{0x00, 0xff, 0xff}; -const Color Color::YELLOW{0xff, 0xff, 0x00}; -const Color Color::MAGENTA{0xff, 0x00, 0xff}; +const Color Color::WHITE {0xff, 0xff, 0xff}; +const Color Color::RED {0xff, 0x00, 0x00}; +const Color Color::GREEN {0x00, 0xff, 0x00}; +const Color Color::BLUE {0x00, 0x00, 0xff}; +const Color Color::BLACK {0x00, 0x00, 0x00}; +const Color Color::CYAN {0x00, 0xff, 0xff}; +const Color Color::YELLOW {0xff, 0xff, 0x00}; +const Color Color::MAGENTA {0xff, 0x00, 0xff}; +const Color Color::GREY {0x80, 0x80, 0x80}; +const Color Color::GOLD {249, 205, 91}; diff --git a/src/crepe/api/Color.h b/src/crepe/api/Color.h index 84edb5c..dbfd0ed 100644 --- a/src/crepe/api/Color.h +++ b/src/crepe/api/Color.h @@ -18,6 +18,8 @@ struct Color { static const Color MAGENTA; static const Color YELLOW; static const Color BLACK; + static const Color GREY; + static const Color GOLD; }; } // namespace crepe diff --git a/src/crepe/api/Components.h b/src/crepe/api/Components.h new file mode 100644 index 0000000..fa0663d --- /dev/null +++ b/src/crepe/api/Components.h @@ -0,0 +1,16 @@ +#pragma once + +#include "AI.h" +#include "Animator.h" +#include "AudioSource.h" +#include "BehaviorScript.h" +#include "BoxCollider.h" +#include "Button.h" +#include "Camera.h" +#include "CircleCollider.h" +#include "Metadata.h" +#include "ParticleEmitter.h" +#include "Rigidbody.h" +#include "Sprite.h" +#include "Text.h" +#include "Transform.h" diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index 13eabd1..ab8bb59 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -1,5 +1,8 @@ #pragma once +#include <string> + +#include "../types.h" #include "../util/Log.h" namespace crepe { @@ -7,27 +10,14 @@ namespace crepe { /** * \brief Global configuration interface * - * This class stores engine default settings. Properties on this class are only supposed to be - * modified *before* execution is handed over from the game programmer to the engine (i.e. the - * main loop is started). + * This struct stores both engine default settings and global configuration parameters. */ -class Config { -public: +struct Config final { //! Retrieve handle to global Config instance static Config & get_instance(); -private: - Config() = default; - - // singleton - Config(const Config &) = delete; - Config(Config &&) = delete; - Config & operator=(const Config &) = delete; - Config & operator=(Config &&) = delete; - -public: //! Logging-related settings - struct { + struct log { // NOLINT /** * \brief Log level * @@ -35,7 +25,7 @@ public: */ Log::Level level = Log::Level::INFO; /** - * \brief Colored log output + * \brief Enable colored log output * * Enables log coloring using ANSI escape codes. */ @@ -43,7 +33,7 @@ public: } log; //! Save manager - struct { + struct savemgr { // NOLINT /** * \brief Save file location * @@ -53,18 +43,26 @@ public: std::string location = "save.crepe.db"; } savemgr; - //! physics-related settings - struct { + //! Physics-related settings + struct physics { // NOLINT /** * \brief gravity value of physics system * * Gravity value of game. */ - double gravity = 1; + float gravity = 10; } physics; + //! Default window settings + struct window_settings { // NOLINT + //! Default window size (in pixels) + ivec2 default_size = {1280, 720}; + //! Default window title + std::string window_title = "crepe window"; + } window_settings; + //! Asset loading options - struct { + struct asset { // NOLINT /** * \brief Pattern to match for Asset base directory * @@ -76,6 +74,28 @@ public: */ std::string root_pattern = ".crepe-root"; } asset; + //! Default font options + struct { + /** + * \brief Default font size + * + * Using the SDL_ttf library the font size needs to be set when loading the font. + * This config option is the font size at which all fonts will be loaded initially. + * + */ + unsigned int size = 100; + } font; + //! Configuration for click tolerance. + struct { + //! The maximum number of pixels the mouse can move between MouseDown and MouseUp events to be considered a click. + int click_tolerance = 5; + } input; + + //! Audio system settings + struct { + //! Max amount of simultanious voices + unsigned int voices = 32; + } audio; }; } // namespace crepe diff --git a/src/crepe/api/Engine.cpp b/src/crepe/api/Engine.cpp new file mode 100644 index 0000000..0bbe51f --- /dev/null +++ b/src/crepe/api/Engine.cpp @@ -0,0 +1,68 @@ +#include "../util/Log.h" + +#include "Engine.h" + +using namespace crepe; +using namespace std; + +int Engine::main() noexcept { + try { + this->setup(); + } catch (const exception & e) { + Log::logf(Log::Level::ERROR, "Uncaught exception in setup: {}\n", e.what()); + return EXIT_FAILURE; + } + + try { + this->loop(); + } catch (const exception & e) { + Log::logf(Log::Level::ERROR, "Uncaught exception in main loop: {}\n", e.what()); + this->event_manager.trigger_event<ShutDownEvent>(); + } + + return EXIT_SUCCESS; +} + +void Engine::setup() { + this->loop_timer.start(); + this->scene_manager.load_next_scene(); + + this->event_manager.subscribe<ShutDownEvent>([this](const ShutDownEvent & event) { + this->game_running = false; + + // propagate to possible user ShutDownEvent listeners + return false; + }); +} + +void Engine::loop() { + LoopTimerManager & timer = this->loop_timer; + SystemManager & systems = this->system_manager; + + while (this->game_running) { + timer.update(); + + while (timer.get_lag() >= timer.get_fixed_delta_time()) { + try { + systems.fixed_update(); + } catch (const exception & e) { + Log::logf( + Log::Level::WARNING, "Uncaught exception in fixed update function: {}\n", + e.what() + ); + } + timer.advance_fixed_elapsed_time(); + } + + try { + systems.frame_update(); + this->scene_manager.load_next_scene(); + } catch (const exception & e) { + Log::logf( + Log::Level::WARNING, "Uncaught exception in frame update function: {}\n", + e.what() + ); + } + timer.enforce_frame_rate(); + } +} diff --git a/src/crepe/api/Engine.h b/src/crepe/api/Engine.h new file mode 100644 index 0000000..452a856 --- /dev/null +++ b/src/crepe/api/Engine.h @@ -0,0 +1,81 @@ +#pragma once + +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/ReplayManager.h" +#include "../manager/ResourceManager.h" +#include "../manager/SaveManager.h" +#include "../manager/SceneManager.h" +#include "../manager/SystemManager.h" + +namespace crepe { + +/** + * \brief Main game entrypoint + * + * This class is responsible for managing the game loop, including initialization and updating. + */ +class Engine { +public: + /** + * \brief Engine entrypoint + * + * This function is called by the game programmer after registering all scenes + * + * \returns process exit code + */ + int main() noexcept; + + //! \copydoc SceneManager::add_scene + template <typename T> + void add_scene(); + +private: + /** + * \brief Setup function for one-time initialization. + * + * This function initializes necessary components for the game. + */ + void setup(); + /** + * \brief Main game loop function. + * + * This function runs the main loop, handling game updates and rendering. + */ + void loop(); + + //! Game loop condition + bool game_running = true; + +private: + //! Global context + Mediator mediator; + + //! SystemManager + SystemManager system_manager {mediator}; + + //! SDLContext instance + SDLContext sdl_context {mediator}; + + //! Resource manager instance + ResourceManager resource_manager {mediator}; + + //! Component manager instance + ComponentManager component_manager {mediator}; + //! Scene manager instance + SceneManager scene_manager {mediator}; + //! LoopTimerManager instance + LoopTimerManager loop_timer {mediator}; + //! EventManager instance + EventManager event_manager {mediator}; + //! Save manager instance + SaveManager save_manager {mediator}; + //! ReplayManager instance + ReplayManager replay_manager {mediator}; +}; + +} // namespace crepe + +#include "Engine.hpp" diff --git a/src/crepe/api/Engine.hpp b/src/crepe/api/Engine.hpp new file mode 100644 index 0000000..f2fdc0a --- /dev/null +++ b/src/crepe/api/Engine.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Engine.h" + +namespace crepe { + +template <class T> +void Engine::add_scene() { + this->scene_manager.add_scene<T>(); +} + +} // namespace crepe diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index 06cf7f3..7d4df21 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -1,20 +1,23 @@ -// TODO discussing the location of these events #pragma once +// TODO discussing the location of these events #include <string> +#include "types.h" + #include "KeyCodes.h" +namespace crepe { + /** - * \brief Base class for all event types in the system. + * \brief Base struct for all event types in the system. */ -class Event {}; +struct Event {}; /** * \brief Event triggered when a key is pressed. */ -class KeyPressEvent : public Event { -public: +struct KeyPressEvent : public Event { //! false if first time press, true if key is repeated bool repeat = false; @@ -25,8 +28,7 @@ public: /** * \brief Event triggered when a key is released. */ -class KeyReleaseEvent : public Event { -public: +struct KeyReleaseEvent : public Event { //! The key that was released. Keycode key = Keycode::NONE; }; @@ -34,13 +36,9 @@ public: /** * \brief Event triggered when a mouse button is pressed. */ -class MousePressEvent : public Event { -public: - //! X-coordinate of the mouse position at the time of the event. - int mouse_x = 0; - - //! Y-coordinate of the mouse position at the time of the event. - int mouse_y = 0; +struct MousePressEvent : public Event { + //! mouse position in world coordinates (game units). + vec2 mouse_pos = {0, 0}; //! The mouse button that was pressed. MouseButton button = MouseButton::NONE; @@ -49,13 +47,9 @@ public: /** * \brief Event triggered when a mouse button is clicked (press and release). */ -class MouseClickEvent : public Event { -public: - //! X-coordinate of the mouse position at the time of the event. - int mouse_x = 0; - - //! Y-coordinate of the mouse position at the time of the event. - int mouse_y = 0; +struct MouseClickEvent : public Event { + //! mouse position in world coordinates (game units). + vec2 mouse_pos = {0, 0}; //! The mouse button that was clicked. MouseButton button = MouseButton::NONE; @@ -64,13 +58,9 @@ public: /** * \brief Event triggered when a mouse button is released. */ -class MouseReleaseEvent : public Event { -public: - //! X-coordinate of the mouse position at the time of the event. - int mouse_x = 0; - - //! Y-coordinate of the mouse position at the time of the event. - int mouse_y = 0; +struct MouseReleaseEvent : public Event { + //! mouse position in world coordinates (game units). + vec2 mouse_pos = {0, 0}; //! The mouse button that was released. MouseButton button = MouseButton::NONE; @@ -79,34 +69,77 @@ public: /** * \brief Event triggered when the mouse is moved. */ -class MouseMoveEvent : public Event { -public: - //! X-coordinate of the mouse position at the time of the event. - int mouse_x = 0; +struct MouseMoveEvent : public Event { + //! mouse position in world coordinates (game units). + vec2 mouse_pos = {0, 0}; + //! The change in mouse position relative to the last position (in pixels). + ivec2 mouse_delta = {0, 0}; +}; - //! Y-coordinate of the mouse position at the time of the event. - int mouse_y = 0; +/** + * \brief Event triggered when the mouse is moved. + */ +struct MouseScrollEvent : public Event { + //! mouse position in world coordinates (game units) when the scroll happened. + vec2 mouse_pos = {0, 0}; + //! scroll direction (-1 = down, 1 = up) + int scroll_direction = 0; + //! scroll amount in y axis (from and away from the person). + float scroll_delta = 0; }; /** - * \brief Event triggered during a collision between objects. + * \brief Event triggered to indicate the application is shutting down. + */ +struct ShutDownEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window is overlapped by another window. + * + * When two windows overlap the bottom window gets distorted and that window has to be redrawn. */ -class CollisionEvent : public Event { -public: - //! Data describing the collision (currently not implemented). - // Collision collisionData; +struct WindowExposeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window is resized. + */ +struct WindowResizeEvent : public Event { + //! new window dimensions + ivec2 dimensions = {0, 0}; }; /** - * \brief Event triggered when text is submitted, e.g., from a text input. + * \brief Event triggered to indicate the window is moved. */ -class TextSubmitEvent : public Event { -public: - //! The submitted text. - std::string text = ""; +struct WindowMoveEvent : public Event { + //! The change in position relative to the last position (in pixels). + ivec2 delta_move = {0, 0}; }; /** - * \brief Event triggered to indicate the application is shutting down. + * \brief Event triggered to indicate the window is minimized. + */ +struct WindowMinimizeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window is maximized */ -class ShutDownEvent : public Event {}; +struct WindowMaximizeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window gained focus + * + * This event is triggered when the window receives focus, meaning it becomes the active window + * for user interaction. + */ +struct WindowFocusGainEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window lost focus + * + * This event is triggered when the window loses focus, meaning it is no longer the active window + * for user interaction. + */ +struct WindowFocusLostEvent : public Event {}; + +} // namespace crepe diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h index ef659fd..7bb501b 100644 --- a/src/crepe/api/EventHandler.h +++ b/src/crepe/api/EventHandler.h @@ -8,12 +8,12 @@ namespace crepe { /** * \brief A type alias for an event handler function. - * - * The EventHandler is a std::function that takes an EventType reference and returns a boolean value + * + * The EventHandler is a std::function that takes an EventType reference and returns a boolean value * indicating whether the event is handled. - * + * * \tparam EventType The type of event this handler will handle. - * + * * Returning \c false from an event handler results in the event being propogated to other listeners for the same event type, while returning \c true stops propogation altogether. */ template <typename EventType> @@ -22,70 +22,70 @@ using EventHandler = std::function<bool(const EventType & e)>; /** * \class IEventHandlerWrapper * \brief An abstract base class for event handler wrappers. - * + * * This class provides the interface for handling events. Derived classes must implement the * `call()` method to process events */ class IEventHandlerWrapper { public: /** - * \brief Virtual destructor for IEventHandlerWrapper. - */ + * \brief Virtual destructor for IEventHandlerWrapper. + */ virtual ~IEventHandlerWrapper() = default; /** - * \brief Executes the handler with the given event. - * - * This method calls the `call()` method of the derived class, passing the event to the handler. - * - * \param e The event to be processed. - * \return A boolean value indicating whether the event is handled. - */ + * \brief Executes the handler with the given event. + * + * This method calls the `call()` method of the derived class, passing the event to the handler. + * + * \param e The event to be processed. + * \return A boolean value indicating whether the event is handled. + */ bool exec(const Event & e); private: /** - * \brief The method responsible for handling the event. - * - * This method is implemented by derived classes to process the event. - * - * \param e The event to be processed. - * \return A boolean value indicating whether the event is handled. - */ + * \brief The method responsible for handling the event. + * + * This method is implemented by derived classes to process the event. + * + * \param e The event to be processed. + * \return A boolean value indicating whether the event is handled. + */ virtual bool call(const Event & e) = 0; }; /** * \class EventHandlerWrapper * \brief A wrapper for event handler functions. - * - * This class wraps an event handler function of a specific event type. It implements the - * `call()` and `get_type()` methods to allow the handler to be executed and its type to be + * + * This class wraps an event handler function of a specific event type. It implements the + * `call()` and `get_type()` methods to allow the handler to be executed and its type to be * queried. - * + * * \tparam EventType The type of event this handler will handle. */ template <typename EventType> class EventHandlerWrapper : public IEventHandlerWrapper { public: /** - * \brief Constructs an EventHandlerWrapper with a given handler. - * - * The constructor takes an event handler function and stores it in the wrapper. - * - * \param handler The event handler function. - */ + * \brief Constructs an EventHandlerWrapper with a given handler. + * + * The constructor takes an event handler function and stores it in the wrapper. + * + * \param handler The event handler function. + */ explicit EventHandlerWrapper(const EventHandler<EventType> & handler); private: /** - * \brief Calls the stored event handler with the event. - * - * This method casts the event to the appropriate type and calls the handler. - * - * \param e The event to be handled. - * \return A boolean value indicating whether the event is handled. - */ + * \brief Calls the stored event handler with the event. + * + * This method casts the event to the appropriate type and calls the handler. + * + * \param e The event to be handled. + * \return A boolean value indicating whether the event is handled. + */ bool call(const Event & e) override; //! The event handler function. EventHandler<EventType> handler; diff --git a/src/crepe/api/EventHandler.hpp b/src/crepe/api/EventHandler.hpp index 391dcca..050e57e 100644 --- a/src/crepe/api/EventHandler.hpp +++ b/src/crepe/api/EventHandler.hpp @@ -1,3 +1,5 @@ +#pragma once + #include <typeindex> #include "EventHandler.h" diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp index 4874426..100e210 100644 --- a/src/crepe/api/GameObject.cpp +++ b/src/crepe/api/GameObject.cpp @@ -7,20 +7,19 @@ using namespace crepe; using namespace std; -GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id, - const std::string & name, const std::string & tag, - const Vector2 & position, double rotation, double scale) +GameObject::GameObject( + Mediator & mediator, game_object_id_t id, const std::string & name, + const std::string & tag, const vec2 & position, double rotation, double scale +) : id(id), - component_manager(component_manager) { - - // Add Transform and Metadata components - ComponentManager & mgr = this->component_manager; - mgr.add_component<Transform>(this->id, position, rotation, scale); - mgr.add_component<Metadata>(this->id, name, tag); -} + mediator(mediator), + transform(mediator.component_manager->add_component<Transform>( + this->id, position, rotation, scale + )), + metadata(mediator.component_manager->add_component<Metadata>(this->id, name, tag)) {} void GameObject::set_parent(const GameObject & parent) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; // Set parent on own Metadata component RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id); @@ -30,3 +29,9 @@ void GameObject::set_parent(const GameObject & parent) { RefVector<Metadata> parent_metadata = mgr.get_components_by_id<Metadata>(parent.id); parent_metadata.at(0).get().children.push_back(this->id); } + +void GameObject::set_persistent(bool persistent) { + ComponentManager & mgr = this->mediator.component_manager; + + mgr.set_persistent(this->id, persistent); +} diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index 34ef8bb..043913a 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -2,16 +2,17 @@ #include <string> -#include "Vector2.h" #include "types.h" namespace crepe { -class ComponentManager; +class Mediator; +class Transform; +class Metadata; /** * \brief Represents a GameObject - * + * * This class represents a GameObject. The GameObject class is only used as an interface for * the game programmer. The actual implementation is done in the ComponentManager. */ @@ -20,8 +21,8 @@ private: /** * This constructor creates a new GameObject. It creates a new Transform and Metadata * component and adds them to the ComponentManager. - * - * \param component_manager Reference to component_manager + * + * \param mediator Reference to mediator * \param id The id of the GameObject * \param name The name of the GameObject * \param tag The tag of the GameObject @@ -29,29 +30,38 @@ private: * \param rotation The rotation of the GameObject * \param scale The scale of the GameObject */ - GameObject(ComponentManager & component_manager, game_object_id_t id, - const std::string & name, const std::string & tag, const Vector2 & position, - double rotation, double scale); + GameObject( + Mediator & mediator, game_object_id_t id, const std::string & name, + const std::string & tag, const vec2 & position, double rotation, double scale + ); //! ComponentManager instances GameObject friend class ComponentManager; public: + //! The id of the GameObject + const game_object_id_t id; + //! This entity's transform + Transform & transform; + //! This entity's metadata + Metadata & metadata; + +public: /** * \brief Set the parent of this GameObject - * + * * This method sets the parent of this GameObject. It sets the parent in the Metadata * component of this GameObject and adds this GameObject to the children list of the parent * GameObject. - * + * * \param parent The parent GameObject */ void set_parent(const GameObject & parent); /** * \brief Add a component to the GameObject - * + * * This method adds a component to the GameObject. It forwards the arguments to the * ComponentManager. - * + * * \tparam T The type of the component * \tparam Args The types of the arguments * \param args The arguments to create the component @@ -59,13 +69,18 @@ public: */ template <typename T, typename... Args> T & add_component(Args &&... args); - -public: - //! The id of the GameObject - const game_object_id_t id; + /** + * \brief Components will not be deleted if this method is called + * + * This method sets the persistent flag of the GameObject to true. If the persistent + * flag is set to true, the GameObject will not be deleted when the scene is changed. + * + * \param persistent The persistent flag + */ + void set_persistent(bool persistent = true); protected: - ComponentManager & component_manager; + Mediator & mediator; }; } // namespace crepe diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp index 17b17d7..69f7d73 100644 --- a/src/crepe/api/GameObject.hpp +++ b/src/crepe/api/GameObject.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../ComponentManager.h" +#include "../manager/ComponentManager.h" #include "GameObject.h" @@ -8,7 +8,7 @@ namespace crepe { template <typename T, typename... Args> T & GameObject::add_component(Args &&... args) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; return mgr.add_component<T>(this->id, std::forward<Args>(args)...); } diff --git a/src/crepe/api/IKeyListener.cpp b/src/crepe/api/IKeyListener.cpp deleted file mode 100644 index 8642655..0000000 --- a/src/crepe/api/IKeyListener.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "IKeyListener.h" - -using namespace crepe; - -// Constructor with specified channel -IKeyListener::IKeyListener(event_channel_t channel) - : event_manager(EventManager::get_instance()) { - this->press_id = event_manager.subscribe<KeyPressEvent>( - [this](const KeyPressEvent & event) { return this->on_key_pressed(event); }, channel); - this->release_id = event_manager.subscribe<KeyReleaseEvent>( - [this](const KeyReleaseEvent & event) { return this->on_key_released(event); }, - channel); -} - -// Destructor, unsubscribe events -IKeyListener::~IKeyListener() { - event_manager.unsubscribe(this->press_id); - event_manager.unsubscribe(this->release_id); -} diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h deleted file mode 100644 index 328a4c2..0000000 --- a/src/crepe/api/IKeyListener.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "Event.h" -#include "EventHandler.h" -#include "EventManager.h" - -namespace crepe { - -/** - * \class IKeyListener - * \brief Interface for keyboard event handling in the application. - */ -class IKeyListener { -public: - /** - * \brief Constructs an IKeyListener with a specified channel. - * \param channel The channel ID for event handling. - */ - IKeyListener(event_channel_t channel = EventManager::CHANNEL_ALL); - virtual ~IKeyListener(); - IKeyListener(const IKeyListener &) = delete; - IKeyListener & operator=(const IKeyListener &) = delete; - IKeyListener & operator=(IKeyListener &&) = delete; - IKeyListener(IKeyListener &&) = delete; - - /** - * \brief Pure virtual function to handle key press events. - * \param event The key press event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_key_pressed(const KeyPressEvent & event) = 0; - - /** - * \brief Pure virtual function to handle key release events. - * \param event The key release event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_key_released(const KeyReleaseEvent & event) = 0; - -private: - //! Key press event id - subscription_t press_id = -1; - //! Key release event id - subscription_t release_id = -1; - //! EventManager reference - EventManager & event_manager; -}; - -} // namespace crepe diff --git a/src/crepe/api/IMouseListener.cpp b/src/crepe/api/IMouseListener.cpp deleted file mode 100644 index 989aeb3..0000000 --- a/src/crepe/api/IMouseListener.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "IMouseListener.h" - -using namespace crepe; - -IMouseListener::IMouseListener(event_channel_t channel) - : event_manager(EventManager::get_instance()) { - this->click_id = event_manager.subscribe<MouseClickEvent>( - [this](const MouseClickEvent & event) { return this->on_mouse_clicked(event); }, - channel); - - this->press_id = event_manager.subscribe<MousePressEvent>( - [this](const MousePressEvent & event) { return this->on_mouse_pressed(event); }, - channel); - - this->release_id = event_manager.subscribe<MouseReleaseEvent>( - [this](const MouseReleaseEvent & event) { return this->on_mouse_released(event); }, - channel); - - this->move_id = event_manager.subscribe<MouseMoveEvent>( - [this](const MouseMoveEvent & event) { return this->on_mouse_moved(event); }, channel); -} - -IMouseListener::~IMouseListener() { - // Unsubscribe event handlers - event_manager.unsubscribe(this->click_id); - event_manager.unsubscribe(this->press_id); - event_manager.unsubscribe(this->release_id); - event_manager.unsubscribe(this->move_id); -} diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h deleted file mode 100644 index 15e1619..0000000 --- a/src/crepe/api/IMouseListener.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include "Event.h" -#include "EventHandler.h" -#include "EventManager.h" - -namespace crepe { - -/** - * \class IMouseListener - * \brief Interface for mouse event handling in the application. - */ -class IMouseListener { -public: - /** - * \brief Constructs an IMouseListener with a specified channel. - * \param channel The channel ID for event handling. - */ - IMouseListener(event_channel_t channel = EventManager::CHANNEL_ALL); - virtual ~IMouseListener(); - IMouseListener & operator=(const IMouseListener &) = delete; - IMouseListener(const IMouseListener &) = delete; - IMouseListener & operator=(const IMouseListener &&) = delete; - IMouseListener(IMouseListener &&) = delete; - - /** - * \brief Move assignment operator (deleted). - */ - IMouseListener & operator=(IMouseListener &&) = delete; - - /** - * \brief Handles a mouse click event. - * \param event The mouse click event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_mouse_clicked(const MouseClickEvent & event) = 0; - - /** - * \brief Handles a mouse press event. - * \param event The mouse press event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_mouse_pressed(const MousePressEvent & event) = 0; - - /** - * \brief Handles a mouse release event. - * \param event The mouse release event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_mouse_released(const MouseReleaseEvent & event) = 0; - - /** - * \brief Handles a mouse move event. - * \param event The mouse move event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_mouse_moved(const MouseMoveEvent & event) = 0; - -private: - //! Mouse click event id - subscription_t click_id = -1; - //! Mouse press event id - subscription_t press_id = -1; - //! Mouse release event id - subscription_t release_id = -1; - //! Mouse move event id - subscription_t move_id = -1; - //! EventManager reference - EventManager & event_manager; -}; - -} //namespace crepe diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h index 9e173e0..1b9573a 100644 --- a/src/crepe/api/KeyCodes.h +++ b/src/crepe/api/KeyCodes.h @@ -1,5 +1,9 @@ #pragma once +#include <unordered_map> + +namespace crepe { + //! Enumeration for mouse button inputs, including standard and extended buttons. enum class MouseButton { NONE = 0, //!< No mouse button input. @@ -85,9 +89,9 @@ enum class Keycode { PRINT_SCREEN = 283, //!< Print Screen key. PAUSE = 284, //!< Pause key. /** - * \name Function keys (F1-F25). - * \{ - */ + * \name Function keys (F1-F25). + * \{ + */ F1 = 290, F2 = 291, F3 = 292, @@ -115,9 +119,9 @@ enum class Keycode { F25 = 314, /// \} /** - * \name Keypad digits and operators. - * \{ - */ + * \name Keypad digits and operators. + * \{ + */ KP0 = 320, KP1 = 321, KP2 = 322, @@ -137,9 +141,9 @@ enum class Keycode { KP_EQUAL = 336, /// \} /** - * \name Modifier keys. - * \{ - */ + * \name Modifier keys. + * \{ + */ LEFT_SHIFT = 340, LEFT_CONTROL = 341, LEFT_ALT = 342, @@ -151,3 +155,6 @@ enum class Keycode { /// \} MENU = 348, //!< Menu key. }; +//! Typedef for keyboard state. +typedef std::unordered_map<Keycode, bool> keyboard_state_t; +} // namespace crepe diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp deleted file mode 100644 index a64366f..0000000 --- a/src/crepe/api/LoopManager.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "../facade/SDLContext.h" - -#include "../system/AnimatorSystem.h" -#include "../system/CollisionSystem.h" -#include "../system/ParticleSystem.h" -#include "../system/PhysicsSystem.h" -#include "../system/RenderSystem.h" -#include "../system/ScriptSystem.h" - -#include "LoopManager.h" -#include "LoopTimer.h" - -using namespace crepe; -using namespace std; - -LoopManager::LoopManager() { - this->load_system<AnimatorSystem>(); - this->load_system<CollisionSystem>(); - this->load_system<ParticleSystem>(); - this->load_system<PhysicsSystem>(); - this->load_system<RenderSystem>(); - this->load_system<ScriptSystem>(); -} - -void LoopManager::process_input() { - SDLContext::get_instance().handle_events(this->game_running); -} - -void LoopManager::start() { - this->setup(); - this->loop(); -} -void LoopManager::set_running(bool running) { this->game_running = running; } - -void LoopManager::fixed_update() {} - -void LoopManager::loop() { - LoopTimer & timer = LoopTimer::get_instance(); - timer.start(); - - while (game_running) { - timer.update(); - - while (timer.get_lag() >= timer.get_fixed_delta_time()) { - this->process_input(); - this->fixed_update(); - timer.advance_fixed_update(); - } - - this->update(); - this->render(); - - timer.enforce_frame_rate(); - } -} - -void LoopManager::setup() { - this->game_running = true; - LoopTimer::get_instance().start(); - LoopTimer::get_instance().set_fps(60); -} - -void LoopManager::render() { - if (this->game_running) { - this->get_system<RenderSystem>().update(); - } -} - -void LoopManager::update() { LoopTimer & timer = LoopTimer::get_instance(); } diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h deleted file mode 100644 index 13e6dac..0000000 --- a/src/crepe/api/LoopManager.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -#include <memory> - -#include "../ComponentManager.h" -#include "../system/System.h" -#include "api/SceneManager.h" - -namespace crepe { - -/** - * \brief Main game loop manager - * - * This class is responsible for managing the game loop, including initialization and updating. - */ -class LoopManager { -public: - void start(); - LoopManager(); - - /** - * \brief Add a new concrete scene to the scene manager - * - * \tparam T Type of concrete scene - */ - template <typename T> - void add_scene(); - -private: - /** - * \brief Setup function for one-time initialization. - * - * This function initializes necessary components for the game. - */ - void setup(); - /** - * \brief Main game loop function. - * - * This function runs the main loop, handling game updates and rendering. - */ - void loop(); - - /** - * \brief Function for handling input-related system calls. - * - * Processes user inputs from keyboard and mouse. - */ - void process_input(); - - /** - * \brief Per-frame update. - * - * Updates the game state based on the elapsed time since the last frame. - */ - void update(); - - /** - * \brief Late update which is called after update(). - * - * This function can be used for final adjustments before rendering. - */ - void late_update(); - - /** - * \brief Fixed update executed at a fixed rate. - * - * This function updates physics and game logic based on LoopTimer's fixed_delta_time. - */ - void fixed_update(); - - /** - * \brief Set game running variable - * - * \param running running (false = game shutdown, true = game running) - */ - void set_running(bool running); - - /** - * \brief Function for executing render-related systems. - * - * Renders the current state of the game to the screen. - */ - void render(); - - bool game_running = false; - -private: - //! Component manager instance - ComponentManager component_manager{}; - //! Scene manager instance - SceneManager scene_manager{component_manager}; - -private: - /** - * \brief Collection of System instances - * - * This map holds System instances indexed by the system's class typeid. It is filled in the - * constructor of \c LoopManager using LoopManager::load_system. - */ - std::unordered_map<std::type_index, std::unique_ptr<System>> systems; - /** - * \brief Initialize a system - * \tparam T System type (must be derivative of \c System) - */ - template <class T> - void load_system(); - /** - * \brief Retrieve a reference to ECS system - * \tparam T System type - * \returns Reference to system instance - * \throws std::runtime_error if the System is not initialized - */ - template <class T> - T & get_system(); -}; - -} // namespace crepe - -#include "LoopManager.hpp" diff --git a/src/crepe/api/LoopManager.hpp b/src/crepe/api/LoopManager.hpp deleted file mode 100644 index 9cf470b..0000000 --- a/src/crepe/api/LoopManager.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include <cassert> -#include <format> -#include <memory> - -#include "../system/System.h" - -#include "LoopManager.h" - -namespace crepe { - -template <class T> -void LoopManager::add_scene() { - this->scene_manager.add_scene<T>(); -} - -template <class T> -T & LoopManager::get_system() { - using namespace std; - static_assert(is_base_of<System, T>::value, - "get_system must recieve a derivative class of System"); - - const type_info & type = typeid(T); - if (!this->systems.contains(type)) - throw runtime_error(format("LoopManager: {} is not initialized", type.name())); - - System * system = this->systems.at(type).get(); - T * concrete_system = dynamic_cast<T *>(system); - assert(concrete_system != nullptr); - - return *concrete_system; -} - -template <class T> -void LoopManager::load_system() { - using namespace std; - static_assert(is_base_of<System, T>::value, - "load_system must recieve a derivative class of System"); - - System * system = new T(this->component_manager); - this->systems[typeid(T)] = unique_ptr<System>(system); -} - -} // namespace crepe diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp deleted file mode 100644 index a9800b7..0000000 --- a/src/crepe/api/LoopTimer.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include <chrono> - -#include "../facade/SDLContext.h" -#include "../util/Log.h" - -#include "LoopTimer.h" - -using namespace crepe; - -LoopTimer::LoopTimer() { dbg_trace(); } - -LoopTimer & LoopTimer::get_instance() { - static LoopTimer instance; - return instance; -} - -void LoopTimer::start() { - this->last_frame_time = std::chrono::steady_clock::now(); - this->elapsed_time = std::chrono::milliseconds(0); - this->elapsed_fixed_time = std::chrono::milliseconds(0); - this->delta_time = std::chrono::milliseconds(0); -} - -void LoopTimer::update() { - auto current_frame_time = std::chrono::steady_clock::now(); - // Convert to duration in seconds for delta time - this->delta_time = std::chrono::duration_cast<std::chrono::duration<double>>( - current_frame_time - last_frame_time); - - if (this->delta_time > this->maximum_delta_time) { - this->delta_time = this->maximum_delta_time; - } - - this->delta_time *= this->game_scale; - this->elapsed_time += this->delta_time; - this->last_frame_time = current_frame_time; -} - -double LoopTimer::get_delta_time() const { return this->delta_time.count(); } - -double LoopTimer::get_current_time() const { return this->elapsed_time.count(); } - -void LoopTimer::advance_fixed_update() { this->elapsed_fixed_time += this->fixed_delta_time; } - -double LoopTimer::get_fixed_delta_time() const { return this->fixed_delta_time.count(); } - -void LoopTimer::set_fps(int fps) { - this->fps = fps; - // target time per frame in seconds - this->frame_target_time = std::chrono::seconds(1) / fps; -} - -int LoopTimer::get_fps() const { return this->fps; } - -void LoopTimer::set_game_scale(double value) { this->game_scale = value; } - -double LoopTimer::get_game_scale() const { return this->game_scale; } -void LoopTimer::enforce_frame_rate() { - std::chrono::steady_clock::time_point current_frame_time - = std::chrono::steady_clock::now(); - std::chrono::milliseconds frame_duration - = std::chrono::duration_cast<std::chrono::milliseconds>(current_frame_time - - this->last_frame_time); - - if (frame_duration < this->frame_target_time) { - std::chrono::milliseconds delay_time - = std::chrono::duration_cast<std::chrono::milliseconds>(this->frame_target_time - - frame_duration); - if (delay_time.count() > 0) { - SDLContext::get_instance().delay(delay_time.count()); - } - } - - this->last_frame_time = current_frame_time; -} - -double LoopTimer::get_lag() const { - return (this->elapsed_time - this->elapsed_fixed_time).count(); -} diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h deleted file mode 100644 index f277d7b..0000000 --- a/src/crepe/api/LoopTimer.h +++ /dev/null @@ -1,144 +0,0 @@ -#pragma once - -#include <chrono> - -namespace crepe { - -class LoopTimer { -public: - /** - * \brief Get the singleton instance of LoopTimer. - * - * \return A reference to the LoopTimer instance. - */ - static LoopTimer & get_instance(); - - /** - * \brief Get the current delta time for the current frame. - * - * \return Delta time in seconds since the last frame. - */ - double get_delta_time() const; - - /** - * \brief Get the current game time. - * - * \note The current game time may vary from real-world elapsed time. It is the cumulative - * sum of each frame's delta time. - * - * \return Elapsed game time in seconds. - */ - double get_current_time() const; - - /** - * \brief Set the target frames per second (FPS). - * - * \param fps The desired frames rendered per second. - */ - void set_fps(int fps); - - /** - * \brief Get the current frames per second (FPS). - * - * \return Current FPS. - */ - int get_fps() const; - - /** - * \brief Get the current game scale. - * - * \return The current game scale, where 0 = paused, 1 = normal speed, and values > 1 speed - * up the game. - */ - double get_game_scale() const; - - /** - * \brief Set the game scale. - * - * \param game_scale The desired game scale (0 = pause, 1 = normal speed, > 1 = speed up). - */ - void set_game_scale(double game_scale); - -private: - friend class LoopManager; - - /** - * \brief Start the loop timer. - * - * Initializes the timer to begin tracking frame times. - */ - void start(); - - /** - * \brief Enforce the frame rate limit. - * - * Ensures that the game loop does not exceed the target FPS by delaying frame updates as - * necessary. - */ - void enforce_frame_rate(); - - /** - * \brief Get the fixed delta time for consistent updates. - * - * Fixed delta time is used for operations that require uniform time steps, such as physics - * calculations. - * - * \return Fixed delta time in seconds. - */ - double get_fixed_delta_time() const; - - /** - * \brief Get the accumulated lag in the game loop. - * - * Lag represents the difference between the target frame time and the actual frame time, - * useful for managing fixed update intervals. - * - * \return Accumulated lag in seconds. - */ - double get_lag() const; - - /** - * \brief Construct a new LoopTimer object. - * - * Private constructor for singleton pattern to restrict instantiation outside the class. - */ - LoopTimer(); - - /** - * \brief Update the timer to the current frame. - * - * Calculates and updates the delta time for the current frame and adds it to the cumulative - * game time. - */ - void update(); - - /** - * \brief Advance the game loop by a fixed update interval. - * - * This method progresses the game state by a consistent, fixed time step, allowing for - * stable updates independent of frame rate fluctuations. - */ - void advance_fixed_update(); - -private: - //! Current frames per second - int fps = 50; - //! Current game scale - double game_scale = 1; - //! Maximum delta time in seconds to avoid large jumps - std::chrono::duration<double> maximum_delta_time{0.25}; - //! Delta time for the current frame in seconds - std::chrono::duration<double> delta_time{0.0}; - //! Target time per frame in seconds - std::chrono::duration<double> frame_target_time = std::chrono::seconds(1) / fps; - //! Fixed delta time for fixed updates in seconds - std::chrono::duration<double> fixed_delta_time = std::chrono::seconds(1) / 50; - //! Total elapsed game time in seconds - std::chrono::duration<double> elapsed_time{0.0}; - //! Total elapsed time for fixed updates in seconds - std::chrono::duration<double> elapsed_fixed_time{0.0}; - //! Time of the last frame - std::chrono::steady_clock::time_point last_frame_time; -}; - -} // namespace crepe diff --git a/src/crepe/api/Metadata.h b/src/crepe/api/Metadata.h index 235d42f..f404703 100644 --- a/src/crepe/api/Metadata.h +++ b/src/crepe/api/Metadata.h @@ -9,7 +9,7 @@ namespace crepe { /** * \brief Metadata component - * + * * This class represents the Metadata component. It stores the name, tag, parent and children * of a GameObject. */ diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 90b77a0..341c1e2 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -1,11 +1,29 @@ #include "ParticleEmitter.h" +#include "api/Sprite.h" using namespace crepe; +using namespace std; -ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Data & data) +ParticleEmitter::ParticleEmitter( + game_object_id_t game_object_id, const Sprite & sprite, const Data & data +) : Component(game_object_id), + sprite(sprite), data(data) { for (size_t i = 0; i < this->data.max_particles; i++) { - this->data.particles.emplace_back(); + this->particles.emplace_back(); } } + +unique_ptr<Component> ParticleEmitter::save() const { + return unique_ptr<Component> {new ParticleEmitter(*this)}; +} + +void ParticleEmitter::restore(const Component & snapshot) { + *this = static_cast<const ParticleEmitter &>(snapshot); +} + +ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter & other) { + this->particles = other.particles; + return *this; +} diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 33112e1..1edd2b5 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -1,10 +1,14 @@ #pragma once +#include <cmath> #include <vector> +#include "system/ParticleSystem.h" +#include "system/RenderSystem.h" + #include "Component.h" #include "Particle.h" -#include "Vector2.h" +#include "types.h" namespace crepe { @@ -26,15 +30,18 @@ public: */ struct Boundary { //! boundary width (midpoint is emitter location) - double width = 0.0; + float width = INFINITY; //! boundary height (midpoint is emitter location) - double height = 0.0; + float height = INFINITY; //! boundary offset from particle emitter location - Vector2 offset; + vec2 offset; //! reset on exit or stop velocity and set max postion bool reset_on_exit = false; }; + //! sprite reference of displayed sprite + const Sprite & sprite; + /** * \brief Holds parameters that control particle emission. * @@ -42,32 +49,28 @@ public: * and the sprite used for rendering particles. */ struct Data { - //! position of the emitter - Vector2 position; + //! offset of the emitter relative to transform + vec2 offset; //! maximum number of particles - const unsigned int max_particles = 0; - //! rate of particle emission per update (Lowest value = 0.001 any lower is ignored) - double emission_rate = 0; + const unsigned int max_particles = 256; + //! rate of particle emission per second + float emission_rate = 50; //! min speed of the particles - double min_speed = 0; + float min_speed = 100; //! min speed of the particles - double max_speed = 0; + float max_speed = 100; //! min angle of particle emission - double min_angle = 0; + float min_angle = 0; //! max angle of particle emission - double max_angle = 0; - //! begin Lifespan of particle (only visual) - double begin_lifespan = 0.0; - //! end Lifespan of particle - double end_lifespan = 0.0; + float max_angle = 0; + //! begin Lifespan of particle in seconds (only visual) + float begin_lifespan = 0.0; + //! end Lifespan of particle in seconds + float end_lifespan = 10.0; //! force over time (physics) - Vector2 force_over_time; + vec2 force_over_time; //! particle boundary Boundary boundary; - //! collection of particles - std::vector<Particle> particles; - //! sprite reference - const Sprite & sprite; }; public: @@ -75,11 +78,27 @@ public: * \param game_object_id Identifier for the game object using this emitter. * \param data Configuration data defining particle properties. */ - ParticleEmitter(game_object_id_t game_object_id, const Data & data); + ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite, const Data & data); public: //! Configuration data for particle emission settings. Data data; + +protected: + virtual std::unique_ptr<Component> save() const; + ParticleEmitter(const ParticleEmitter &) = default; + virtual void restore(const Component & snapshot); + virtual ParticleEmitter & operator=(const ParticleEmitter &); + +private: + //! Only ParticleSystem can move and read particles + friend ParticleSystem; + //! Only RenderSystem can read particles + friend RenderSystem; + //! Saves time left over from last update event. + float spawn_accumulator = 0; + //! collection of particles + std::vector<Particle> particles; }; } // namespace crepe diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp index 6b87695..8213afb 100644 --- a/src/crepe/api/Rigidbody.cpp +++ b/src/crepe/api/Rigidbody.cpp @@ -6,10 +6,8 @@ crepe::Rigidbody::Rigidbody(game_object_id_t id, const Data & data) : Component(id), data(data) {} -void crepe::Rigidbody::add_force_linear(const Vector2 & force) { +void crepe::Rigidbody::add_force_linear(const vec2 & force) { this->data.linear_velocity += force; } -void crepe::Rigidbody::add_force_angular(double force) { - this->data.angular_velocity += force; -} +void crepe::Rigidbody::add_force_angular(float force) { this->data.angular_velocity += force; } diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index 3e5c7a3..b63d941 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -1,14 +1,18 @@ #pragma once +#include <cmath> +#include <set> +#include <string> + #include "../Component.h" -#include "Vector2.h" +#include "types.h" namespace crepe { /** * \brief Rigidbody class - * + * * This class is used by the physics sytem and collision system. It configures how to system * interact with the gameobject for movement and collisions. */ @@ -16,7 +20,7 @@ class Rigidbody : public Component { public: /** * \brief BodyType enum - * + * * This enum provides three bodytypes the physics sytem and collision system use. */ enum class BodyType { @@ -29,54 +33,141 @@ public: }; /** * \brief PhysicsConstraints to constrain movement - * + * * This struct configures the movement constraint for this object. If a constraint is enabled * the systems will not move the object. */ struct PhysicsConstraints { - //! X constraint + //! Prevent movement along X axis bool x = false; - //! Y constraint + //! Prevent movement along Y axis bool y = false; - //! rotation constraint + //! Prevent rotation bool rotation = false; }; public: - /** + /** * \brief struct for Rigidbody data - * + * * This struct holds the data for the Rigidbody. */ struct Data { //! objects mass - double mass = 0.0; - //! gravtiy scale - double gravity_scale = 0.0; - //! Changes if physics apply + float mass = 1; + /** + * \brief Gravity scale factor. + * + * The `gravity_scale` controls how much gravity affects the object. It is a multiplier applied to the default + * gravity force, allowing for fine-grained control over how the object responds to gravity. + * + */ + float gravity_scale = 0; + + //! Defines the type of the physics body, which determines how the physics system interacts with the object. BodyType body_type = BodyType::DYNAMIC; - //! linear velocity of object - Vector2 linear_velocity; - //! maximum linear velocity of object - Vector2 max_linear_velocity; - //! linear damping of object - Vector2 linear_damping; - //! angular velocity of object - double angular_velocity = 0.0; - //! max angular velocity of object - double max_angular_velocity = 0.0; - //! angular damping of object - double angular_damping = 0.0; - //! movements constraints of object + + /** + * \name Linear (positional) motion + * + * These variables define the linear motion (movement along the position) of an object. + * The linear velocity is applied to the object's position in each update of the PhysicsSystem. + * The motion is affected by the object's linear velocity, its maximum velocity, and a coefficient + * that can scale the velocity over time. + * + * \{ + */ + //! Linear velocity of the object (speed and direction). + vec2 linear_velocity; + //! Maximum linear velocity of the object. This limits the object's speed. + float max_linear_velocity = INFINITY; + //! Linear velocity coefficient. This scales the object's velocity for adjustment or damping. + vec2 linear_velocity_coefficient = {1, 1}; + //! \} + + /** + * \name Angular (rotational) motion + * + * These variables define the angular motion (rotation) of an object. + * The angular velocity determines how quickly the object rotates, while the maximum angular velocity + * sets a limit on the rotation speed. The angular velocity coefficient applies damping or scaling + * to the angular velocity, which can be used to simulate friction or other effects that slow down rotation. + * + * \{ + */ + //! Angular velocity of the object, representing the rate of rotation (in degrees). + float angular_velocity = 0; + //! Maximum angular velocity of the object. This limits the maximum rate of rotation. + float max_angular_velocity = INFINITY; + //! Angular velocity coefficient. This scales the object's angular velocity, typically used for damping. + float angular_velocity_coefficient = 1; + //! \} + + /** + * \brief Movement constraints for an object. + * + * The `PhysicsConstraints` struct defines the constraints that restrict an object's movement + * in certain directions or prevent rotation. These constraints effect only the physics system + * to prevent the object from moving or rotating in specified ways. + * + */ PhysicsConstraints constraints; - //! if gravity applies - bool use_gravity = true; - //! if object bounces - bool bounce = false; + + /** + * \brief Elasticity factor of the material (bounce factor). + * + * The `elasticity_coefficient` controls how much of the object's velocity is retained after a collision. + * It represents the material's ability to bounce or recover velocity upon impact. The coefficient is a value + * above 0.0. + * + */ + float elasticity_coefficient = 0.0; + + /** + * \brief Enables collision handling for objects colliding with kinematic objects. + * + * Enables collision handling for objects colliding with kinematic objects in the collision system. + * If `kinematic_collision` is true, dynamic objects cannot pass through this kinematic object. + * This ensures that kinematic objects delegate collision handling to the collision system. + */ + bool kinematic_collision = true; + + /** + * \brief Defines the collision layers a GameObject interacts with. + * + * The `collision_layers` represent the set of layers the GameObject can detect collisions with. + * Each element in this set corresponds to a layer ID. The GameObject will only collide with other + * GameObjects that belong to one these layers. + */ + std::set<int> collision_layers = {0}; + + /** + * \brief Specifies the collision layer of the GameObject. + * + * The `collision_layer` indicates the single layer that this GameObject belongs to. + * This determines which layers other objects must match to detect collisions with this object. + */ + int collision_layer = 0; + + /** + * \brief Defines the collision layers of a GameObject. + * + * The `collision_names` specifies where the GameObject will collide with. + * Each element represents a name from the Metadata of the gameobject. + */ + std::set<std::string> collision_names; + + /** + * \brief Defines the collision layers of a GameObject. + * + * The `collision_tags` specifies where the GameObject will collide with. + * Each element represents a tag from the Metadata of the gameobject. + */ + std::set<std::string> collision_tags; }; public: - /** + /** * \param game_object_id id of the gameobject the rigibody is added to. * \param data struct to configure the rigidbody. */ @@ -85,18 +176,27 @@ public: Data data; public: - /** + /** * \brief add a linear force to the Rigidbody. - * + * * \param force Vector2 that is added to the linear force. */ - void add_force_linear(const Vector2 & force); - /** + void add_force_linear(const vec2 & force); + /** * \brief add a angular force to the Rigidbody. - * + * * \param force Vector2 that is added to the angular force. */ - void add_force_angular(double force); + void add_force_angular(float force); + +protected: + /** + * Ensures there is at most one Rigidbody component per entity. + * \return Always returns 1, indicating this constraint. + */ + virtual int get_instances_max() const { return 1; } + //! ComponentManager instantiates all components + friend class ComponentManager; }; } // namespace crepe diff --git a/src/crepe/api/Scene.cpp b/src/crepe/api/Scene.cpp index 849945e..84da7e8 100644 --- a/src/crepe/api/Scene.cpp +++ b/src/crepe/api/Scene.cpp @@ -2,4 +2,16 @@ using namespace crepe; -Scene::Scene(ComponentManager & mgr) : component_manager(mgr) {} +SaveManager & Scene::get_save_manager() const { return mediator->save_manager; } + +GameObject Scene::new_object( + const std::string & name, const std::string & tag, const vec2 & position, double rotation, + double scale +) { + // Forward the call to ComponentManager's new_object method + return mediator->component_manager->new_object(name, tag, position, rotation, scale); +} + +void Scene::set_persistent(const Asset & asset, bool persistent) { + mediator->resource_manager->set_persistent(asset, persistent); +} diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h index 869bf6f..b50a0fc 100644 --- a/src/crepe/api/Scene.h +++ b/src/crepe/api/Scene.h @@ -2,24 +2,30 @@ #include <string> +#include "../manager/ComponentManager.h" +#include "../manager/Mediator.h" +#include "../manager/ResourceManager.h" +#include "../util/Log.h" +#include "../util/OptionalRef.h" + +#include "GameObject.h" + namespace crepe { class SceneManager; class ComponentManager; +class Asset; /** * \brief Represents a Scene - * + * * This class represents a Scene. The Scene class is only used as an interface for the game * programmer. */ class Scene { protected: - //TODO: Use Loek's custom reference class to set ComponentManger via SceneManager instead of via constructor - /** - * \param mgr Reference to the ComponentManager - */ - Scene(ComponentManager & mgr); + // NOTE: This must be the only constructor on Scene, see "Late references" below + Scene() = default; //! SceneManager instances Scene friend class SceneManager; @@ -35,9 +41,55 @@ public: */ virtual std::string get_name() const = 0; -protected: - //! Reference to the ComponentManager - ComponentManager & component_manager; + // TODO: Late references should ALWAYS be private! This is currently kept as-is so unit tests + // keep passing, but this reference should not be directly accessible by the user!!! + +private: + /** + * \name Late references + * + * These references are set by SceneManager immediately after calling the constructor of Scene. + * + * \note Scene must have a constructor without arguments so the game programmer doesn't need to + * manually add `using Scene::Scene` to their concrete scene class, if they want to add a + * constructor with arguments (e.g. for passing references to their own concrete Scene classes). + * + * \{ + */ + //! Mediator reference + OptionalRef<Mediator> mediator; + //! \} + +public: + /** + * \brief Retrieve the reference to the SaveManager instance + * + * \returns A reference to the SaveManager instance held by the Mediator. + */ + SaveManager & get_save_manager() const; + + //! \copydoc ComponentManager::new_object + GameObject new_object( + const std::string & name, const std::string & tag = "", const vec2 & position = {0, 0}, + double rotation = 0, double scale = 1 + ); + + //! \copydoc ResourceManager::set_persistent + void set_persistent(const Asset & asset, bool persistent); + /** + * \name Logging functions + * \see Log + * \{ + */ + //! \copydoc Log::logf + template <class... Args> + void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args); + //! \copydoc Log::logf + template <class... Args> + void logf(std::format_string<Args...> fmt, Args &&... args); + //! \} }; } // namespace crepe + +#include "Scene.hpp" diff --git a/src/crepe/api/Scene.hpp b/src/crepe/api/Scene.hpp new file mode 100644 index 0000000..14635df --- /dev/null +++ b/src/crepe/api/Scene.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "../util/Log.h" + +#include "Scene.h" + +namespace crepe { + +template <class... Args> +void Scene::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { + Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Scene::logf(std::format_string<Args...> fmt, Args &&... args) { + Log::logf(fmt, std::forward<Args>(args)...); +} + +} // namespace crepe diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp new file mode 100644 index 0000000..06b535f --- /dev/null +++ b/src/crepe/api/Script.cpp @@ -0,0 +1,78 @@ +#include <string> + +#include "../facade/SDLContext.h" +#include "../manager/SceneManager.h" + +#include "Script.h" + +using namespace crepe; +using namespace std; + +Script::~Script() { + EventManager & mgr = this->mediator->event_manager; + for (auto id : this->listeners) { + mgr.unsubscribe(id); + } +} + +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback) { + this->subscribe_internal(callback, this->game_object_id); +} + +template <> +void Script::subscribe(const EventHandler<ButtonExitEvent> & callback) { + this->subscribe_internal(callback, this->game_object_id); +} + +template <> +void Script::subscribe(const EventHandler<ButtonPressEvent> & callback) { + this->subscribe_internal(callback, this->game_object_id); +} + +template <> +void Script::subscribe(const EventHandler<ButtonEnterEvent> & callback) { + this->subscribe_internal(callback, this->game_object_id); +} + +void Script::set_next_scene(const string & name) { + SceneManager & mgr = this->mediator->scene_manager; + mgr.set_next_scene(name); +} + +SaveManager & Script::get_save_manager() const { return this->mediator->save_manager; } + +LoopTimerManager & Script::get_loop_timer() const { return this->mediator->loop_timer; } + +void Script::replay::record_start() { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.record_start(); +} + +recording_t Script::replay::record_end() { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.record_end(); +} + +void Script::replay::play(recording_t recording) { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.play(recording); +} + +void Script::replay::release(recording_t recording) { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.release(recording); +} + +const keyboard_state_t & Script::get_keyboard_state() const { + SDLContext & sdl_context = this->mediator->sdl_context; + return sdl_context.get_keyboard_state(); +} + +bool Script::get_key_state(Keycode key) const noexcept { + try { + return this->get_keyboard_state().at(key); + } catch (...) { + return false; + } +} diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index 839d937..b000d9d 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -2,7 +2,16 @@ #include <vector> +#include "../api/KeyCodes.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h" +#include "../manager/ReplayManager.h" +#include "../system/CollisionSystem.h" +#include "../system/InputSystem.h" #include "../types.h" +#include "../util/Log.h" +#include "../util/OptionalRef.h" namespace crepe { @@ -18,11 +27,21 @@ class ComponentManager; * * \note Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as * member or lambda methods in derivative user script classes and registered in \c init(). + * + * \warning Concrete scripts are allowed do create a custom constructor, but the utility + * functions should not be called inside the constructor as they rely on late references that + * are only available after the constructor returns. + * + * \see feature_script */ class Script { protected: /** - * \brief Script initialization function + * \name Interface functions + * \{ + */ + /** + * \brief Script initialization function (empty by default) * * This function is called during the ScriptSystem::update() routine *before* * Script::update() if it (a) has not yet been called and (b) the \c BehaviorScript component @@ -30,59 +49,266 @@ protected: */ virtual void init() {} /** - * \brief Script update function + * \brief Script fixed update function (empty by default) + * + * \param delta_time Time since last fixed update + * + * \note This function is called during the ScriptSystem::update() routine if the \c + * BehaviorScript component holding this script instance is active. + */ + virtual void fixed_update(duration_t delta_time) {} + /** + * \brief Script frame update function (empty by default) + * + * \param delta_time Time since last frame update * - * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript - * component holding this script instance is active. + * \note This function is called during the ScriptSystem::update() routine if the \c + * BehaviorScript component holding this script instance is active. */ - virtual void update() {} + virtual void frame_update(duration_t delta_time) {} + //! \} + //! ScriptSystem calls \c init() and \c update() friend class crepe::ScriptSystem; protected: /** - * \brief Get single component of type \c T on this game object (utility) - * + * \name Component query functions + * \see ComponentManager + * \{ + */ + /** + * \brief Get single component of type \c T on this game object * \tparam T Type of component - * * \returns Reference to component - * - * \throws nullptr if this game object does not have a component matching type \c T + * \throws std::runtime_error if this game object does not have a component with type \c T */ template <typename T> T & get_component() const; - // TODO: make get_component calls for component types that can have more than 1 instance - // cause compile-time errors - /** - * \brief Get all components of type \c T on this game object (utility) - * + * \brief Get all components of type \c T on this game object * \tparam T Type of component - * * \returns List of component references */ template <typename T> RefVector<T> get_components() const; + //! \copydoc ComponentManager::get_components_by_id + template <typename T> + RefVector<T> get_components_by_id(game_object_id_t id) const; + //! \copydoc ComponentManager::get_components_by_name + template <typename T> + RefVector<T> get_components_by_name(const std::string & name) const; + //! \copydoc ComponentManager::get_components_by_tag + template <typename T> + RefVector<T> get_components_by_tag(const std::string & tag) const; + //! \} + + /** + * \name Logging functions + * \see Log + * \{ + */ + //! \copydoc Log::logf + template <class... Args> + void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args); + //! \copydoc Log::logf + template <class... Args> + void logf(std::format_string<Args...> fmt, Args &&... args); + // \} + + /** + * \name Event manager functions + * \see EventManager + * \{ + */ + //! \copydoc EventManager::subscribe + template <typename EventType> + void subscribe(const EventHandler<EventType> & callback, event_channel_t channel); + //! \copydoc EventManager::subscribe + template <typename EventType> + void subscribe(const EventHandler<EventType> & callback); + //! \copydoc EventManager::trigger_event + template <typename EventType> + void trigger_event( + const EventType & event = {}, event_channel_t channel = EventManager::CHANNEL_ALL + ); + //! \copydoc EventManager::queue_event + template <typename EventType> + void queue_event( + const EventType & event = {}, event_channel_t channel = EventManager::CHANNEL_ALL + ); + //! \} + + /** + * \name Scene-related functions + * \see SceneManager + * \{ + */ + //! \copydoc SceneManager::set_next_scene + void set_next_scene(const std::string & name); + //! \} + + /** + * \name Save data management functions + * \see SaveManager + * \{ + */ + //! Retrieve SaveManager reference + SaveManager & get_save_manager() const; + //! \} + + /** + * \name Timing functions + * \see LoopTimerManager + * \{ + */ + //! Retrieve LoopTimerManager reference + LoopTimerManager & get_loop_timer() const; + //! \} + + //! Replay management functions + struct replay { // NOLINT + //! \copydoc ReplayManager::record_start + void record_start(); + //! \copydoc ReplayManager::record_end + recording_t record_end(); + //! \copydoc ReplayManager::play + void play(recording_t); + //! \copydoc ReplayManager::release + void release(recording_t); + + private: + OptionalRef<Mediator> & mediator; + replay(OptionalRef<Mediator> & mediator) : mediator(mediator) {} + friend class Script; + } replay {mediator}; + + /** + * \brief Utility function to retrieve the keyboard state + * \see SDLContext::get_keyboard_state + * + * \return current keyboard state map with Keycode as key and bool as value(true = pressed, false = not pressed) + */ + const keyboard_state_t & get_keyboard_state() const; + /** + * \brief Utility function to retrieve a single key state. + * \see SDLContext::get_keyboard_state + * + * \return Keycode state (true if pressed, false if not pressed). + */ + bool get_key_state(Keycode key) const noexcept; + +private: + /** + * \brief Internal subscribe function + * + * This function exists so certain template specializations of Script::subscribe can be + * explicitly deleted, and does the following: + * - Wrap the user-provided callback in a check that tests if the parent BehaviorScript + * component is still active + * - Store the subscriber handle returned by the event manager so this listener is + * automatically unsubscribed at the end of this Script instance's life + * + * \tparam EventType concrete Event class + * \param callback User-provided callback function + * \param channel Event channel (may have been overridden by template specializations) + */ + template <typename EventType> + void subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel); protected: - // NOTE: Script must have a constructor without arguments so the game programmer doesn't need - // to manually add `using Script::Script` to their concrete script class. + // NOTE: This must be the only constructor on Script, see "Late references" below Script() = default; //! Only \c BehaviorScript instantiates Script friend class BehaviorScript; +public: + // std::unique_ptr destroys script + virtual ~Script(); + +private: + Script(const Script &) = delete; + Script(Script &&) = delete; + Script & operator=(const Script &) = delete; + Script & operator=(Script &&) = delete; + private: - // These references are set by BehaviorScript immediately after calling the constructor of - // Script. - game_object_id_t game_object_id = -1; - ComponentManager * component_manager_ref = nullptr; - // TODO: use OptionalRef instead of pointer + /** + * \name Late references + * + * These references are set by BehaviorScript immediately after calling the constructor of + * Script. + * + * \note Script must have a constructor without arguments so the game programmer doesn't need + * to manually add `using Script::Script` to their concrete script class if they want to + * implement a non-default constructor (e.g. for passing references to their own concrete + * Script classes). + * + * \{ + */ + //! Game object ID of game object parent BehaviorScript is attached to + game_object_id_t game_object_id; + //! Reference to parent component + OptionalRef<bool> active; + //! Mediator reference + OptionalRef<Mediator> mediator; + //! \} private: //! Flag to indicate if \c init() has been called already bool initialized = false; + //! List of subscribed events + std::vector<subscription_t> listeners; }; +/** + * \brief Subscribe to CollisionEvent for the current GameObject + * + * This is a template specialization for Script::subscribe which automatically sets the event + * channel so the callback handler is only called for CollisionEvent events that apply to the + * current GameObject the parent BehaviorScript is attached to. + */ +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback); +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback, event_channel_t) + = delete; +/** + * \brief Subscribe to ButtonPressEvent for the current GameObject + * + * This is a template specialization for Script::subscribe which automatically sets the event + * channel so the callback handler is only called for ButtonPressEvent events that apply to the + * current GameObject the parent BehaviorScript is attached to. + */ +template <> +void Script::subscribe(const EventHandler<ButtonPressEvent> & callback); +template <> +void Script::subscribe(const EventHandler<ButtonPressEvent> & callback, event_channel_t) + = delete; +/** + * \brief Subscribe to ButtonExitEvent for the current GameObject + * + * This is a template specialization for Script::subscribe which automatically sets the event + * channel so the callback handler is only called for ButtonExitEvent events that apply to the + * current GameObject the parent BehaviorScript is attached to. + */ +template <> +void Script::subscribe(const EventHandler<ButtonExitEvent> & callback); +template <> +void Script::subscribe(const EventHandler<ButtonExitEvent> & callback, event_channel_t) + = delete; +/** + * \brief Subscribe to ButtonEnterEvent for the current GameObject + * + * This is a template specialization for Script::subscribe which automatically sets the event + * channel so the callback handler is only called for ButtonEnterEvent events that apply to the + * current GameObject the parent BehaviorScript is attached to. + */ +template <> +void Script::subscribe(const EventHandler<ButtonEnterEvent> & callback); +template <> +void Script::subscribe(const EventHandler<ButtonEnterEvent> & callback, event_channel_t) + = delete; } // namespace crepe #include "Script.hpp" diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index a85d814..c7fa6ff 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -1,6 +1,7 @@ #pragma once -#include "../ComponentManager.h" +#include "../manager/ComponentManager.h" +#include "../manager/ReplayManager.h" #include "BehaviorScript.h" #include "Script.h" @@ -13,15 +14,95 @@ T & Script::get_component() const { RefVector<T> all_components = this->get_components<T>(); if (all_components.size() < 1) throw runtime_error( - format("Script: no component found with type = {}", typeid(T).name())); + format("Script: no component found with type = {}", typeid(T).name()) + ); return all_components.back().get(); } template <typename T> RefVector<T> Script::get_components() const { - auto & mgr = *this->component_manager_ref; - return mgr.get_components_by_id<T>(this->game_object_id); + return this->get_components_by_id<T>(this->game_object_id); +} + +template <class... Args> +void Script::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { + Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Script::logf(std::format_string<Args...> fmt, Args &&... args) { + Log::logf(fmt, std::forward<Args>(args)...); +} + +template <typename EventType> +void Script::subscribe_internal( + const EventHandler<EventType> & callback, event_channel_t channel +) { + EventManager & mgr = this->mediator->event_manager; + subscription_t listener = mgr.subscribe<EventType>( + [this, callback](const EventType & data) -> bool { + // check if (parent) BehaviorScript component is active + bool & active = this->active; + if (!active) return false; + + // check if replay manager is playing (if initialized) + try { + ReplayManager & replay = this->mediator->replay_manager; + if (replay.get_state() == ReplayManager::PLAYING) return false; + } catch (const std::runtime_error &) { + } + + // call user-provided callback + return callback(data); + }, + channel + ); + this->listeners.push_back(listener); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) { + this->subscribe_internal(callback, channel); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback) { + this->subscribe_internal(callback, EventManager::CHANNEL_ALL); +} + +template <typename EventType> +void Script::trigger_event(const EventType & event, event_channel_t channel) { + EventManager & mgr = this->mediator->event_manager; + mgr.trigger_event(event, channel); +} + +template <typename EventType> +void Script::queue_event(const EventType & event, event_channel_t channel) { + EventManager & mgr = this->mediator->event_manager; + mgr.queue_event(event, channel); +} + +template <typename T> +RefVector<T> Script::get_components_by_id(game_object_id_t id) const { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + return mgr.get_components_by_id<T>(id); +} +template <typename T> +RefVector<T> Script::get_components_by_name(const std::string & name) const { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + return mgr.get_components_by_name<T>(name); +} +template <typename T> +RefVector<T> Script::get_components_by_tag(const std::string & tag) const { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + return mgr.get_components_by_tag<T>(tag); } } // namespace crepe diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index bd2d5cf..3c77e2e 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -1,25 +1,34 @@ -#include <memory> +#include <cmath> -#include "../util/Log.h" -#include "facade/SDLContext.h" +#include "../util/dbg.h" +#include "api/Asset.h" #include "Component.h" #include "Sprite.h" -#include "Texture.h" +#include "types.h" using namespace std; using namespace crepe; -Sprite::Sprite(game_object_id_t id, const shared_ptr<Texture> image, const Color & color, - const FlipSettings & flip) +Sprite::Sprite(game_object_id_t id, const Asset & texture, const Sprite::Data & data) : Component(id), - color(color), - flip(flip), - sprite_image(image) { - dbg_trace(); + source(texture), + data(data) { - this->sprite_rect.w = sprite_image->get_width(); - this->sprite_rect.h = sprite_image->get_height(); + dbg_trace(); } Sprite::~Sprite() { dbg_trace(); } + +unique_ptr<Component> Sprite::save() const { return unique_ptr<Component>(new Sprite(*this)); } + +void Sprite::restore(const Component & snapshot) { + *this = static_cast<const Sprite &>(snapshot); +} + +Sprite & Sprite::operator=(const Sprite & snapshot) { + this->active = snapshot.active; + this->data = snapshot.data; + this->mask = snapshot.mask; + return *this; +} diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index 74a55d4..3565bed 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -1,26 +1,13 @@ #pragma once -#include <memory> - #include "../Component.h" +#include "api/Asset.h" #include "Color.h" -#include "Texture.h" +#include "types.h" namespace crepe { -struct Rect { - int w = 0; - int h = 0; - int x = 0; - int y = 0; -}; - -struct FlipSettings { - bool flip_x = false; - bool flip_y = false; -}; - class SDLContext; class Animator; class AnimatorSystem; @@ -32,58 +19,112 @@ class AnimatorSystem; * flip settings, and is managed in layers with defined sorting orders. */ class Sprite : public Component { +public: + //! settings to flip the image + struct FlipSettings { + //! horizantal flip + bool flip_x = false; + //! vertical flip + bool flip_y = false; + }; + + //! Sprite data that does not have to be set in the constructor + struct Data { + /** + * \brief Sprite tint (multiplied) + * + * The sprite texture's pixels are multiplied by this color before being displayed + * (including alpha channel for transparency). + */ + Color color = Color::WHITE; + + //! Flip settings for the sprite + FlipSettings flip; + + //! Layer sorting level of the sprite + int sorting_in_layer = 0; + + //! Order within the sorting layer + int order_in_layer = 0; + + /** + * \brief width and height of the sprite in game units + * + * - if exclusively width is specified, the height is calculated using the texture's aspect + * ratio + * - if exclusively height is specified, the width is calculated using the texture's aspect + * ratio + * - if both are specified the texture is streched to fit the specified size + */ + vec2 size = {0, 0}; + + //! independent sprite angle. rotating clockwise direction in degrees + float angle_offset = 0; + + //! independent sprite scale multiplier + float scale_offset = 1; + + //! independent sprite offset position + vec2 position_offset; + + /** + * \brief gives the user the option to render this in world space or in camera space + * + * - if true will this be rendered in world space this means that the sprite can be + * rendered off the screen + * - if false --> will the sprite be rendered in camera space. this means that the + * coordinates given on the \c Sprite and \c Transform will be inside the camera + */ + bool world_space = true; + }; public: - // TODO: Loek comment in github #27 will be looked another time - // about shared_ptr Texture /** - * \brief Constructs a Sprite with specified parameters. * \param game_id Unique identifier for the game object this sprite belongs to. - * \param image Shared pointer to the texture for this sprite. - * \param color Color tint applied to the sprite. - * \param flip Flip settings for horizontal and vertical orientation. - */ - Sprite(game_object_id_t id, const std::shared_ptr<Texture> image, const Color & color, - const FlipSettings & flip); - - /** - * \brief Destroys the Sprite instance. + * \param texture asset of the image + * \param ctx all the sprite data */ + Sprite(game_object_id_t id, const Asset & texture, const Data & data); ~Sprite(); //! Texture used for the sprite - const std::shared_ptr<Texture> sprite_image; - //! Color tint of the sprite - Color color; - //! Flip settings for the sprite - FlipSettings flip; - //! Layer sorting level of the sprite - uint8_t sorting_in_layer = 0; - //! Order within the sorting layer - uint8_t order_in_layer = 0; + const Asset source; -public: - /** - * \brief Gets the maximum number of instances allowed for this sprite. - * \return Maximum instance count as an integer. - * - * For now is this number randomly picked. I think it will eventually be 1. - */ - virtual int get_instances_max() const { return 10; } + Data data; private: - //! Reads the sprite_rect of sprite + //! Reads the mask of sprite friend class SDLContext; - //! Reads the all the variables plus the sprite_rect + //! Reads the all the variables plus the mask friend class Animator; - //! Reads the all the variables plus the sprite_rect + //! Reads the all the variables plus the mask friend class AnimatorSystem; + /** + * \aspect_ratio the ratio of the sprite image + * + * - this value will only be set by the \c Animator component for the ratio of the Animation + * - if \c Animator component is not added it will not use this ratio (because 0) and will use aspect_ratio of the Asset. + */ + float aspect_ratio = 0; + + struct Rect { + int w = 0; + int h = 0; + int x = 0; + int y = 0; + }; //! Render area of the sprite this will also be adjusted by the AnimatorSystem if an Animator - // object is present in GameObject - Rect sprite_rect; + // object is present in GameObject. this is in sprite pixels + Rect mask; + +protected: + virtual std::unique_ptr<Component> save() const; + Sprite(const Sprite &) = default; + virtual void restore(const Component & snapshot); + virtual Sprite & operator=(const Sprite &); }; } // namespace crepe diff --git a/src/crepe/api/Text.cpp b/src/crepe/api/Text.cpp new file mode 100644 index 0000000..e5cc39d --- /dev/null +++ b/src/crepe/api/Text.cpp @@ -0,0 +1,27 @@ +#include "../types.h" + +#include "Text.h" + +using namespace crepe; +using namespace std; + +Text::Text( + game_object_id_t id, const vec2 & dimensions, const std::string & font_family, + const Data & data, const vec2 & offset, const std::string & text +) + : UIObject(id, dimensions, offset), + text(text), + data(data), + font_family(font_family) {} + +unique_ptr<Component> Text::save() const { return unique_ptr<Component>(new Text(*this)); } + +void Text::restore(const Component & snapshot) { *this = static_cast<const Text &>(snapshot); } + +Text & Text::operator=(const Text & snapshot) { + this->active = snapshot.active; + this->data = snapshot.data; + this->text = snapshot.text; + this->font_family = snapshot.font_family; + return *this; +} diff --git a/src/crepe/api/Text.h b/src/crepe/api/Text.h new file mode 100644 index 0000000..859490e --- /dev/null +++ b/src/crepe/api/Text.h @@ -0,0 +1,60 @@ +#pragma once + +#include <optional> +#include <string> + +#include "../types.h" + +#include "Asset.h" +#include "Color.h" +#include "UIObject.h" + +namespace crepe { +/** + * \brief Text UIObject component for displaying text + * + * This class can be used to display text on screen. By setting the font_family to a font already stored on the current device it will automatically be loaded in. + */ +class Text : public UIObject { +public: + //! Text data that does not have to be set in the constructor + struct Data { + //! variable indicating if transform is relative to camera(false) or world(true) + bool world_space = false; + + //! Label text color. + Color text_color = Color::BLACK; + }; + +public: + /** + * + * \param dimensions Width and height of the UIObject. + * \param offset Offset of the UIObject relative to its transform + * \param text The text to be displayed. + * \param font_family The font style name to be displayed. + * \param data Data struct containing extra text parameters. + * \param font Optional font asset that can be passed or left empty. + */ + Text( + game_object_id_t id, const vec2 & dimensions, const std::string & font_family, + const Data & data, const vec2 & offset = {0, 0}, const std::string & text = "" + ); + + //! Label text. + std::string text = ""; + //! font family name + std::string font_family = ""; + //! Font asset variable if this is not set, it will use the font_family to create an asset. + std::optional<Asset> font; + //! Data instance + Data data; + +protected: + virtual std::unique_ptr<Component> save() const; + Text(const Text &) = default; + virtual void restore(const Component & snapshot); + virtual Text & operator=(const Text &); +}; + +} // namespace crepe diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp deleted file mode 100644 index 9be9421..0000000 --- a/src/crepe/api/Texture.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include <SDL2/SDL_render.h> - -#include "../facade/SDLContext.h" -#include "../util/Log.h" - -#include "Asset.h" -#include "Texture.h" - -using namespace crepe; -using namespace std; - -Texture::Texture(unique_ptr<Asset> res) { - dbg_trace(); - this->load(std::move(res)); -} - -Texture::Texture(const char * src) { - dbg_trace(); - this->load(make_unique<Asset>(src)); -} - -Texture::~Texture() { - dbg_trace(); - this->texture.reset(); -} - -void Texture::load(unique_ptr<Asset> res) { - SDLContext & ctx = SDLContext::get_instance(); - this->texture = std::move(ctx.texture_from_path(res->get_path())); -} - -int Texture::get_width() const { - if (this->texture == nullptr) return 0; - return SDLContext::get_instance().get_width(*this); -} -int Texture::get_height() const { - if (this->texture == nullptr) return 0; - return SDLContext::get_instance().get_height(*this); -} diff --git a/src/crepe/api/Texture.h b/src/crepe/api/Texture.h deleted file mode 100644 index 6965223..0000000 --- a/src/crepe/api/Texture.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -// FIXME: this header can't be included because this is an API header, and SDL2 development -// headers won't be bundled with crepe. Why is this facade in the API namespace? - -#include <SDL2/SDL_render.h> -#include <functional> -#include <memory> - -#include "Asset.h" - -namespace crepe { - -class SDLContext; -class Animator; - -/** - * \class Texture - * \brief Manages texture loading and properties. - * - * The Texture class is responsible for loading an image from a source and providing access to - * its dimensions. Textures can be used for rendering. - */ -class Texture { - -public: - /** - * \brief Constructs a Texture from a file path. - * \param src Path to the image file to be loaded as a texture. - */ - Texture(const char * src); - - /** - * \brief Constructs a Texture from an Asset resource. - * \param res Unique pointer to an Asset resource containing texture data. - */ - Texture(std::unique_ptr<Asset> res); - - /** - * \brief Destroys the Texture instance, freeing associated resources. - */ - ~Texture(); - // FIXME: this constructor shouldn't be necessary because this class doesn't manage memory - - /** - * \brief Gets the width of the texture. - * \return Width of the texture in pixels. - */ - int get_width() const; - - /** - * \brief Gets the height of the texture. - * \return Height of the texture in pixels. - */ - int get_height() const; - -private: - /** - * \brief Loads the texture from an Asset resource. - * \param res Unique pointer to an Asset resource to load the texture from. - */ - void load(std::unique_ptr<Asset> res); - -private: - //! The texture of the class from the library - std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture; - - //! Grants SDLContext access to private members. - friend class SDLContext; - - //! Grants Animator access to private members. - friend class Animator; -}; - -} // namespace crepe diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp index cd944bd..b70174c 100644 --- a/src/crepe/api/Transform.cpp +++ b/src/crepe/api/Transform.cpp @@ -1,13 +1,22 @@ -#include "../util/Log.h" +#include "../util/dbg.h" #include "Transform.h" using namespace crepe; +using namespace std; -Transform::Transform(game_object_id_t id, const Vector2 & point, double rotation, double scale) +Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale) : Component(id), position(point), rotation(rotation), scale(scale) { dbg_trace(); } + +unique_ptr<Component> Transform::save() const { + return unique_ptr<Component> {new Transform(*this)}; +} + +void Transform::restore(const Component & snapshot) { + *this = static_cast<const Transform &>(snapshot); +} diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h index 18aa293..a6f3486 100644 --- a/src/crepe/api/Transform.h +++ b/src/crepe/api/Transform.h @@ -1,25 +1,24 @@ #pragma once -#include "api/Vector2.h" - #include "Component.h" +#include "types.h" namespace crepe { /** * \brief Transform component - * + * * This class represents the Transform component. It stores the position, rotation and scale of * a GameObject. */ class Transform : public Component { public: //! Translation (shift) - Vector2 position = {0, 0}; - //! Rotation, in degrees - double rotation = 0; + vec2 position = {0, 0}; + //! Rotation, in degrees clockwise + float rotation = 0; //! Multiplication factor - double scale = 0; + float scale = 0; protected: /** @@ -28,7 +27,7 @@ protected: * \param rotation The rotation of the GameObject * \param scale The scale of the GameObject */ - Transform(game_object_id_t id, const Vector2 & point, double rotation, double scale); + Transform(game_object_id_t id, const vec2 & point, double rotation, double scale); /** * There is always exactly one transform component per entity * \return 1 @@ -36,6 +35,12 @@ protected: virtual int get_instances_max() const { return 1; } //! ComponentManager instantiates all components friend class ComponentManager; + +protected: + virtual std::unique_ptr<Component> save() const; + Transform(const Transform &) = default; + virtual void restore(const Component & snapshot); + virtual Transform & operator=(const Transform &) = default; }; } // namespace crepe diff --git a/src/crepe/api/UIObject.cpp b/src/crepe/api/UIObject.cpp new file mode 100644 index 0000000..d239b89 --- /dev/null +++ b/src/crepe/api/UIObject.cpp @@ -0,0 +1,8 @@ +#include "UIObject.h" + +using namespace crepe; + +UIObject::UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset) + : Component(id), + dimensions(dimensions), + offset(offset) {} diff --git a/src/crepe/api/UIObject.h b/src/crepe/api/UIObject.h new file mode 100644 index 0000000..0d9b1f7 --- /dev/null +++ b/src/crepe/api/UIObject.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../Component.h" + +namespace crepe { + +/** + * \brief Represents a UI object in the game, derived from the Component class. + */ +class UIObject : public Component { +public: + /** + * \brief Constructs a UiObject with the specified game object ID. + * \param id The unique ID of the game object associated with this UI object. + * \param dimensions width and height of the UIObject + * \param offset Offset relative to the GameObject Transform + */ + UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset = {0, 0}); + //! Width and height of the UIObject + vec2 dimensions; + //! Position offset relative to this GameObjects Transform + vec2 offset; +}; + +} // namespace crepe diff --git a/src/crepe/api/Vector2.cpp b/src/crepe/api/Vector2.cpp deleted file mode 100644 index 30b968e..0000000 --- a/src/crepe/api/Vector2.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "Vector2.h" - -using namespace crepe; - -Vector2 Vector2::operator-(const Vector2 & other) const { return {x - other.x, y - other.y}; } - -Vector2 Vector2::operator+(const Vector2 & other) const { return {x + other.x, y + other.y}; } - -Vector2 Vector2::operator*(double scalar) const { return {x * scalar, y * scalar}; } - -Vector2 & Vector2::operator*=(const Vector2 & other) { - x *= other.x; - y *= other.y; - return *this; -} - -Vector2 & Vector2::operator+=(const Vector2 & other) { - x += other.x; - y += other.y; - return *this; -} - -Vector2 & Vector2::operator+=(double other) { - x += other; - y += other; - return *this; -} - -Vector2 Vector2::operator-() const { return {-x, -y}; } - -bool Vector2::operator==(const Vector2 & other) const { return x == other.x && y == other.y; } - -bool Vector2::operator!=(const Vector2 & other) const { return !(*this == other); } diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index 2fb6136..6613641 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -1,40 +1,110 @@ #pragma once +#include <format> + namespace crepe { //! 2D vector +template <class T> struct Vector2 { //! X component of the vector - double x = 0; + T x = 0; //! Y component of the vector - double y = 0; + T y = 0; //! Subtracts another vector from this vector and returns the result. - Vector2 operator-(const Vector2 & other) const; + Vector2<T> operator-(const Vector2<T> & other) const; + + //! Subtracts a scalar value from both components of this vector and returns the result. + Vector2<T> operator-(T scalar) const; //! Adds another vector to this vector and returns the result. - Vector2 operator+(const Vector2 & other) const; + Vector2<T> operator+(const Vector2<T> & other) const; + + //! Adds a scalar value to both components of this vector and returns the result. + Vector2<T> operator+(T scalar) const; + + //! Multiplies this vector by another vector element-wise and returns the result. + Vector2<T> operator*(const Vector2<T> & other) const; //! Multiplies this vector by a scalar and returns the result. - Vector2 operator*(double scalar) const; + Vector2<T> operator*(T scalar) const; - //! Multiplies this vector by another vector element-wise and updates this vector. - Vector2 & operator*=(const Vector2 & other); + //! Divides this vector by another vector element-wise and returns the result. + Vector2<T> operator/(const Vector2<T> & other) const; + + //! Divides this vector by a scalar and returns the result. + Vector2<T> operator/(T scalar) const; //! Adds another vector to this vector and updates this vector. - Vector2 & operator+=(const Vector2 & other); + Vector2<T> & operator+=(const Vector2<T> & other); //! Adds a scalar value to both components of this vector and updates this vector. - Vector2 & operator+=(double other); + Vector2<T> & operator+=(T other); + + //! Subtracts another vector from this vector and updates this vector. + Vector2<T> & operator-=(const Vector2<T> & other); + + //! Subtracts a scalar value from both components of this vector and updates this vector. + Vector2<T> & operator-=(T other); + + //! Multiplies this vector by another vector element-wise and updates this vector. + Vector2<T> & operator*=(const Vector2<T> & other); + + //! Multiplies this vector by a scalar and updates this vector. + Vector2<T> & operator*=(T other); + + //! Divides this vector by another vector element-wise and updates this vector. + Vector2<T> & operator/=(const Vector2<T> & other); + + //! Divides this vector by a scalar and updates this vector. + Vector2<T> & operator/=(T other); //! Returns the negation of this vector. - Vector2 operator-() const; + Vector2<T> operator-() const; //! Checks if this vector is equal to another vector. - bool operator==(const Vector2 & other) const; + bool operator==(const Vector2<T> & other) const; //! Checks if this vector is not equal to another vector. - bool operator!=(const Vector2 & other) const; + bool operator!=(const Vector2<T> & other) const; + + //! Truncates the vector to a maximum length. + void truncate(T max); + + //! Normalizes the vector (resulting in vector with a length of 1). + void normalize(); + + //! Returns the length of the vector. + T length() const; + + //! Returns the squared length of the vector. + T length_squared() const; + + //! Returns the dot product (inwendig product) of this vector and another vector. + T dot(const Vector2<T> & other) const; + + //! Returns the distance between this vector and another vector. + T distance(const Vector2<T> & other) const; + + //! Returns the squared distance between this vector and another vector. + T distance_squared(const Vector2<T> & other) const; + + //! Returns the perpendicular vector to this vector. + Vector2<T> perpendicular() const; + + //! Checks if both components of the vector are NaN. + bool is_nan() const; + + //! Rotate this vector clockwise by \c deg degrees + Vector2<T> rotate(float deg) const; }; } // namespace crepe + +template <typename T> +struct std::formatter<crepe::Vector2<T>> : std::formatter<std::string> { + format_context::iterator format(crepe::Vector2<T> vec, format_context & ctx) const; +}; + +#include "Vector2.hpp" diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp new file mode 100644 index 0000000..30441d2 --- /dev/null +++ b/src/crepe/api/Vector2.hpp @@ -0,0 +1,186 @@ +#pragma once + +#include <cmath> + +#include "Vector2.h" + +namespace crepe { + +template <class T> +Vector2<T> Vector2<T>::operator-(const Vector2<T> & other) const { + return {x - other.x, y - other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator-(T scalar) const { + return {x - scalar, y - scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator+(const Vector2<T> & other) const { + return {x + other.x, y + other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator+(T scalar) const { + return {x + scalar, y + scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator*(const Vector2<T> & other) const { + return {x * other.x, y * other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator*(T scalar) const { + return {x * scalar, y * scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator/(const Vector2<T> & other) const { + return {x / other.x, y / other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator/(T scalar) const { + return {x / scalar, y / scalar}; +} + +template <class T> +Vector2<T> & Vector2<T>::operator+=(const Vector2<T> & other) { + x += other.x; + y += other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator+=(T other) { + x += other; + y += other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator-=(const Vector2<T> & other) { + x -= other.x; + y -= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator-=(T other) { + x -= other; + y -= other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator*=(const Vector2<T> & other) { + x *= other.x; + y *= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator*=(T other) { + x *= other; + y *= other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator/=(const Vector2<T> & other) { + x /= other.x; + y /= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator/=(T other) { + x /= other; + y /= other; + return *this; +} + +template <class T> +Vector2<T> Vector2<T>::operator-() const { + return {-x, -y}; +} + +template <class T> +bool Vector2<T>::operator==(const Vector2<T> & other) const { + return x == other.x && y == other.y; +} + +template <class T> +bool Vector2<T>::operator!=(const Vector2<T> & other) const { + return !(*this == other); +} + +template <class T> +void Vector2<T>::truncate(T max) { + if (length() > max) { + normalize(); + *this *= max; + } +} + +template <class T> +void Vector2<T>::normalize() { + T len = length(); + if (len > 0) { + *this /= len; + } +} + +template <class T> +T Vector2<T>::length() const { + return std::sqrt(x * x + y * y); +} + +template <class T> +T Vector2<T>::length_squared() const { + return x * x + y * y; +} + +template <class T> +T Vector2<T>::dot(const Vector2<T> & other) const { + return x * other.x + y * other.y; +} + +template <class T> +T Vector2<T>::distance(const Vector2<T> & other) const { + return (*this - other).length(); +} + +template <class T> +T Vector2<T>::distance_squared(const Vector2<T> & other) const { + return (*this - other).length_squared(); +} + +template <class T> +Vector2<T> Vector2<T>::perpendicular() const { + return {-y, x}; +} + +template <class T> +bool Vector2<T>::is_nan() const { + return std::isnan(x) && std::isnan(y); +} + +template <class T> +Vector2<T> Vector2<T>::rotate(float deg) const { + float rad = -deg / 180 * M_PI; + return { + x * std::cos(rad) - y * std::sin(rad), + x * std::sin(rad) + y * std::cos(rad), + }; +} + +} // namespace crepe + +template <typename T> +std::format_context::iterator +std::formatter<crepe::Vector2<T>>::format(crepe::Vector2<T> vec, format_context & ctx) const { + return formatter<string>::format(std::format("{{{}, {}}}", vec.x, vec.y), ctx); +} diff --git a/src/crepe/facade/CMakeLists.txt b/src/crepe/facade/CMakeLists.txt index 4cc53bc..243ae46 100644 --- a/src/crepe/facade/CMakeLists.txt +++ b/src/crepe/facade/CMakeLists.txt @@ -1,14 +1,20 @@ target_sources(crepe PUBLIC Sound.cpp + Texture.cpp SoundContext.cpp SDLContext.cpp DB.cpp + FontFacade.cpp + Font.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES Sound.h + Texture.h SoundContext.h SDLContext.h DB.h + FontFacade.h + Font.h ) diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp index 95cf606..7a3e473 100644 --- a/src/crepe/facade/DB.cpp +++ b/src/crepe/facade/DB.cpp @@ -1,6 +1,6 @@ #include <cstring> -#include "util/Log.h" +#include "util/dbg.h" #include "DB.h" @@ -21,12 +21,6 @@ DB::DB(const string & path) { const char * file = path.empty() ? NULL : path.c_str(); ret = this->db->open(this->db.get(), NULL, file, NULL, libdb::DB_BTREE, DB_CREATE, 0); if (ret != 0) throw runtime_error(format("db->open: {}", libdb::db_strerror(ret))); - - // create cursor - libdb::DBC * cursor; - ret = this->db->cursor(this->db.get(), NULL, &cursor, 0); - if (ret != 0) throw runtime_error(format("db->cursor: {}", libdb::db_strerror(ret))); - this->cursor = {cursor, [](libdb::DBC * cursor) { cursor->close(cursor); }}; } libdb::DBT DB::to_thing(const string & thing) const noexcept { @@ -42,10 +36,10 @@ string DB::get(const string & key) { libdb::DBT db_val; memset(&db_val, 0, sizeof(libdb::DBT)); - int ret = this->cursor->get(this->cursor.get(), &db_key, &db_val, DB_FIRST); + int ret = this->db->get(this->db.get(), NULL, &db_key, &db_val, 0); if (ret == 0) return {static_cast<char *>(db_val.data), db_val.size}; - string err = format("cursor->get: {}", libdb::db_strerror(ret)); + string err = format("db->get: {}", libdb::db_strerror(ret)); if (ret == DB_NOTFOUND) throw out_of_range(err); else throw runtime_error(err); } @@ -54,7 +48,7 @@ void DB::set(const string & key, const string & value) { libdb::DBT db_key = this->to_thing(key); libdb::DBT db_val = this->to_thing(value); int ret = this->db->put(this->db.get(), NULL, &db_key, &db_val, 0); - if (ret != 0) throw runtime_error(format("cursor->get: {}", libdb::db_strerror(ret))); + if (ret != 0) throw runtime_error(format("db->get: {}", libdb::db_strerror(ret))); } bool DB::has(const std::string & key) { diff --git a/src/crepe/facade/DB.h b/src/crepe/facade/DB.h index 115c0f1..84cdf19 100644 --- a/src/crepe/facade/DB.h +++ b/src/crepe/facade/DB.h @@ -61,8 +61,6 @@ public: private: //! RAII wrapper around \c DB struct std::unique_ptr<libdb::DB, std::function<void(libdb::DB *)>> db; - //! RAII wrapper around \c DBC struct - std::unique_ptr<libdb::DBC, std::function<void(libdb::DBC *)>> cursor; private: /** diff --git a/src/crepe/facade/EventData.h b/src/crepe/facade/EventData.h new file mode 100644 index 0000000..a7526b4 --- /dev/null +++ b/src/crepe/facade/EventData.h @@ -0,0 +1,54 @@ +#pragma once +#include "../api/KeyCodes.h" +#include "../types.h" +namespace crepe { +//! EventType enum for passing eventType +enum EventType { + NONE = 0, + MOUSE_DOWN, + MOUSE_UP, + MOUSE_MOVE, + MOUSE_WHEEL, + KEY_UP, + KEY_DOWN, + SHUTDOWN, + WINDOW_MINIMIZE, + WINDOW_MAXIMIZE, + WINDOW_FOCUS_GAIN, + WINDOW_FOCUS_LOST, + WINDOW_MOVE, + WINDOW_RESIZE, + WINDOW_EXPOSE, +}; + +//! Struct for storing key data. +struct KeyData { + Keycode key = Keycode::NONE; + bool key_repeat = false; +}; + +//! Struct for storing mouse data. +struct MouseData { + MouseButton mouse_button = MouseButton::NONE; + ivec2 mouse_position = {-1, -1}; + int scroll_direction = -1; + float scroll_delta = INFINITY; + ivec2 rel_mouse_move = {-1, -1}; +}; + +//! Struct for storing window data. +struct WindowData { + ivec2 move_delta; + ivec2 resize_dimension; +}; + +//! EventData struct for passing event data from facade +struct EventData { + EventType event_type = EventType::NONE; + union { + KeyData key_data; + MouseData mouse_data; + WindowData window_data; + } data; +}; +} // namespace crepe diff --git a/src/crepe/facade/Font.cpp b/src/crepe/facade/Font.cpp new file mode 100644 index 0000000..771002f --- /dev/null +++ b/src/crepe/facade/Font.cpp @@ -0,0 +1,21 @@ +#include <SDL2/SDL_ttf.h> + +#include "../api/Asset.h" +#include "../api/Config.h" + +#include "Font.h" + +using namespace std; +using namespace crepe; + +Font::Font(const Asset & src, Mediator & mediator) : Resource(src, mediator) { + const Config & config = Config::get_instance(); + const std::string FONT_PATH = src.get_path(); + TTF_Font * loaded_font = TTF_OpenFont(FONT_PATH.c_str(), config.font.size); + if (loaded_font == NULL) { + throw runtime_error(format("Font: {} (path: {})", TTF_GetError(), FONT_PATH)); + } + this->font = {loaded_font, [](TTF_Font * close_font) { TTF_CloseFont(close_font); }}; +} + +TTF_Font * Font::get_font() const { return this->font.get(); } diff --git a/src/crepe/facade/Font.h b/src/crepe/facade/Font.h new file mode 100644 index 0000000..b208d96 --- /dev/null +++ b/src/crepe/facade/Font.h @@ -0,0 +1,42 @@ +#pragma once + +#include <SDL2/SDL_ttf.h> +#include <memory> + +#include "../Resource.h" +#include "../api/Config.h" + +namespace crepe { + +class Asset; +/** + * \brief Resource for managing font creation and destruction + * + * This class is a wrapper around an SDL_ttf font instance, encapsulating font loading and usage. + * It loads a font from an Asset and manages its lifecycle. The font is automatically unloaded + * when this object is destroyed. + */ +class Font : public Resource { + +public: + /** + * \param src The Asset containing the font file path and metadata to load the font. + * \param mediator The Mediator object used for managing the SDL context or related systems. + */ + Font(const Asset & src, Mediator & mediator); + /** + * \brief Gets the underlying TTF_Font resource. + * + * This function returns the raw pointer to the SDL_ttf TTF_Font object that represents + * the loaded font. This can be used with SDL_ttf functions to render text. + * + * \return The raw TTF_Font object wrapped in a unique pointer. + */ + TTF_Font * get_font() const; + +private: + //! The SDL_ttf font object with custom deleter. + std::unique_ptr<TTF_Font, std::function<void(TTF_Font *)>> font = nullptr; +}; + +} // namespace crepe diff --git a/src/crepe/facade/FontFacade.cpp b/src/crepe/facade/FontFacade.cpp new file mode 100644 index 0000000..e284f5a --- /dev/null +++ b/src/crepe/facade/FontFacade.cpp @@ -0,0 +1,44 @@ +#include <fontconfig/fontconfig.h> +#include <functional> +#include <memory> +#include <stdexcept> +#include <string> + +#include "FontFacade.h" + +using namespace std; +using namespace crepe; + +FontFacade::FontFacade() { + if (!FcInit()) throw runtime_error("Failed to initialize Fontconfig."); +} + +FontFacade::~FontFacade() { FcFini(); } + +Asset FontFacade::get_font_asset(const string & font_family) { + FcPattern * raw_pattern + = FcNameParse(reinterpret_cast<const FcChar8 *>(font_family.c_str())); + if (raw_pattern == NULL) throw runtime_error("Failed to create font pattern."); + + unique_ptr<FcPattern, function<void(FcPattern *)>> pattern { + raw_pattern, [](FcPattern * p) { FcPatternDestroy(p); } + }; + + FcConfig * config = FcConfigGetCurrent(); + if (config == NULL) throw runtime_error("Failed to get current Fontconfig configuration."); + + FcResult result; + FcPattern * raw_matched_pattern = FcFontMatch(config, pattern.get(), &result); + if (raw_matched_pattern == NULL) throw runtime_error("No matching font found."); + + unique_ptr<FcPattern, function<void(FcPattern *)>> matched_pattern + = {raw_matched_pattern, [](FcPattern * p) { FcPatternDestroy(p); }}; + + FcChar8 * file_path = nullptr; + FcResult res = FcPatternGetString(matched_pattern.get(), FC_FILE, 0, &file_path); + if (res != FcResultMatch || file_path == NULL) + throw runtime_error("Failed to get font file path."); + + string font_file_path = reinterpret_cast<const char *>(file_path); + return Asset(font_file_path); +} diff --git a/src/crepe/facade/FontFacade.h b/src/crepe/facade/FontFacade.h new file mode 100644 index 0000000..9761070 --- /dev/null +++ b/src/crepe/facade/FontFacade.h @@ -0,0 +1,34 @@ +#pragma once + +#include <memory> + +#include "../api/Asset.h" + +namespace crepe { + +/** + * + * \brief Font facade class for converting font family names to absolute file paths + * + */ +class FontFacade { +public: + FontFacade(); + ~FontFacade(); + FontFacade(const FontFacade & other) = delete; + FontFacade & operator=(const FontFacade & other) = delete; + FontFacade(FontFacade && other) noexcept = delete; + FontFacade & operator=(FontFacade && other) noexcept = delete; + /** + * + * \brief Facade function to convert a font_family into an asset. + * + * This function uses the FontConfig library to convert a font family name (Arial, Inter, Helvetica) and converts it to the font source path. + * This function returns a default font path if the font_family name doesnt exist or cant be found + * \param font_family Name of the font family name. + * \return Asset with filepath to the corresponding font. + */ + Asset get_font_asset(const std::string & font_family); +}; + +} // namespace crepe diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index 00523a6..6c93fb2 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -1,44 +1,50 @@ #include <SDL2/SDL.h> +#include <SDL2/SDL_blendmode.h> +#include <SDL2/SDL_hints.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_keycode.h> +#include <SDL2/SDL_pixels.h> #include <SDL2/SDL_rect.h> #include <SDL2/SDL_render.h> #include <SDL2/SDL_surface.h> +#include <SDL2/SDL_ttf.h> #include <SDL2/SDL_video.h> +#include <array> #include <cmath> #include <cstddef> #include <functional> #include <memory> #include <stdexcept> -#include <string> #include "../api/Camera.h" +#include "../api/Color.h" +#include "../api/Config.h" #include "../api/Sprite.h" -#include "../api/Texture.h" -#include "../api/Transform.h" -#include "../api/Vector2.h" -#include "../util/Log.h" +#include "../util/dbg.h" +#include "api/Text.h" +#include "api/Transform.h" +#include "facade/Font.h" +#include "manager/Mediator.h" +#include "util/AbsolutePosition.h" #include "SDLContext.h" +#include "Texture.h" +#include "types.h" using namespace crepe; using namespace std; -SDLContext & SDLContext::get_instance() { - static SDLContext instance; - return instance; -} - -SDLContext::SDLContext() { +SDLContext::SDLContext(Mediator & mediator) { dbg_trace(); - // FIXME: read window defaults from config manager - if (SDL_Init(SDL_INIT_VIDEO) != 0) { throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError())); } - SDL_Window * tmp_window - = SDL_CreateWindow("Crepe Game Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - this->viewport.w, this->viewport.h, 0); + + auto & cfg = Config::get_instance().window_settings; + SDL_Window * tmp_window = SDL_CreateWindow( + cfg.window_title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + cfg.default_size.x, cfg.default_size.y, 0 + ); if (!tmp_window) { throw runtime_error(format("SDLContext: SDL_Window error: {}", SDL_GetError())); } @@ -47,8 +53,8 @@ SDLContext::SDLContext() { SDL_Renderer * tmp_renderer = SDL_CreateRenderer(this->game_window.get(), -1, SDL_RENDERER_ACCELERATED); if (!tmp_renderer) { - throw runtime_error( - format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError())); + throw runtime_error(format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError()) + ); } this->game_renderer @@ -58,6 +64,12 @@ SDLContext::SDLContext() { if (!(IMG_Init(img_flags) & img_flags)) { throw runtime_error("SDLContext: SDL_image could not initialize!"); } + + if (TTF_Init() == -1) { + throw runtime_error(format("SDL_ttf initialization failed: {}", TTF_GetError())); + } + + mediator.sdl_context = *this; } SDLContext::~SDLContext() { @@ -69,96 +81,242 @@ SDLContext::~SDLContext() { // TODO: how are we going to ensure that these are called from the same // thread that SDL_Init() was called on? This has caused problems for me // before. + TTF_Quit(); IMG_Quit(); SDL_Quit(); } -void SDLContext::handle_events(bool & running) { - //TODO: wouter i need events - /* - SDL_Event event; - SDL_PollEvent(&event); - switch (event.type) { - case SDL_QUIT: - running = false; - break; - case SDL_KEYDOWN: - triggerEvent(KeyPressedEvent(getCustomKey(event.key.keysym.sym))); - break; - case SDL_MOUSEBUTTONDOWN: - int x, y; - SDL_GetMouseState(&x, &y); - triggerEvent(MousePressedEvent(x, y)); - break; + +Keycode SDLContext::sdl_to_keycode(SDL_Scancode sdl_key) { + if (!lookup_table.contains(sdl_key)) return Keycode::NONE; + return lookup_table.at(sdl_key); +} + +const keyboard_state_t & SDLContext::get_keyboard_state() { + SDL_PumpEvents(); + const Uint8 * current_state = SDL_GetKeyboardState(nullptr); + + for (int i = 0; i < SDL_NUM_SCANCODES; ++i) { + + Keycode key = sdl_to_keycode(static_cast<SDL_Scancode>(i)); + if (key != Keycode::NONE) { + this->keyboard_state[key] = current_state[i] != 0; + } } - */ + return this->keyboard_state; +} + +MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) { + static const std::array<MouseButton, 5> MOUSE_BUTTON_LOOKUP_TABLE = [] { + std::array<MouseButton, 5> table {}; + table.fill(MouseButton::NONE); + + table[SDL_BUTTON_LEFT] = MouseButton::LEFT_MOUSE; + table[SDL_BUTTON_RIGHT] = MouseButton::RIGHT_MOUSE; + table[SDL_BUTTON_MIDDLE] = MouseButton::MIDDLE_MOUSE; + table[SDL_BUTTON_X1] = MouseButton::X1_MOUSE; + table[SDL_BUTTON_X2] = MouseButton::X2_MOUSE; + + return table; + }(); + + if (sdl_button >= MOUSE_BUTTON_LOOKUP_TABLE.size()) { + // Return NONE for invalid or unmapped button + return MouseButton::NONE; + } + + return MOUSE_BUTTON_LOOKUP_TABLE[sdl_button]; } void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); } -void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); } - -SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { - return SDL_Rect{ - .x = sprite.sprite_rect.x, - .y = sprite.sprite_rect.y, - .w = sprite.sprite_rect.w, - .h = sprite.sprite_rect.h, - }; +void SDLContext::present_screen() { + SDL_SetRenderDrawColor(this->game_renderer.get(), 0, 0, 0, 255); + SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[0]); + SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[1]); + SDL_RenderPresent(this->game_renderer.get()); } -SDL_Rect SDLContext::get_dst_rect(const Sprite & sprite, const Vector2 & pos, - const double & scale, const Camera & cam) const { - double adjusted_x = (pos.x - cam.x) * cam.zoom; - double adjusted_y = (pos.y - cam.y) * cam.zoom; - double adjusted_w = sprite.sprite_rect.w * scale * cam.zoom; - double adjusted_h = sprite.sprite_rect.h * scale * cam.zoom; +SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const { + + const Sprite::Data & data = ctx.sprite.data; + + float aspect_ratio + = (ctx.sprite.aspect_ratio == 0) ? ctx.texture.get_ratio() : ctx.sprite.aspect_ratio; + + vec2 size = data.size; + vec2 screen_pos = ctx.pos; - return SDL_Rect{ - .x = static_cast<int>(adjusted_x), - .y = static_cast<int>(adjusted_y), - .w = static_cast<int>(adjusted_w), - .h = static_cast<int>(adjusted_h), + if (data.size.x == 0 && data.size.y != 0) { + size.x = data.size.y * aspect_ratio; + } + if (data.size.y == 0 && data.size.x != 0) { + size.y = data.size.x / aspect_ratio; + } + size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset; + + if (ctx.sprite.data.world_space) { + screen_pos = (screen_pos - cam_aux_data.cam_pos + cam_aux_data.zoomed_viewport / 2) + * cam_aux_data.render_scale + - size / 2 + cam_aux_data.bar_size; + } else { + screen_pos + = (screen_pos + cam_aux_data.zoomed_viewport / 2) * cam_aux_data.render_scale + - size / 2 + cam_aux_data.bar_size; + } + + return SDL_FRect { + .x = screen_pos.x, + .y = screen_pos.y, + .w = size.x, + .h = size.y, }; } -void SDLContext::draw_particle(const Sprite & sprite, const Vector2 & pos, - const double & angle, const double & scale, - const Camera & camera) { - +void SDLContext::draw(const RenderContext & ctx) { + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); + const Sprite::Data & data = ctx.sprite.data; SDL_RendererFlip render_flip - = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) - | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * data.flip.flip_x) + | (SDL_FLIP_VERTICAL * data.flip.flip_y)); + + SDL_Rect srcrect; + SDL_Rect * srcrect_ptr = NULL; + if (ctx.sprite.mask.w != 0 || ctx.sprite.mask.h != 0) { + srcrect.w = ctx.sprite.mask.w; + srcrect.h = ctx.sprite.mask.h; + srcrect.x = ctx.sprite.mask.x; + srcrect.y = ctx.sprite.mask.y; + srcrect_ptr = &srcrect; + } - SDL_Rect srcrect = this->get_src_rect(sprite); - SDL_Rect dstrect = this->get_dst_rect(sprite, pos, scale, camera); + SDL_FRect dstrect = this->get_dst_rect(SDLContext::DestinationRectangleData { + .sprite = ctx.sprite, + .texture = ctx.texture, + .pos = ctx.pos, + .img_scale = ctx.scale, + }); - SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect, - &dstrect, angle, NULL, render_flip); + double angle = ctx.angle + data.angle_offset; + + this->set_color_texture(ctx.texture, ctx.sprite.data.color); + SDL_RenderCopyExF( + this->game_renderer.get(), ctx.texture.get_img(), srcrect_ptr, &dstrect, angle, NULL, + render_flip + ); } -void SDLContext::draw(const Sprite & sprite, const Transform & transform, const Camera & cam) { +void SDLContext::draw_text(const RenderText & data) { + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); - SDL_RendererFlip render_flip - = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) - | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + const Text & text = data.text; + const Font & font = data.font; + vec2 absoluut_pos = AbsolutePosition::get_position(data.transform, data.text.offset); + std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> font_surface; + std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> font_texture; - SDL_Rect srcrect = this->get_src_rect(sprite); - SDL_Rect dstrect = this->get_dst_rect(sprite, transform.position, transform.scale, cam); + SDL_Color color { + .r = text.data.text_color.r, + .g = text.data.text_color.g, + .b = text.data.text_color.b, + .a = text.data.text_color.a, + }; + SDL_Surface * tmp_font_surface + = TTF_RenderText_Solid(font.get_font(), text.text.c_str(), color); + if (tmp_font_surface == NULL) { + throw runtime_error(format("draw_text: font surface error: {}", SDL_GetError())); + } + font_surface = {tmp_font_surface, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }}; - SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect, - &dstrect, transform.rotation, NULL, render_flip); -} + SDL_Texture * tmp_font_texture + = SDL_CreateTextureFromSurface(this->game_renderer.get(), font_surface.get()); + if (tmp_font_texture == NULL) { + throw runtime_error(format("draw_text: font texture error: {}", SDL_GetError())); + } + font_texture + = {tmp_font_texture, [](SDL_Texture * texture) { SDL_DestroyTexture(texture); }}; + + vec2 size = text.dimensions * cam_aux_data.render_scale * data.transform.scale; + vec2 screen_pos = absoluut_pos; + if (text.data.world_space) { + screen_pos = (screen_pos - cam_aux_data.cam_pos + (cam_aux_data.zoomed_viewport) / 2) + * cam_aux_data.render_scale + - size / 2 + cam_aux_data.bar_size; + } else { + screen_pos + = (screen_pos + (cam_aux_data.zoomed_viewport) / 2) * cam_aux_data.render_scale + - size / 2 + cam_aux_data.bar_size; + } -void SDLContext::set_camera(const Camera & cam) { - this->viewport.w = static_cast<int>(cam.aspect_width); - this->viewport.h = static_cast<int>(cam.aspect_height); - this->viewport.x = static_cast<int>(cam.x) - (this->viewport.w / 2); - this->viewport.y = static_cast<int>(cam.y) - (this->viewport.h / 2); + SDL_FRect dstrect { + .x = screen_pos.x, + .y = screen_pos.y, + .w = size.x, + .h = size.y, + }; - SDL_SetRenderDrawColor(this->game_renderer.get(), cam.bg_color.r, cam.bg_color.g, - cam.bg_color.b, cam.bg_color.a); + SDL_RenderCopyExF( + this->game_renderer.get(), font_texture.get(), NULL, &dstrect, data.transform.rotation, + NULL, SDL_FLIP_NONE + ); } -uint64_t SDLContext::get_ticks() const { return SDL_GetTicks64(); } +void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) { + + const Camera::Data & cam_data = cam.data; + // resize window + int w, h; + SDL_GetWindowSize(this->game_window.get(), &w, &h); + if (w != cam.screen.x || h != cam.screen.y) { + SDL_SetWindowSize(this->game_window.get(), cam.screen.x, cam.screen.y); + } + + vec2 & zoomed_viewport = this->cam_aux_data.zoomed_viewport; + vec2 & bar_size = this->cam_aux_data.bar_size; + vec2 & render_scale = this->cam_aux_data.render_scale; + this->cam_aux_data.cam_pos = new_pos; + + zoomed_viewport = cam.viewport_size * cam_data.zoom; + float screen_aspect = static_cast<float>(cam.screen.x) / cam.screen.y; + float viewport_aspect = zoomed_viewport.x / zoomed_viewport.y; + + // calculate black bars + if (screen_aspect > viewport_aspect) { + // pillarboxing + float scale = cam.screen.y / zoomed_viewport.y; + float adj_width = zoomed_viewport.x * scale; + float bar_width = (cam.screen.x - adj_width) / 2; + this->black_bars[0] = {0, 0, bar_width, (float) cam.screen.y}; + this->black_bars[1] = {(cam.screen.x - bar_width), 0, bar_width, (float) cam.screen.y}; + + bar_size = {bar_width, 0}; + render_scale.x = render_scale.y = scale; + } else { + // letterboxing + float scale = cam.screen.x / (cam.viewport_size.x * cam_data.zoom); + float adj_height = cam.viewport_size.y * scale; + float bar_height = (cam.screen.y - adj_height) / 2; + this->black_bars[0] = {0, 0, (float) cam.screen.x, bar_height}; + this->black_bars[1] + = {0, (cam.screen.y - bar_height), (float) cam.screen.x, bar_height}; + + bar_size = {0, bar_height}; + render_scale.x = render_scale.y = scale; + } + + SDL_SetRenderDrawColor( + this->game_renderer.get(), cam_data.bg_color.r, cam_data.bg_color.g, + cam_data.bg_color.b, cam_data.bg_color.a + ); + + SDL_Rect bg = { + .x = 0, + .y = 0, + .w = cam.screen.x, + .h = cam.screen.y, + }; + + // fill bg color + SDL_RenderFillRect(this->game_renderer.get(), &bg); +} std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> SDLContext::texture_from_path(const std::string & path) { @@ -180,16 +338,156 @@ SDLContext::texture_from_path(const std::string & path) { std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> img_texture; img_texture = {tmp_texture, [](SDL_Texture * texture) { SDL_DestroyTexture(texture); }}; + SDL_SetTextureBlendMode(img_texture.get(), SDL_BLENDMODE_BLEND); return img_texture; } -int SDLContext::get_width(const Texture & ctx) const { - int w; - SDL_QueryTexture(ctx.texture.get(), NULL, NULL, &w, NULL); - return w; + +ivec2 SDLContext::get_size(const Texture & ctx) { + ivec2 size; + SDL_QueryTexture(ctx.get_img(), NULL, NULL, &size.x, &size.y); + return size; } -int SDLContext::get_height(const Texture & ctx) const { - int h; - SDL_QueryTexture(ctx.texture.get(), NULL, NULL, NULL, &h); - return h; + +std::vector<EventData> SDLContext::get_events() { + std::vector<EventData> event_list; + SDL_Event event; + const CameraAuxiliaryData & cam = this->cam_aux_data; + while (SDL_PollEvent(&event)) { + ivec2 mouse_pos; + mouse_pos.x = (event.button.x - cam.bar_size.x) / cam.render_scale.x; + mouse_pos.y = (event.button.y - cam.bar_size.y) / cam.render_scale.y; + switch (event.type) { + case SDL_QUIT: + event_list.push_back({.event_type = EventType::SHUTDOWN}); + break; + case SDL_KEYDOWN: + event_list.push_back(EventData{ + .event_type = EventType::KEY_DOWN, + .data = { + .key_data = { + .key = this->sdl_to_keycode(event.key.keysym.scancode), + .key_repeat = event.key.repeat != 0, + }, + }, + }); + break; + + case SDL_KEYUP: + event_list.push_back(EventData{ + .event_type = EventType::KEY_UP, + .data = { + .key_data = { + .key = this->sdl_to_keycode(event.key.keysym.scancode), + .key_repeat = event.key.repeat != 0, + }, + }, + }); + break; + + case SDL_MOUSEBUTTONDOWN: + event_list.push_back(EventData{ + .event_type = EventType::MOUSE_DOWN, + .data = { + .mouse_data = { + .mouse_button = this->sdl_to_mousebutton(event.button.button), + .mouse_position = mouse_pos, + }, + }, + }); + break; + case SDL_MOUSEBUTTONUP: + event_list.push_back(EventData{ + .event_type = EventType::MOUSE_UP, + .data = { + .mouse_data = { + .mouse_button = this->sdl_to_mousebutton(event.button.button), + .mouse_position = mouse_pos, + }, + }, + }); + break; + + case SDL_MOUSEMOTION: + event_list.push_back(EventData{ + .event_type = EventType::MOUSE_MOVE, + .data = { + .mouse_data = { + .mouse_position = mouse_pos, + .rel_mouse_move = {event.motion.xrel, event.motion.yrel}, + }, + }, + }); + break; + + case SDL_MOUSEWHEEL: + event_list.push_back(EventData{ + .event_type = EventType::MOUSE_WHEEL, + .data = { + .mouse_data = { + .mouse_position = mouse_pos, + .scroll_direction = event.wheel.y < 0 ? -1 : 1, + .scroll_delta = event.wheel.preciseY, + }, + }, + }); + break; + case SDL_WINDOWEVENT: + this->handle_window_event(event.window, event_list); + break; + } + } + + return event_list; +} + +void SDLContext::handle_window_event( + const SDL_WindowEvent & window_event, std::vector<EventData> & event_list +) { + switch (window_event.event) { + case SDL_WINDOWEVENT_EXPOSED: + event_list.push_back(EventData {EventType::WINDOW_EXPOSE}); + break; + case SDL_WINDOWEVENT_RESIZED: + event_list.push_back(EventData{ + .event_type = EventType::WINDOW_RESIZE, + .data = { + .window_data = { + .resize_dimension = {window_event.data1, window_event.data2} + }, + }, + }); + break; + case SDL_WINDOWEVENT_MOVED: + event_list.push_back(EventData{ + .event_type = EventType::WINDOW_MOVE, + .data = { + .window_data = { + .move_delta = {window_event.data1, window_event.data2} + }, + }, + }); + break; + + case SDL_WINDOWEVENT_MINIMIZED: + event_list.push_back(EventData {EventType::WINDOW_MINIMIZE}); + break; + case SDL_WINDOWEVENT_MAXIMIZED: + event_list.push_back(EventData {EventType::WINDOW_MAXIMIZE}); + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + event_list.push_back(EventData {EventType::WINDOW_FOCUS_GAIN}); + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + event_list.push_back(EventData {EventType::WINDOW_FOCUS_LOST}); + break; + } +} + +void SDLContext::set_color_texture(const Texture & texture, const Color & color) { + SDL_SetTextureColorMod(texture.get_img(), color.r, color.g, color.b); + SDL_SetTextureAlphaMod(texture.get_img(), color.a); +} + +Asset SDLContext::get_font_from_name(const std::string & font_family) { + return this->font_facade.get_font_asset(font_family); } -void SDLContext::delay(int ms) const { SDL_Delay(ms); } diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index 841ffc9..bc118f9 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -1,5 +1,6 @@ #pragma once +#include <SDL2/SDL.h> #include <SDL2/SDL_keycode.h> #include <SDL2/SDL_rect.h> #include <SDL2/SDL_render.h> @@ -8,53 +9,149 @@ #include <functional> #include <memory> #include <string> +#include <unordered_map> -#include "../api/Sprite.h" -#include "../api/Transform.h" +#include "../types.h" +#include "EventData.h" #include "api/Camera.h" -#include "api/Vector2.h" +#include "api/Color.h" +#include "api/KeyCodes.h" +#include "api/Sprite.h" +#include "api/Transform.h" -namespace crepe { +#include "EventData.h" +#include "FontFacade.h" +#include "types.h" -// TODO: SDL_Keycode is defined in a header not distributed with crepe, which means this -// typedef is unusable when crepe is packaged. Wouter will fix this later. -typedef SDL_Keycode CREPE_KEYCODES; +namespace crepe { +class Texture; +class Text; +class Font; +class Mediator; /** - * \class SDLContext * \brief Facade for the SDL library - * + * * SDLContext is a singleton that handles the SDL window and renderer, provides methods for * event handling, and rendering to the screen. It is never used directly by the user */ class SDLContext { - public: - /** - * \brief Gets the singleton instance of SDLContext. - * \return Reference to the SDLContext instance. - */ - static SDLContext & get_instance(); + //! data that the camera component cannot hold + struct CameraAuxiliaryData { + + //! zoomed in viewport in game_units + vec2 zoomed_viewport; + + /** + * \brief scaling factor + * + * depending on the black bars type will the scaling be different. + * - letterboxing --> scaling on the y-as + * - pillarboxing --> scaling on the x-as + */ + vec2 render_scale; + + /** + * \brief size of calculated black bars + * + * depending on the black bars type will the size be different + * - lettorboxing --> {0, bar_height} + * - pillarboxing --> {bar_width , 0} + */ + vec2 bar_size; + + //! Calculated camera position + vec2 cam_pos; + }; + + //! rendering data needed to render on screen + struct RenderContext { + const Sprite & sprite; + const Texture & texture; + const vec2 & pos; + const double & angle; + const double & scale; + }; + + struct RenderText { + const Text & text; + const Font & font; + const Transform & transform; + }; +public: SDLContext(const SDLContext &) = delete; SDLContext(SDLContext &&) = delete; SDLContext & operator=(const SDLContext &) = delete; SDLContext & operator=(SDLContext &&) = delete; -private: - //! will only use handle_events - friend class LoopManager; +public: /** - * \brief Handles SDL events such as window close and input. - * \param running Reference to a boolean flag that controls the main loop. + * \brief Constructs an SDLContext instance. + * Initializes SDL, creates a window and renderer. */ - void handle_events(bool & running); + SDLContext(Mediator & mediator); -private: - //! Will only use get_ticks - friend class AnimatorSystem; - //! Will only use delay - friend class LoopTimer; + /** + * \brief Destroys the SDLContext instance. + * Cleans up SDL resources, including the window and renderer. + */ + ~SDLContext(); + +public: + /** + * \brief Retrieves a list of all events from the SDL context. + * + * This method retrieves all the events from the SDL context that are currently + * available. It is primarily used by the InputSystem to process various + * input events such as mouse clicks, mouse movements, and keyboard presses. + * + * \return Events that occurred since last call to `get_events()` + */ + std::vector<EventData> get_events(); + /** + * \brief Fills event_list with triggered window events + * + * This method checks if any window events are triggered and adds them to the event_list. + * + */ + void handle_window_event( + const SDL_WindowEvent & window_event, std::vector<EventData> & event_list + ); + /** + * \brief Converts an SDL scan code to the custom Keycode type. + * + * This method maps an SDL scan code to the corresponding `Keycode` enum value, + * which is used internally by the system to identify the keys. + * + * \param sdl_key The SDL scan code to convert. + * \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized. + */ + Keycode sdl_to_keycode(SDL_Scancode sdl_key); + + /** + * \brief Converts an SDL mouse button code to the custom MouseButton type. + * + * This method maps an SDL mouse button code to the corresponding `MouseButton` + * enum value, which is used internally by the system to identify mouse buttons. + * + * \param sdl_button The SDL mouse button code to convert. + * \return The corresponding `MouseButton` value or `MouseButton::NONE` if the key is unrecognized + */ + MouseButton sdl_to_mousebutton(Uint8 sdl_button); + /** + * \brief Gets the current state of the keyboard. + * + * Updates the internal keyboard state by checking the current key states using + * SDL's `SDL_GetKeyboardState()`, and returns a reference to the `keyboard_state_t`. + * + * \return A constant reference to the `keyboard_state_t`, which holds the state + * of each key (true = pressed, false = not pressed). + */ + const keyboard_state_t & get_keyboard_state(); + +public: /** * \brief Gets the current SDL ticks since the program started. * \return Current ticks in milliseconds as a constant uint64_t. @@ -70,23 +167,7 @@ private: */ void delay(int ms) const; -private: - /** - * \brief Constructs an SDLContext instance. - * Initializes SDL, creates a window and renderer. - */ - SDLContext(); - - /** - * \brief Destroys the SDLContext instance. - * Cleans up SDL resources, including the window and renderer. - */ - ~SDLContext(); - -private: - //! Will use the funtions: texture_from_path, get_width,get_height. - friend class Texture; - +public: /** * \brief Loads a texture from a file path. * \param path Path to the image file. @@ -95,33 +176,25 @@ private: std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture_from_path(const std::string & path); /** - * \brief Gets the width of a texture. + * \brief Gets the size of a texture. * \param texture Reference to the Texture object. - * \return Width of the texture as an integer. + * \return Width and height of the texture as an integer in pixels. */ - int get_width(const Texture &) const; + ivec2 get_size(const Texture & ctx); +public: /** - * \brief Gets the height of a texture. - * \param texture Reference to the Texture object. - * \return Height of the texture as an integer. + * \brief Draws a sprite to the screen using the specified transform and camera. + * \param RenderContext Reference to rendering data to draw */ - int get_height(const Texture &) const; - -private: - //! Will use draw,clear_screen, present_screen, camera. - friend class RenderSystem; + void draw(const RenderContext & ctx); /** - * \brief Draws a sprite to the screen using the specified transform and camera. - * \param sprite Reference to the Sprite to draw. - * \param transform Reference to the Transform for positioning. - * \param camera Reference to the Camera for view adjustments. + * \brief draws a text to the screen + * + * \param data Reference to the rendering data needed to draw */ - void draw(const Sprite & sprite, const Transform & transform, const Camera & camera); - - void draw_particle(const Sprite & sprite, const Vector2 & pos, const double & angle, - const double & scale, const Camera & camera); + void draw_text(const RenderText & data); //! Clears the screen, preparing for a new frame. void clear_screen(); @@ -130,31 +203,38 @@ private: void present_screen(); /** - * \brief sets the background of the camera (will be adjusted in future PR) - * \param camera Reference to the Camera object. + * \brief calculates camera view settings. such as black_bars, zoomed_viewport, scaling and + * adjusting window size. + * + * \note only supports windowed mode. + * \param camera Reference to the current Camera object in the scene. + * \param new_pos new camera position from transform and offset */ - void set_camera(const Camera & camera); + void update_camera_view(const Camera & camera, const vec2 & new_pos); + +public: + //! the data needed to construct a sdl dst rectangle + struct DestinationRectangleData { + const Sprite & sprite; + const Texture & texture; + const vec2 & pos; + const double & img_scale; + }; -private: /** - * \brief calculates the sqaure size of the image + * \brief calculates the sqaure size of the image for destination * - * \param sprite Reference to the sprite to calculate the rectangle - * \return sdl rectangle to draw a src image + * \param data needed to calculate a destination rectangle + * \return sdl rectangle to draw a dst image to draw on the screen */ - SDL_Rect get_src_rect(const Sprite & sprite) const; + SDL_FRect get_dst_rect(const DestinationRectangleData & data) const; /** - * \brief calculates the sqaure size of the image for an destination + * \brief Set an additional color value multiplied into render copy operations. * - * \param sprite Reference to the sprite to calculate the rectangle - * \param pos the pos in pixel positions - * \param scale the multiplier to increase of decrease for the specified sprite - * \param cam Reference to the current camera in the scene to calculate the position based - * on the camera - * \return sdl rectangle to draw a dst image to draw on the screen + * \param texture the given texture to adjust + * \param color the color data for the texture */ - SDL_Rect get_dst_rect(const Sprite & sprite, const Vector2 & pos, const double & scale, - const Camera & cam) const; + void set_color_texture(const Texture & texture, const Color & color); private: //! sdl Window @@ -163,8 +243,133 @@ private: //! renderer for the crepe engine std::unique_ptr<SDL_Renderer, std::function<void(SDL_Renderer *)>> game_renderer; - //! viewport for the camera window - SDL_Rect viewport = {0, 0, 640, 480}; + //! black bars rectangle to draw + SDL_FRect black_bars[2] = {}; + + /** + * \cam_aux_data extra data that the component cannot hold. + * + * - this is defined in this class because get_events() needs this information aswell + */ + CameraAuxiliaryData cam_aux_data; + +private: + //! instance of the font_facade + FontFacade font_facade {}; + +public: + /** + * \brief Function to Get asset from font_family + * + * This function uses the FontFacade function to convert a font_family to an asset. + * + * \param font_family name of the font style that needs to be used (will return an asset with default font path of the font_family doesnt exist) + * + * \return asset with the font style absolute path + */ + Asset get_font_from_name(const std::string & font_family); + //! variable to store the state of each key (true = pressed, false = not pressed) + keyboard_state_t keyboard_state; + //! lookup table for converting SDL_SCANCODES to Keycodes + const std::unordered_map<SDL_Scancode, Keycode> lookup_table + = {{SDL_SCANCODE_SPACE, Keycode::SPACE}, + {SDL_SCANCODE_APOSTROPHE, Keycode::APOSTROPHE}, + {SDL_SCANCODE_COMMA, Keycode::COMMA}, + {SDL_SCANCODE_MINUS, Keycode::MINUS}, + {SDL_SCANCODE_PERIOD, Keycode::PERIOD}, + {SDL_SCANCODE_SLASH, Keycode::SLASH}, + {SDL_SCANCODE_0, Keycode::D0}, + {SDL_SCANCODE_1, Keycode::D1}, + {SDL_SCANCODE_2, Keycode::D2}, + {SDL_SCANCODE_3, Keycode::D3}, + {SDL_SCANCODE_4, Keycode::D4}, + {SDL_SCANCODE_5, Keycode::D5}, + {SDL_SCANCODE_6, Keycode::D6}, + {SDL_SCANCODE_7, Keycode::D7}, + {SDL_SCANCODE_8, Keycode::D8}, + {SDL_SCANCODE_9, Keycode::D9}, + {SDL_SCANCODE_SEMICOLON, Keycode::SEMICOLON}, + {SDL_SCANCODE_EQUALS, Keycode::EQUAL}, + {SDL_SCANCODE_A, Keycode::A}, + {SDL_SCANCODE_B, Keycode::B}, + {SDL_SCANCODE_C, Keycode::C}, + {SDL_SCANCODE_D, Keycode::D}, + {SDL_SCANCODE_E, Keycode::E}, + {SDL_SCANCODE_F, Keycode::F}, + {SDL_SCANCODE_G, Keycode::G}, + {SDL_SCANCODE_H, Keycode::H}, + {SDL_SCANCODE_I, Keycode::I}, + {SDL_SCANCODE_J, Keycode::J}, + {SDL_SCANCODE_K, Keycode::K}, + {SDL_SCANCODE_L, Keycode::L}, + {SDL_SCANCODE_M, Keycode::M}, + {SDL_SCANCODE_N, Keycode::N}, + {SDL_SCANCODE_O, Keycode::O}, + {SDL_SCANCODE_P, Keycode::P}, + {SDL_SCANCODE_Q, Keycode::Q}, + {SDL_SCANCODE_R, Keycode::R}, + {SDL_SCANCODE_S, Keycode::S}, + {SDL_SCANCODE_T, Keycode::T}, + {SDL_SCANCODE_U, Keycode::U}, + {SDL_SCANCODE_V, Keycode::V}, + {SDL_SCANCODE_W, Keycode::W}, + {SDL_SCANCODE_X, Keycode::X}, + {SDL_SCANCODE_Y, Keycode::Y}, + {SDL_SCANCODE_Z, Keycode::Z}, + {SDL_SCANCODE_LEFTBRACKET, Keycode::LEFT_BRACKET}, + {SDL_SCANCODE_BACKSLASH, Keycode::BACKSLASH}, + {SDL_SCANCODE_RIGHTBRACKET, Keycode::RIGHT_BRACKET}, + {SDL_SCANCODE_GRAVE, Keycode::GRAVE_ACCENT}, + {SDL_SCANCODE_ESCAPE, Keycode::ESCAPE}, + {SDL_SCANCODE_RETURN, Keycode::ENTER}, + {SDL_SCANCODE_TAB, Keycode::TAB}, + {SDL_SCANCODE_BACKSPACE, Keycode::BACKSPACE}, + {SDL_SCANCODE_INSERT, Keycode::INSERT}, + {SDL_SCANCODE_DELETE, Keycode::DELETE}, + {SDL_SCANCODE_RIGHT, Keycode::RIGHT}, + {SDL_SCANCODE_LEFT, Keycode::LEFT}, + {SDL_SCANCODE_DOWN, Keycode::DOWN}, + {SDL_SCANCODE_UP, Keycode::UP}, + {SDL_SCANCODE_PAGEUP, Keycode::PAGE_UP}, + {SDL_SCANCODE_PAGEDOWN, Keycode::PAGE_DOWN}, + {SDL_SCANCODE_HOME, Keycode::HOME}, + {SDL_SCANCODE_END, Keycode::END}, + {SDL_SCANCODE_CAPSLOCK, Keycode::CAPS_LOCK}, + {SDL_SCANCODE_SCROLLLOCK, Keycode::SCROLL_LOCK}, + {SDL_SCANCODE_NUMLOCKCLEAR, Keycode::NUM_LOCK}, + {SDL_SCANCODE_PRINTSCREEN, Keycode::PRINT_SCREEN}, + {SDL_SCANCODE_PAUSE, Keycode::PAUSE}, + {SDL_SCANCODE_F1, Keycode::F1}, + {SDL_SCANCODE_F2, Keycode::F2}, + {SDL_SCANCODE_F3, Keycode::F3}, + {SDL_SCANCODE_F4, Keycode::F4}, + {SDL_SCANCODE_F5, Keycode::F5}, + {SDL_SCANCODE_F6, Keycode::F6}, + {SDL_SCANCODE_F7, Keycode::F7}, + {SDL_SCANCODE_F8, Keycode::F8}, + {SDL_SCANCODE_F9, Keycode::F9}, + {SDL_SCANCODE_F10, Keycode::F10}, + {SDL_SCANCODE_F11, Keycode::F11}, + {SDL_SCANCODE_F12, Keycode::F12}, + {SDL_SCANCODE_KP_0, Keycode::KP0}, + {SDL_SCANCODE_KP_1, Keycode::KP1}, + {SDL_SCANCODE_KP_2, Keycode::KP2}, + {SDL_SCANCODE_KP_3, Keycode::KP3}, + {SDL_SCANCODE_KP_4, Keycode::KP4}, + {SDL_SCANCODE_KP_5, Keycode::KP5}, + {SDL_SCANCODE_KP_6, Keycode::KP6}, + {SDL_SCANCODE_KP_7, Keycode::KP7}, + {SDL_SCANCODE_KP_8, Keycode::KP8}, + {SDL_SCANCODE_KP_9, Keycode::KP9}, + {SDL_SCANCODE_LSHIFT, Keycode::LEFT_SHIFT}, + {SDL_SCANCODE_LCTRL, Keycode::LEFT_CONTROL}, + {SDL_SCANCODE_LALT, Keycode::LEFT_ALT}, + {SDL_SCANCODE_LGUI, Keycode::LEFT_SUPER}, + {SDL_SCANCODE_RSHIFT, Keycode::RIGHT_SHIFT}, + {SDL_SCANCODE_RCTRL, Keycode::RIGHT_CONTROL}, + {SDL_SCANCODE_RALT, Keycode::RIGHT_ALT}, + {SDL_SCANCODE_RGUI, Keycode::RIGHT_SUPER}, + {SDL_SCANCODE_MENU, Keycode::MENU}}; }; } // namespace crepe diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 4d3abf5..b1e6463 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -1,59 +1,13 @@ -#include "../util/Log.h" +#include "../api/Asset.h" +#include "../util/dbg.h" #include "Sound.h" -#include "SoundContext.h" using namespace crepe; using namespace std; -Sound::Sound(unique_ptr<Asset> res) { +Sound::Sound(const Asset & src, Mediator & mediator) : Resource(src, mediator) { + this->sample.load(src.get_path().c_str()); dbg_trace(); - this->load(std::move(res)); -} - -Sound::Sound(const char * src) { - dbg_trace(); - this->load(make_unique<Asset>(src)); -} - -void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_path().c_str()); } - -void Sound::play() { - SoundContext & ctx = SoundContext::get_instance(); - if (ctx.engine.getPause(this->handle)) { - // resume if paused - ctx.engine.setPause(this->handle, false); - } else { - // or start new sound - this->handle = ctx.engine.play(this->sample, this->volume); - ctx.engine.setLooping(this->handle, this->looping); - } -} - -void Sound::pause() { - SoundContext & ctx = SoundContext::get_instance(); - if (ctx.engine.getPause(this->handle)) return; - ctx.engine.setPause(this->handle, true); -} - -void Sound::rewind() { - SoundContext & ctx = SoundContext::get_instance(); - if (!ctx.engine.isValidVoiceHandle(this->handle)) return; - ctx.engine.seek(this->handle, 0); -} - -void Sound::set_volume(float volume) { - this->volume = volume; - - SoundContext & ctx = SoundContext::get_instance(); - if (!ctx.engine.isValidVoiceHandle(this->handle)) return; - ctx.engine.setVolume(this->handle, this->volume); -} - -void Sound::set_looping(bool looping) { - this->looping = looping; - - SoundContext & ctx = SoundContext::get_instance(); - if (!ctx.engine.isValidVoiceHandle(this->handle)) return; - ctx.engine.setLooping(this->handle, this->looping); } +Sound::~Sound() { dbg_trace(); } diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 4c68f32..4a5d692 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -1,84 +1,31 @@ #pragma once -#include <memory> #include <soloud/soloud.h> #include <soloud/soloud_wav.h> -#include "../api/Asset.h" +#include "../Resource.h" namespace crepe { +class SoundContext; +class Mediator; + /** * \brief Sound resource facade * - * This class is a wrapper around a \c SoLoud::Wav instance, which holds a - * single sample. It is part of the sound facade. + * This class is a wrapper around a \c SoLoud::Wav instance, which holds a single sample. It is + * part of the sound facade. */ -class Sound { -public: - /** - * \brief Pause this sample - * - * Pauses this sound if it is playing, or does nothing if it is already paused. The playhead - * position is saved, such that calling \c play() after this function makes the sound resume. - */ - void pause(); - /** - * \brief Play this sample - * - * Resume playback if this sound is paused, or start from the beginning of the sample. - * - * \note This class only saves a reference to the most recent 'voice' of this sound. Calling - * \c play() while the sound is already playing causes multiple instances of the sample to - * play simultaniously. The sample started last is the one that is controlled afterwards. - */ - void play(); - /** - * \brief Reset playhead position - * - * Resets the playhead position so that calling \c play() after this function makes it play - * from the start of the sample. If the sound is not paused before calling this function, - * this function will stop playback. - */ - void rewind(); - /** - * \brief Set playback volume / gain - * - * \param volume Volume (0 = muted, 1 = full volume) - */ - void set_volume(float volume); - /** - * \brief Get playback volume / gain - * - * \return Volume - */ - float get_volume() const { return this->volume; } - /** - * \brief Set looping behavior for this sample - * - * \param looping Looping behavior (false = one-shot, true = loop) - */ - void set_looping(bool looping); - /** - * \brief Get looping behavior - * - * \return true if looping, false if one-shot - */ - bool get_looping() const { return this->looping; } - +class Sound : public Resource { public: - Sound(const char * src); - Sound(std::unique_ptr<Asset> res); - -private: - void load(std::unique_ptr<Asset> res); + Sound(const Asset & src, Mediator & mediator); + ~Sound(); // dbg_trace private: + //! Deserialized resource (soloud) SoLoud::Wav sample; - SoLoud::handle handle; - - float volume = 1.0f; - bool looping = false; + //! SoundContext uses \c sample + friend class SoundContext; }; } // namespace crepe diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index deb2b62..5091e07 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -1,20 +1,36 @@ -#include "../util/Log.h" +#include "../util/dbg.h" #include "SoundContext.h" using namespace crepe; -SoundContext & SoundContext::get_instance() { - static SoundContext instance; - return instance; -} - SoundContext::SoundContext() { dbg_trace(); - engine.init(); + this->engine.init(); + this->engine.setMaxActiveVoiceCount(this->config.audio.voices); } SoundContext::~SoundContext() { dbg_trace(); - engine.deinit(); + this->engine.deinit(); +} + +SoundHandle SoundContext::play(Sound & resource) { + SoLoud::handle real_handle = this->engine.play(resource.sample, 1.0f); + SoundHandle handle = this->next_handle; + this->registry[handle] = real_handle; + this->next_handle++; + return handle; +} + +void SoundContext::stop(const SoundHandle & handle) { + this->engine.stop(this->registry[handle]); +} + +void SoundContext::set_volume(const SoundHandle & handle, float volume) { + this->engine.setVolume(this->registry[handle], volume); +} + +void SoundContext::set_loop(const SoundHandle & handle, bool loop) { + this->engine.setLooping(this->registry[handle], loop); } diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h index d703c16..d986c59 100644 --- a/src/crepe/facade/SoundContext.h +++ b/src/crepe/facade/SoundContext.h @@ -2,30 +2,80 @@ #include <soloud/soloud.h> +#include "../api/Config.h" + #include "Sound.h" +#include "SoundHandle.h" namespace crepe { /** * \brief Sound engine facade * - * This class is a wrapper around a \c SoLoud::Soloud instance, which provides - * the methods for playing \c Sound instances. It is part of the sound facade. + * This class is a wrapper around a \c SoLoud::Soloud instance, which provides the methods for + * playing \c Sound instances. It is part of the sound facade. */ class SoundContext { -private: - // singleton +public: SoundContext(); virtual ~SoundContext(); + SoundContext(const SoundContext &) = delete; SoundContext(SoundContext &&) = delete; SoundContext & operator=(const SoundContext &) = delete; SoundContext & operator=(SoundContext &&) = delete; + /** + * \brief Play a sample + * + * Plays a Sound from the beginning of the sample and returns a handle to control it later. + * + * \param resource Sound instance to play + * + * \returns Handle to control this voice + */ + virtual SoundHandle play(Sound & resource); + /** + * \brief Stop a voice immediately if it is still playing + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + */ + virtual void stop(const SoundHandle & handle); + /** + * \brief Change the volume of a voice + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + * \param volume New gain value (0=silent, 1=default) + */ + virtual void set_volume(const SoundHandle & handle, float volume); + /** + * \brief Set the looping behavior of a voice + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + * \param loop Looping behavior (false=oneshot, true=loop) + */ + virtual void set_loop(const SoundHandle & handle, bool loop); + private: - static SoundContext & get_instance(); + //! Abstracted class SoLoud::Soloud engine; - friend class Sound; + + //! Config reference + Config & config = Config::get_instance(); + + //! Sound handle registry + std::unordered_map<SoundHandle, SoLoud::handle> registry; + //! Unique handle counter + SoundHandle next_handle = 0; }; } // namespace crepe diff --git a/src/crepe/facade/SoundHandle.h b/src/crepe/facade/SoundHandle.h new file mode 100644 index 0000000..b7925fc --- /dev/null +++ b/src/crepe/facade/SoundHandle.h @@ -0,0 +1,12 @@ +#pragma once + +#include <cstddef> + +namespace crepe { + +/** + * \brief Voice handle returned by + */ +typedef size_t SoundHandle; + +} // namespace crepe diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp new file mode 100644 index 0000000..06caa54 --- /dev/null +++ b/src/crepe/facade/Texture.cpp @@ -0,0 +1,30 @@ +#include "../Resource.h" +#include "../facade/SDLContext.h" +#include "../manager/Mediator.h" +#include "../types.h" +#include "../util/dbg.h" + +#include "SDLContext.h" +#include "Texture.h" + +using namespace crepe; +using namespace std; + +Texture::Texture(const Asset & src, Mediator & mediator) : Resource(src, mediator) { + dbg_trace(); + SDLContext & ctx = mediator.sdl_context; + this->texture = ctx.texture_from_path(src.get_path()); + this->size = ctx.get_size(*this); + this->aspect_ratio = static_cast<float>(this->size.x) / this->size.y; +} + +Texture::~Texture() { + dbg_trace(); + this->texture.reset(); +} + +const ivec2 & Texture::get_size() const noexcept { return this->size; } + +const float & Texture::get_ratio() const noexcept { return this->aspect_ratio; } + +SDL_Texture * Texture::get_img() const noexcept { return this->texture.get(); } diff --git a/src/crepe/facade/Texture.h b/src/crepe/facade/Texture.h new file mode 100644 index 0000000..cdacac4 --- /dev/null +++ b/src/crepe/facade/Texture.h @@ -0,0 +1,69 @@ +#pragma once + +#include <SDL2/SDL_render.h> +#include <memory> + +#include "../Resource.h" + +#include "types.h" + +namespace crepe { + +class Mediator; +class Asset; + +/** + * \class Texture + * \brief Manages texture loading and properties. + * + * The Texture class is responsible for loading an image from a source and providing access to + * its dimensions. Textures can be used for rendering. + */ +class Texture : public Resource { + +public: + /** + * \brief Constructs a Texture from an Asset resource. + * \param src Asset with texture data to load. + * \param mediator use the SDLContext reference to load the image + */ + Texture(const Asset & src, Mediator & mediator); + + /** + * \brief Destroys the Texture instance + */ + ~Texture(); + + /** + * \brief get width and height of image in pixels + * \return pixel size width and height + * + */ + const ivec2 & get_size() const noexcept; + + /** + * \brief aspect_ratio of image + * \return ratio + * + */ + const float & get_ratio() const noexcept; + + /** + * \brief get the image texture + * \return SDL_Texture + * + */ + SDL_Texture * get_img() const noexcept; + +private: + //! The texture of the class from the library + std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture; + + // texture size in pixel + ivec2 size; + + //! ratio of image + float aspect_ratio; +}; + +} // namespace crepe diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt new file mode 100644 index 0000000..48e444f --- /dev/null +++ b/src/crepe/manager/CMakeLists.txt @@ -0,0 +1,30 @@ +target_sources(crepe PUBLIC + ComponentManager.cpp + EventManager.cpp + Manager.cpp + SaveManager.cpp + SceneManager.cpp + LoopTimerManager.cpp + ResourceManager.cpp + ReplayManager.cpp + SystemManager.cpp +) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES + ComponentManager.h + ComponentManager.hpp + EventManager.h + EventManager.hpp + Manager.h + Mediator.h + SaveManager.h + SceneManager.h + SceneManager.hpp + LoopTimerManager.h + ResourceManager.h + ResourceManager.hpp + ReplayManager.h + SystemManager.h + SystemManager.hpp +) + diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp new file mode 100644 index 0000000..245419d --- /dev/null +++ b/src/crepe/manager/ComponentManager.cpp @@ -0,0 +1,103 @@ +#include "../api/GameObject.h" +#include "../api/Metadata.h" +#include "../types.h" +#include "../util/dbg.h" + +#include "ComponentManager.h" + +using namespace crepe; +using namespace std; + +ComponentManager::ComponentManager(Mediator & mediator) : Manager(mediator) { + mediator.component_manager = *this; + dbg_trace(); +} +ComponentManager::~ComponentManager() { dbg_trace(); } + +void ComponentManager::delete_all_components_of_id(game_object_id_t id) { + // Do not delete persistent objects + if (this->persistent[id]) { + return; + } + + // Loop through all the types (in the unordered_map<>) + for (auto & [type, component_array] : this->components) { + // Make sure that the id (that we are looking for) is within the boundaries of the vector<> + if (id < component_array.size()) { + // Clear the components at this specific id + component_array[id].clear(); + } + } +} + +void ComponentManager::delete_all_components() { + // Loop through all the types (in the unordered_map<>) + for (auto & [type, component_array] : this->components) { + // Loop through all the ids (in the vector<>) + for (game_object_id_t id = 0; id < component_array.size(); id++) { + // Do not delete persistent objects + if (!this->persistent[id]) { + // Clear the components at this specific id + component_array[id].clear(); + } + } + } + + this->next_id = 0; +} + +GameObject ComponentManager::new_object( + const string & name, const string & tag, const vec2 & position, double rotation, + double scale +) { + // Find the first available id (taking persistent objects into account) + while (this->persistent[this->next_id]) { + this->next_id++; + } + + GameObject object {this->mediator, this->next_id, name, tag, position, rotation, scale}; + this->next_id++; + + return object; +} + +void ComponentManager::set_persistent(game_object_id_t id, bool persistent) { + this->persistent[id] = persistent; +} + +set<game_object_id_t> ComponentManager::get_objects_by_name(const string & name) const { + return this->get_objects_by_predicate<Metadata>([name](const Metadata & data) { + return data.name == name; + }); +} + +set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) const { + return this->get_objects_by_predicate<Metadata>([tag](const Metadata & data) { + return data.tag == tag; + }); +} + +ComponentManager::Snapshot ComponentManager::save() { + Snapshot snapshot {}; + for (const auto & [type, by_id_index] : this->components) { + for (game_object_id_t id = 0; id < by_id_index.size(); id++) { + const auto & components = by_id_index[id]; + for (size_t index = 0; index < components.size(); index++) { + const Component & component = *components[index]; + snapshot.components.push_back(SnapshotComponent { + .type = type, + .id = id, + .index = index, + .component = component.save(), + }); + } + } + } + return snapshot; +} + +void ComponentManager::restore(const Snapshot & snapshot) { + for (const SnapshotComponent & info : snapshot.components) { + this->components[info.type][info.id][info.index]->restore(*info.component); + } +} diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h new file mode 100644 index 0000000..2eb1f7e --- /dev/null +++ b/src/crepe/manager/ComponentManager.h @@ -0,0 +1,253 @@ +#pragma once + +#include <memory> +#include <set> +#include <typeindex> +#include <unordered_map> +#include <vector> + +#include "../Component.h" +#include "../types.h" + +#include "Manager.h" + +namespace crepe { + +class GameObject; + +/** + * \brief Manages all components + * + * This class manages all components. It provides methods to add, delete and get components. + */ +class ComponentManager : public Manager { +public: + ComponentManager(Mediator & mediator); + ~ComponentManager(); // dbg_trace + + /** + * \brief Create a new game object using the component manager + * + * \param name Metadata::name (required) + * \param tag Metadata::tag (optional, empty by default) + * \param position Transform::position (optional, origin by default) + * \param rotation Transform::rotation (optional, 0 by default) + * \param scale Transform::scale (optional, 1 by default) + * + * \returns GameObject interface + * + * \note This method automatically assigns a new entity ID + */ + GameObject new_object( + const std::string & name, const std::string & tag = "", const vec2 & position = {0, 0}, + double rotation = 0, double scale = 1 + ); + +public: + /** + * \brief Add a component to the ComponentManager + * + * This method adds a component to the ComponentManager. The component is created with the + * given arguments and added to the ComponentManager. + * + * \tparam T The type of the component + * \tparam Args The types of the arguments + * \param id The id of the GameObject this component belongs to + * \param args The arguments to create the component + * \return The created component + */ + template <typename T, typename... Args> + T & add_component(game_object_id_t id, Args &&... args); + /** + * \brief Delete all components of a specific type and id + * + * This method deletes all components of a specific type and id. + * + * \tparam T The type of the component + * \param id The id of the GameObject this component belongs to + */ + template <typename T> + void delete_components_by_id(game_object_id_t id); + /** + * \brief Delete all components of a specific type + * + * This method deletes all components of a specific type. + * + * \tparam T The type of the component + */ + template <typename T> + void delete_components(); + /** + * \brief Delete all components of a specific id + * + * This method deletes all components of a specific id. + * + * \param id The id of the GameObject this component belongs to + */ + void delete_all_components_of_id(game_object_id_t id); + /** + * \brief Delete all components + * + * This method deletes all components. + */ + void delete_all_components(); + /** + * \brief Set a GameObject as persistent + * + * This method sets a GameObject as persistent. If a GameObject is persistent, its + * components will not be deleted. + * + * \param id The id of the GameObject to set as persistent + * \param persistent The persistent flag + */ + void set_persistent(game_object_id_t id, bool persistent); + +public: + /** + * \brief Get all components of a specific type and id + * + * This method gets all components of a specific type and id. + * + * \tparam T The type of the component + * \param id The id of the GameObject this component belongs to + * \return A vector of all components of the specific type and id + */ + template <typename T> + RefVector<T> get_components_by_id(game_object_id_t id) const; + /** + * \brief Get all components of a specific type + * + * This method gets all components of a specific type. + * + * \tparam T The type of the component + * \return A vector of all components of the specific type + */ + template <typename T> + RefVector<T> get_components_by_type() const; + /** + * \brief Get all components of a specific type on a GameObject with name \c name + * + * \tparam T The type of the component + * \param name Metadata::name for the same game_object_id as the returned components + * \return Components matching criteria + */ + template <typename T> + RefVector<T> get_components_by_name(const std::string & name) const; + /** + * \brief Get all components of a specific type on a GameObject with tag \c tag + * + * \tparam T The type of the component + * \param name Metadata::tag for the same game_object_id as the returned components + * \return Components matching criteria + */ + template <typename T> + RefVector<T> get_components_by_tag(const std::string & tag) const; + + //! Snapshot of single component (including path in \c components) + struct SnapshotComponent { + //! \c components path + std::type_index type; + //! \c components path + game_object_id_t id; + //! \c components path + size_t index; + //! Actual component snapshot + std::unique_ptr<Component> component; + }; + //! Snapshot of the entire component manager state + struct Snapshot { + //! All components + std::vector<SnapshotComponent> components; + // TODO: some kind of hash code that ensures components exist in all the same places as + // this snapshot + }; + /** + * \name ReplayManager (Memento) functions + * \{ + */ + /** + * \brief Save a snapshot of the component manager state + * \returns Deep copy of the component manager's internal state + */ + Snapshot save(); + /** + * \brief Restore component manager from a snapshot + * \param snapshot Snapshot to restore from (as returned by \c save()) + */ + void restore(const Snapshot & snapshot); + //! \} + +private: + /** + * \brief Get object IDs by predicate function + * + * This function calls the predicate function \c pred for all components matching type \c T, + * and adds their parent game_object_id to a \c std::set if the predicate returns true. + * + * \tparam T The type of the component to check the predicate against + * \param pred Predicate function + * + * \note The predicate function may be called for multiple components with the same \c + * game_object_id. In this case, the ID is added if *any* call returns \c true. + * + * \returns game_object_id for all components where the predicate returned true + */ + template <typename T> + std::set<game_object_id_t> + get_objects_by_predicate(const std::function<bool(const T &)> & pred) const; + + /** + * \brief Get components of type \c T for multiple game object IDs + * + * \tparam T The type of the components to return + * \param ids The object IDs + * + * \return All components matching type \c T and one of the IDs in \c ids + */ + template <typename T> + RefVector<T> get_components_by_ids(const std::set<game_object_id_t> & ids) const; + + /** + * \brief Get object IDs for objects with name \c name + * + * \param name Object name to match + * \returns Object IDs where Metadata::name is equal to \c name + */ + std::set<game_object_id_t> get_objects_by_name(const std::string & name) const; + /** + * \brief Get object IDs for objects with tag \c tag + * + * \param tag Object tag to match + * \returns Object IDs where Metadata::tag is equal to \c tag + */ + std::set<game_object_id_t> get_objects_by_tag(const std::string & tag) const; + +private: + //! By Component \c std::type_index (readability helper type) + template <typename T> + using by_type = std::unordered_map<std::type_index, T>; + //! By \c game_object_id index (readability helper type) + template <typename T> + using by_id_index = std::vector<T>; + /** + * \brief The components + * + * This unordered_map stores all components. The key is the type of the component and the + * value is a vector of vectors of unique pointers to the components. + * + * Every component type has its own vector of vectors of unique pointers to the components. + * The first vector is for the ids of the GameObjects and the second vector is for the + * components (because a GameObject might have multiple components). + */ + by_type<by_id_index<std::vector<std::unique_ptr<Component>>>> components; + + //! Persistent flag for each GameObject + std::unordered_map<game_object_id_t, bool> persistent; + + //! ID of next GameObject allocated by \c ComponentManager::new_object + game_object_id_t next_id = 0; +}; + +} // namespace crepe + +#include "ComponentManager.hpp" diff --git a/src/crepe/ComponentManager.hpp b/src/crepe/manager/ComponentManager.hpp index 4d5eaf4..6d32edb 100644 --- a/src/crepe/ComponentManager.hpp +++ b/src/crepe/manager/ComponentManager.hpp @@ -11,8 +11,10 @@ template <class T, typename... Args> T & ComponentManager::add_component(game_object_id_t id, Args &&... args) { using namespace std; - static_assert(is_base_of<Component, T>::value, - "add_component must recieve a derivative class of Component"); + static_assert( + is_base_of<Component, T>::value, + "add_component must recieve a derivative class of Component" + ); // Determine the type of T (this is used as the key of the unordered_map<>) type_index type = typeid(T); @@ -40,8 +42,8 @@ T & ComponentManager::add_component(game_object_id_t id, Args &&... args) { // Check if the vector size is not greater than get_instances_max int max_instances = instance->get_instances_max(); if (max_instances != -1 && components[type][id].size() >= max_instances) { - throw std::runtime_error( - "Exceeded maximum number of instances for this component type"); + throw std::runtime_error("Exceeded maximum number of instances for this component type" + ); } // store its unique_ptr in the vector<> @@ -54,6 +56,11 @@ template <typename T> void ComponentManager::delete_components_by_id(game_object_id_t id) { using namespace std; + // Do not delete persistent objects + if (this->persistent[id]) { + return; + } + // Determine the type of T (this is used as the key of the unordered_map<>) type_index type = typeid(T); @@ -77,39 +84,40 @@ void ComponentManager::delete_components() { if (this->components.find(type) == this->components.end()) return; - this->components[type].clear(); + // Loop through the whole vector<> of this specific type + for (game_object_id_t i = 0; i < this->components[type].size(); ++i) { + // Do not delete persistent objects + if (!this->persistent[i]) { + this->components[type][i].clear(); + } + } } template <typename T> RefVector<T> ComponentManager::get_components_by_id(game_object_id_t id) const { using namespace std; - // Determine the type of T (this is used as the key of the unordered_map<>) - type_index type = typeid(T); - - // Create an empty vector<> - RefVector<T> component_vector; - - if (this->components.find(type) == this->components.end()) return component_vector; - - // Get the correct vector<> - const vector<vector<unique_ptr<Component>>> & component_array = this->components.at(type); - - // Make sure that the id (that we are looking for) is within the boundaries of the vector<> - if (id >= component_array.size()) return component_vector; + static_assert( + is_base_of<Component, T>::value, + "get_components_by_id must recieve a derivative class of Component" + ); - // Loop trough the whole vector<> - for (const unique_ptr<Component> & component_ptr : component_array[id]) { - // Cast the unique_ptr to a raw pointer - T * casted_component = static_cast<T *>(component_ptr.get()); - - if (casted_component == nullptr) continue; - - // Add the dereferenced raw pointer to the vector<> - component_vector.push_back(*casted_component); + type_index type = typeid(T); + if (!this->components.contains(type)) return {}; + + const by_id_index<vector<unique_ptr<Component>>> & components_by_id + = this->components.at(type); + if (id >= components_by_id.size()) return {}; + + RefVector<T> out = {}; + const vector<unique_ptr<Component>> & components = components_by_id.at(id); + for (auto & component_ptr : components) { + if (component_ptr == nullptr) continue; + Component & component = *component_ptr.get(); + out.push_back(static_cast<T &>(component)); } - return component_vector; + return out; } template <typename T> @@ -147,4 +155,46 @@ RefVector<T> ComponentManager::get_components_by_type() const { return component_vector; } +template <typename T> +std::set<game_object_id_t> +ComponentManager::get_objects_by_predicate(const std::function<bool(const T &)> & pred) const { + using namespace std; + + set<game_object_id_t> objects = {}; + RefVector<T> components = this->get_components_by_type<T>(); + + for (const T & component : components) { + game_object_id_t id = dynamic_cast<const Component &>(component).game_object_id; + if (objects.contains(id)) continue; + if (!pred(component)) continue; + objects.insert(id); + } + + return objects; +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_ids(const std::set<game_object_id_t> & ids +) const { + using namespace std; + + RefVector<T> out = {}; + for (game_object_id_t id : ids) { + RefVector<T> components = get_components_by_id<T>(id); + out.insert(out.end(), components.begin(), components.end()); + } + + return out; +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_name(const std::string & name) const { + return this->get_components_by_ids<T>(this->get_objects_by_name(name)); +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_tag(const std::string & tag) const { + return this->get_components_by_ids<T>(this->get_objects_by_tag(tag)); +} + } // namespace crepe diff --git a/src/crepe/api/EventManager.cpp b/src/crepe/manager/EventManager.cpp index 20f0dd3..6aa49ee 100644 --- a/src/crepe/api/EventManager.cpp +++ b/src/crepe/manager/EventManager.cpp @@ -3,11 +3,9 @@ using namespace crepe; using namespace std; -EventManager & EventManager::get_instance() { - static EventManager instance; - return instance; +EventManager::EventManager(Mediator & mediator) : Manager(mediator) { + this->mediator.event_manager = *this; } - void EventManager::dispatch_events() { for (auto & event : this->events_queue) { this->handle_event(event.type, event.channel, *event.event.get()); diff --git a/src/crepe/api/EventManager.h b/src/crepe/manager/EventManager.h index 348a04d..5766a0c 100644 --- a/src/crepe/api/EventManager.h +++ b/src/crepe/manager/EventManager.h @@ -5,8 +5,10 @@ #include <unordered_map> #include <vector> -#include "Event.h" -#include "EventHandler.h" +#include "../api/Event.h" +#include "../api/EventHandler.h" + +#include "Manager.h" namespace crepe { @@ -22,78 +24,70 @@ typedef size_t subscription_t; typedef size_t event_channel_t; /** - * \class EventManager * \brief Manages event subscriptions, triggers, and queues, enabling decoupled event handling. - * + * * The `EventManager` acts as a centralized event system. It allows for registering callbacks * for specific event types, triggering events synchronously, queueing events for later * processing, and managing subscriptions via unique identifiers. */ -class EventManager { +class EventManager : public Manager { public: static constexpr const event_channel_t CHANNEL_ALL = -1; - /** - * \brief Get the singleton instance of the EventManager. - * - * This method returns the unique instance of the EventManager, creating it if it - * doesn't already exist. Ensures only one instance is active in the program. - * - * \return Reference to the singleton instance of the EventManager. + * \param mediator A reference to a Mediator object used for transfering managers. */ - static EventManager & get_instance(); - + EventManager(Mediator & mediator); /** * \brief Subscribe to a specific event type. - * + * * Registers a callback for a given event type and optional channel. Each callback * is assigned a unique subscription ID that can be used for later unsubscription. - * + * * \tparam EventType The type of the event to subscribe to. * \param callback The callback function to be invoked when the event is triggered. * \param channel The channel number to subscribe to (default is CHANNEL_ALL, which listens to all channels). * \return A unique subscription ID associated with the registered callback. */ template <typename EventType> - subscription_t subscribe(const EventHandler<EventType> & callback, - event_channel_t channel = CHANNEL_ALL); + subscription_t + subscribe(const EventHandler<EventType> & callback, event_channel_t channel = CHANNEL_ALL); /** * \brief Unsubscribe a previously registered callback. - * + * * Removes a callback from the subscription list based on its unique subscription ID. - * + * * \param event_id The unique subscription ID of the callback to remove. */ void unsubscribe(subscription_t event_id); /** * \brief Trigger an event immediately. - * + * * Synchronously invokes all registered callbacks for the given event type on the specified channel. - * + * * \tparam EventType The type of the event to trigger. * \param event The event instance to pass to the callbacks. * \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels). */ template <typename EventType> - void trigger_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); + void trigger_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL); /** * \brief Queue an event for later processing. - * + * * Adds an event to the event queue to be processed during the next call to `dispatch_events`. - * + * * \tparam EventType The type of the event to queue. * \param event The event instance to queue. * \param channel The channel to associate with the event (default is CHANNEL_ALL). */ template <typename EventType> - void queue_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); + void queue_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL); /** * \brief Process all queued events. - * + * * Iterates through the event queue and triggers callbacks for each queued event. * Events are removed from the queue once processed. */ @@ -101,25 +95,18 @@ public: /** * \brief Clear all subscriptions. - * + * * Removes all registered event handlers and clears the subscription list. */ void clear(); private: /** - * \brief Default constructor for the EventManager. - * - * Constructor is private to enforce the singleton pattern. - */ - EventManager() = default; - - /** * \struct QueueEntry * \brief Represents an entry in the event queue. */ struct QueueEntry { - std::unique_ptr<Event> event; ///< The event instance. + std::unique_ptr<Event, std::function<void(Event *)>> event; ///< The event instance. event_channel_t channel = CHANNEL_ALL; ///< The channel associated with the event. std::type_index type; ///< The type of the event. }; diff --git a/src/crepe/api/EventManager.hpp b/src/crepe/manager/EventManager.hpp index a5f4556..1f44943 100644 --- a/src/crepe/api/EventManager.hpp +++ b/src/crepe/manager/EventManager.hpp @@ -5,24 +5,31 @@ namespace crepe { template <typename EventType> -subscription_t EventManager::subscribe(const EventHandler<EventType> & callback, - event_channel_t channel) { +subscription_t +EventManager::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) { subscription_counter++; std::type_index event_type = typeid(EventType); std::unique_ptr<EventHandlerWrapper<EventType>> handler = std::make_unique<EventHandlerWrapper<EventType>>(callback); std::vector<CallbackEntry> & handlers = this->subscribers[event_type]; - handlers.emplace_back(CallbackEntry{ - .callback = std::move(handler), .channel = channel, .id = subscription_counter}); + handlers.emplace_back(CallbackEntry { + .callback = std::move(handler), .channel = channel, .id = subscription_counter + }); return subscription_counter; } template <typename EventType> void EventManager::queue_event(const EventType & event, event_channel_t channel) { - static_assert(std::is_base_of<Event, EventType>::value, - "EventType must derive from Event"); + static_assert( + std::is_base_of<Event, EventType>::value, "EventType must derive from Event" + ); this->events_queue.push_back(QueueEntry{ - .event = std::make_unique<EventType>(event), + // unique_ptr w/ custom destructor implementation is used because the base Event interface + // can't be polymorphic (= have default virtual destructor) + .event = { + new EventType(event), + [](Event * ev) { delete static_cast<EventType *>(ev); }, + }, .channel = channel, .type = typeid(EventType), }); diff --git a/src/crepe/manager/LoopTimerManager.cpp b/src/crepe/manager/LoopTimerManager.cpp new file mode 100644 index 0000000..b4cd07f --- /dev/null +++ b/src/crepe/manager/LoopTimerManager.cpp @@ -0,0 +1,91 @@ +#include <chrono> +#include <thread> + +#include "../util/dbg.h" + +#include "LoopTimerManager.h" + +using namespace crepe; +using namespace std::chrono; +using namespace std::chrono_literals; + +LoopTimerManager::LoopTimerManager(Mediator & mediator) : Manager(mediator) { + this->mediator.loop_timer = *this; + dbg_trace(); +} + +void LoopTimerManager::start() { + this->last_frame_time = std::chrono::steady_clock::now(); + + this->elapsed_time = elapsed_time_t {0}; + this->elapsed_fixed_time = elapsed_time_t {0}; + this->delta_time = duration_t {0}; +} + +void LoopTimerManager::update() { + time_point_t current_frame_time = std::chrono::steady_clock::now(); + // Convert to duration in seconds for delta time + this->delta_time = current_frame_time - last_frame_time; + + if (this->delta_time > this->maximum_delta_time) { + this->delta_time = this->maximum_delta_time; + } + if (this->delta_time > 0s) { + this->actual_fps = static_cast<unsigned>(1.0 / this->delta_time.count()); + } else { + this->actual_fps = 0; + } + this->elapsed_time += duration_cast<elapsed_time_t>(this->delta_time); + this->last_frame_time = current_frame_time; +} + +duration_t LoopTimerManager::get_delta_time() const { + return this->delta_time * this->time_scale; +} + +elapsed_time_t LoopTimerManager::get_elapsed_time() const { return this->elapsed_time; } + +void LoopTimerManager::advance_fixed_elapsed_time() { + this->elapsed_fixed_time + += std::chrono::duration_cast<elapsed_time_t>(this->fixed_delta_time); +} + +void LoopTimerManager::set_target_framerate(unsigned fps) { + this->target_fps = fps; + //check if fps is lower or equals 0 + if (fps <= 0) return; + // target time per frame in seconds + this->frame_target_time = duration_t(1s) / this->target_fps; +} + +unsigned LoopTimerManager::get_fps() const { return this->actual_fps; } + +void LoopTimerManager::set_time_scale(double value) { this->time_scale = value; } + +float LoopTimerManager::get_time_scale() const { return this->time_scale; } + +void LoopTimerManager::enforce_frame_rate() { + time_point_t current_frame_time = std::chrono::steady_clock::now(); + duration_t frame_duration = current_frame_time - this->last_frame_time; + // Check if frame duration is less than the target frame time + if (frame_duration < this->frame_target_time) { + duration_t delay_time = this->frame_target_time - frame_duration; + if (delay_time > 0s) { + std::this_thread::sleep_for(delay_time); + } + } +} + +duration_t LoopTimerManager::get_lag() const { + return this->elapsed_time - this->elapsed_fixed_time; +} + +duration_t LoopTimerManager::get_scaled_fixed_delta_time() const { + return this->fixed_delta_time * this->time_scale; +} + +void LoopTimerManager::set_fixed_delta_time(float seconds) { + this->fixed_delta_time = duration_t(seconds); +} + +duration_t LoopTimerManager::get_fixed_delta_time() const { return this->fixed_delta_time; } diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h new file mode 100644 index 0000000..279d6b2 --- /dev/null +++ b/src/crepe/manager/LoopTimerManager.h @@ -0,0 +1,177 @@ +#pragma once + +#include <chrono> + +#include "Manager.h" + +namespace crepe { + +class Engine; + +typedef std::chrono::duration<float> duration_t; +typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t; + +/** + * \brief Manages timing and frame rate for the game loop. + * + * The LoopTimerManager class is responsible for calculating and managing timing functions + * such as delta time, frames per second (FPS), fixed time steps, and time scaling. It ensures + * consistent frame updates and supports game loop operations, such as handling fixed updates + * for physics and other time-sensitive operations. + */ +class LoopTimerManager : public Manager { +public: + /** + * \param mediator A reference to a Mediator object used for transfering managers. + */ + LoopTimerManager(Mediator & mediator); + /** + * \brief Get the current delta time for the current frame. + * + * This value represents the estimated frame duration of the current frame. + * This value can be used in the frame_update to convert pixel based values to time based values. + * + * \return Delta time in seconds since the last frame. + */ + duration_t get_delta_time() const; + + /** + * \brief Get the current elapsed time (total time passed ) + * + * \note The current game time may vary from real-world elapsed time. It is the cumulative + * sum of each frame's delta time. + * + * \return Elapsed game time in seconds. + */ + elapsed_time_t get_elapsed_time() const; + + /** + * \brief Set the target frames per second (FPS). + * + * \param fps The desired frames rendered per second. + */ + void set_target_framerate(unsigned fps); + + /** + * \brief Get the current frames per second (FPS). + * + * \return Current FPS. + */ + unsigned int get_fps() const; + + /** + * \brief Get the current time scale. + * + * \return The current time scale, where (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). + * up the game. + */ + float get_time_scale() const; + + /** + * \brief Set the time scale. + * + * time_scale is a value that changes the delta time that can be retrieved using get_delta_time function. + * + * \param time_scale The desired time scale (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). + */ + void set_time_scale(double time_scale); + + /** + * \brief Get the fixed delta time in seconds without scaling by the time scale. + * + * This value is used in the LoopManager to determine how many times + * the fixed_update should be called within a given interval. + * + * \return The unscaled fixed delta time in seconds. + */ + duration_t get_fixed_delta_time() const; + + /** + * \brief Set the fixed_delta_time in seconds. + * + * \param seconds fixed_delta_time in seconds. + * + * The fixed_delta_time value is used to determine how many times per second the fixed_update and process_input functions are called. + * + */ + void set_fixed_delta_time(float seconds); + + /** + * \brief Retrieves the scaled fixed delta time in seconds. + * + * The scaled fixed delta time is the timing value used within the `fixed_update` function. + * It is adjusted by the time_scale to account for any changes in the simulation's + * speed. + * + * \return The fixed delta time, scaled by the current time scale, in seconds. + */ + duration_t get_scaled_fixed_delta_time() const; + +private: + //! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update. + friend class Engine; + /** + * \brief Start the loop timer. + * + * Initializes the timer to begin tracking frame times. + */ + void start(); + /** + * \brief Enforce the frame rate limit. + * + * Ensures that the game loop does not exceed the target FPS by delaying frame updates as + * necessary. + */ + void enforce_frame_rate(); + /** + * \brief Get the accumulated lag in the game loop. + * + * Lag represents the difference between the target frame time and the actual frame time, + * useful for managing fixed update intervals. + * + * \return Accumulated lag in seconds. + */ + duration_t get_lag() const; + + /** + * \brief Update the timer to the current frame. + * + * Calculates and updates the delta time for the current frame and adds it to the cumulative + * game time. + */ + void update(); + + /** + * \brief Progress the elapsed fixed time by the fixed delta time interval. + * + * This method advances the game's fixed update loop by adding the fixed_delta_time + * to elapsed_fixed_time, ensuring the fixed update catches up with the elapsed time. + */ + void advance_fixed_elapsed_time(); + +private: + //! Target frames per second. + unsigned int target_fps = 60; + //! Actual frames per second. + unsigned int actual_fps = 0; + //! Time scale for speeding up or slowing down the game (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). + float time_scale = 1; + //! Maximum delta time in seconds to avoid large jumps. + duration_t maximum_delta_time {0.25}; + //! Delta time for the current frame in seconds. + duration_t delta_time {0.0}; + //! Target time per frame in seconds + duration_t frame_target_time {1.0 / target_fps}; + //! Fixed delta time for fixed updates in seconds. + duration_t fixed_delta_time {1.0 / 50.0}; + //! Total elapsed game time in microseconds. + elapsed_time_t elapsed_time {0}; + //! Total elapsed time for fixed updates in microseconds. + elapsed_time_t elapsed_fixed_time {0}; + + typedef std::chrono::steady_clock::time_point time_point_t; + //! Time of the last frame. + time_point_t last_frame_time; +}; + +} // namespace crepe diff --git a/src/crepe/manager/Manager.cpp b/src/crepe/manager/Manager.cpp new file mode 100644 index 0000000..1182785 --- /dev/null +++ b/src/crepe/manager/Manager.cpp @@ -0,0 +1,5 @@ +#include "Manager.h" + +using namespace crepe; + +Manager::Manager(Mediator & mediator) : mediator(mediator) {} diff --git a/src/crepe/manager/Manager.h b/src/crepe/manager/Manager.h new file mode 100644 index 0000000..84d80fe --- /dev/null +++ b/src/crepe/manager/Manager.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Mediator.h" + +namespace crepe { + +/** + * \brief Base manager class + * + * Managers are used for various tasks that fall outside the ECS system category. All managers + * are required to register themselves to the mediator passed to the constructor, and this + * mutable reference is saved for convenience, even though not all managers use the mediator + * directly. + */ +class Manager { +public: + Manager(Mediator & mediator); + virtual ~Manager() = default; + +protected: + Mediator & mediator; +}; + +} // namespace crepe diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h new file mode 100644 index 0000000..842f1de --- /dev/null +++ b/src/crepe/manager/Mediator.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../util/OptionalRef.h" + +namespace crepe { + +class ComponentManager; +class SceneManager; +class EventManager; +class LoopTimerManager; +class SaveManager; +class ResourceManager; +class SDLContext; +class ReplayManager; +class SystemManager; + +/** + * Struct to pass references to classes that would otherwise need to be singletons down to + * other classes within the engine hierarchy. Made to prevent constant changes to subclasses to + * pass specific references through dependency injection. All references on this struct + * *should* be explicitly checked for availability as this struct does not guarantee anything. + * + * \note Dereferencing members of this struct should be deferred. If you are a user of this + * class, keep a reference to this mediator instead of just picking references from it when you + * receive an instance. + * + * \warning This class should never be directly accessible from the API + */ +struct Mediator { + OptionalRef<SDLContext> sdl_context; + OptionalRef<ComponentManager> component_manager; + OptionalRef<SceneManager> scene_manager; + OptionalRef<EventManager> event_manager; + OptionalRef<LoopTimerManager> loop_timer; + OptionalRef<SaveManager> save_manager; + OptionalRef<ResourceManager> resource_manager; + OptionalRef<ReplayManager> replay_manager; + OptionalRef<SystemManager> system_manager; +}; + +} // namespace crepe diff --git a/src/crepe/manager/ReplayManager.cpp b/src/crepe/manager/ReplayManager.cpp new file mode 100644 index 0000000..090a94e --- /dev/null +++ b/src/crepe/manager/ReplayManager.cpp @@ -0,0 +1,70 @@ +#include <format> + +#include "Manager.h" +#include "ReplayManager.h" + +using namespace crepe; +using namespace std; + +ReplayManager::ReplayManager(Mediator & mediator) : Manager(mediator) { + mediator.replay_manager = *this; +} + +void ReplayManager::record_start() { + if (this->state == RECORDING) this->release(this->id); + this->id++; + this->memory[this->id] = make_unique<Recording>(); + this->recording = *this->memory.at(this->id); + this->state = RECORDING; +} + +recording_t ReplayManager::record_end() { + this->state = IDLE; + return this->id; +} + +void ReplayManager::play(recording_t handle) { + if (!this->memory.contains(handle)) + throw out_of_range(format("ReplayManager: no recording for handle {}", handle)); + this->recording = *this->memory.at(handle); + this->recording->frame = 0; + this->state = PLAYING; +} + +void ReplayManager::release(recording_t handle) { + if (!this->memory.contains(handle)) return; + this->memory.erase(handle); +} + +void ReplayManager::frame_record() { + if (this->state != RECORDING) + throw runtime_error("ReplayManager: frame_step called while not playing"); + + ComponentManager & components = this->mediator.component_manager; + Recording & recording = this->recording; + + recording.frames.push_back(components.save()); + recording.frame++; +} + +bool ReplayManager::frame_step() { + if (this->state != PLAYING) + throw runtime_error("ReplayManager: frame_step called while not playing"); + + ComponentManager & components = this->mediator.component_manager; + Recording & recording = this->recording; + + ComponentManager::Snapshot & frame = recording.frames.at(recording.frame); + + components.restore(frame); + recording.frame++; + + if (recording.frame < recording.frames.size()) return false; + // end of recording + recording.frame = 0; + this->state = IDLE; + this->recording.clear(); + return true; +} + +ReplayManager::State ReplayManager::get_state() const { return this->state; } diff --git a/src/crepe/manager/ReplayManager.h b/src/crepe/manager/ReplayManager.h new file mode 100644 index 0000000..f06a58b --- /dev/null +++ b/src/crepe/manager/ReplayManager.h @@ -0,0 +1,96 @@ +#pragma once + +#include <unordered_map> + +#include "../util/OptionalRef.h" + +#include "ComponentManager.h" +#include "Manager.h" + +namespace crepe { + +//! Handle to recording held by ReplayManager +typedef size_t recording_t; + +/** + * \brief Replay manager + * + * The replay manager is responsible for creating, storing and restoring ComponentManager + * snapshots. Sequential snapshots can be recorded and replayed in combination with + * ReplaySystem. + */ +class ReplayManager : public Manager { + // TODO: Delete recordings at end of scene + +public: + ReplayManager(Mediator & mediator); + +public: + //! Start a new recording + void record_start(); + /** + * \brief End the latest recording started by \c record_start() + * \returns Handle to recording + */ + recording_t record_end(); + /** + * \brief Play a recording + * \param handle Handle to recording (as returned by \c record_end()) + */ + void play(recording_t handle); + /** + * \brief Delete a recording from memory + * \param handle Handle to recording (as returned by \c record_end()) + */ + void release(recording_t handle); + +public: + //! Internal state + enum State { + IDLE, //!< Not doing anything + RECORDING, //!< Currently recording + PLAYING, //!< Currently playing back a recording + }; + //! Get current internal state + State get_state() const; + +public: + /** + * \brief Record a single frame to the current recording + * + * This function is called by ReplaySystem after the game programmer has called \c + * record_start() + */ + void frame_record(); + /** + * \brief Play the next frame of the current recording + * + * \returns `true` if the recording is finished playing + * \returns `false` if there are more frames + * + * This function also automatically resets the internal state from PLAYING to IDLE at the end + * of a recording. + */ + bool frame_step(); + +private: + /** + * \brief Recording data + */ + struct Recording { + //! Current frame being shown + size_t frame = 0; + //! All frames in recording + std::vector<ComponentManager::Snapshot> frames; + }; + //! Internal state + State state = IDLE; + //! Current recording handle + recording_t id = -1; + //! Current recording data + OptionalRef<Recording> recording; + //! Recording storage + std::unordered_map<recording_t, std::unique_ptr<Recording>> memory; +}; + +} // namespace crepe diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp new file mode 100644 index 0000000..5713183 --- /dev/null +++ b/src/crepe/manager/ResourceManager.cpp @@ -0,0 +1,30 @@ +#include "util/dbg.h" + +#include "ResourceManager.h" + +using namespace crepe; +using namespace std; + +ResourceManager::ResourceManager(Mediator & mediator) : Manager(mediator) { + dbg_trace(); + mediator.resource_manager = *this; +} +ResourceManager::~ResourceManager() { dbg_trace(); } + +void ResourceManager::clear() { + std::erase_if(this->resources, [](const pair<const Asset, CacheEntry> & pair) { + const CacheEntry & entry = pair.second; + return entry.persistent == false; + }); +} + +void ResourceManager::clear_all() { this->resources.clear(); } + +void ResourceManager::set_persistent(const Asset & asset, bool persistent) { + this->get_entry(asset).persistent = persistent; +} + +ResourceManager::CacheEntry & ResourceManager::get_entry(const Asset & asset) { + if (!this->resources.contains(asset)) this->resources[asset] = {}; + return this->resources.at(asset); +} diff --git a/src/crepe/manager/ResourceManager.h b/src/crepe/manager/ResourceManager.h new file mode 100644 index 0000000..84b275d --- /dev/null +++ b/src/crepe/manager/ResourceManager.h @@ -0,0 +1,78 @@ +#pragma once + +#include <memory> +#include <unordered_map> + +#include "../Resource.h" +#include "../api/Asset.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief Owner of concrete Resource instances + * + * ResourceManager caches concrete Resource instances per Asset. Concrete resources are + * destroyed at the end of scenes by default, unless the game programmer marks them as + * persistent. + */ +class ResourceManager : public Manager { +public: + ResourceManager(Mediator & mediator); + virtual ~ResourceManager(); // dbg_trace + +private: + //! Cache entry + struct CacheEntry { + //! Concrete resource instance + std::unique_ptr<Resource> resource = nullptr; + //! Prevent ResourceManager::clear from removing this entry + bool persistent = false; + }; + //! Internal cache + std::unordered_map<const Asset, CacheEntry> resources; + /** + * \brief Ensure a cache entry exists for this asset and return a mutable reference to it + * + * \param asset Asset the concrete resource is instantiated from + * + * \returns Mutable reference to cache entry + */ + CacheEntry & get_entry(const Asset & asset); + +public: + /** + * \brief Mark a resource as persistent (i.e. used across multiple scenes) + * + * \param asset Asset the concrete resource is instantiated from + * \param persistent Whether this resource is persistent (true=keep, false=destroy) + */ + void set_persistent(const Asset & asset, bool persistent); + + /** + * \brief Retrieve reference to concrete Resource by Asset + * + * \param asset Asset the concrete resource is instantiated from + * \tparam Resource Concrete derivative of Resource + * + * This class instantiates the concrete resource if it is not yet stored in the internal + * cache, or returns a reference to the cached resource if it already exists. + * + * \returns Reference to concrete resource + * + * \throws std::runtime_error if the \c Resource parameter does not match with the actual + * type of the resource stored in the cache for this Asset + */ + template <typename Resource> + Resource & get(const Asset & asset); + + //! Clear non-persistent resources from cache + void clear(); + //! Clear all resources from cache regardless of persistence + void clear_all(); +}; + +} // namespace crepe + +#include "ResourceManager.hpp" diff --git a/src/crepe/manager/ResourceManager.hpp b/src/crepe/manager/ResourceManager.hpp new file mode 100644 index 0000000..4ca6be0 --- /dev/null +++ b/src/crepe/manager/ResourceManager.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include <format> + +#include "ResourceManager.h" + +namespace crepe { + +template <typename T> +T & ResourceManager::get(const Asset & asset) { + using namespace std; + static_assert( + is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource" + ); + + CacheEntry & entry = this->get_entry(asset); + if (entry.resource == nullptr) entry.resource = make_unique<T>(asset, this->mediator); + + T * concrete_resource = dynamic_cast<T *>(entry.resource.get()); + if (concrete_resource == nullptr) + throw runtime_error(format( + "ResourceManager: mismatch between requested type and " + "actual type of resource ({})", + asset.get_path() + )); + + return *concrete_resource; +} + +} // namespace crepe diff --git a/src/crepe/api/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp index c5f43ea..f313ed2 100644 --- a/src/crepe/api/SaveManager.cpp +++ b/src/crepe/manager/SaveManager.cpp @@ -1,13 +1,25 @@ +#include "../ValueBroker.h" +#include "../api/Config.h" #include "../facade/DB.h" -#include "../util/Log.h" -#include "Config.h" #include "SaveManager.h" -#include "ValueBroker.h" using namespace std; using namespace crepe; +SaveManager::SaveManager(Mediator & mediator) : Manager(mediator) { + mediator.save_manager = *this; +} + +DB & SaveManager::get_db() { + if (this->db == nullptr) { + Config & cfg = Config::get_instance(); + this->db + = {new DB(cfg.savemgr.location), [](void * db) { delete static_cast<DB *>(db); }}; + } + return *static_cast<DB *>(this->db.get()); +} + template <> string SaveManager::serialize(const string & value) const noexcept { return value; @@ -90,22 +102,6 @@ int32_t SaveManager::deserialize(const string & value) const noexcept { return deserialize<int64_t>(value); } -SaveManager::SaveManager() { dbg_trace(); } - -SaveManager & SaveManager::get_instance() { - dbg_trace(); - static SaveManager instance; - return instance; -} - -DB & SaveManager::get_db() { - Config & cfg = Config::get_instance(); - // TODO: make this path relative to XDG_DATA_HOME on Linux and whatever the - // default equivalent is on Windows using some third party library - static DB db(cfg.savemgr.location); - return db; -} - bool SaveManager::has(const string & key) { DB & db = this->get_db(); return db.has(key); @@ -133,9 +129,32 @@ template void SaveManager::set(const string &, const float &); template void SaveManager::set(const string &, const double &); template <typename T> +T SaveManager::get(const string & key) { + return this->deserialize<T>(this->get_db().get(key)); +} +template uint8_t SaveManager::get(const string &); +template int8_t SaveManager::get(const string &); +template uint16_t SaveManager::get(const string &); +template int16_t SaveManager::get(const string &); +template uint32_t SaveManager::get(const string &); +template int32_t SaveManager::get(const string &); +template uint64_t SaveManager::get(const string &); +template int64_t SaveManager::get(const string &); +template float SaveManager::get(const string &); +template double SaveManager::get(const string &); +template string SaveManager::get(const string &); + +template <typename T> ValueBroker<T> SaveManager::get(const string & key, const T & default_value) { if (!this->has(key)) this->set<T>(key, default_value); - return this->get<T>(key); + T value; + return { + [this, key](const T & target) { this->set<T>(key, target); }, + [this, key, value]() mutable -> const T & { + value = this->get<T>(key); + return value; + }, + }; } template ValueBroker<uint8_t> SaveManager::get(const string &, const uint8_t &); template ValueBroker<int8_t> SaveManager::get(const string &, const int8_t &); @@ -148,26 +167,3 @@ template ValueBroker<int64_t> SaveManager::get(const string &, const int64_t &); template ValueBroker<float> SaveManager::get(const string &, const float &); template ValueBroker<double> SaveManager::get(const string &, const double &); template ValueBroker<string> SaveManager::get(const string &, const string &); - -template <typename T> -ValueBroker<T> SaveManager::get(const string & key) { - T value; - return { - [this, key](const T & target) { this->set<T>(key, target); }, - [this, key, value]() mutable -> const T & { - value = this->deserialize<T>(this->get_db().get(key)); - return value; - }, - }; -} -template ValueBroker<uint8_t> SaveManager::get(const string &); -template ValueBroker<int8_t> SaveManager::get(const string &); -template ValueBroker<uint16_t> SaveManager::get(const string &); -template ValueBroker<int16_t> SaveManager::get(const string &); -template ValueBroker<uint32_t> SaveManager::get(const string &); -template ValueBroker<int32_t> SaveManager::get(const string &); -template ValueBroker<uint64_t> SaveManager::get(const string &); -template ValueBroker<int64_t> SaveManager::get(const string &); -template ValueBroker<float> SaveManager::get(const string &); -template ValueBroker<double> SaveManager::get(const string &); -template ValueBroker<string> SaveManager::get(const string &); diff --git a/src/crepe/api/SaveManager.h b/src/crepe/manager/SaveManager.h index 3d8c852..1e34bc0 100644 --- a/src/crepe/api/SaveManager.h +++ b/src/crepe/manager/SaveManager.h @@ -1,9 +1,12 @@ #pragma once +#include <functional> #include <memory> #include "../ValueBroker.h" +#include "Manager.h" + namespace crepe { class DB; @@ -18,7 +21,7 @@ class DB; * * The underlying database is a key-value store. */ -class SaveManager { +class SaveManager : public Manager { public: /** * \brief Get a read/write reference to a value and initialize it if it does not yet exist @@ -33,17 +36,17 @@ public: ValueBroker<T> get(const std::string & key, const T & default_value); /** - * \brief Get a read/write reference to a value + * \brief Get a value directly * * \param key The value key * - * \return Read/write reference to the value + * \return The value * * \note Attempting to read this value before it is initialized (i.e. set) will result in an * exception */ template <typename T> - ValueBroker<T> get(const std::string & key); + T get(const std::string & key); /** * \brief Set a value directly @@ -63,8 +66,8 @@ public: */ bool has(const std::string & key); -private: - SaveManager(); +public: + SaveManager(Mediator & mediator); virtual ~SaveManager() = default; private: @@ -89,26 +92,13 @@ private: template <typename T> T deserialize(const std::string & value) const noexcept; -public: - // singleton - static SaveManager & get_instance(); - SaveManager(const SaveManager &) = delete; - SaveManager(SaveManager &&) = delete; - SaveManager & operator=(const SaveManager &) = delete; - SaveManager & operator=(SaveManager &&) = delete; +protected: + //! Create or return DB + virtual DB & get_db(); private: - /** - * \brief Create an instance of DB and return its reference - * - * \returns DB instance - * - * This function exists because DB is a facade class, which can't directly be used in the API - * without workarounds - * - * TODO: better solution - */ - static DB & get_db(); + //! Database + std::unique_ptr<void, std::function<void(void *)>> db = nullptr; }; } // namespace crepe diff --git a/src/crepe/api/SceneManager.cpp b/src/crepe/manager/SceneManager.cpp index 1f783ad..e6f92db 100644 --- a/src/crepe/api/SceneManager.cpp +++ b/src/crepe/manager/SceneManager.cpp @@ -1,14 +1,15 @@ #include <algorithm> #include <memory> -#include "../ComponentManager.h" - +#include "ComponentManager.h" #include "SceneManager.h" using namespace crepe; using namespace std; -SceneManager::SceneManager(ComponentManager & mgr) : component_manager(mgr) {} +SceneManager::SceneManager(Mediator & mediator) : Manager(mediator) { + mediator.scene_manager = *this; +} void SceneManager::set_next_scene(const string & name) { next_scene = name; } @@ -16,19 +17,24 @@ void SceneManager::load_next_scene() { // next scene not set if (this->next_scene.empty()) return; - auto it = find_if(this->scenes.begin(), this->scenes.end(), - [&next_scene = this->next_scene](unique_ptr<Scene> & scene) { - return scene.get()->get_name() == next_scene; - }); + auto it = find_if( + this->scenes.begin(), this->scenes.end(), + [&next_scene = this->next_scene](unique_ptr<Scene> & scene) { + return scene.get()->get_name() == next_scene; + } + ); // next scene not found if (it == this->scenes.end()) return; unique_ptr<Scene> & scene = *it; // Delete all components of the current scene - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; mgr.delete_all_components(); // Load the new scene scene->load_scene(); + + //clear the next scene + next_scene.clear(); } diff --git a/src/crepe/api/SceneManager.h b/src/crepe/manager/SceneManager.h index 45ba668..e0955c2 100644 --- a/src/crepe/api/SceneManager.h +++ b/src/crepe/manager/SceneManager.h @@ -3,7 +3,9 @@ #include <memory> #include <vector> -#include "Scene.h" +#include "../api/Scene.h" + +#include "Manager.h" namespace crepe { @@ -15,10 +17,9 @@ class ComponentManager; * This class manages scenes. It can add new scenes and load them. It also manages the current scene * and the next scene. */ -class SceneManager { +class SceneManager : public Manager { public: - //! \param mgr Reference to the ComponentManager - SceneManager(ComponentManager & mgr); + SceneManager(Mediator & mediator); public: /** @@ -26,8 +27,8 @@ public: * * \tparam T Type of concrete scene */ - template <typename T> - void add_scene(); + template <typename T, typename... Args> + void add_scene(Args &&... args); /** * \brief Set the next scene * @@ -44,8 +45,6 @@ private: std::vector<std::unique_ptr<Scene>> scenes; //! Next scene to load std::string next_scene; - //! Reference to the ComponentManager - ComponentManager & component_manager; }; } // namespace crepe diff --git a/src/crepe/api/SceneManager.hpp b/src/crepe/manager/SceneManager.hpp index 94e5946..dff4e51 100644 --- a/src/crepe/api/SceneManager.hpp +++ b/src/crepe/manager/SceneManager.hpp @@ -4,13 +4,17 @@ namespace crepe { -template <typename T> -void SceneManager::add_scene() { +template <typename T, typename... Args> +void SceneManager::add_scene(Args &&... args) { using namespace std; static_assert(is_base_of<Scene, T>::value, "T must be derived from Scene"); - Scene * scene = new T(this->component_manager); - this->scenes.emplace_back(unique_ptr<Scene>(scene)); + Scene * scene = new T(std::forward<Args>(args)...); + unique_ptr<Scene> unique_scene(scene); + + unique_scene->mediator = this->mediator; + + this->scenes.emplace_back(std::move(unique_scene)); // The first scene added, is the one that will be loaded at the beginning if (next_scene.empty()) { diff --git a/src/crepe/manager/SystemManager.cpp b/src/crepe/manager/SystemManager.cpp new file mode 100644 index 0000000..eabc022 --- /dev/null +++ b/src/crepe/manager/SystemManager.cpp @@ -0,0 +1,66 @@ +#include "../system/AISystem.h" +#include "../system/AnimatorSystem.h" +#include "../system/AudioSystem.h" +#include "../system/CollisionSystem.h" +#include "../system/EventSystem.h" +#include "../system/InputSystem.h" +#include "../system/ParticleSystem.h" +#include "../system/PhysicsSystem.h" +#include "../system/RenderSystem.h" +#include "../system/ReplaySystem.h" +#include "../system/ScriptSystem.h" + +#include "SystemManager.h" + +using namespace crepe; +using namespace std; + +SystemManager::SystemManager(Mediator & mediator) : Manager(mediator) { + this->load_system<InputSystem>(); + this->load_system<EventSystem>(); + this->load_system<ScriptSystem>(); + this->load_system<ParticleSystem>(); + this->load_system<AISystem>(); + this->load_system<PhysicsSystem>(); + this->load_system<CollisionSystem>(); + this->load_system<AudioSystem>(); + this->load_system<AnimatorSystem>(); + this->load_system<RenderSystem>(); + this->load_system<ReplaySystem>(); + + this->mediator.system_manager = *this; +} + +void SystemManager::fixed_update() { + for (System & system : this->system_order) { + if (!system.active) continue; + system.fixed_update(); + } +} + +void SystemManager::frame_update() { + for (System & system : this->system_order) { + if (!system.active) continue; + system.frame_update(); + } +} + +SystemManager::Snapshot SystemManager::save() { + Snapshot snapshot; + for (auto & [type, system] : this->systems) { + snapshot[type] = system->active; + } + return snapshot; +} + +void SystemManager::restore(const Snapshot & snapshot) { + for (auto & [type, active] : snapshot) { + this->systems[type]->active = active; + } +} + +void SystemManager::disable_all() { + for (auto & [type, system] : this->systems) { + system->active = false; + } +} diff --git a/src/crepe/manager/SystemManager.h b/src/crepe/manager/SystemManager.h new file mode 100644 index 0000000..614d90c --- /dev/null +++ b/src/crepe/manager/SystemManager.h @@ -0,0 +1,93 @@ +#pragma once + +#include <memory> +#include <typeindex> +#include <unordered_map> +#include <vector> + +#include "../system/System.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief Collection of all systems + * + * This manager aggregates all systems and provides utility functions to retrieve references to + * and update systems. + */ +class SystemManager : public Manager { +public: + SystemManager(Mediator &); + + /** + * \brief Per-frame update. + * + * Updates the game state based on the elapsed time since the last frame. + */ + void frame_update(); + + /** + * \brief Fixed update executed at a fixed rate. + * + * This function updates physics and game logic based on LoopTimer's fixed_delta_time. + */ + void fixed_update(); + +private: + /** + * \brief Collection of System instances + * + * This map holds System instances indexed by the system's class typeid. It is filled in the + * constructor of \c SystemManager using SystemManager::load_system. + */ + std::unordered_map<std::type_index, std::unique_ptr<System>> systems; + /** + * \brief Collection of System instances + * + * This map holds System instances indexed by the system's class typeid. It is filled in the + * constructor of \c SystemManager using SystemManager::load_system. + */ + std::vector<std::reference_wrapper<System>> system_order; + /** + * \brief Initialize a system + * \tparam T System type (must be derivative of \c System) + */ + template <class T> + void load_system(); + +public: + /** + * \brief Retrieve a reference to ECS system + * \tparam T System type + * \returns Reference to system instance + * \throws std::runtime_error if the System is not initialized + */ + template <class T> + T & get_system(); + +public: + /** + * \brief SystemManager snapshot + * + * The SystemManager snapshot only stores which systems are active + */ + typedef std::unordered_map<std::type_index, bool> Snapshot; + /** + * \brief Save a snapshot of the systems' state + * \returns Copy of each system's active property + */ + Snapshot save(); + /** + * \brief Restore system active state from a snapshot + * \param snapshot Snapshot to restore from (as returned by \c save()) + */ + void restore(const Snapshot & snapshot); + //! Disable all systems + void disable_all(); +}; + +} // namespace crepe + +#include "SystemManager.hpp" diff --git a/src/crepe/manager/SystemManager.hpp b/src/crepe/manager/SystemManager.hpp new file mode 100644 index 0000000..addd274 --- /dev/null +++ b/src/crepe/manager/SystemManager.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include <cassert> +#include <format> +#include <memory> + +#include "SystemManager.h" + +namespace crepe { + +template <class T> +T & SystemManager::get_system() { + using namespace std; + static_assert( + is_base_of<System, T>::value, "get_system must recieve a derivative class of System" + ); + + const type_info & type = typeid(T); + if (!this->systems.contains(type)) + throw runtime_error(format("SystemManager: {} is not initialized", type.name())); + + System * system = this->systems.at(type).get(); + T * concrete_system = dynamic_cast<T *>(system); + assert(concrete_system != nullptr); + + return *concrete_system; +} + +template <class T> +void SystemManager::load_system() { + using namespace std; + static_assert( + is_base_of<System, T>::value, "load_system must recieve a derivative class of System" + ); + + const type_info & type = typeid(T); + if (this->systems.contains(type)) + throw runtime_error(format("SystemManager: {} is already initialized", type.name())); + System * system = new T(this->mediator); + this->systems[type] = unique_ptr<System>(system); + this->system_order.push_back(*this->systems[type]); +} + +} // namespace crepe diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp new file mode 100644 index 0000000..94445c7 --- /dev/null +++ b/src/crepe/system/AISystem.cpp @@ -0,0 +1,187 @@ +#include <algorithm> +#include <cmath> + +#include "manager/ComponentManager.h" +#include "manager/LoopTimerManager.h" +#include "manager/Mediator.h" + +#include "AISystem.h" + +using namespace crepe; +using namespace std::chrono; + +void AISystem::fixed_update() { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + LoopTimerManager & loop_timer = mediator.loop_timer; + RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); + + float dt = loop_timer.get_scaled_fixed_delta_time().count(); + + // Loop through all AI components + for (AI & ai : ai_components) { + if (!ai.active) { + continue; + } + + RefVector<Rigidbody> rigidbodies + = mgr.get_components_by_id<Rigidbody>(ai.game_object_id); + if (rigidbodies.empty()) { + throw std::runtime_error( + "AI component must be attached to a GameObject with a Rigidbody component" + ); + } + Rigidbody & rigidbody = rigidbodies.front().get(); + if (!rigidbody.active) { + continue; + } + if (rigidbody.data.mass <= 0) { + throw std::runtime_error("Mass must be greater than 0"); + } + + // Calculate the force to apply to the entity + vec2 force = this->calculate(ai, rigidbody); + // Calculate the acceleration (using the above calculated force) + vec2 acceleration = force / rigidbody.data.mass; + // Finally, update Rigidbody's velocity + rigidbody.data.linear_velocity += acceleration * dt; + } +} + +vec2 AISystem::calculate(AI & ai, const Rigidbody & rigidbody) { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); + Transform & transform = transforms.front().get(); + + vec2 force; + + // Run all the behaviors that are on, and stop if the force gets too high + if (ai.on(AI::BehaviorTypeMask::FLEE)) { + vec2 force_to_add = this->flee(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::ARRIVE)) { + vec2 force_to_add = this->arrive(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::SEEK)) { + vec2 force_to_add = this->seek(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::PATH_FOLLOW)) { + vec2 force_to_add = this->path_follow(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + + return force; +} + +bool AISystem::accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add) { + float magnitude = running_total.length(); + float magnitude_remaining = ai.max_force - magnitude; + + if (magnitude_remaining <= 0.0f) { + // If the force is already at/above the max force, return false + return false; + } + + float magnitude_to_add = force_to_add.length(); + if (magnitude_to_add < magnitude_remaining) { + // If the force to add is less than the remaining force, add it + running_total += force_to_add; + } else { + // If the force to add is greater than the remaining force, add a fraction of it + force_to_add.normalize(); + running_total += force_to_add * magnitude_remaining; + } + + return true; +} + +vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) + const { + // Calculate the desired velocity + vec2 desired_velocity = ai.seek_target - transform.position; + desired_velocity.normalize(); + desired_velocity *= rigidbody.data.max_linear_velocity; + + return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) + const { + // Calculate the desired velocity if the entity is within the panic distance + vec2 desired_velocity = transform.position - ai.flee_target; + if (desired_velocity.length_squared() > ai.square_flee_panic_distance) { + return vec2 {0, 0}; + } + desired_velocity.normalize(); + desired_velocity *= rigidbody.data.max_linear_velocity; + + return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) + const { + // Calculate the desired velocity (taking into account the deceleration rate) + vec2 to_target = ai.arrive_target - transform.position; + float distance = to_target.length(); + if (distance > 0.0f) { + if (ai.arrive_deceleration <= 0.0f) { + throw std::runtime_error("Deceleration rate must be greater than 0"); + } + + float speed = distance / ai.arrive_deceleration; + speed = std::min(speed, rigidbody.data.max_linear_velocity); + vec2 desired_velocity = to_target * (speed / distance); + + return desired_velocity - rigidbody.data.linear_velocity; + } + + return vec2 {0, 0}; +} + +vec2 AISystem::path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform) { + if (ai.path.empty()) { + return vec2 {0, 0}; + } + + // Get the target node + vec2 target = ai.path.at(ai.path_index); + // Calculate the force to apply to the entity + vec2 to_target = target - transform.position; + if (to_target.length_squared() > ai.path_node_distance * ai.path_node_distance) { + // If the entity is not close enough to the target node, seek it + ai.seek_target = target; + ai.arrive_target = target; + } else { + // If the entity is close enough to the target node, move to the next node + ai.path_index++; + if (ai.path_index >= ai.path.size()) { + if (ai.path_loop) { + // If the path is looping, reset the path index + ai.path_index = 0; + } else { + // If the path is not looping, arrive at the last node + ai.path_index = ai.path.size() - 1; + return this->arrive(ai, rigidbody, transform); + } + } + } + + // Seek the target node + return this->seek(ai, rigidbody, transform); +} diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h new file mode 100644 index 0000000..04807cf --- /dev/null +++ b/src/crepe/system/AISystem.h @@ -0,0 +1,81 @@ +#pragma once + +#include "api/AI.h" +#include "api/Rigidbody.h" + +#include "System.h" +#include "api/Transform.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AISystem is used to control the movement of entities using AI. + * + * The AISystem is used to control the movement of entities using AI. The AISystem can be used to + * implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AISystem : public System { +public: + using System::System; + + //! Update the AI system + void fixed_update() override; + +private: + /** + * \brief Calculate the total force to apply to the entity + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + */ + vec2 calculate(AI & ai, const Rigidbody & rigidbody); + /** + * \brief Accumulate the force to apply to the entity + * + * \param ai The AI component + * \param running_total The running total of the force + * \param force_to_add The force to add + * \return true if the force was added, false otherwise + */ + bool accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add); + + /** + * \brief Calculate the seek force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The seek force + */ + vec2 seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the flee force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The flee force + */ + vec2 flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the arrive force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The arrive force + */ + vec2 arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the path follow force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The path follow force + */ + vec2 path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform); +}; + +} // namespace crepe diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 676e485..143d5d6 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,24 +1,44 @@ -#include <cstdint> +#include <chrono> -#include "api/Animator.h" -#include "facade/SDLContext.h" +#include "../api/Animator.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" #include "AnimatorSystem.h" -#include "ComponentManager.h" using namespace crepe; +using namespace std::chrono; -void AnimatorSystem::update() { - ComponentManager & mgr = this->component_manager; - +void AnimatorSystem::frame_update() { + ComponentManager & mgr = this->mediator.component_manager; + LoopTimerManager & timer = this->mediator.loop_timer; RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); - uint64_t tick = SDLContext::get_instance().get_ticks(); + duration_t elapsed_time = timer.get_delta_time(); + for (Animator & a : animations) { - if (a.active) { - a.curr_row = (tick / 100) % a.row; - a.animator_rect.x = (a.curr_row * a.animator_rect.w) + a.curr_col; - a.spritesheet.sprite_rect = a.animator_rect; + if (!a.active) continue; + if (a.data.fps == 0) continue; + + Animator::Data & ctx = a.data; + + a.elapsed_time += elapsed_time; + duration_t frame_duration = 1000ms / ctx.fps; + + int cycle_end = (ctx.cycle_end == -1) ? a.grid_size.x : ctx.cycle_end; + if (a.elapsed_time >= frame_duration) { + a.elapsed_time = 0ms; + a.frame++; + if (a.frame == cycle_end) { + a.frame = ctx.cycle_start; + if (!ctx.looping) { + a.active = false; + continue; + } + } } + + ctx.row = ctx.cycle_start + a.frame; + a.spritesheet.mask.x = ctx.row * a.spritesheet.mask.w; } } diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h index 56cc7b3..092e131 100644 --- a/src/crepe/system/AnimatorSystem.h +++ b/src/crepe/system/AnimatorSystem.h @@ -2,9 +2,6 @@ #include "System.h" -//TODO: -// control if flip works with animation system - namespace crepe { /** @@ -21,12 +18,11 @@ public: /** * \brief Updates the Animator components. * - * This method is called periodically (likely every frame) to update the state of all + * This method is called to update the state of all * Animator components, moving the animations forward and managing their behavior (e.g., * looping). */ - void update() override; - // FIXME: never say "likely" in the documentation lmao + void frame_update() override; }; } // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp new file mode 100644 index 0000000..3c2232f --- /dev/null +++ b/src/crepe/system/AudioSystem.cpp @@ -0,0 +1,64 @@ +#include "AudioSystem.h" + +#include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h" +#include "../types.h" + +using namespace crepe; +using namespace std; + +void AudioSystem::fixed_update() { + ComponentManager & component_manager = this->mediator.component_manager; + ResourceManager & resource_manager = this->mediator.resource_manager; + RefVector<AudioSource> components + = component_manager.get_components_by_type<AudioSource>(); + + for (AudioSource & component : components) { + Sound & resource = resource_manager.get<Sound>(component.source); + + this->diff_update(component, resource); + + this->update_last(component); + } +} + +void AudioSystem::diff_update(AudioSource & component, Sound & resource) { + SoundContext & context = this->get_context(); + + if (component.active != component.last_active) { + if (!component.active) { + context.stop(component.voice); + return; + } + if (component.play_on_awake) component.oneshot_play = true; + } + if (!component.active) return; + + if (component.oneshot_play) { + component.voice = context.play(resource); + context.set_loop(component.voice, component.loop); + context.set_volume(component.voice, component.volume); + component.oneshot_play = false; + } + if (component.oneshot_stop) { + context.stop(component.voice); + component.oneshot_stop = false; + } + if (component.volume != component.last_volume) { + context.set_volume(component.voice, component.volume); + } + if (component.loop != component.last_loop) { + context.set_loop(component.voice, component.loop); + } +} + +void AudioSystem::update_last(AudioSource & component) { + component.last_active = component.active; + component.last_loop = component.loop; + component.last_volume = component.volume; +} + +SoundContext & AudioSystem::get_context() { + if (this->context == nullptr) this->context = make_unique<SoundContext>(); + return *this->context.get(); +} diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h new file mode 100644 index 0000000..56fc98c --- /dev/null +++ b/src/crepe/system/AudioSystem.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../api/AudioSource.h" +#include "../facade/Sound.h" +#include "../facade/SoundContext.h" + +#include "System.h" + +namespace crepe { + +class AudioSystem : public System { +public: + using System::System; + void fixed_update() override; + +private: + /** + * \brief Update `last_*` members of \c component + * + * Copies all component properties stored for comparison between AudioSystem::update() calls + * + * \param component AudioSource component to update + */ + void update_last(AudioSource & component); + + /** + * \brief Compare update component + * + * Compares properties of \c component and \c data, and calls SoundContext functions where + * applicable. + * + * \param component AudioSource component to update + * \param resource Sound instance for AudioSource's Asset + */ + void diff_update(AudioSource & component, Sound & resource); + +protected: + /** + * \brief Get SoundContext + * + * SoundContext is retrieved through this function instead of being a direct member of + * AudioSystem to aid with testability. + */ + virtual SoundContext & get_context(); + +private: + //! SoundContext + std::unique_ptr<SoundContext> context = nullptr; +}; + +} // namespace crepe diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index d658b25..52369d0 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -5,7 +5,12 @@ target_sources(crepe PUBLIC PhysicsSystem.cpp CollisionSystem.cpp RenderSystem.cpp + AudioSystem.cpp AnimatorSystem.cpp + InputSystem.cpp + EventSystem.cpp + ReplaySystem.cpp + AISystem.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -14,5 +19,10 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES PhysicsSystem.h CollisionSystem.h RenderSystem.h + AudioSystem.h AnimatorSystem.h + InputSystem.h + EventSystem.h + ReplaySystem.h + AISystem.h ) diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index c74ca1d..571ac70 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -1,5 +1,604 @@ +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <emmintrin.h> +#include <functional> +#include <optional> +#include <utility> +#include <variant> + +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" +#include "util/AbsolutePosition.h" +#include "util/OptionalRef.h" + #include "CollisionSystem.h" +#include "types.h" using namespace crepe; +using enum Rigidbody::BodyType; + +CollisionSystem::CollisionInfo CollisionSystem::CollisionInfo::operator-() const { + return { + .self = this->other, + .other = this->self, + .resolution = -this->resolution, + .resolution_direction = this->resolution_direction, + }; +} + +void CollisionSystem::fixed_update() { + std::vector<CollisionInternal> all_colliders; + game_object_id_t id = 0; + ComponentManager & mgr = this->mediator.component_manager; + RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); + // Collisions can only happen on object with a rigidbody + for (Rigidbody & rigidbody : rigidbodies) { + if (!rigidbody.active) continue; + id = rigidbody.game_object_id; + Transform & transform = mgr.get_components_by_id<Transform>(id).front().get(); + Metadata & metadata = mgr.get_components_by_id<Metadata>(id).front().get(); + // Check if the boxcollider is active and has the same id as the rigidbody. + RefVector<BoxCollider> boxcolliders = mgr.get_components_by_type<BoxCollider>(); + for (BoxCollider & boxcollider : boxcolliders) { + if (boxcollider.game_object_id != id) continue; + if (!boxcollider.active) continue; + all_colliders.push_back( + {.id = id, + .collider = collider_variant {boxcollider}, + .info = {transform, rigidbody, metadata}} + ); + } + // Check if the circlecollider is active and has the same id as the rigidbody. + RefVector<CircleCollider> circlecolliders + = mgr.get_components_by_type<CircleCollider>(); + for (CircleCollider & circlecollider : circlecolliders) { + if (circlecollider.game_object_id != id) continue; + if (!circlecollider.active) continue; + all_colliders.push_back( + {.id = id, + .collider = collider_variant {circlecollider}, + .info = {transform, rigidbody, metadata}} + ); + } + } + + // Check between all colliders if there is a collision (collision handling) + std::vector<std::pair<CollisionInternal, CollisionInternal>> collided + = this->gather_collisions(all_colliders); + + // For the object convert the info and call the collision handler if needed + for (auto & collision_pair : collided) { + // Convert internal struct to external struct + CollisionInfo info + = this->get_collision_info(collision_pair.first, collision_pair.second); + // Determine if and/or what collison handler is needed. + this->determine_collision_handler(info); + } +} + +// Below is for collision detection +std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> +CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { + + // TODO: + // If no colliders skip + // Check if colliders has rigidbody if not skip + + // TODO: + // If amount is higer than lets say 16 for now use quadtree otwerwise skip + // Quadtree code + // Quadtree is placed over the input vector + + // Return data of collided colliders which are variants + std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret; + //using visit to visit the variant to access the active and id. + for (size_t i = 0; i < colliders.size(); ++i) { + for (size_t j = i + 1; j < colliders.size(); ++j) { + if (colliders[i].id == colliders[j].id) continue; + if (!should_collide(colliders[i], colliders[j])) continue; + CollisionInternalType type + = get_collider_type(colliders[i].collider, colliders[j].collider); + if (!detect_collision(colliders[i], colliders[j], type)) continue; + //fet + collisions_ret.emplace_back(colliders[i], colliders[j]); + } + } + return collisions_ret; +} + +bool CollisionSystem::should_collide( + const CollisionInternal & self, const CollisionInternal & other +) const { + + const Rigidbody::Data & self_rigidbody = self.info.rigidbody.data; + const Rigidbody::Data & other_rigidbody = other.info.rigidbody.data; + const Metadata & self_metadata = self.info.metadata; + const Metadata & other_metadata = other.info.metadata; + + // Check collision layers + if (self_rigidbody.collision_layers.contains(other_rigidbody.collision_layer)) return true; + if (other_rigidbody.collision_layers.contains(self_rigidbody.collision_layer)) return true; + + // Check names + if (self_rigidbody.collision_names.contains(other_metadata.name)) return true; + if (other_rigidbody.collision_names.contains(self_metadata.name)) return true; + + // Check tags + if (self_rigidbody.collision_tags.contains(other_metadata.tag)) return true; + if (other_rigidbody.collision_tags.contains(self_metadata.tag)) return true; + + return false; +} + +CollisionSystem::CollisionInternalType CollisionSystem::get_collider_type( + const collider_variant & collider1, const collider_variant & collider2 +) const { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { + return CollisionInternalType::CIRCLE_CIRCLE; + } else { + return CollisionInternalType::CIRCLE_BOX; + } + } else { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { + return CollisionInternalType::BOX_CIRCLE; + } else { + return CollisionInternalType::BOX_BOX; + } + } +} + +bool CollisionSystem::detect_collision( + CollisionInternal & self, CollisionInternal & other, const CollisionInternalType & type +) { + vec2 resolution; + switch (type) { + case CollisionInternalType::BOX_BOX: { + // Box-Box collision detection + const BoxColliderInternal BOX1 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const BoxColliderInternal BOX2 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-box collision detection + resolution = this->get_box_box_detection(BOX1, BOX2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; + break; + } + case CollisionInternalType::BOX_CIRCLE: { + // Box-Circle collision detection + const BoxColliderInternal BOX1 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const CircleColliderInternal CIRCLE2 + = {.collider + = std::get<std::reference_wrapper<CircleCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-circle collision detection + resolution = this->get_box_circle_detection(BOX1, CIRCLE2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; + // Invert the resolution vector for proper collision response + resolution = -resolution; + break; + } + case CollisionInternalType::CIRCLE_CIRCLE: { + // Circle-Circle collision detection + const CircleColliderInternal CIRCLE1 + = {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const CircleColliderInternal CIRCLE2 + = {.collider + = std::get<std::reference_wrapper<CircleCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from circle-circle collision detection + resolution = this->get_circle_circle_detection(CIRCLE1, CIRCLE2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; + break; + } + case CollisionInternalType::CIRCLE_BOX: { + // Circle-Box collision detection + const CircleColliderInternal CIRCLE1 + = {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const BoxColliderInternal BOX2 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-circle collision detection (order swapped) + resolution = this->get_box_circle_detection(BOX2, CIRCLE1); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; + break; + } + case CollisionInternalType::NONE: + // No collision detection needed if the type is NONE + return false; + break; + } + // Store the calculated resolution vector for the 'self' collider + self.resolution = resolution; + // Calculate the resolution direction based on the rigidbody data + self.resolution_direction + = this->resolution_correction(self.resolution, self.info.rigidbody.data); + // For the 'other' collider, the resolution is the opposite direction of 'self' + other.resolution = -self.resolution; + other.resolution_direction = self.resolution_direction; + + // Return true if a collision was detected and resolution was calculated + return true; +} + +vec2 CollisionSystem::get_box_box_detection( + const BoxColliderInternal & box1, const BoxColliderInternal & box2 +) const { + vec2 resolution {NAN, NAN}; + // Get current positions of colliders + vec2 pos1 = AbsolutePosition::get_position(box1.transform, box1.collider.offset); + vec2 pos2 = AbsolutePosition::get_position(box2.transform, box2.collider.offset); + + // Scale dimensions + vec2 scaled_box1 = box1.collider.dimensions * box1.transform.scale; + vec2 scaled_box2 = box2.collider.dimensions * box2.transform.scale; + vec2 delta = pos2 - pos1; + + // Calculate half-extents (half width and half height) + float half_width1 = scaled_box1.x / 2.0; + float half_height1 = scaled_box1.y / 2.0; + float half_width2 = scaled_box2.x / 2.0; + float half_height2 = scaled_box2.y / 2.0; + + if (pos1.x + half_width1 > pos2.x - half_width2 + && pos1.x - half_width1 < pos2.x + half_width2 + && pos1.y + half_height1 > pos2.y - half_height2 + && pos1.y - half_height1 < pos2.y + half_height2) { + resolution = {0, 0}; + float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); + float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); + if (overlap_x > 0 && overlap_y > 0) { + // Determine the direction of resolution + if (overlap_x < overlap_y) { + // Resolve along the X-axis (smallest overlap) + resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; + } else if (overlap_y < overlap_x) { + // Resolve along the Y-axis (smallest overlap) + resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + } else { + // Equal overlap, resolve both directions with preference + resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; + resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + } + } + } + return resolution; +} + +vec2 CollisionSystem::get_box_circle_detection( + const BoxColliderInternal & box, const CircleColliderInternal & circle +) const { + /// Get current positions of colliders + vec2 box_pos = AbsolutePosition::get_position(box.transform, box.collider.offset); + vec2 circle_pos = AbsolutePosition::get_position(circle.transform, circle.collider.offset); + + // Scale dimensions + vec2 scaled_box = box.collider.dimensions * box.transform.scale; + float scaled_circle_radius = circle.collider.radius * circle.transform.scale; + + // Calculate box half-extents + float half_width = scaled_box.x / 2.0f; + float half_height = scaled_box.y / 2.0f; + + // Find the closest point on the box to the circle's center + float closest_x + = std::max(box_pos.x - half_width, std::min(circle_pos.x, box_pos.x + half_width)); + float closest_y + = std::max(box_pos.y - half_height, std::min(circle_pos.y, box_pos.y + half_height)); + + float distance_x = circle_pos.x - closest_x; + float distance_y = circle_pos.y - closest_y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; + if (distance_squared < scaled_circle_radius * scaled_circle_radius) { + vec2 delta = circle_pos - box_pos; + + // Clamp circle center to the nearest point on the box + vec2 closest_point; + closest_point.x = std::clamp(delta.x, -half_width, half_width); + closest_point.y = std::clamp(delta.y, -half_height, half_height); + + // Find the vector from the circle center to the closest point + vec2 closest_delta = delta - closest_point; + + float distance + = std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y); + vec2 collision_normal = closest_delta / distance; + + // Compute penetration depth + float penetration_depth = scaled_circle_radius - distance; + + // Compute the resolution vector + return vec2 {collision_normal * penetration_depth}; + } + // No collision + return vec2 {NAN, NAN}; +} + +vec2 CollisionSystem::get_circle_circle_detection( + const CircleColliderInternal & circle1, const CircleColliderInternal & circle2 +) const { + // Get current positions of colliders + vec2 final_position1 + = AbsolutePosition::get_position(circle1.transform, circle1.collider.offset); + vec2 final_position2 + = AbsolutePosition::get_position(circle2.transform, circle2.collider.offset); + + // Scale dimensions + float scaled_circle1 = circle1.collider.radius * circle1.transform.scale; + float scaled_circle2 = circle2.collider.radius * circle2.transform.scale; + + float distance_x = final_position1.x - final_position2.x; + float distance_y = final_position1.y - final_position2.y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; + + // Calculate the sum of the radii + float radius_sum = scaled_circle1 + scaled_circle2; + + // Check for collision (distance squared must be less than the square of the radius sum) + if (distance_squared < radius_sum * radius_sum) { + vec2 delta = final_position2 - final_position1; + + // Compute the distance between the two circle centers + float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); + + // Compute the combined radii of the two circles + float combined_radius = scaled_circle1 + scaled_circle2; + + // Compute the penetration depth + float penetration_depth = combined_radius - distance; + + // Normalize the delta vector to get the collision direction + vec2 collision_normal = delta / distance; + + // Compute the resolution vector + vec2 resolution = -collision_normal * penetration_depth; + + return resolution; + } + // No collision + return vec2 {NAN, NAN}; + ; +} + +CollisionSystem::Direction +CollisionSystem::resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody) { + + // Calculate the other value to move back correctly + // If only X or Y has a value determine what is should be to move back. + Direction resolution_direction = Direction::NONE; + // If both are not zero a perfect corner has been hit + if (resolution.x != 0 && resolution.y != 0) { + resolution_direction = Direction::BOTH; + // If x is not zero a horizontal action was latest action. + } else if (resolution.x != 0) { + resolution_direction = Direction::X_DIRECTION; + // If both are 0 resolution y should not be changed (y_velocity can be 0 by kinematic object movement) + if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0) + resolution.y + = -rigidbody.linear_velocity.y * (resolution.x / rigidbody.linear_velocity.x); + } else if (resolution.y != 0) { + resolution_direction = Direction::Y_DIRECTION; + // If both are 0 resolution x should not be changed (x_velocity can be 0 by kinematic object movement) + if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0) + resolution.x + = -rigidbody.linear_velocity.x * (resolution.y / rigidbody.linear_velocity.y); + } + + return resolution_direction; +} + +CollisionSystem::CollisionInfo CollisionSystem::get_collision_info( + const CollisionInternal & in_self, const CollisionInternal & in_other +) const { + + crepe::CollisionSystem::ColliderInfo self { + .transform = in_self.info.transform, + .rigidbody = in_self.info.rigidbody, + .metadata = in_self.info.metadata, + }; + + crepe::CollisionSystem::ColliderInfo other { + .transform = in_other.info.transform, + .rigidbody = in_other.info.rigidbody, + .metadata = in_other.info.metadata, + }; + + struct CollisionInfo collision_info { + .self = self, .other = other, .resolution = in_self.resolution, + .resolution_direction = in_self.resolution_direction, + }; + return collision_info; +} + +void CollisionSystem::determine_collision_handler(const CollisionInfo & info) { + Rigidbody::BodyType self_type = info.self.rigidbody.data.body_type; + Rigidbody::BodyType other_type = info.other.rigidbody.data.body_type; + bool self_kinematic = info.self.rigidbody.data.kinematic_collision; + bool other_kinematic = info.other.rigidbody.data.kinematic_collision; + // Inverted collision info + CollisionInfo inverted = -info; + // If both objects are static skip handle call collision script + if (self_type == STATIC && other_type == STATIC) return; + + // First body is not dynamic + if (self_type != DYNAMIC) { + bool static_collision = self_type == STATIC && other_type == DYNAMIC; + bool kinematic_collision + = self_type == KINEMATIC && other_type == DYNAMIC && self_kinematic; + + // Handle collision + if (static_collision || kinematic_collision) this->static_collision_handler(inverted); + // Call scripts + this->call_collision_events(inverted); + return; + } + + // Second body is not dynamic + if (other_type != DYNAMIC) { + bool static_collision = other_type == STATIC; + bool kinematic_collision = other_type == KINEMATIC && other_kinematic; + // Handle collision + if (static_collision || kinematic_collision) this->static_collision_handler(info); + // Call scripts + this->call_collision_events(info); + return; + } + + // Dynamic + // Handle collision + this->dynamic_collision_handler(info); + // Call scripts + this->call_collision_events(info); +} + +void CollisionSystem::static_collision_handler(const CollisionInfo & info) { + + vec2 & transform_pos = info.self.transform.position; + float elasticity = info.self.rigidbody.data.elasticity_coefficient; + vec2 & rigidbody_vel = info.self.rigidbody.data.linear_velocity; + + // Move object back using calculate move back value + transform_pos += info.resolution; + + switch (info.resolution_direction) { + case Direction::BOTH: + //bounce + if (elasticity > 0) { + rigidbody_vel = -rigidbody_vel * elasticity; + } + //stop movement + else { + rigidbody_vel = {0, 0}; + } + break; + case Direction::Y_DIRECTION: + // Bounce + if (elasticity > 0) { + rigidbody_vel.y = -rigidbody_vel.y * elasticity; + } + // Stop movement + else { + rigidbody_vel.y = 0; + transform_pos.x -= info.resolution.x; + } + break; + case Direction::X_DIRECTION: + // Bounce + if (elasticity > 0) { + rigidbody_vel.x = -rigidbody_vel.x * elasticity; + } + // Stop movement + else { + rigidbody_vel.x = 0; + transform_pos.y -= info.resolution.y; + } + break; + case Direction::NONE: + // Not possible + break; + } +} + +void CollisionSystem::dynamic_collision_handler(const CollisionInfo & info) { + + vec2 & self_transform_pos = info.self.transform.position; + vec2 & other_transform_pos = info.other.transform.position; + float self_elasticity = info.self.rigidbody.data.elasticity_coefficient; + float other_elasticity = info.other.rigidbody.data.elasticity_coefficient; + vec2 & self_rigidbody_vel = info.self.rigidbody.data.linear_velocity; + vec2 & other_rigidbody_vel = info.other.rigidbody.data.linear_velocity; + + self_transform_pos += info.resolution / 2; + other_transform_pos += -(info.resolution / 2); + + switch (info.resolution_direction) { + case Direction::BOTH: + if (self_elasticity > 0) { + self_rigidbody_vel = -self_rigidbody_vel * self_elasticity; + } else { + self_rigidbody_vel = {0, 0}; + } + + if (other_elasticity > 0) { + other_rigidbody_vel = -other_rigidbody_vel * other_elasticity; + } else { + other_rigidbody_vel = {0, 0}; + } + break; + case Direction::Y_DIRECTION: + if (self_elasticity > 0) { + self_rigidbody_vel.y = -self_rigidbody_vel.y * self_elasticity; + } + // Stop movement + else { + self_rigidbody_vel.y = 0; + self_transform_pos.x -= info.resolution.x; + } + + if (other_elasticity > 0) { + other_rigidbody_vel.y = -other_rigidbody_vel.y * other_elasticity; + } + // Stop movement + else { + other_rigidbody_vel.y = 0; + other_transform_pos.x -= info.resolution.x; + } + break; + case Direction::X_DIRECTION: + if (self_elasticity > 0) { + self_rigidbody_vel.x = -self_rigidbody_vel.x * self_elasticity; + } + // Stop movement + else { + self_rigidbody_vel.x = 0; + self_transform_pos.y -= info.resolution.y; + } + + if (other_elasticity > 0) { + other_rigidbody_vel.x = -other_rigidbody_vel.x * other_elasticity; + } + // Stop movement + else { + other_rigidbody_vel.x = 0; + other_transform_pos.y -= info.resolution.y; + } + break; + case Direction::NONE: + // Not possible + break; + } +} -void CollisionSystem::update() {} +void CollisionSystem::call_collision_events(const CollisionInfo & info) { + CollisionEvent data(info); + CollisionEvent data_inverted(-info); + EventManager & emgr = this->mediator.event_manager; + emgr.trigger_event<CollisionEvent>(data, info.self.transform.game_object_id); + emgr.trigger_event<CollisionEvent>(data_inverted, info.other.transform.game_object_id); +} diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index c1a70d8..ff2d35f 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -1,13 +1,303 @@ #pragma once +#include <optional> +#include <variant> +#include <vector> + +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h" #include "System.h" namespace crepe { +//! A system responsible for detecting and handling collisions between colliders. class CollisionSystem : public System { public: using System::System; - void update() override; + +private: + //! Enum representing movement directions during collision resolution. + enum class Direction { + //! No movement required. + NONE, + //! Movement in the X direction. + X_DIRECTION, + //! Movement in the Y direction. + Y_DIRECTION, + //! Movement in both X and Y directions. + BOTH, + }; + +public: + //! Structure representing components of the collider + struct ColliderInfo { + Transform & transform; + Rigidbody & rigidbody; + Metadata & metadata; + }; + + /** + * \brief Structure representing detailed collision information between two colliders. + * + * Includes information about the colliding objects and the resolution data for handling the collision. + */ + struct CollisionInfo { + ColliderInfo self; + ColliderInfo other; + //! The resolution vector for the collision. + vec2 resolution; + //! The direction of movement for resolving the collision. + Direction resolution_direction = Direction::NONE; + CollisionInfo operator-() const; + }; + +private: + //! A variant type that can hold either a BoxCollider or a CircleCollider. + using collider_variant = std::variant< + std::reference_wrapper<BoxCollider>, std::reference_wrapper<CircleCollider>>; + + //! Enum representing the types of collider pairs for collision detection. + enum class CollisionInternalType { + BOX_BOX, + CIRCLE_CIRCLE, + BOX_CIRCLE, + CIRCLE_BOX, + NONE, + }; + + /** + * \brief A structure to store the collision data of a single collider. + * + * This structure all components and id that are for needed within this system when calculating or handling collisions. + * The transform and rigidbody are mostly needed for location and rotation. + * In rigidbody additional info is written about what the body of the object is, + * and how it should respond on a collision. + */ + struct CollisionInternal { + game_object_id_t id = 0; + collider_variant collider; + ColliderInfo info; + vec2 resolution; + Direction resolution_direction = Direction::NONE; + }; + + //! Structure of a collider with additional components + template <typename ColliderType> + struct ColliderInternal { + ColliderType & collider; + Transform & transform; + Rigidbody & rigidbody; + }; + //! Predefined BoxColliderInternal. (System is only made for this type) + using BoxColliderInternal = ColliderInternal<BoxCollider>; + //! Predefined CircleColliderInternal. (System is only made for this type) + using CircleColliderInternal = ColliderInternal<CircleCollider>; + +public: + //! Updates the collision system by checking for collisions between colliders and handling them. + void fixed_update() override; + +private: + /** + * \brief Determines the type of collider pair from two colliders. + * + * Uses std::holds_alternative to identify the types of the provided colliders. + * + * \param collider1 First collider variant (BoxCollider or CircleCollider). + * \param collider2 Second collider variant (BoxCollider or CircleCollider). + * \return The combined type of the two colliders. + */ + CollisionInternalType get_collider_type( + const collider_variant & collider1, const collider_variant & collider2 + ) const; + +private: + /** + * \brief Converts internal collision data into user-accessible collision information. + * + * This function processes collision data from two colliding entities and packages it + * into a structured format that is accessible for further use, + * such as resolving collisions and triggering user-defined collision scripts. + * + * \param data1 Collision data for the first collider. + * \param data2 Collision data for the second collider. + */ + CollisionInfo + get_collision_info(const CollisionInternal & data1, const CollisionInternal & data2) const; + + /** + * \brief Corrects the collision resolution vector and determines its direction. + * + * This function adjusts the provided resolution vector based on the + * rigidbody's linear velocity to ensure consistent collision correction. If the resolution + * vector has only one non-zero component (either x or y), the missing component is computed + * based on the rigidbody's velocity. If both components are non-zero, it indicates a corner + * collision. The function also identifies the direction of the resolution and returns it. + * + * \param resolution resolution vector that needs to be corrected + * \param rigidbody rigidbody data used to correct resolution + * \return A Direction indicating the resolution direction + */ + Direction resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody); + + /** + * \brief Determines the appropriate collision handler for a given collision event. + * + * This function identifies the correct collision resolution process based on the body types + * of the colliders involved in the collision. It delegates + * collision handling to specific handlers and calls collision event scripts + * as needed. + * + * \param info Collision information containing data about both colliders. + */ + void determine_collision_handler(const CollisionInfo & info); + + /** + * \brief Calls both collision script + * + * Calls both collision script to let user add additonal handling or handle full collision. + * + * \param info Collision information containing data about both colliders. + */ + void call_collision_events(const CollisionInfo & info); + + /** + * \brief Handles collisions involving static objects. + * + * This function resolves collisions between static and dynamic objects by adjusting + * the position of the static object and modifying the velocity of the dynamic object + * if elasticity is enabled. The position of the static object is corrected + * based on the collision resolution, and the dynamic object's velocity is adjusted + * accordingly to reflect the collision response. + * + * The handling includes stopping movement, applying bouncing based on the elasticity + * coefficient, and adjusting the position of the dynamic object if needed. + * + * \param info Collision information containing data about both colliders. + */ + void static_collision_handler(const CollisionInfo & info); + + /** + * \brief Handles collisions involving dynamic objects. + * + * Resolves collisions between two dynamic objects by adjusting their positions and modifying + * their velocities based on the collision resolution. If elasticity is enabled, + * the velocity of both objects is reversed and scaled by the respective elasticity coefficient. + * The positions of the objects are adjusted based on the collision resolution. + * + * \param info Collision information containing data about both colliders. + */ + void dynamic_collision_handler(const CollisionInfo & info); + +private: + /** + * \brief Checks for collisions between colliders. + * + * This function checks all active colliders and identifies pairs of colliding objects. + * For each identified collision, the appropriate collision data is returned as pairs for further processing. + * + * \param colliders A collection of all active colliders. + * \return A list of collision pairs with their associated data. + */ + std::vector<std::pair<CollisionInternal, CollisionInternal>> + gather_collisions(std::vector<CollisionInternal> & colliders); + + /** + * \brief Checks if the settings allow collision + * + * This function checks if there is any collison layer where each object is located in. + * After checking the layers it checks the names and at last the tags. + * if in all three sets nothing is found collision can not happen. + * + * \param this_rigidbody Rigidbody of first object + * \param other_rigidbody Rigidbody of second collider + * \param this_metadata Rigidbody of first object + * \param other_metadata Rigidbody of second object + * \return Returns true if there is at least one comparison found. + */ + bool should_collide( + const CollisionInternal & self, + const CollisionInternal & other + ) const; //done + + /** + * \brief Checks for collision between two colliders. + * + * This function determines whether two colliders are colliding based on their types. + * It calls the appropriate collision detection function based on the collider pair type and stores the collision resolution data. + * If a collision is detected, it returns true, otherwise false. + * + * \param first_info Collision data for the first collider. + * \param second_info Collision data for the second collider. + * \param type The type of collider pair. + * \return True if a collision is detected, otherwise false. + */ + bool detect_collision( + CollisionInternal & first_info, CollisionInternal & second_info, + const CollisionInternalType & type + ); + + /** + * \brief Detects collisions between two BoxColliders. + * + * This function checks whether two `BoxCollider` are colliding based on their positions and scaled dimensions. + * If a collision is detected, it calculates the overlap along the X and Y axes and returns the resolution vector. + * If no collision is detected, it returns a vector with NaN values. + + * \param box1 Information about the first BoxCollider. + * \param box2 Information about the second BoxCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. + */ + vec2 get_box_box_detection( + const BoxColliderInternal & box1, const BoxColliderInternal & box2 + ) const; + + /** + * \brief Check collision for box on circle collider + * + * This function detects if a collision occurs between a rectangular box and a circular collider. + * If a collision is detected, the function calculates the resolution vector to resolve the collision. + * If no collision is detected, it returns a vector with NaN values + * + * \param box1 Information about the BoxCollider. + * \param circle2 Information about the circleCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. + */ + vec2 get_box_circle_detection( + const BoxColliderInternal & box1, const CircleColliderInternal & circle2 + ) const; + + /** + * \brief Check collision for circle on circle collider + * + * This function detects if a collision occurs between two circular colliders. + * If a collision is detected, it calculates the resolution vector to resolve the collision. + * If no collision is detected, it returns a vector with NaN values. + * + * \param circle1 Information about the first circleCollider. + * \param circle2 Information about the second circleCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. + */ + vec2 get_circle_circle_detection( + const CircleColliderInternal & circle1, const CircleColliderInternal & circle2 + ) const; +}; + +/** + * \brief Event triggered during a collision between objects. + */ +class CollisionEvent : public Event { +public: + crepe::CollisionSystem::CollisionInfo info; + CollisionEvent(const crepe::CollisionSystem::CollisionInfo & collisionInfo) + : info(collisionInfo) {} }; } // namespace crepe diff --git a/src/crepe/system/EventSystem.cpp b/src/crepe/system/EventSystem.cpp new file mode 100644 index 0000000..7e168ab --- /dev/null +++ b/src/crepe/system/EventSystem.cpp @@ -0,0 +1,9 @@ +#include "EventSystem.h" +#include "../manager/EventManager.h" + +using namespace crepe; + +void EventSystem::fixed_update() { + EventManager & ev = this->mediator.event_manager; + ev.dispatch_events(); +} diff --git a/src/crepe/system/EventSystem.h b/src/crepe/system/EventSystem.h new file mode 100644 index 0000000..0ae48d2 --- /dev/null +++ b/src/crepe/system/EventSystem.h @@ -0,0 +1,21 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +/** + * \brief EventManager dispatch helper system + */ +class EventSystem : public System { +public: + using System::System; + + /** + * \brief Dispatch queued events + * \see EventManager::dispatch_events + */ + void fixed_update() override; +}; + +} // namespace crepe diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp new file mode 100644 index 0000000..be7eda6 --- /dev/null +++ b/src/crepe/system/InputSystem.cpp @@ -0,0 +1,225 @@ +#include "../api/Button.h" +#include "../api/Config.h" +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" + +#include "InputSystem.h" + +using namespace crepe; + +void InputSystem::fixed_update() { + ComponentManager & mgr = this->mediator.component_manager; + SDLContext & context = this->mediator.sdl_context; + std::vector<EventData> event_list = context.get_events(); + const RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); + OptionalRef<Camera> curr_cam_ref; + + // Find the active camera + for (Camera & cam : cameras) { + if (!cam.active) continue; + curr_cam_ref = cam; + break; + } + if (!curr_cam_ref) return; + + Camera & current_cam = curr_cam_ref; + const Transform & cam_transform + = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front(); + + vec2 camera_origin = cam_transform.position + current_cam.data.postion_offset + - (current_cam.viewport_size / 2); + + for (const EventData & event : event_list) { + // Only calculate mouse coordinates for relevant events + if (event.event_type == EventType::MOUSE_DOWN + || event.event_type == EventType::MOUSE_UP + || event.event_type == EventType::MOUSE_MOVE + || event.event_type == EventType::MOUSE_WHEEL) { + this->handle_mouse_event(event, camera_origin, current_cam); + + } else { + this->handle_non_mouse_event(event); + } + } +} + +void InputSystem::handle_mouse_event( + const EventData & event, const vec2 & camera_origin, const Camera & current_cam +) { + EventManager & event_mgr = this->mediator.event_manager; + vec2 adjusted_mouse; + adjusted_mouse.x = event.data.mouse_data.mouse_position.x + camera_origin.x; + adjusted_mouse.y = event.data.mouse_data.mouse_position.y + camera_origin.y; + // Check if the mouse is within the viewport + if ((adjusted_mouse.x < camera_origin.x + || adjusted_mouse.x > camera_origin.x + current_cam.viewport_size.x + || adjusted_mouse.y < camera_origin.y + || adjusted_mouse.y > camera_origin.y + current_cam.viewport_size.y)) + return; + + // Handle mouse-specific events + switch (event.event_type) { + case EventType::MOUSE_DOWN: + event_mgr.queue_event<MousePressEvent>({ + .mouse_pos = adjusted_mouse, + .button = event.data.mouse_data.mouse_button, + }); + this->last_mouse_down_position = adjusted_mouse; + this->last_mouse_button = event.data.mouse_data.mouse_button; + break; + + case EventType::MOUSE_UP: { + event_mgr.queue_event<MouseReleaseEvent>({ + .mouse_pos = adjusted_mouse, + .button = event.data.mouse_data.mouse_button, + }); + vec2 delta_move = adjusted_mouse - this->last_mouse_down_position; + int click_tolerance = Config::get_instance().input.click_tolerance; + if (this->last_mouse_button == event.data.mouse_data.mouse_button + && std::abs(delta_move.x) <= click_tolerance + && std::abs(delta_move.y) <= click_tolerance) { + event_mgr.queue_event<MouseClickEvent>({ + .mouse_pos = adjusted_mouse, + .button = event.data.mouse_data.mouse_button, + }); + this->handle_click( + event.data.mouse_data.mouse_button, adjusted_mouse, current_cam + ); + } + break; + } + + case EventType::MOUSE_MOVE: + event_mgr.queue_event<MouseMoveEvent>({ + .mouse_pos = adjusted_mouse, + .mouse_delta = event.data.mouse_data.rel_mouse_move, + }); + this->handle_move(event, adjusted_mouse, current_cam); + break; + + case EventType::MOUSE_WHEEL: + event_mgr.queue_event<MouseScrollEvent>({ + .mouse_pos = adjusted_mouse, + .scroll_direction = event.data.mouse_data.scroll_direction, + .scroll_delta = event.data.mouse_data.scroll_delta, + }); + break; + + default: + break; + } +} + +void InputSystem::handle_non_mouse_event(const EventData & event) { + EventManager & event_mgr = this->mediator.event_manager; + switch (event.event_type) { + case EventType::KEY_DOWN: + + event_mgr.queue_event<KeyPressEvent>( + {.repeat = event.data.key_data.key_repeat, .key = event.data.key_data.key} + ); + break; + case EventType::KEY_UP: + event_mgr.queue_event<KeyReleaseEvent>({.key = event.data.key_data.key}); + break; + case EventType::SHUTDOWN: + event_mgr.queue_event<ShutDownEvent>({}); + break; + case EventType::WINDOW_EXPOSE: + event_mgr.queue_event<WindowExposeEvent>({}); + break; + case EventType::WINDOW_RESIZE: + event_mgr.queue_event<WindowResizeEvent>( + WindowResizeEvent {.dimensions = event.data.window_data.resize_dimension} + ); + break; + case EventType::WINDOW_MOVE: + event_mgr.queue_event<WindowMoveEvent>( + {.delta_move = event.data.window_data.move_delta} + ); + break; + case EventType::WINDOW_MINIMIZE: + event_mgr.queue_event<WindowMinimizeEvent>({}); + break; + case EventType::WINDOW_MAXIMIZE: + event_mgr.queue_event<WindowMaximizeEvent>({}); + break; + case EventType::WINDOW_FOCUS_GAIN: + event_mgr.queue_event<WindowFocusGainEvent>({}); + break; + case EventType::WINDOW_FOCUS_LOST: + event_mgr.queue_event<WindowFocusLostEvent>({}); + break; + default: + break; + } +} + +void InputSystem::handle_move( + const EventData & event_data, const vec2 & mouse_pos, const Camera & current_cam +) { + ComponentManager & mgr = this->mediator.component_manager; + EventManager & event_mgr = this->mediator.event_manager; + const RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + + for (Button & button : buttons) { + if (!button.active) continue; + + const Transform & transform + = mgr.get_components_by_id<Transform>(button.game_object_id).front(); + const Transform & cam_transform + = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front(); + const Metadata & metadata + = mgr.get_components_by_id<Metadata>(button.game_object_id).front(); + bool was_hovering = button.hover; + + if (this->is_mouse_inside_button(mouse_pos, button, transform, cam_transform)) { + button.hover = true; + if (!was_hovering) { + event_mgr.trigger_event<ButtonEnterEvent>(metadata, metadata.game_object_id); + } + } else { + button.hover = false; + if (was_hovering) { + event_mgr.trigger_event<ButtonExitEvent>(metadata, metadata.game_object_id); + } + } + } +} + +void InputSystem::handle_click( + const MouseButton & mouse_button, const vec2 & mouse_pos, const Camera & current_cam +) { + ComponentManager & mgr = this->mediator.component_manager; + EventManager & event_mgr = this->mediator.event_manager; + const RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + const Transform & cam_transform + = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front(); + for (Button & button : buttons) { + if (!button.active) continue; + const Metadata & metadata + = mgr.get_components_by_id<Metadata>(button.game_object_id).front(); + const Transform & transform + = mgr.get_components_by_id<Transform>(button.game_object_id).front(); + if (this->is_mouse_inside_button(mouse_pos, button, transform, cam_transform)) { + event_mgr.trigger_event<ButtonPressEvent>(metadata, metadata.game_object_id); + } + } +} + +bool InputSystem::is_mouse_inside_button( + const vec2 & mouse_pos, const Button & button, const Transform & transform, + const Transform & cam_transform +) { + vec2 actual_pos = transform.position + button.offset; + if (!button.data.world_space) { + actual_pos += cam_transform.position; + } + vec2 half_dimensions = button.dimensions * transform.scale / 2; + + return mouse_pos.x >= actual_pos.x - half_dimensions.x + && mouse_pos.x <= actual_pos.x + half_dimensions.x + && mouse_pos.y >= actual_pos.y - half_dimensions.y + && mouse_pos.y <= actual_pos.y + half_dimensions.y; +} diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h new file mode 100644 index 0000000..be62367 --- /dev/null +++ b/src/crepe/system/InputSystem.h @@ -0,0 +1,139 @@ +#pragma once + +#include "../api/Event.h" +#include "../api/Metadata.h" +#include "../facade/EventData.h" +#include "../types.h" + +#include "System.h" + +namespace crepe { + +class Camera; +class Button; +class Transform; +//! Event triggered when a button is clicked +class ButtonPressEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonPressEvent(const Metadata & metadata) : metadata(metadata) {}; +}; +//! Event triggered when the mouse enters a button +class ButtonEnterEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonEnterEvent(const Metadata & metadata) : metadata(metadata) {}; +}; +//! Event triggered when the mouse leaves a button +class ButtonExitEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonExitEvent(const Metadata & metadata) : metadata(metadata) {}; +}; + +/** + * \brief Handles the processing of input events created by SDLContext + * + * This system processes events such as mouse clicks, mouse movement, and keyboard + * actions. It is responsible for detecting interactions with UI buttons and + * passing the corresponding events to the registered listeners. + */ +class InputSystem : public System { +public: + using System::System; + + /** + * \brief Updates the system, processing all input events. + * This method processes all events and triggers corresponding actions. + */ + void fixed_update() override; + +private: + //! Stores the last position of the mouse when the button was pressed. + vec2 last_mouse_down_position; + // TODO: specify world/hud space and make regular `vec2` + + //! Stores the last mouse button pressed. + MouseButton last_mouse_button = MouseButton::NONE; + /** + * \brief Handles mouse-related events. + * \param event The event data for the mouse event. + * \param camera_origin The origin position of the camera in world space. + * \param current_cam The currently active camera. + * + * This method processes mouse events, adjusts the mouse position to world coordinates, + * and triggers the appropriate mouse-specific event handling logic. + */ + void handle_mouse_event( + const EventData & event, const vec2 & camera_origin, const Camera & current_cam + ); + /** + * \brief Handles non-mouse-related events. + * \param event The event data for the non-mouse event. + * + * This method processes events that do not involve the mouse, such as keyboard events, + * window events, and shutdown events, and triggers the corresponding event actions. + */ + void handle_non_mouse_event(const EventData & event); + /** + * \brief Handles the mouse click event. + * \param mouse_button The mouse button involved in the click. + * \param world_mouse_x The X coordinate of the mouse in world space. + * \param world_mouse_y The Y coordinate of the mouse in world space. + * \param current_cam The current active camera. + * + * This method processes the mouse click event and triggers the corresponding button action. + */ + void handle_click( + const MouseButton & mouse_button, const vec2 & mouse_pos, const Camera & current_cam + ); + + /** + * \brief Handles the mouse movement event. + * \param event_data The event data containing information about the mouse movement. + * \param world_mouse_x The X coordinate of the mouse in world space. + * \param world_mouse_y The Y coordinate of the mouse in world space. + * \param current_cam The current active camera. + * + * This method processes the mouse movement event and updates the button hover state. + */ + void handle_move( + const EventData & event_data, const vec2 & mouse_pos, const Camera & current_cam + ); + + /** + * \brief Checks if the mouse position is inside the bounds of the button. + * \param world_mouse_x The X coordinate of the mouse in world space. + * \param world_mouse_y The Y coordinate of the mouse in world space. + * \param button The button to check. + * \param transform The transform component of the button. + * \param cam_transform the transform of the current active camera + * \return True if the mouse is inside the button, false otherwise. + */ + bool is_mouse_inside_button( + const vec2 & mouse_pos, const Button & button, const Transform & transform, + const Transform & cam_transform + ); + + /** + * \brief Handles the button press event, calling the on_click callback if necessary. + * \param button The button being pressed. + * + * This method triggers the on_click action for the button when it is pressed. + */ + void handle_button_press(Button & button); +}; + +} // namespace crepe diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index fcf7522..f026390 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -1,19 +1,25 @@ +#include <chrono> #include <cmath> #include <cstdlib> #include <ctime> -#include "api/ParticleEmitter.h" -#include "api/Transform.h" -#include "api/Vector2.h" +#include "../api/ParticleEmitter.h" +#include "../api/Transform.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include "util/AbsolutePosition.h" -#include "ComponentManager.h" #include "ParticleSystem.h" using namespace crepe; -void ParticleSystem::update() { +void ParticleSystem::fixed_update() { // Get all emitters - ComponentManager & mgr = this->component_manager; + const Mediator & mediator = this->mediator; + LoopTimerManager & loop_timer = mediator.loop_timer; + ComponentManager & mgr = mediator.component_manager; + float dt = loop_timer.get_scaled_fixed_delta_time().count(); + RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>(); for (ParticleEmitter & emitter : emitters) { @@ -22,108 +28,99 @@ void ParticleSystem::update() { = mgr.get_components_by_id<Transform>(emitter.game_object_id).front().get(); // Emit particles based on emission_rate - int updates = calculate_update(this->update_count, emitter.data.emission_rate); - for (size_t i = 0; i < updates; i++) { - emit_particle(emitter, transform); + emitter.spawn_accumulator += emitter.data.emission_rate * dt; + while (emitter.spawn_accumulator >= 1.0) { + this->emit_particle(emitter, transform); + emitter.spawn_accumulator -= 1.0; } // Update all particles - for (Particle & particle : emitter.data.particles) { + for (Particle & particle : emitter.particles) { if (particle.active) { - particle.update(); + particle.update(dt); } } // Check if within boundary - check_bounds(emitter, transform); + this->check_bounds(emitter, transform); } - - this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT; } void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) { - constexpr double DEG_TO_RAD = M_PI / 180.0; + constexpr float DEG_TO_RAD = M_PI / 180.0; - Vector2 initial_position = emitter.data.position + transform.position; - double random_angle - = generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); + vec2 initial_position = AbsolutePosition::get_position(transform, emitter.data.offset); + float random_angle = this->generate_random_angle( + emitter.data.min_angle + transform.rotation, + emitter.data.max_angle + transform.rotation + ); - double random_speed - = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); - double angle_radians = random_angle * DEG_TO_RAD; + float random_speed + = this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); + float angle_radians = random_angle * DEG_TO_RAD; - Vector2 velocity + vec2 velocity = {random_speed * std::cos(angle_radians), random_speed * std::sin(angle_radians)}; - for (Particle & particle : emitter.data.particles) { + for (Particle & particle : emitter.particles) { if (!particle.active) { - particle.reset(emitter.data.end_lifespan, initial_position, velocity, - random_angle); + particle.reset( + emitter.data.end_lifespan, initial_position, velocity, random_angle + ); break; } } } -int ParticleSystem::calculate_update(int count, double emission) const { - double integer_part = std::floor(emission); - double fractional_part = emission - integer_part; - - if (fractional_part > 0) { - int denominator = static_cast<int>(1.0 / fractional_part); - return (count % denominator == 0) ? 1 : 0; - } - - return static_cast<int>(emission); -} - void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & transform) { - Vector2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position; - double half_width = emitter.data.boundary.width / 2.0; - double half_height = emitter.data.boundary.height / 2.0; - - const double LEFT = offset.x - half_width; - const double RIGHT = offset.x + half_width; - const double TOP = offset.y - half_height; - const double BOTTOM = offset.y + half_height; - - for (Particle & particle : emitter.data.particles) { - const Vector2 & position = particle.position; - bool within_bounds = (position.x >= LEFT && position.x <= RIGHT && position.y >= TOP - && position.y <= BOTTOM); - + vec2 offset = emitter.data.boundary.offset + transform.position + emitter.data.offset; + float half_width = emitter.data.boundary.width / 2.0; + float half_height = emitter.data.boundary.height / 2.0; + + float left = offset.x - half_width; + float right = offset.x + half_width; + float top = offset.y - half_height; + float bottom = offset.y + half_height; + + for (Particle & particle : emitter.particles) { + const vec2 & position = particle.position; + bool within_bounds + = (position.x >= left && position.x <= right && position.y >= top + && position.y <= bottom); + //if not within bounds do a reset or stop velocity if (!within_bounds) { if (emitter.data.boundary.reset_on_exit) { particle.active = false; } else { particle.velocity = {0, 0}; - if (position.x < LEFT) particle.position.x = LEFT; - else if (position.x > RIGHT) particle.position.x = RIGHT; - if (position.y < TOP) particle.position.y = TOP; - else if (position.y > BOTTOM) particle.position.y = BOTTOM; + if (position.x < left) particle.position.x = left; + else if (position.x > right) particle.position.x = right; + if (position.y < top) particle.position.y = top; + else if (position.y > bottom) particle.position.y = bottom; } } } } -double ParticleSystem::generate_random_angle(double min_angle, double max_angle) const { +float ParticleSystem::generate_random_angle(float min_angle, float max_angle) const { if (min_angle == max_angle) { return min_angle; } else if (min_angle < max_angle) { return min_angle - + static_cast<double>(std::rand() % static_cast<int>(max_angle - min_angle)); + + static_cast<float>(std::rand() % static_cast<int>(max_angle - min_angle)); } else { - double angle_offset = (360 - min_angle) + max_angle; - double random_angle - = min_angle + static_cast<double>(std::rand() % static_cast<int>(angle_offset)); + float angle_offset = (360 - min_angle) + max_angle; + float random_angle + = min_angle + static_cast<float>(std::rand() % static_cast<int>(angle_offset)); return (random_angle >= 360) ? random_angle - 360 : random_angle; } } -double ParticleSystem::generate_random_speed(double min_speed, double max_speed) const { +float ParticleSystem::generate_random_speed(float min_speed, float max_speed) const { if (min_speed == max_speed) { return min_speed; } else { return min_speed - + static_cast<double>(std::rand() % static_cast<int>(max_speed - min_speed)); + + static_cast<float>(std::rand() % static_cast<int>(max_speed - min_speed)); } } diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index c647284..4296ff3 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -20,31 +20,21 @@ public: * \brief Updates all particle emitters by emitting particles, updating particle states, and * checking bounds. */ - void update() override; + void fixed_update() override; private: /** * \brief Emits a particle from the specified emitter based on its emission properties. - * + * * \param emitter Reference to the ParticleEmitter. * \param transform Const reference to the Transform component associated with the emitter. */ void emit_particle(ParticleEmitter & emitter, const Transform & transform); /** - * \brief Calculates the number of times particles should be emitted based on emission rate - * and update count. - * - * \param count Current update count. - * \param emission Emission rate. - * \return The number of particles to emit. - */ - int calculate_update(int count, double emission) const; - - /** * \brief Checks whether particles are within the emitter’s boundary, resets or stops * particles if they exit. - * + * * \param emitter Reference to the ParticleEmitter. * \param transform Const reference to the Transform component associated with the emitter. */ @@ -52,29 +42,21 @@ private: /** * \brief Generates a random angle for particle emission within the specified range. - * + * * \param min_angle Minimum emission angle in degrees. * \param max_angle Maximum emission angle in degrees. * \return Random angle in degrees. */ - double generate_random_angle(double min_angle, double max_angle) const; + float generate_random_angle(float min_angle, float max_angle) const; /** * \brief Generates a random speed for particle emission within the specified range. - * + * * \param min_speed Minimum emission speed. * \param max_speed Maximum emission speed. * \return Random speed. */ - double generate_random_speed(double min_speed, double max_speed) const; - -private: - //! Counter to count updates to determine how many times emit_particle is - // called. - unsigned int update_count = 0; - //! Determines the lowest amount of emission rate (1000 = 0.001 = 1 particle per 1000 - // updates). - static constexpr unsigned int MAX_UPDATE_COUNT = 100; + float generate_random_speed(float min_speed, float max_speed) const; }; } // namespace crepe diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index bcde431..62f8132 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -1,89 +1,98 @@ #include <cmath> -#include "../ComponentManager.h" #include "../api/Config.h" #include "../api/Rigidbody.h" #include "../api/Transform.h" #include "../api/Vector2.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h" #include "PhysicsSystem.h" using namespace crepe; -void PhysicsSystem::update() { - ComponentManager & mgr = this->component_manager; +void PhysicsSystem::fixed_update() { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + LoopTimerManager & loop_timer = mediator.loop_timer; RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); - RefVector<Transform> transforms = mgr.get_components_by_type<Transform>(); + float dt = loop_timer.get_scaled_fixed_delta_time().count(); - double gravity = Config::get_instance().physics.gravity; + float gravity = Config::get_instance().physics.gravity; for (Rigidbody & rigidbody : rigidbodies) { if (!rigidbody.active) continue; + Transform & transform + = mgr.get_components_by_id<Transform>(rigidbody.game_object_id).front().get(); switch (rigidbody.data.body_type) { case Rigidbody::BodyType::DYNAMIC: - for (Transform & transform : transforms) { - if (transform.game_object_id == rigidbody.game_object_id) { + if (transform.game_object_id == rigidbody.game_object_id) { + // Add gravity - // Add gravity - if (rigidbody.data.use_gravity) { - rigidbody.data.linear_velocity.y - += (rigidbody.data.mass * rigidbody.data.gravity_scale - * gravity); - } - // Add damping - if (rigidbody.data.angular_damping != 0) { - rigidbody.data.angular_velocity *= rigidbody.data.angular_damping; - } - if (rigidbody.data.linear_damping != Vector2{0, 0}) { - rigidbody.data.linear_velocity *= rigidbody.data.linear_damping; - } + if (rigidbody.data.mass <= 0) { + throw std::runtime_error("Mass must be greater than 0"); + } - // Max velocity check - if (rigidbody.data.angular_velocity - > rigidbody.data.max_angular_velocity) { - rigidbody.data.angular_velocity - = rigidbody.data.max_angular_velocity; - } else if (rigidbody.data.angular_velocity - < -rigidbody.data.max_angular_velocity) { - rigidbody.data.angular_velocity - = -rigidbody.data.max_angular_velocity; - } + if (gravity <= 0) { + throw std::runtime_error("Config Gravity must be greater than 0"); + } - if (rigidbody.data.linear_velocity.x - > rigidbody.data.max_linear_velocity.x) { - rigidbody.data.linear_velocity.x - = rigidbody.data.max_linear_velocity.x; - } else if (rigidbody.data.linear_velocity.x - < -rigidbody.data.max_linear_velocity.x) { - rigidbody.data.linear_velocity.x - = -rigidbody.data.max_linear_velocity.x; - } + if (rigidbody.data.gravity_scale > 0 && !rigidbody.data.constraints.y) { + rigidbody.data.linear_velocity.y + += (rigidbody.data.mass * rigidbody.data.gravity_scale * gravity + * dt); + } + // Add coefficient rotation + if (rigidbody.data.angular_velocity_coefficient > 0) { + rigidbody.data.angular_velocity + *= std::pow(rigidbody.data.angular_velocity_coefficient, dt); + } - if (rigidbody.data.linear_velocity.y - > rigidbody.data.max_linear_velocity.y) { - rigidbody.data.linear_velocity.y - = rigidbody.data.max_linear_velocity.y; - } else if (rigidbody.data.linear_velocity.y - < -rigidbody.data.max_linear_velocity.y) { - rigidbody.data.linear_velocity.y - = -rigidbody.data.max_linear_velocity.y; - } + // Add coefficient movement horizontal + if (rigidbody.data.linear_velocity_coefficient.x > 0 + && !rigidbody.data.constraints.x) { + rigidbody.data.linear_velocity.x + *= std::pow(rigidbody.data.linear_velocity_coefficient.x, dt); + } - // Move object - if (!rigidbody.data.constraints.rotation) { - transform.rotation += rigidbody.data.angular_velocity; - transform.rotation = std::fmod(transform.rotation, 360.0); - if (transform.rotation < 0) { - transform.rotation += 360.0; - } - } - if (!rigidbody.data.constraints.x) { - transform.position.x += rigidbody.data.linear_velocity.x; - } - if (!rigidbody.data.constraints.y) { - transform.position.y += rigidbody.data.linear_velocity.y; + // Add coefficient movement horizontal + if (rigidbody.data.linear_velocity_coefficient.y > 0 + && !rigidbody.data.constraints.y) { + rigidbody.data.linear_velocity.y + *= std::pow(rigidbody.data.linear_velocity_coefficient.y, dt); + } + + // Max velocity check + if (rigidbody.data.angular_velocity + > rigidbody.data.max_angular_velocity) { + rigidbody.data.angular_velocity = rigidbody.data.max_angular_velocity; + } else if (rigidbody.data.angular_velocity + < -rigidbody.data.max_angular_velocity) { + rigidbody.data.angular_velocity = -rigidbody.data.max_angular_velocity; + } + + // Set max velocity to maximum length + if (rigidbody.data.linear_velocity.length() + > rigidbody.data.max_linear_velocity) { + rigidbody.data.linear_velocity.normalize(); + rigidbody.data.linear_velocity *= rigidbody.data.max_linear_velocity; + } + + // Move object + if (!rigidbody.data.constraints.rotation) { + transform.rotation += rigidbody.data.angular_velocity * dt; + transform.rotation = std::fmod(transform.rotation, 360.0); + if (transform.rotation < 0) { + transform.rotation += 360.0; } } + if (!rigidbody.data.constraints.x) { + transform.position.x += rigidbody.data.linear_velocity.x * dt; + } + if (!rigidbody.data.constraints.y) { + transform.position.y += rigidbody.data.linear_velocity.y * dt; + } } break; case Rigidbody::BodyType::KINEMATIC: diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h index 227ab69..5ed624f 100644 --- a/src/crepe/system/PhysicsSystem.h +++ b/src/crepe/system/PhysicsSystem.h @@ -6,7 +6,7 @@ namespace crepe { /** * \brief System that controls all physics - * + * * This class is a physics system that uses a rigidbody and transform to add physics to a game * object. */ @@ -15,10 +15,10 @@ public: using System::System; /** * \brief updates the physics system. - * + * * It calculates new velocties and changes the postion in the transform. */ - void update() override; + void fixed_update() override; }; } // namespace crepe diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index ad510f5..30bb422 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -2,42 +2,63 @@ #include <cassert> #include <cmath> #include <functional> -#include <iostream> +#include <optional> #include <stdexcept> #include <vector> -#include "../ComponentManager.h" +#include "../api/Camera.h" #include "../api/ParticleEmitter.h" #include "../api/Sprite.h" +#include "../api/Text.h" #include "../api/Transform.h" -#include "../api/Vector2.h" +#include "../facade/Font.h" #include "../facade/SDLContext.h" +#include "../facade/Texture.h" +#include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h" +#include "api/Text.h" +#include "facade/Font.h" +#include "util/AbsolutePosition.h" #include "RenderSystem.h" +#include "types.h" using namespace crepe; using namespace std; -void RenderSystem::clear_screen() { this->context.clear_screen(); } +void RenderSystem::clear_screen() { + SDLContext & ctx = this->mediator.sdl_context; + ctx.clear_screen(); +} -void RenderSystem::present_screen() { this->context.present_screen(); } -void RenderSystem::update_camera() { - ComponentManager & mgr = this->component_manager; +void RenderSystem::present_screen() { + SDLContext & ctx = this->mediator.sdl_context; + ctx.present_screen(); +} +void RenderSystem::update_camera() { + ComponentManager & mgr = this->mediator.component_manager; + SDLContext & ctx = this->mediator.sdl_context; RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene"); for (Camera & cam : cameras) { if (!cam.active) continue; - this->context.set_camera(cam); - this->curr_cam_ref = &cam; + const Transform & transform + = mgr.get_components_by_id<Transform>(cam.game_object_id).front().get(); + vec2 new_camera_pos = transform.position + cam.data.postion_offset; + ctx.update_camera_view(cam, new_camera_pos); + return; } + throw std::runtime_error("No active cameras in current scene"); } bool sorting_comparison(const Sprite & a, const Sprite & b) { - if (a.sorting_in_layer < b.sorting_in_layer) return true; - if (a.sorting_in_layer == b.sorting_in_layer) return a.order_in_layer < b.order_in_layer; + if (a.data.sorting_in_layer != b.data.sorting_in_layer) + return a.data.sorting_in_layer < b.data.sorting_in_layer; + if (a.data.order_in_layer != b.data.order_in_layer) + return a.data.order_in_layer < b.data.order_in_layer; return false; } @@ -49,16 +70,41 @@ RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const { return sorted_objs; } -void RenderSystem::update() { +void RenderSystem::frame_update() { this->clear_screen(); - this->update_camera(); this->render(); + this->render_text(); this->present_screen(); } -bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { +void RenderSystem::render_text() { + SDLContext & ctx = this->mediator.sdl_context; + ComponentManager & mgr = this->mediator.component_manager; + ResourceManager & resource_manager = this->mediator.resource_manager; + + RefVector<Text> texts = mgr.get_components_by_type<Text>(); + + for (Text & text : texts) { + if (!text.active) continue; + if (!text.font.has_value()) + text.font.emplace(ctx.get_font_from_name(text.font_family)); + + const Font & font = resource_manager.get<Font>(text.font.value()); + const auto & transform + = mgr.get_components_by_id<Transform>(text.game_object_id).front().get(); + ctx.draw_text(SDLContext::RenderText { + .text = text, + .font = font, + .transform = transform, + }); + } +} - ComponentManager & mgr = this->component_manager; +bool RenderSystem::render_particle(const Sprite & sprite, const Transform & transform) { + ComponentManager & mgr = this->mediator.component_manager; + SDLContext & ctx = this->mediator.sdl_context; + ResourceManager & resource_manager = this->mediator.resource_manager; + Texture & res = resource_manager.get<Texture>(sprite.source); vector<reference_wrapper<ParticleEmitter>> emitters = mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); @@ -66,34 +112,54 @@ bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) bool rendering_particles = false; for (const ParticleEmitter & em : emitters) { - if (!(&em.data.sprite == &sprite)) continue; + if (&em.sprite != &sprite) continue; rendering_particles = true; if (!em.active) continue; - for (const Particle & p : em.data.particles) { + for (const Particle & p : em.particles) { if (!p.active) continue; - this->context.draw_particle(sprite, p.position, p.angle, scale, - *this->curr_cam_ref); + if (p.time_in_life < em.data.begin_lifespan) continue; + + ctx.draw(SDLContext::RenderContext { + .sprite = sprite, + .texture = res, + .pos = p.position, + .angle = p.angle + transform.rotation, + .scale = transform.scale, + }); } } return rendering_particles; } -void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { - this->context.draw(sprite, tm, *this->curr_cam_ref); +void RenderSystem::render_normal(const Sprite & sprite, const Transform & transform) { + SDLContext & ctx = this->mediator.sdl_context; + ResourceManager & resource_manager = this->mediator.resource_manager; + const Texture & res = resource_manager.get<Texture>(sprite.source); + vec2 pos = AbsolutePosition::get_position(transform, sprite.data.position_offset); + ctx.draw(SDLContext::RenderContext { + .sprite = sprite, + .texture = res, + .pos = pos, + .angle = transform.rotation, + .scale = transform.scale, + }); } void RenderSystem::render() { + ComponentManager & mgr = this->mediator.component_manager; + this->update_camera(); - ComponentManager & mgr = this->component_manager; RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>(); + ResourceManager & resource_manager = this->mediator.resource_manager; RefVector<Sprite> sorted_sprites = this->sort(sprites); + RefVector<Text> text_components = mgr.get_components_by_type<Text>(); for (const Sprite & sprite : sorted_sprites) { if (!sprite.active) continue; const Transform & transform = mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); - bool rendered_particles = this->render_particle(sprite, transform.scale); + bool rendered_particles = this->render_particle(sprite, transform); if (rendered_particles) continue; diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 30b41cf..627a743 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -1,24 +1,21 @@ #pragma once -#include <functional> -#include <vector> - -#include "facade/SDLContext.h" +#include <cmath> #include "System.h" -#include <cmath> +#include "types.h" namespace crepe { class Camera; class Sprite; - +class Transform; +class Text; /** - * \class RenderSystem * \brief Manages rendering operations for all game objects. * * RenderSystem is responsible for rendering, clearing and presenting the screen, and - * managing the active camera. + * managing the active camera. */ class RenderSystem : public System { public: @@ -27,7 +24,7 @@ public: * \brief Updates the RenderSystem for the current frame. * This method is called to perform all rendering operations for the current game frame. */ - void update() override; + void frame_update() override; private: //! Clears the screen in preparation for rendering. @@ -39,49 +36,36 @@ private: //! Updates the active camera used for rendering. void update_camera(); - //! Renders the whole screen + //! Renders all the sprites and particles void render(); + //! Renders all Text components + void render_text(); + +private: /** * \brief Renders all the particles on the screen from a given sprite. * * \param sprite renders the particles with given texture - * \param tm the Transform component for scale + * \param transform the component that holds the position, rotation, and scale. * \return true if particles have been rendered */ - bool render_particle(const Sprite & sprite, const double & scale); - + bool render_particle(const Sprite & sprite, const Transform & transform); /** - * \brief renders a sprite with a Transform component on the screen + * \brief renders a sprite with a Transform component on the screen * * \param sprite the sprite component that holds all the data - * \param tm the Transform component that holds the position,rotation and scale + * \param transform the Transform component that holds the position,rotation and scale */ - void render_normal(const Sprite & sprite, const Transform & tm); + void render_normal(const Sprite & sprite, const Transform & transform); /** * \brief sort a vector sprite objects with * - * \param objs the vector that will do a sorting algorithm on + * \param objs the vector that will do a sorting algorithm on * \return returns a sorted reference vector */ RefVector<Sprite> sort(RefVector<Sprite> & objs) const; - - /** - * \todo Include color handling for sprites. - * \todo Add text rendering using SDL_ttf for text components. - * \todo Implement a text component and a button component. - * \todo Ensure each sprite is checked for active status before rendering. - * \todo Sort all layers by order before rendering. - * \todo Consider adding text input functionality. - */ - -private: - //! Pointer to the current active camera for rendering - Camera * curr_cam_ref = nullptr; - // TODO: needs a better solution - - SDLContext & context = SDLContext::get_instance(); }; } // namespace crepe diff --git a/src/crepe/system/ReplaySystem.cpp b/src/crepe/system/ReplaySystem.cpp new file mode 100644 index 0000000..efc3be4 --- /dev/null +++ b/src/crepe/system/ReplaySystem.cpp @@ -0,0 +1,54 @@ +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "EventSystem.h" +#include "RenderSystem.h" +#include "ReplaySystem.h" + +using namespace crepe; +using namespace std; + +void ReplaySystem::fixed_update() { + ReplayManager & replay = this->mediator.replay_manager; + ReplayManager::State state = replay.get_state(); + ReplayManager::State last_state = this->last_state; + this->last_state = state; + + switch (state) { + case ReplayManager::IDLE: + break; + case ReplayManager::RECORDING: { + replay.frame_record(); + break; + } + case ReplayManager::PLAYING: { + if (last_state != ReplayManager::PLAYING) this->playback_begin(); + bool last = replay.frame_step(); + if (last) this->playback_end(); + break; + } + } +} + +void ReplaySystem::playback_begin() { + SystemManager & systems = this->mediator.system_manager; + ComponentManager & components = this->mediator.component_manager; + + this->playback = { + .components = components.save(), + .systems = systems.save(), + }; + + systems.disable_all(); + systems.get_system<RenderSystem>().active = true; + systems.get_system<ReplaySystem>().active = true; + systems.get_system<EventSystem>().active = true; +} + +void ReplaySystem::playback_end() { + SystemManager & systems = this->mediator.system_manager; + ComponentManager & components = this->mediator.component_manager; + + components.restore(this->playback.components); + systems.restore(this->playback.systems); +} diff --git a/src/crepe/system/ReplaySystem.h b/src/crepe/system/ReplaySystem.h new file mode 100644 index 0000000..bbc8d76 --- /dev/null +++ b/src/crepe/system/ReplaySystem.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "System.h" + +namespace crepe { + +/** + * \brief ReplayManager helper system + * + * This system records and replays recordings using ReplayManager. + */ +class ReplaySystem : public System { +public: + using System::System; + + void fixed_update() override; + +private: + //! Last ReplayManager state + ReplayManager::State last_state = ReplayManager::IDLE; + + /** + * \brief Playback snapshot + * + * When starting playback, the component state is saved and most systems are disabled. This + * struct stores the engine state before ReplayManager::play is called. + */ + struct Snapshot { + ComponentManager::Snapshot components; + SystemManager::Snapshot systems; + }; + //! Before playback snapshot + Snapshot playback; + + //! Snapshot state and disable systems during playback + void playback_begin(); + //! Restore state from before \c playback_begin() + void playback_end(); +}; + +} // namespace crepe diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index c33309c..ed0c7cc 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -1,41 +1,41 @@ -#include <functional> - -#include "../ComponentManager.h" #include "../api/BehaviorScript.h" #include "../api/Script.h" +#include "../manager/ComponentManager.h" #include "ScriptSystem.h" using namespace std; using namespace crepe; -void ScriptSystem::update() { - dbg_trace(); - - RefVector<Script> scripts = this->get_scripts(); +void ScriptSystem::fixed_update() { + LoopTimerManager & timer = this->mediator.loop_timer; + duration_t delta_time = timer.get_scaled_fixed_delta_time(); + this->update(&Script::fixed_update, delta_time); +} - for (auto & script_ref : scripts) { - Script & script = script_ref.get(); - if (!script.initialized) { - script.init(); - script.initialized = true; - } - script.update(); - } +void ScriptSystem::frame_update() { + LoopTimerManager & timer = this->mediator.loop_timer; + duration_t delta_time = timer.get_delta_time(); + this->update(&Script::frame_update, delta_time); } -RefVector<Script> ScriptSystem::get_scripts() const { - RefVector<Script> scripts = {}; - ComponentManager & mgr = this->component_manager; +void ScriptSystem::update( + void (Script::*update_function)(duration_t), const duration_t & delta_time +) { + ComponentManager & mgr = this->mediator.component_manager; RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>(); - for (auto behavior_script_ref : behavior_scripts) { - BehaviorScript & behavior_script = behavior_script_ref.get(); + for (BehaviorScript & behavior_script : behavior_scripts) { if (!behavior_script.active) continue; + Script * script = behavior_script.script.get(); if (script == nullptr) continue; - scripts.push_back(*script); - } - return scripts; + if (!script->initialized) { + script->init(); + script->initialized = true; + } + + (*script.*update_function)(delta_time); + } } diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index 32e1fcd..257b615 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -1,8 +1,8 @@ #pragma once -#include "System.h" +#include "../manager/LoopTimerManager.h" -#include "../types.h" +#include "System.h" namespace crepe { @@ -10,30 +10,28 @@ class Script; /** * \brief Script system - * - * The script system is responsible for all \c BehaviorScript components, and - * calls the methods on classes derived from \c Script. + * + * The script system is responsible for all \c BehaviorScript components, and calls the methods + * on classes derived from \c Script. */ class ScriptSystem : public System { public: using System::System; - /** - * \brief Call Script::update() on all active \c BehaviorScript instances - * - * This routine updates all scripts sequentially using the Script::update() - * method. It also calls Script::init() if this has not been done before on - * the \c BehaviorScript instance. - */ - void update() override; + +public: + //! Call Script::fixed_update() on all active \c BehaviorScript instances + void fixed_update() override; + //! Call Script::frame_update() on all active \c BehaviorScript instances + void frame_update() override; private: /** - * \brief Aggregate all active \c BehaviorScript components and return a list - * of references to their \c Script instances (utility) + * \brief Call Script `*_update` member function on all active \c BehaviorScript instances * - * \returns List of active \c Script instances + * \note This routine also calls Script::init() if this has not been done before on the \c + * BehaviorScript instance. */ - RefVector<Script> get_scripts() const; + void update(void (Script::*update_function)(duration_t), const duration_t & delta_time); }; } // namespace crepe diff --git a/src/crepe/system/System.cpp b/src/crepe/system/System.cpp index 937a423..ecc740d 100644 --- a/src/crepe/system/System.cpp +++ b/src/crepe/system/System.cpp @@ -1,7 +1,5 @@ -#include "../util/Log.h" - #include "System.h" using namespace crepe; -System::System(ComponentManager & mgr) : component_manager(mgr) { dbg_trace(); } +System::System(const Mediator & mediator) : mediator(mediator) {} diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h index 28ea20e..e2ce7eb 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -1,5 +1,7 @@ #pragma once +#include "../manager/Mediator.h" + namespace crepe { class ComponentManager; @@ -7,23 +9,24 @@ class ComponentManager; /** * \brief Base ECS system class * - * This class is used as the base for all system classes. Classes derived from - * System must implement the System::update() method and copy Script::Script - * with the `using`-syntax. + * This class is used as the base for all system classes. Classes derived from System must + * implement the System::update() method and copy Script::Script with the `using`-syntax. */ class System { public: - /** - * \brief Process all components this system is responsible for. - */ - virtual void update() = 0; + //! Code that runs in the fixed loop + virtual void fixed_update() {}; + //! Code that runs in the frame loop + virtual void frame_update() {}; + //! Indicates that the update functions of this system should be run + bool active = true; public: - System(ComponentManager &); + System(const Mediator & m); virtual ~System() = default; protected: - ComponentManager & component_manager; + const Mediator & mediator; }; } // namespace crepe diff --git a/src/crepe/types.h b/src/crepe/types.h index 914c76c..69cc526 100644 --- a/src/crepe/types.h +++ b/src/crepe/types.h @@ -1,5 +1,7 @@ #pragma once +#include "api/Vector2.h" + #include <cstdint> #include <functional> #include <vector> @@ -13,4 +15,16 @@ typedef uint32_t game_object_id_t; template <typename T> using RefVector = std::vector<std::reference_wrapper<T>>; -} // namespace crepe +//! Default Vector2<int> type +typedef Vector2<int> ivec2; + +//! Default Vector2<unsigned int> type +typedef Vector2<unsigned int> uvec2; + +//! Default Vector2<float> type +typedef Vector2<float> vec2; + +//! Default Vector2<double> type +typedef Vector2<double> dvec2; + +}; // namespace crepe diff --git a/src/crepe/util/AbsolutePosition.cpp b/src/crepe/util/AbsolutePosition.cpp new file mode 100644 index 0000000..29ade23 --- /dev/null +++ b/src/crepe/util/AbsolutePosition.cpp @@ -0,0 +1,20 @@ +#include "AbsolutePosition.h" + +using namespace crepe; + +vec2 AbsolutePosition::get_position(const Transform & transform, const vec2 & offset) { + // Get the rotation in radians + float radians1 = transform.rotation * (M_PI / 180.0); + + // Calculate total offset with scale + vec2 total_offset = offset * transform.scale; + + // Rotate + float rotated_total_offset_x1 + = total_offset.x * cos(radians1) - total_offset.y * sin(radians1); + float rotated_total_offset_y1 + = total_offset.x * sin(radians1) + total_offset.y * cos(radians1); + + // Final positions considering scaling and rotation + return (transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1)); +} diff --git a/src/crepe/util/AbsolutePosition.h b/src/crepe/util/AbsolutePosition.h new file mode 100644 index 0000000..857c1ac --- /dev/null +++ b/src/crepe/util/AbsolutePosition.h @@ -0,0 +1,29 @@ +#pragma once + +#include "api/Transform.h" + +#include "types.h" + +namespace crepe { + +/** + * \brief A class for calculating the absolute position of an object. + * + * This class provides a utility function to get the position of an object in the world space, + * taking into account the transform and any additional offset. + */ +class AbsolutePosition { +public: + /** + * \brief Get the absolute position of an object. + * + * This function calculates the absolute position by combining the transform position with an optional offset. + * + * \param transform The transform of the object, which contains its position, rotation, and scale. + * \param offset The offset to apply to the object's position (in local space). + * \return The absolute position of the object as a 2D vector. + */ + static vec2 get_position(const Transform & transform, const vec2 & offset); +}; + +} // namespace crepe diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt index 94ed906..33160a7 100644 --- a/src/crepe/util/CMakeLists.txt +++ b/src/crepe/util/CMakeLists.txt @@ -1,6 +1,7 @@ target_sources(crepe PUBLIC LogColor.cpp Log.cpp + AbsolutePosition.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -11,5 +12,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Proxy.hpp OptionalRef.h OptionalRef.hpp + AbsolutePosition.h ) diff --git a/src/crepe/util/Log.cpp b/src/crepe/util/Log.cpp index 84d80a8..ce25a1d 100644 --- a/src/crepe/util/Log.cpp +++ b/src/crepe/util/Log.cpp @@ -4,6 +4,7 @@ #include "../api/Config.h" #include "Log.h" +#include "LogColor.h" using namespace crepe; using namespace std; diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h index d55b11e..b43fe30 100644 --- a/src/crepe/util/Log.h +++ b/src/crepe/util/Log.h @@ -2,27 +2,6 @@ #include <format> -// allow user to disable debug macros -#ifndef CREPE_DISABLE_MACROS - -#include "LogColor.h" - -// utility macros -#define _crepe_logf_here(level, fmt, ...) \ - crepe::Log::logf(level, "{}" fmt, \ - crepe::LogColor().fg_white(false).str(std::format( \ - "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \ - __VA_ARGS__) - -// very illegal global function-style macros -// NOLINTBEGIN -#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__) -#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str) -#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "") -// NOLINTEND - -#endif - namespace crepe { /** @@ -34,11 +13,11 @@ class Log { public: //! Log message severity enum Level { - TRACE, //< Include (internal) function calls - DEBUG, //< Include dbg_logf output - INFO, //< General-purpose messages - WARNING, //< Non-fatal errors - ERROR, //< Fatal errors + TRACE, //!< Include (internal) function calls + DEBUG, //!< Include dbg_logf output + INFO, //!< General-purpose messages + WARNING, //!< Non-fatal errors + ERROR, //!< Fatal errors }; /** diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h index 253bc07..1b2cb3f 100644 --- a/src/crepe/util/OptionalRef.h +++ b/src/crepe/util/OptionalRef.h @@ -25,13 +25,21 @@ public: */ OptionalRef<T> & operator=(T & ref); /** - * \brief Retrieve this reference + * \brief Retrieve this reference (cast) * * \returns Internal reference if it is set * * \throws std::runtime_error if this function is called while the reference it not set */ - operator T & () const; + operator T &() const; + /** + * \brief Retrieve this reference (member access) + * + * \returns Internal reference if it is set + * + * \throws std::runtime_error if this function is called while the reference it not set + */ + T * operator->() const; /** * \brief Check if this reference is not empty * diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp index ae7c73e..5e36b3a 100644 --- a/src/crepe/util/OptionalRef.hpp +++ b/src/crepe/util/OptionalRef.hpp @@ -12,13 +12,20 @@ OptionalRef<T>::OptionalRef(T & ref) { } template <typename T> -OptionalRef<T>::operator T & () const { +OptionalRef<T>::operator T &() const { if (this->ref == nullptr) throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); return *this->ref; } template <typename T> +T * OptionalRef<T>::operator->() const { + if (this->ref == nullptr) + throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); + return this->ref; +} + +template <typename T> OptionalRef<T> & OptionalRef<T>::operator=(T & ref) { this->ref = &ref; return *this; diff --git a/src/crepe/util/dbg.h b/src/crepe/util/dbg.h new file mode 100644 index 0000000..e448070 --- /dev/null +++ b/src/crepe/util/dbg.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Log.h" +#include "LogColor.h" + +// utility macros +#define _crepe_logf_here(level, fmt, ...) \ + crepe::Log::logf( \ + level, "{}" fmt, \ + crepe::LogColor().fg_white(false).str( \ + std::format("{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__) \ + ), \ + __VA_ARGS__ \ + ) + +// very illegal global function-style macros +// NOLINTBEGIN +#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__) +#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str) +#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "") +// NOLINTEND diff --git a/src/doc/feature/animator_creation.dox b/src/doc/feature/animator_creation.dox new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/doc/feature/animator_creation.dox diff --git a/src/doc/feature/bgm.dox b/src/doc/feature/bgm.dox new file mode 100644 index 0000000..968abb8 --- /dev/null +++ b/src/doc/feature/bgm.dox @@ -0,0 +1,22 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_bgm Playing background music +\ingroup feature +\brief Add background music to a scene using the AudioSource component + +This page shows how to implement background music using the AudioSource +effects. + +\see AudioSource + +\par Example + +\note This example assumes you already have a GameObject. If not, read +\"\ref feature_gameobject\" first. + +\todo Merge #60 + +*/ +} diff --git a/src/doc/feature/config.dox b/src/doc/feature/config.dox new file mode 100644 index 0000000..ae3a054 --- /dev/null +++ b/src/doc/feature/config.dox @@ -0,0 +1,61 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_config Engine configuration +\ingroup feature +\brief Configure default values and global options + +Default values and options that apply to the engine globally are read from a +singleton struct named Config. + +\see Config + +\par Example + +Configuration options may be set individually or by assigning a [designated +initializer list][desginit]. All of Config's members have default values and can +safely be omitted from initializer lists. + +[desginit]: https://en.cppreference.com/w/cpp/language/aggregate_initialization#Designated_initializers + +```cpp +#include <crepe/api/Config.h> + +int main() { + auto & config = crepe::Config::get_instance(); + + // Designated initializer list + config = { + // specify options here + }; + + // Reset default options + config = {}; + + // Set specific option + config.log.color = false; +} +``` + +\par Options + +\noop Display config properties in monospace font +\htmlonly +<style> +tr td:first-child { font-family: monospace; } +</style> +\endhtmlonly + +|Option|Description| +|-|-| +|\ref Config::asset::root_pattern ".asset.root_pattern"|\copybrief Config::asset::root_pattern| +|\ref Config::log::color ".log.color"|\copybrief Config::log::color| +|\ref Config::log::level ".log.level"|\copybrief Config::log::level| +|\ref Config::physics::gravity ".physics.gravity"|\copybrief Config::physics::gravity| +|\ref Config::savemgr::location ".savemgr.location"|\copybrief Config::savemgr::location| +|\ref Config::window_settings::default_size ".window_settings.default_size"|\copybrief Config::window_settings::default_size| +|\ref Config::window_settings::window_title ".window_settings.window_title"|\copybrief Config::window_settings::window_title| + +*/ +} diff --git a/src/doc/feature/gameobject.dox b/src/doc/feature/gameobject.dox index c561874..ac3927c 100644 --- a/src/doc/feature/gameobject.dox +++ b/src/doc/feature/gameobject.dox @@ -2,9 +2,9 @@ namespace crepe { /** -\defgroup feature_gameobject GameObjects +\defgroup feature_gameobject Entity basics \ingroup feature -\brief GameObject to create a Scene +\brief Building game entities using a GameObject GameObjects are the fundamental building blocks of a Scene. They represent entities in the game world and can have various components attached to them to define their diff --git a/src/doc/feature/proxy.dox b/src/doc/feature/proxy.dox new file mode 100644 index 0000000..66bbd2f --- /dev/null +++ b/src/doc/feature/proxy.dox @@ -0,0 +1,43 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_proxy Proxy utility +\ingroup feature +\brief Use ValueBroker as if it were a regular variable + +Proxy provides operators that allow you to use a ValueBroker instance as if it +were a regular variable. Proxy implements a constructor that allows it to be +used as a substitute return type for any function that returns ValueBroker. + +\see ValueBroker +\see Proxy + +\par Example + +```cpp +#include <crepe/util/Proxy.h> +#include <crepe/ValueBroker.h> + +int calculation(int value) { + return 3 * value; +} + +void anywhere() { + crepe::ValueBroker<int> foo_handle; + crepe::Proxy foo = foo_handle; + + // implicitly calls .set() + foo += 10; + + // implicitly calls .get() + int out = calculation(foo); + + // explicitly cast (also calls .get()) + int casted = int(foo); +} + +``` + +*/ +} diff --git a/src/doc/feature/savemgr.dox b/src/doc/feature/savemgr.dox new file mode 100644 index 0000000..6aeab03 --- /dev/null +++ b/src/doc/feature/savemgr.dox @@ -0,0 +1,80 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_savemgr Save data +\ingroup feature +\brief Functions to persistently store and retrieve arbitrary values + +The SaveManager may be used to persistently store game state such as player +progress, statistics, achievements, etc. It works like a key-value store, where +the key is a string and the value is an arbitrary type. + +SaveManager implements the following: + +- Storage and retrieval of primitive types and strings. +- Automatic initialization of the database using default values. +- The underlying database format is journaled, which reduces the likelihood of + players losing save data when an unexpected crash occurs while the SaveManager + is writing to disk. + +\see SaveManager + +\par Example + +The SaveManager instance reference may be retrieved by calling \c +get_save_manager(). This function is available--- + +- Within (derivatives of) Script + +- \todo Within (derivatives of) Scene + +- \todo As a public member function of LoopManager + +```cpp +// Retrieve save manager +crepe::SaveManager & save_manager = get_save_manager(); +``` + +SaveManager may be used *explicitly*, using the \ref SaveManager::set "set()", +\ref SaveManager::get "get()" and \ref SaveManager::has "has()" methods: +```cpp +// Check if the key "foo" exists, and initialize it to 3 if it doesn't +if (!save_manager.has("foo")) + save_manager.set<int>("foo", 3); +// Get value of key "foo" +int foo = save_manager.get<int>("foo"); + +// ~~~ arbitrary game code ~~~ +foo += 10; +// ~~~ arbitrary game code ~~~ + +// Save "foo" back to database +save_manager.set<int>("foo", foo); +``` + +Alternatively, SaveManager::get may be called with a default value as second +parameter. This changes its return type to ValueBroker, which acts as a +read/write handle to the specific key requested, and remembers the key and its +value type for you: +```cpp +// Get a read/write handle to the value stored in key "foo", and initialize it +// to 3 if it doesn't exist yet +ValueBroker foo_handle = save_manager.get<int>("foo", 3); +int foo = foo_handle.get(); + +// ~~~ arbitrary game code ~~~ +foo += 10; +// ~~~ arbitrary game code ~~~ + +// Save back to database +foo_handle.set(foo); +``` + +To further simplify game code, the return value of SaveManager::get may be +implicitly cast to Proxy instead of ValueBroker. This allows the database value +to be used as if it were a regular variable. This usage is detailed separately +in \"\ref feature_proxy\". + +*/ +} diff --git a/src/doc/feature/scene.dox b/src/doc/feature/scene.dox index 5f34446..b680eec 100644 --- a/src/doc/feature/scene.dox +++ b/src/doc/feature/scene.dox @@ -6,10 +6,11 @@ namespace crepe { \ingroup feature \brief User-defined scenes -Scenes can be used to implement game environments, and allow arbitrary game objects to be organized -as part of the game structure. Scenes are implemented as derivative classes of Scene, which are -added to the game using the SceneManager. Scenes describe the start of a Scene and cannot modify -GameObjects during runtime of a Scene (use \ref feature_script "Scripting" for this purpose). +Scenes can be used to implement game environments, and allow arbitrary game +objects to be organized as part of the game structure. Scenes are implemented as +derivative classes of Scene, which are added to the game using the SceneManager. +Scenes describe the start of a Scene and cannot modify GameObjects during +runtime of a Scene (use \ref feature_script for this purpose). \see SceneManager \see GameObject @@ -18,48 +19,49 @@ GameObjects during runtime of a Scene (use \ref feature_script "Scripting" for t \par Example -This example demonstrates how to define and add scenes to the loop/scene manager in the `crepe` framework. -Each concrete scene should be derived from Scene. In the example below, the concrete scene is named MyScene. -A concrete scene should, at least, implement (override) two methods, namely load_scene() and get_name(). The -scene is build (using GameObjects) in the load_scene() method. GameObjects should be made using the -component_manager::new_object(). In the example below, two GameObjects (named object1 and object2) are added -to MyScene. object1 and object2 do not have any non-default Components attached to them, however, if needed, -this should also be done in load_scene(). Each concrete scene must have a unique name. This unique name is -used to load a new concrete scene (via a Script). The unique name is set using the get_name() method. In the -example below, MyScene's unique name is my_scene. -After setting up one or more concrete scene(s), the concrete scene(s) should be added to the loop/scene manager. -This is done in your main(). Firstly, the LoopManager should be instantiated. Than, all the concrete scene(s) -should be added to the loop/scene manger via loop_mgr::add_scene<>(). The templated argument should define the -concrete scene to be added. +This example demonstrates how to define and add scenes to the loop/scene manager +in the `crepe` framework. Each concrete scene should be derived from Scene. In +the example below, the concrete scene is named MyScene. A concrete scene should, +at least, implement (override) two methods, namely load_scene() and get_name(). +The scene is build (using GameObjects) in the load_scene() method. GameObjects +should be made using the component_manager::new_object(). + +In the example below, two GameObjects (named object1 and object2) are added to +MyScene. object1 and object2 do not have any non-default Components attached to +them, however, if needed, this should also be done in load_scene(). Each +concrete scene must have a unique name. This unique name is used to load a new +concrete scene (via a Script). The unique name is set using the get_name() +method. In the example below, MyScene's unique name is my_scene. + +After setting up one or more concrete scene(s), the concrete scene(s) should be +added to the loop/scene manager. This is done in your main(). Firstly, the +LoopManager should be instantiated. Than, all the concrete scene(s) should be +added to the loop/scene manger via loop_mgr::add_scene<>(). The templated +argument should define the concrete scene to be added. ```cpp -#include <crepe/api/LoopManager.h> -#include <crepe/api/GameObject.h> +#include <crepe/api/Engine.h> #include <crepe/api/Scene.h> -#include <crepe/api/Vector2.h> using namespace crepe; class MyScene : public Scene { public: - using Scene::Scene; - void load_scene() { - auto & mgr = this->component_manager; - GameObject object1 = mgr.new_object("object1", "tag_my_scene", Vector2{0, 0}, 0, 1); - GameObject object2 = mgr.new_object("object2", "tag_my_scene", Vector2{1, 0}, 0, 1); + GameObject object1 = new_object("object1", "tag_my_scene", vec2{0, 0}, 0, 1); + GameObject object2 = new_object("object2", "tag_my_scene", vec2{1, 0}, 0, 1); } string get_name() const { return "my_scene"; } }; int main() { - LoopManager loop_mgr; + Engine foo; // Add the scenes to the loop manager - loop_mgr.add_scene<MyScene>(); + foo.add_scene<MyScene>(); - loop_mgr.start(); + return foo.main(); } ``` diff --git a/src/doc/feature/script.dox b/src/doc/feature/script.dox index d25a63b..162b0f5 100644 --- a/src/doc/feature/script.dox +++ b/src/doc/feature/script.dox @@ -2,19 +2,14 @@ namespace crepe { /** -\defgroup feature_script Scripting +\defgroup feature_script Scripting basics \ingroup feature -\brief User-defined scripts for game objects +\brief Create a concrete Script and attach it to a GameObject Scripts can be used to implement game behavior, and allow arbitrary code to run as part of the game loop. Scripts are implemented as derivative classes of -Script, which are added to game objects using the BehaviorScript \ref Component -"component". - -\todo This section is incomplete: -- Utility functions to get components/events/etc inside script -- How to listen for events -- Extensions of script (keylistener) +Script, which are added to \ref GameObject "game objects" using the \ref +BehaviorScript \ref Component "component". \see Script \see BehaviorScript @@ -22,11 +17,14 @@ Script, which are added to game objects using the BehaviorScript \ref Component \par Example -First, define a class that inherits from Script. This class acts as an -interface, and has two functions (\ref Script::init "\c init()" and \ref -Script::update "\c update()"), which may be implemented (they are empty by -default). From now on, this derivative class will be referred to as a *concrete -script*. +\note This example assumes you already have a GameObject. If not, read +\"\ref feature_gameobject\" first. + +First, define a class (anywhere) that inherits from Script. The Script class +acts as an interface, and has three functions (\ref Script::init "\c init()", +\ref Script::fixed_update "\c fixed_update()" and \ref Script::frame_update +"\c frame_update()"), which *may* be implemented (they are empty by default). +From now on, this derivative class will be referred to as a *concrete script*. ```cpp #include <crepe/api/Script.h> @@ -36,25 +34,33 @@ class MyScript : public crepe::Script { void init() { // called once } - void update() { + + void fixed_update(crepe::duration_t delta_time) { // called on fixed update } + void frame_update(crepe::duration_t delta_time) { + // called for every rendered frame + } }; ``` -Concrete scripts can be instantiated and attached to \ref GameObject -"game objects" using the BehaviorScript \ref Component "component". +After defining a concrete script, it can be instantiated and attached to \ref +feature_gameobject "game objects" during \ref feature_scene +"scene initialization" using a BehaviorScript component: ```cpp using namespace crepe; -GameObject obj = component_manager.new_object("name"); +GameObject obj; -// create BehaviorScript instance +// Create a BehaviorScript component to hold MyScript BehaviorScript & behavior_script = obj.add_component<BehaviorScript>(); -// attach (and instantiate) MyScript to behavior_script + +// Instantiate (and attach) MyScript to behavior_script behavior_script.set_script<MyScript>(); +``` -// the above can also be done in a single call for convenience: +The above can also be done in a single call for convenience: +```cpp obj.add_component<BehaviorScript>().set_script<MyScript>(); ``` diff --git a/src/doc/feature/script_ecs.dox b/src/doc/feature/script_ecs.dox new file mode 100644 index 0000000..8bd3376 --- /dev/null +++ b/src/doc/feature/script_ecs.dox @@ -0,0 +1,57 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_script_ecs Using ECS inside Script +\ingroup feature +\brief Query the component manager inside a concrete Script + +Script provides several methods to request references to components during +runtime. These methods may be used in cases where it is either not practical or +impossible to manually pass the references required to implement a certain +behavior. + +\see Script +\see ComponentManager + +\par Example + +\note This example assumes you already have a concrete Script. If not, read +\"\ref feature_script\" first. + +The component manager can be queried for components inside Script using the +following methods: + +- For requesting components on the same GameObject as this Script instance: + - Script::get_component(): \copybrief Script::get_component + - Script::get_components(): \copybrief Script::get_components +- For requesting components in the current Scene: + - Script::get_components_by_id(): \copybrief Script::get_components_by_id + - Script::get_components_by_name(): \copybrief Script::get_components_by_name + - Script::get_components_by_tag(): \copybrief Script::get_components_by_tag + +```cpp +#include <crepe/util/Log.h> +#include <crepe/api/Script.h> +#include <crepe/api/Metadata.h> + +using namespace crepe; + +class MyScript : public Script { + void show_self() { + Metadata & own_metadata = get_component<Metadata>(); + logf("My name is {}", own_metadata.name); + } + + void list_enemies() { + RefVector<Metadata> enemies = get_components_by_tag<Metadata>("enemy"); + logf("There are {} enemies:", enemies.size()); + for (const Metadata & enemy : enemies) { + logf("- {}", enemy.name); + } + } +}; +``` + +*/ +} diff --git a/src/doc/feature/sfx.dox b/src/doc/feature/sfx.dox new file mode 100644 index 0000000..2a5c9cc --- /dev/null +++ b/src/doc/feature/sfx.dox @@ -0,0 +1,24 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_sfx Playing sound effects +\ingroup feature +\brief Fire a sound effect using the AudioSource component + +This page shows how to implement one-shot sound effects using the AudioSource +component's 'fire and forget'-style API. + +\see AudioSource + +\par Example + +\note This example assumes you already have a GameObject to attach the +AudioSource component to, and uses a Script to control the AudioSource instance. +Separate pages describing these features in more detail can be found at \"\ref +feature_gameobject\" and \"\ref feature_script\" respectively. + +\todo Merge #60 + +*/ +} diff --git a/src/doc/features.dox b/src/doc/features.dox index 4786bed..56d17c7 100644 --- a/src/doc/features.dox +++ b/src/doc/features.dox @@ -1,10 +1,69 @@ // vim:ft=doxygen /** +\htmlonly +<style> +table.memberdecls, +.groupheader +{ display: none; } +ul, +li +{ margin: 1ex 0pt; } +</style> +\endhtmlonly + \defgroup feature Features \brief Engine components This page lists engine features and contains usage instructions for each feature. +- Basics + - \todo Hello world / engine initialization + + - \ref feature_config \n\copybrief feature_config + +- Scenes + - \ref feature_scene \n\copybrief feature_scene + - \todo Navigating between scenes + +- Input + - \todo Key/Mouse events (w/ Script) + +- Actors / game objects + - \ref feature_gameobject \n\copybrief feature_gameobject + +- \todo HUD + +- Animation + - \todo Animation using spritesheet + + - \todo Particle effects + +- Save data + - \ref feature_savemgr \n\copybrief feature_savemgr + +- Audio + - \ref feature_sfx \n\copybrief feature_sfx + - \ref feature_bgm \n\copybrief feature_bgm + +- \todo AI + +- \todo Physics + +- Scripting + - \ref feature_script \n\copybrief feature_script + - \ref feature_script_ecs \n\copybrief feature_script_ecs + + - \todo Subscribing to *any* event inside Script + + - \todo Creating and dispatching custom events + +- \todo Replay + +- Utilities + - \todo Logging + + - \ref feature_proxy \n\copybrief feature_proxy + */ diff --git a/src/doc/index.dox b/src/doc/index.dox index 5ec7889..342db98 100644 --- a/src/doc/index.dox +++ b/src/doc/index.dox @@ -5,6 +5,20 @@ Welcome to the documentation for the crêpe game engine. -\see feature +\see \ref install "Engine installation instructions" +\see \ref feature "Example code and usage instructions" +\see [API documentation](annotated.html) + +\noop No bold links in "See also" section +\htmlonly +<style> .section.see a { font-weight: normal; } </style> +\endhtmlonly + +*/ + +/** + +\namespace crepe +\brief Engine namespace */ diff --git a/src/doc/internal/component.dox b/src/doc/internal/component.dox new file mode 100644 index 0000000..0dd4cb5 --- /dev/null +++ b/src/doc/internal/component.dox @@ -0,0 +1,41 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup internal_component Components +\ingroup internal +\brief ECS Components + +Components are attached to GameObject instances and are composed by the game +programmer to create specific entities in the game world. While they are +implemented as C++ classes, components should be treated as C-style structs, +meaning all members are public and they do not contain functions. + +A basic component has the following structure: +```cpp +#include <crepe/Component.h> + +class MyComponent : public crepe::Component { +public: + // Add your custom component's ininitializer properties after the `id` + // parameter. The first parameter is controlled by GameObject::add_component, + // while all other parameters are forwarded using std::forward. + MyComponent(game_object_id_t id, ...); + + // Optionally define the `get_instances_max` method to limit the amount of + // instances of this component per GameObject. The default implementation for + // this function returns -1, which means the instance count does not have an + // upper limit: + virtual int get_instances_max() const { return -1; } + + // Properties + // ... +}; +``` + +Generally, components are "handled" by \ref internal_system "systems", which may +optionally change the components' state. Components' state may also be +controlled by the game programmer through \ref feature_script "scripts". + +*/ +} diff --git a/src/doc/internal/resource.dox b/src/doc/internal/resource.dox new file mode 100644 index 0000000..56f1de0 --- /dev/null +++ b/src/doc/internal/resource.dox @@ -0,0 +1,12 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup internal_resource Resources +\ingroup internal +\brief Concrete resources + +\todo This section is incomplete + +*/ +} diff --git a/src/doc/internal/style.dox b/src/doc/internal/style.dox new file mode 100644 index 0000000..dad2df0 --- /dev/null +++ b/src/doc/internal/style.dox @@ -0,0 +1,9 @@ +// vim:ft=doxygen +/** + +\defgroup internal_style Code style +\ingroup internal +\brief Coding conventions +\include{doc} contributing.md + +*/ diff --git a/src/doc/internal/system.dox b/src/doc/internal/system.dox new file mode 100644 index 0000000..17a101e --- /dev/null +++ b/src/doc/internal/system.dox @@ -0,0 +1,26 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup internal_system Systems +\ingroup internal +\brief ECS Systems + +\todo This section is incomplete + +A system is responsible for processing the data stored in \ref +internal_component "components". + +A basic system has the following structure: +```cpp +#include <crepe/system/System.h> + +class MySystem : public System { +public: + using System::System; + void update() override; +}; +``` + +*/ +} diff --git a/src/doc/internals.dox b/src/doc/internals.dox new file mode 100644 index 0000000..2d2ca56 --- /dev/null +++ b/src/doc/internals.dox @@ -0,0 +1,10 @@ +// vim:ft=doxygen +/** + +\defgroup internal Internals +\brief Internal engine structure and other conventions + +\todo This page is incomplete +\todo Anything about Contexts? + +*/ diff --git a/src/doc/layout.xml b/src/doc/layout.xml index 2244fa7..c98c790 100644 --- a/src/doc/layout.xml +++ b/src/doc/layout.xml @@ -1,61 +1,65 @@ <?xml version="1.0" encoding="UTF-8"?> <doxygenlayout version="1.0"> <navindex> - <tab type="mainpage" visible="yes" title=""/> + <tab type="mainpage" visible="yes" title="Intro"/> + <tab type="user" url="@ref install" title="Installation"/> + <tab type="user" url="@ref feature" title="Features"/> + <!-- <tab type="user" url="@ref internal" title="Internals"/> --> <tab type="pages" visible="no" title="" intro=""/> - <tab type="topics" visible="yes" title="" intro=""/> - <tab type="modules" visible="yes" title="" intro=""> + <tab type="topics" visible="no" title="" intro=""/> + <tab type="modules" visible="no" title="" intro=""> <tab type="modulelist" visible="yes" title="" intro=""/> <tab type="modulemembers" visible="yes" title="" intro=""/> </tab> - <tab type="namespaces" visible="no" title=""> + <tab type="namespaces" visible="yes" title=""> <tab type="namespacelist" visible="yes" title="" intro=""/> <tab type="namespacemembers" visible="yes" title="" intro=""/> </tab> - <tab type="concepts" visible="yes" title=""> + <tab type="concepts" visible="no" title=""> </tab> - <tab type="interfaces" visible="yes" title=""> + <tab type="interfaces" visible="no" title=""> <tab type="interfacelist" visible="yes" title="" intro=""/> <tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/> <tab type="interfacehierarchy" visible="yes" title="" intro=""/> </tab> - <tab type="classes" visible="yes" title=""> + <tab type="classes" visible="yes" title="API"> <tab type="classlist" visible="yes" title="" intro=""/> <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> <tab type="hierarchy" visible="yes" title="" intro=""/> <tab type="classmembers" visible="yes" title="" intro=""/> </tab> - <tab type="structs" visible="yes" title=""> + <tab type="structs" visible="no" title=""> <tab type="structlist" visible="yes" title="" intro=""/> <tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/> </tab> - <tab type="exceptions" visible="yes" title=""> + <tab type="exceptions" visible="no" title=""> <tab type="exceptionlist" visible="yes" title="" intro=""/> <tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/> <tab type="exceptionhierarchy" visible="yes" title="" intro=""/> </tab> - <tab type="files" visible="yes" title=""> + <tab type="files" visible="no" title=""> <tab type="filelist" visible="yes" title="" intro=""/> <tab type="globals" visible="yes" title="" intro=""/> </tab> - <tab type="examples" visible="yes" title="" intro=""/> + <tab type="examples" visible="no" title="" intro=""/> </navindex> <class> <briefdescription visible="yes"/> + <detaileddescription title=""/> <includes visible="$SHOW_HEADERFILE"/> <inheritancegraph visible="yes"/> <collaborationgraph visible="yes"/> <memberdecl> + <publicmethods title=""/> <nestedclasses visible="yes" title=""/> <publictypes title=""/> <services title=""/> <interfaces title=""/> <publicslots title=""/> <signals title=""/> - <publicmethods title=""/> - <publicstaticmethods title=""/> <publicattributes title=""/> <publicstaticattributes title=""/> + <publicstaticmethods title=""/> <protectedtypes title=""/> <protectedslots title=""/> <protectedmethods title=""/> @@ -79,7 +83,6 @@ <related title="" subtitle=""/> <membergroups visible="yes"/> </memberdecl> - <detaileddescription title=""/> <memberdef> <inlineclasses title=""/> <typedefs title=""/> @@ -99,11 +102,12 @@ </class> <namespace> <briefdescription visible="yes"/> + <detaileddescription title=""/> <memberdecl> <nestednamespaces visible="yes" title=""/> <constantgroups visible="yes" title=""/> <interfaces visible="yes" title=""/> - <classes visible="yes" title=""/> + <classes visible="no" title=""/> <concepts visible="yes" title=""/> <structs visible="yes" title=""/> <exceptions visible="yes" title=""/> @@ -116,7 +120,6 @@ <properties title=""/> <membergroups visible="yes"/> </memberdecl> - <detaileddescription title=""/> <memberdef> <inlineclasses title=""/> <typedefs title=""/> diff --git a/src/doc/style.css b/src/doc/style.css index 08bc9f5..efc669b 100644 --- a/src/doc/style.css +++ b/src/doc/style.css @@ -1,4 +1,33 @@ #titlearea, -address { - display: none; +address, +a[href="namespaces.html"] +{ display: none; } + +h2.groupheader { margin-top: revert; } + +dl { + padding: 4px 12px !important; + border-radius: 8px !important; + border: 0 !important; +} +dt { + margin-bottom: 0.5ex; +} + +a:hover { + text-decoration: revert !important; + background: unset !important; +} + +dl.section.see, +dl.section.user { + padding: 0 !important; + border-radius: 0 !important; + margin-top: 0; +} +dl.section.see dt, +dl.section.user dt { + font-size: 130%; + margin-bottom: 0.5ex; + margin-top: 1.5ex; } diff --git a/src/example/AITest.cpp b/src/example/AITest.cpp new file mode 100644 index 0000000..4c4e25e --- /dev/null +++ b/src/example/AITest.cpp @@ -0,0 +1,96 @@ +#include <crepe/api/AI.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/LoopManager.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/manager/Mediator.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +class Script1 : public Script { + bool shutdown(const ShutDownEvent & event) { + // Very dirty way of shutting down the game + throw "ShutDownEvent"; + return true; + } + + bool mousemove(const MouseMoveEvent & event) { + /*RefVector<AI> aivec = this->get_components<AI>(); + AI & ai = aivec.front().get(); + ai.flee_target + = vec2{static_cast<float>(event.mouse_x), static_cast<float>(event.mouse_y)};*/ + return true; + } + + void init() { + subscribe<ShutDownEvent>([this](const ShutDownEvent & ev) -> bool { + return this->shutdown(ev); + }); + subscribe<MouseMoveEvent>([this](const MouseMoveEvent & ev) -> bool { + return this->mousemove(ev); + }); + } +}; + +class Scene1 : public Scene { +public: + void load_scene() override { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + GameObject game_object1 = mgr.new_object("", "", vec2 {0, 0}, 0, 1); + GameObject game_object2 = mgr.new_object("", "", vec2 {0, 0}, 0, 1); + + Asset img {"asset/texture/test_ap43.png"}; + + Sprite & test_sprite = game_object1.add_component<Sprite>( + img, + Sprite::Data { + .color = Color::MAGENTA, + .flip = Sprite::FlipSettings {false, false}, + .sorting_in_layer = 2, + .order_in_layer = 2, + .size = {0, 100}, + .angle_offset = 0, + .position_offset = {0, 0}, + } + ); + + AI & ai = game_object1.add_component<AI>(3000); + // ai.arrive_on(); + // ai.flee_on(); + ai.path_follow_on(); + ai.make_oval_path(500, 1000, {0, -1000}, 1.5708, true); + ai.make_oval_path(1000, 500, {0, 500}, 4.7124, false); + game_object1.add_component<Rigidbody>(Rigidbody::Data { + .mass = 0.1f, + .max_linear_velocity = 40, + }); + game_object1.add_component<BehaviorScript>().set_script<Script1>(); + + game_object2.add_component<Camera>( + ivec2 {1080, 720}, vec2 {5000, 5000}, + Camera::Data { + .bg_color = Color::WHITE, + .zoom = 1, + } + ); + } + + string get_name() const override { return "Scene1"; } +}; + +int main() { + LoopManager engine; + engine.add_scene<Scene1>(); + engine.start(); + + return 0; +} diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 560e2bc..afe6cb7 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -16,8 +16,8 @@ function(add_example target_name) add_dependencies(examples ${target_name}) endfunction() -add_example(asset_manager) -add_example(savemgr) add_example(rendering_particle) -add_example(gameloop) - +add_example(button) +add_example(replay) +add_example(loadfont) +add_example(AITest) diff --git a/src/example/asset_manager.cpp b/src/example/asset_manager.cpp deleted file mode 100644 index 917b547..0000000 --- a/src/example/asset_manager.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include <crepe/api/AssetManager.h> -#include <crepe/api/Texture.h> -#include <crepe/facade/Sound.h> - -using namespace crepe; - -int main() { - - // this needs to be called before the asset manager otherwise the destructor of sdl is not in - // the right order - { Texture test("../asset/texture/img.png"); } - // FIXME: make it so the issue described by the above comment is not possible (i.e. the order - // in which internal classes are instantiated should not impact the way the engine works). - - auto & mgr = AssetManager::get_instance(); - - { - // TODO: [design] the Sound class can't be directly included by the user as it includes - // SoLoud headers. - auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg"); - auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav"); - auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav"); - - auto img = mgr.cache<Texture>("../asset/texture/img.png"); - auto img1 = mgr.cache<Texture>("../asset/texture/second.png"); - } - - { - auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg"); - auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav"); - auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav"); - - auto img = mgr.cache<Texture>("../asset/texture/img.png"); - auto img1 = mgr.cache<Texture>("../asset/texture/second.png"); - } -} diff --git a/src/example/button.cpp b/src/example/button.cpp new file mode 100644 index 0000000..ea7f528 --- /dev/null +++ b/src/example/button.cpp @@ -0,0 +1,43 @@ +#include <SDL2/SDL_timer.h> +#include <chrono> +#include <crepe/Component.h> +#include <crepe/api/Animator.h> +#include <crepe/api/Button.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> +#include <crepe/facade/SDLContext.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/system/AnimatorSystem.h> +#include <crepe/system/InputSystem.h> +#include <crepe/system/RenderSystem.h> +#include <crepe/types.h> +using namespace crepe; +using namespace std; + +int main(int argc, char * argv[]) { + Mediator mediator; + ComponentManager mgr {mediator}; + RenderSystem sys {mediator}; + EventManager event_mgr {mediator}; + InputSystem input_sys {mediator}; + SDLContext sdl_context {mediator}; + GameObject obj = mgr.new_object("camera", "camera", vec2 {0, 0}, 0, 1); + auto & camera = obj.add_component<Camera>( + ivec2 {500, 500}, vec2 {500, 500}, + Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f} + ); + auto start = std::chrono::steady_clock::now(); + while (true) { + const keyboard_state_t & keyboard_state = sdl_context.get_keyboard_state(); + input_sys.update(); + sys.update(); + event_mgr.dispatch_events(); + SDL_Delay(30); + } + return 0; +} diff --git a/src/example/gameloop.cpp b/src/example/gameloop.cpp deleted file mode 100644 index a676f20..0000000 --- a/src/example/gameloop.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "crepe/api/LoopManager.h" -using namespace crepe; -int main() { - LoopManager gameloop; - gameloop.start(); - return 1; -} diff --git a/src/example/loadfont.cpp b/src/example/loadfont.cpp new file mode 100644 index 0000000..dd7caff --- /dev/null +++ b/src/example/loadfont.cpp @@ -0,0 +1,51 @@ +#include <SDL2/SDL_ttf.h> +#include <crepe/api/Asset.h> +#include <crepe/api/Text.h> +#include <crepe/facade/Font.h> +#include <crepe/facade/FontFacade.h> +#include <crepe/facade/SDLContext.h> +#include <crepe/manager/Mediator.h> +#include <crepe/manager/ResourceManager.h> +#include <exception> +#include <iostream> +#include <memory> +#include <optional> +using namespace crepe; +int main() { + + // SDLFontContext font_facade; + Mediator mediator; + FontFacade font_facade {}; + SDLContext sdl_context {mediator}; + // ComponentManager component_manager{mediator}; + ResourceManager resource_manager {mediator}; + try { + // Correct way to create a unique pointer for Text + std::unique_ptr<Text> label = std::make_unique<Text>( + 1, vec2(100, 100), vec2(0, 0), "OpenSymbol", Text::Data {}, "test text" + ); + // std::cout << "Path: " << label->font.get_path() << std::endl; + Asset asset1 = font_facade.get_font_asset("OpenSymbol"); + std::cout << asset1.get_path() << std::endl; + std::unique_ptr<Text> label2 = std::make_unique<Text>( + 1, vec2(100, 100), vec2(0, 0), "fsaafdafsdafsdafsdasfdds", Text::Data {} + ); + Asset asset = Asset("test test"); + label->font.emplace(asset); + std::cout << label->font.value().get_path() << std::endl; + // label2->font = std::make_optional(asset); + // std::cout << "Path: " << label2->font.get_path() << std::endl; + ResourceManager & resource_mgr = mediator.resource_manager; + const Font & res = resource_manager.get<Font>(label->font.value()); + // TTF_Font * test_font = res.get_font(); + // if (test_font == NULL) { + // std::cout << "error with font" << std::endl; + // } else { + // std::cout << "correct font retrieved" << std::endl; + // } + } catch (const std::exception & e) { + std::cout << "Standard exception thrown: " << e.what() << std::endl; + } + + return 0; +} diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp index 4571afb..e6b31a7 100644 --- a/src/example/rendering_particle.cpp +++ b/src/example/rendering_particle.cpp @@ -1,71 +1,74 @@ -#include "api/Camera.h" -#include "system/ParticleSystem.h" -#include <SDL2/SDL_timer.h> -#include <crepe/ComponentManager.h> + +#include "api/Asset.h" +#include "api/Text.h" #include <crepe/Component.h> +#include <crepe/api/Animator.h> +#include <crepe/api/Button.h> +#include <crepe/api/Camera.h> #include <crepe/api/Color.h> +#include <crepe/api/Engine.h> #include <crepe/api/GameObject.h> #include <crepe/api/ParticleEmitter.h> #include <crepe/api/Rigidbody.h> #include <crepe/api/Sprite.h> -#include <crepe/api/Texture.h> #include <crepe/api/Transform.h> -#include <crepe/api/Vector2.h> -#include <crepe/system/RenderSystem.h> - -#include <chrono> -#include <iostream> -#include <memory> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/Mediator.h> +#include <crepe/types.h> using namespace crepe; using namespace std; -int main(int argc, char * argv[]) { - ComponentManager mgr; - GameObject game_object = mgr.new_object("", "", Vector2{100, 100}, 0, 0.1); - RenderSystem sys{mgr}; - ParticleSystem psys{mgr}; +class TestScene : public Scene { +public: + void load_scene() { + GameObject game_object = new_object("", "", vec2 {0, 0}, 0, 1); - Color color(255, 255, 255, 255); + Color color(255, 255, 255, 255); - Sprite & test_sprite = game_object.add_component<Sprite>( - make_shared<Texture>("../asset/texture/img.png"), color, FlipSettings{false, false}); - test_sprite.order_in_layer = 5; + Asset img {"asset/spritesheet/pokemon_spritesheet.png"}; - auto & test = game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{ - .position = {0, 0}, - .max_particles = 10, - .emission_rate = 0.1, - .min_speed = 6, - .max_speed = 20, - .min_angle = -20, - .max_angle = 20, - .begin_lifespan = 0, - .end_lifespan = 60, - .force_over_time = Vector2{0, 0}, - .boundary{ - .width = 1000, - .height = 1000, - .offset = Vector2{0, 0}, - .reset_on_exit = false, - }, - .sprite = test_sprite, - }); - game_object.add_component<Camera>(Color::WHITE); + Sprite & test_sprite = game_object.add_component<Sprite>( + img, + Sprite::Data { + .color = color, + .flip = Sprite::FlipSettings {false, false}, + .sorting_in_layer = 2, + .order_in_layer = 2, + .size = {1, 0}, + .angle_offset = 0, + .position_offset = {0, 1}, + .world_space = false, + } + ); - game_object - .add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), color, - FlipSettings{false, false}) - .order_in_layer - = 6; + auto & anim = game_object.add_component<Animator>( + test_sprite, ivec2 {56, 56}, uvec2 {4, 4}, + Animator::Data { + .looping = 0, + } + ); - auto start = std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { - psys.update(); - sys.update(); - SDL_Delay(10); + anim.set_anim(1); + anim.pause(); + anim.next_anim(); + + auto & cam = game_object.add_component<Camera>( + ivec2 {1280, 720}, vec2 {5, 5}, + Camera::Data { + .bg_color = Color::WHITE, + .postion_offset = {1000, 1000}, + } + ); } + string get_name() const { return "TestScene"; }; +}; + +int main(int argc, char * argv[]) { + Engine engine; + engine.add_scene<TestScene>(); + engine.main(); return 0; } diff --git a/src/example/replay.cpp b/src/example/replay.cpp new file mode 100644 index 0000000..00a6502 --- /dev/null +++ b/src/example/replay.cpp @@ -0,0 +1,89 @@ +#include <crepe/api/Config.h> +#include <crepe/api/Engine.h> +#include <crepe/api/Script.h> + +using namespace crepe; +using namespace std; + +class AnimationScript : public Script { + Transform * transform; + float t = 0; + + void init() { transform = &get_component<Transform>(); } + + void update() { + t += 0.05; + transform->position = {sin(t), cos(t)}; + } +}; + +class Timeline : public Script { + unsigned i = 0; + recording_t recording; + + void update() { + switch (i++) { + default: + break; + case 10: + logf("record start"); + replay.record_start(); + break; + case 60: + logf("record end, playing recording"); + this->recording = replay.record_end(); + replay.play(this->recording); + break; + case 61: + logf("done, releasing recording"); + replay.release(this->recording); + break; + case 72: + logf("exit"); + queue_event<ShutDownEvent>(); + break; + }; + } +}; + +class TestScene : public Scene { +public: + using Scene::Scene; + + void load_scene() { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + GameObject cam = mgr.new_object("cam"); + cam.add_component<Camera>( + ivec2 {640, 480}, vec2 {3, 3}, + Camera::Data { + .bg_color = Color::WHITE, + } + ); + + GameObject square = mgr.new_object("square"); + square.add_component<Sprite>( + Asset {"asset/texture/square.png"}, + Sprite::Data { + .size = {0.5, 0.5}, + } + ); + square.add_component<BehaviorScript>().set_script<AnimationScript>(); + + GameObject scapegoat = mgr.new_object(""); + scapegoat.add_component<BehaviorScript>().set_script<Timeline>(); + } + + string get_name() const { return "scene1"; } +}; + +int main(int argc, char * argv[]) { + Config & cfg = Config::get_instance(); + cfg.log.level = Log::Level::DEBUG; + + Engine engine; + + engine.add_scene<TestScene>(); + return engine.main(); +} diff --git a/src/example/savemgr.cpp b/src/example/savemgr.cpp deleted file mode 100644 index 65c4a34..0000000 --- a/src/example/savemgr.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/** \file - * - * Standalone example for usage of the save manager - */ - -#include <cassert> -#include <crepe/api/Config.h> -#include <crepe/api/SaveManager.h> -#include <crepe/util/Log.h> -#include <crepe/util/Proxy.h> - -using namespace crepe; - -// unrelated setup code -int _ = []() { - // make sure all log messages get printed - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::TRACE; - - return 0; // satisfy compiler -}(); - -int main() { - const char * key = "mygame.test"; - - SaveManager & mgr = SaveManager::get_instance(); - - dbg_logf("has key = {}", mgr.has(key)); - ValueBroker<int> prop = mgr.get<int>(key, 0); - Proxy<int> val = mgr.get<int>(key, 0); - - dbg_logf("val = {}", mgr.get<int>(key).get()); - prop.set(1); - dbg_logf("val = {}", mgr.get<int>(key).get()); - val = 2; - dbg_logf("val = {}", mgr.get<int>(key).get()); - mgr.set<int>(key, 3); - dbg_logf("val = {}", mgr.get<int>(key).get()); - - dbg_logf("has key = {}", mgr.has(key)); - assert(true == mgr.has(key)); - - return 0; -} diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp index 8aa7629..f41e9de 100644 --- a/src/test/AssetTest.cpp +++ b/src/test/AssetTest.cpp @@ -7,20 +7,15 @@ using namespace std; using namespace crepe; using namespace testing; -class AssetTest : public Test { -public: - Config & cfg = Config::get_instance(); - void SetUp() override { this->cfg.asset.root_pattern = ".crepe-root"; } -}; - -TEST_F(AssetTest, Existant) { ASSERT_NO_THROW(Asset{"asset/texture/img.png"}); } +TEST(AssetTest, Existant) { ASSERT_NO_THROW(Asset {"asset/texture/img.png"}); } -TEST_F(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); } +TEST(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset {"asset/nonexistant"}); } -TEST_F(AssetTest, Rootless) { +TEST(AssetTest, Rootless) { + Config & cfg = Config::get_instance(); cfg.asset.root_pattern.clear(); string arbitrary = "\\/this is / /../passed through as-is"; - Asset asset{arbitrary}; + Asset asset {arbitrary}; ASSERT_EQ(arbitrary, asset.get_path()); } diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp new file mode 100644 index 0000000..e548221 --- /dev/null +++ b/src/test/AudioTest.cpp @@ -0,0 +1,170 @@ +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <crepe/api/AudioSource.h> +#include <crepe/api/GameObject.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/system/AudioSystem.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class AudioTest : public Test { +private: + class TestSoundContext : public SoundContext { + public: + MOCK_METHOD(SoundHandle, play, (Sound & resource), (override)); + MOCK_METHOD(void, stop, (const SoundHandle &), (override)); + MOCK_METHOD(void, set_volume, (const SoundHandle &, float), (override)); + MOCK_METHOD(void, set_loop, (const SoundHandle &, bool), (override)); + }; + + class TestAudioSystem : public AudioSystem { + public: + using AudioSystem::AudioSystem; + StrictMock<TestSoundContext> context; + virtual SoundContext & get_context() { return this->context; } + }; + +private: + Mediator mediator; + ComponentManager component_manager {mediator}; + ResourceManager resource_manager {mediator}; + +public: + TestAudioSystem system {mediator}; + TestSoundContext & context = system.context; + +private: + GameObject entity = component_manager.new_object("name"); + +public: + AudioSource & component = entity.add_component<AudioSource>("mwe/audio/bgm.ogg"); +}; + +TEST_F(AudioTest, Default) { + EXPECT_CALL(context, play(_)).Times(0); + EXPECT_CALL(context, stop(_)).Times(0); + EXPECT_CALL(context, set_volume(_, _)).Times(0); + EXPECT_CALL(context, set_loop(_, _)).Times(0); + system.fixed_update(); +} + +TEST_F(AudioTest, Play) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, play(_)).Times(0); + EXPECT_CALL(context, set_loop(_, _)).Times(0); + EXPECT_CALL(context, set_volume(_, _)).Times(0); + component.play(); + } + + { + InSequence seq; + + EXPECT_CALL(context, play(_)).Times(1); + EXPECT_CALL(context, set_loop(_, _)).Times(1); + EXPECT_CALL(context, set_volume(_, _)).Times(1); + system.fixed_update(); + } +} + +TEST_F(AudioTest, Stop) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, stop(_)).Times(0); + component.stop(); + } + + { + InSequence seq; + + EXPECT_CALL(context, stop(_)).Times(1); + system.fixed_update(); + } +} + +TEST_F(AudioTest, Volume) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, set_volume(_, _)).Times(0); + component.volume += 0.2; + } + + { + InSequence seq; + + EXPECT_CALL(context, set_volume(_, component.volume)).Times(1); + system.fixed_update(); + } +} + +TEST_F(AudioTest, Looping) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, set_loop(_, _)).Times(0); + component.loop = !component.loop; + } + + { + InSequence seq; + + EXPECT_CALL(context, set_loop(_, component.loop)).Times(1); + system.fixed_update(); + } +} + +TEST_F(AudioTest, StopOnDeactivate) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, stop(_)).Times(1); + component.active = false; + system.fixed_update(); + } +} + +TEST_F(AudioTest, PlayOnActive) { + component.active = false; + component.play_on_awake = true; + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, play(_)).Times(1); + EXPECT_CALL(context, set_loop(_, _)).Times(1); + EXPECT_CALL(context, set_volume(_, _)).Times(1); + + component.active = true; + system.fixed_update(); + } +} + +TEST_F(AudioTest, PlayImmediately) { + component.play_on_awake = false; + component.play(); + + EXPECT_CALL(context, play(_)).Times(1); + EXPECT_CALL(context, set_volume(_, _)).Times(1); + EXPECT_CALL(context, set_loop(_, _)).Times(1); + + system.fixed_update(); +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 8cb4232..ea92d96 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,9 +1,12 @@ target_sources(test_main PUBLIC main.cpp + CollisionTest.cpp PhysicsTest.cpp ScriptTest.cpp ParticleTest.cpp + AudioTest.cpp AssetTest.cpp + ResourceManagerTest.cpp OptionalRefTest.cpp RenderSystemTest.cpp EventTest.cpp @@ -11,5 +14,15 @@ target_sources(test_main PUBLIC SceneManagerTest.cpp ValueBrokerTest.cpp DBTest.cpp + Vector2Test.cpp + # LoopManagerTest.cpp + LoopTimerTest.cpp + InputTest.cpp + ScriptEventTest.cpp + ScriptSceneTest.cpp + Profiling.cpp + SaveManagerTest.cpp + ScriptSaveManagerTest.cpp + ScriptECSTest.cpp + ReplayManagerTest.cpp ) - diff --git a/src/test/CollisionTest.cpp b/src/test/CollisionTest.cpp new file mode 100644 index 0000000..c571c1a --- /dev/null +++ b/src/test/CollisionTest.cpp @@ -0,0 +1,364 @@ +#include "api/BoxCollider.h" +#include "manager/Mediator.h" +#include <cmath> +#include <cstddef> +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/Mediator.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/types.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class CollisionHandler : public Script { +public: + int box_id; + function<void(const CollisionEvent & ev)> test_fn = [](const CollisionEvent & ev) {}; + + CollisionHandler(int box_id) { this->box_id = box_id; } + + bool on_collision(const CollisionEvent & ev) { + //Log::logf("Box {} script on_collision()", box_id); + test_fn(ev); + return true; + } + + void init() { + subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { + return this->on_collision(ev); + }); + } + void update() { + // Retrieve component from the same GameObject this script is on + } +}; + +class CollisionTest : public Test { +public: + Mediator m; + EventManager event_mgr {m}; + ComponentManager mgr {m}; + CollisionSystem collision_sys {m}; + ScriptSystem script_sys {m}; + LoopTimerManager loop_timer {m}; + + GameObject world = mgr.new_object("world", "", {50, 50}); + GameObject game_object1 = mgr.new_object("object1", "", {50, 50}); + GameObject game_object2 = mgr.new_object("object2", "", {50, 30}); + + CollisionHandler * script_object1_ref = nullptr; + CollisionHandler * script_object2_ref = nullptr; + + void SetUp() override { + world.add_component<Rigidbody>(Rigidbody::Data { + // TODO: remove unrelated properties: + .body_type = Rigidbody::BodyType::STATIC, + }); + // Create a box with an inner size of 10x10 units + world.add_component<BoxCollider>(vec2 {100, 100}, vec2 {0, -100}); // Top + world.add_component<BoxCollider>(vec2 {100, 100}, vec2 {0, 100}); // Bottom + world.add_component<BoxCollider>(vec2 {100, 100}, vec2 {-100, 0}); // Left + world.add_component<BoxCollider>(vec2 {100, 100}, vec2 {100, 0}); // right + + game_object1.add_component<Rigidbody>(Rigidbody::Data { + .mass = 1, + .gravity_scale = 0.01, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = {0, 0}, + .constraints = {0, 0, 0}, + .elasticity_coefficient = 1, + .collision_layers = {0}, + }); + game_object1.add_component<BoxCollider>(vec2 {10, 10}, vec2 {0, 0}); + BehaviorScript & script_object1 + = game_object1.add_component<BehaviorScript>().set_script<CollisionHandler>(1); + script_object1_ref = static_cast<CollisionHandler *>(script_object1.script.get()); + ASSERT_NE(script_object1_ref, nullptr); + + game_object2.add_component<Rigidbody>(Rigidbody::Data { + .mass = 1, + .gravity_scale = 0.01, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = {0, 0}, + .constraints = {0, 0, 0}, + .elasticity_coefficient = 1, + .collision_layers = {0}, + }); + game_object2.add_component<BoxCollider>(vec2 {10, 10}, vec2 {0, 0}); + BehaviorScript & script_object2 + = game_object2.add_component<BehaviorScript>().set_script<CollisionHandler>(2); + script_object2_ref = static_cast<CollisionHandler *>(script_object2.script.get()); + ASSERT_NE(script_object2_ref, nullptr); + + // Ensure Script::init() is called on all BehaviorScript instances + script_sys.fixed_update(); + } +}; + +TEST_F(CollisionTest, collision_example) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + }; + EXPECT_FALSE(collision_happend); + collision_sys.fixed_update(); + EXPECT_FALSE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_both_no_velocity) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 10); + EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, -10); + EXPECT_EQ(ev.info.resolution.y, -10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 30}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, -5); + EXPECT_EQ(ev.info.resolution.y, 0); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION + ); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, 5); + EXPECT_EQ(ev.info.resolution.y, 0); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION + ); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {45, 30}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 0); + EXPECT_EQ(ev.info.resolution.y, -5); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION + ); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, 0); + EXPECT_EQ(ev.info.resolution.y, 5); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION + ); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 25}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_both) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 10); + EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, -10); + EXPECT_EQ(ev.info.resolution.y, -10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 30}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.linear_velocity = {10, 10}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, -5); + EXPECT_EQ(ev.info.resolution.y, 5); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION + ); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, 5); + EXPECT_EQ(ev.info.resolution.y, -5); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION + ); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {45, 30}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.linear_velocity = {10, 10}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 5); + EXPECT_EQ(ev.info.resolution.y, -5); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION + ); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, -5); + EXPECT_EQ(ev.info.resolution.y, 5); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION + ); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 25}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.linear_velocity = {10, 10}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_both) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 10); + EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + script_object2_ref->test_fn + = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 30}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_x_direction) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, -5); + EXPECT_EQ(ev.info.resolution.y, 5); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION + ); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + // is static should not be called + //FAIL(); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {45, 30}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_y_direction) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 5); + EXPECT_EQ(ev.info.resolution.y, -5); + EXPECT_EQ( + ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION + ); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + // is static should not be called + //FAIL(); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 25}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} diff --git a/src/test/DBTest.cpp b/src/test/DBTest.cpp index e80814c..7f2c339 100644 --- a/src/test/DBTest.cpp +++ b/src/test/DBTest.cpp @@ -1,6 +1,7 @@ -#include <crepe/facade/DB.h> #include <gtest/gtest.h> +#include <crepe/facade/DB.h> + using namespace std; using namespace crepe; using namespace testing; @@ -26,3 +27,11 @@ TEST_F(DBTest, Has) { db.set("foo", "bar"); EXPECT_EQ(db.has("foo"), true); } + +TEST_F(DBTest, MultipleKeys) { + db.set("foo", "foo"); + db.set("bar", "bar"); + + EXPECT_EQ(db.get("foo"), "foo"); + EXPECT_EQ(db.get("bar"), "bar"); +} diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp index d5a5826..92436a9 100644 --- a/src/test/ECSTest.cpp +++ b/src/test/ECSTest.cpp @@ -1,23 +1,30 @@ #include <gtest/gtest.h> #define protected public +#define private public -#include <crepe/ComponentManager.h> #include <crepe/api/GameObject.h> #include <crepe/api/Metadata.h> #include <crepe/api/Transform.h> #include <crepe/api/Vector2.h> +#include <crepe/manager/ComponentManager.h> using namespace std; using namespace crepe; class ECSTest : public ::testing::Test { + Mediator m; + public: - ComponentManager mgr{}; + ComponentManager mgr {m}; + + class TestComponent : public Component { + using Component::Component; + }; }; TEST_F(ECSTest, createGameObject) { - GameObject obj = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); @@ -37,8 +44,8 @@ TEST_F(ECSTest, createGameObject) { } TEST_F(ECSTest, deleteAllGameObjects) { - GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj0 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); mgr.delete_all_components(); @@ -48,7 +55,7 @@ TEST_F(ECSTest, deleteAllGameObjects) { EXPECT_EQ(metadata.size(), 0); EXPECT_EQ(transform.size(), 0); - GameObject obj2 = mgr.new_object("body2", "person2", Vector2{1, 0}, 5, 1); + GameObject obj2 = mgr.new_object("body2", "person2", vec2 {1, 0}, 5, 1); metadata = mgr.get_components_by_type<Metadata>(); transform = mgr.get_components_by_type<Transform>(); @@ -70,8 +77,8 @@ TEST_F(ECSTest, deleteAllGameObjects) { } TEST_F(ECSTest, deleteGameObject) { - GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj0 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); mgr.delete_all_components_of_id(0); @@ -96,7 +103,7 @@ TEST_F(ECSTest, deleteGameObject) { TEST_F(ECSTest, manyGameObjects) { for (int i = 0; i < 5000; i++) { - GameObject obj = mgr.new_object("body", "person", Vector2{0, 0}, 0, i); + GameObject obj = mgr.new_object("body", "person", vec2 {0, 0}, 0, i); } vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); @@ -128,7 +135,7 @@ TEST_F(ECSTest, manyGameObjects) { for (int i = 0; i < 10000 - 5000; i++) { string tag = "person" + to_string(i); - GameObject obj = mgr.new_object("body", tag, Vector2{0, 0}, i, 0); + GameObject obj = mgr.new_object("body", tag, vec2 {0, 0}, i, 0); } metadata = mgr.get_components_by_type<Metadata>(); @@ -136,11 +143,58 @@ TEST_F(ECSTest, manyGameObjects) { EXPECT_EQ(metadata.size(), 10000 - 5000); EXPECT_EQ(transform.size(), 10000); + + for (int i = 0; i < 10000 - 5000; i++) { + EXPECT_EQ(metadata[i].get().game_object_id, i + 5000); + EXPECT_EQ(metadata[i].get().name, "body"); + EXPECT_EQ(metadata[i].get().tag, "person" + to_string(i)); + EXPECT_EQ(metadata[i].get().parent, -1); + EXPECT_EQ(metadata[i].get().children.size(), 0); + + EXPECT_EQ(transform[i].get().game_object_id, i); + EXPECT_EQ(transform[i].get().position.x, 0); + EXPECT_EQ(transform[i].get().position.y, 0); + EXPECT_EQ(transform[i].get().rotation, 0); + EXPECT_EQ(transform[i].get().scale, i); + } + + mgr.delete_all_components(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 0); + EXPECT_EQ(transform.size(), 0); + + for (int i = 0; i < 10000; i++) { + string name = "body" + to_string(i); + GameObject obj = mgr.new_object(name, "person", vec2 {0, 0}, 0, 0); + } + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 10000); + EXPECT_EQ(transform.size(), 10000); + + for (int i = 0; i < 10000; i++) { + EXPECT_EQ(metadata[i].get().game_object_id, i); + EXPECT_EQ(metadata[i].get().name, "body" + to_string(i)); + EXPECT_EQ(metadata[i].get().tag, "person"); + EXPECT_EQ(metadata[i].get().parent, -1); + EXPECT_EQ(metadata[i].get().children.size(), 0); + + EXPECT_EQ(transform[i].get().game_object_id, i); + EXPECT_EQ(transform[i].get().position.x, 0); + EXPECT_EQ(transform[i].get().position.y, 0); + EXPECT_EQ(transform[i].get().rotation, 0); + EXPECT_EQ(transform[i].get().scale, 0); + } } TEST_F(ECSTest, getComponentsByID) { - GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj0 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_id<Metadata>(0); vector<reference_wrapper<Transform>> transform = mgr.get_components_by_id<Transform>(1); @@ -163,19 +217,21 @@ TEST_F(ECSTest, getComponentsByID) { TEST_F(ECSTest, tooMuchComponents) { try { - GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - obj0.add_component<Transform>(Vector2{10, 10}, 0, 1); + GameObject obj0 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); + obj0.add_component<Transform>(vec2 {10, 10}, 0, 1); } catch (const exception & e) { - EXPECT_EQ(e.what(), - string("Exceeded maximum number of instances for this component type")); + EXPECT_EQ( + e.what(), string("Exceeded maximum number of instances for this component type") + ); } try { - GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); obj1.add_component<Metadata>("body", "person"); } catch (const exception & e) { - EXPECT_EQ(e.what(), - string("Exceeded maximum number of instances for this component type")); + EXPECT_EQ( + e.what(), string("Exceeded maximum number of instances for this component type") + ); } vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); @@ -187,11 +243,11 @@ TEST_F(ECSTest, tooMuchComponents) { TEST_F(ECSTest, partentChild) { { - GameObject body = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - GameObject right_leg = mgr.new_object("rightLeg", "person", Vector2{1, 1}, 0, 1); - GameObject left_leg = mgr.new_object("leftLeg", "person", Vector2{1, 1}, 0, 1); - GameObject right_foot = mgr.new_object("rightFoot", "person", Vector2{2, 2}, 0, 1); - GameObject left_foot = mgr.new_object("leftFoot", "person", Vector2{2, 2}, 0, 1); + GameObject body = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); + GameObject right_leg = mgr.new_object("rightLeg", "person", vec2 {1, 1}, 0, 1); + GameObject left_leg = mgr.new_object("leftLeg", "person", vec2 {1, 1}, 0, 1); + GameObject right_foot = mgr.new_object("rightFoot", "person", vec2 {2, 2}, 0, 1); + GameObject left_foot = mgr.new_object("leftFoot", "person", vec2 {2, 2}, 0, 1); // Set the parent of each GameObject right_foot.set_parent(right_leg); @@ -234,3 +290,195 @@ TEST_F(ECSTest, partentChild) { EXPECT_EQ(metadata[1].get().children[0], 3); EXPECT_EQ(metadata[2].get().children[0], 4); } + +TEST_F(ECSTest, persistent) { + GameObject obj0 = mgr.new_object("obj0", "obj0", vec2 {0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("obj1", "obj1", vec2 {0, 0}, 0, 1); + obj1.set_persistent(); + GameObject obj2 = mgr.new_object("obj2", "obj2", vec2 {0, 0}, 0, 1); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 3); + EXPECT_EQ(transform.size(), 3); + + mgr.delete_components_by_id<Metadata>(1); + mgr.delete_components<Metadata>(); + mgr.delete_all_components_of_id(1); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 3); + + mgr.delete_all_components(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 1); + EXPECT_EQ(metadata[0].get().name, "obj1"); + EXPECT_EQ(metadata[0].get().tag, "obj1"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + + EXPECT_EQ(transform[0].get().game_object_id, 1); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); + + GameObject obj3 = mgr.new_object("obj3", "obj3", vec2 {0, 0}, 0, 5); + GameObject obj4 = mgr.new_object("obj4", "obj4", vec2 {0, 0}, 0, 5); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 3); + EXPECT_EQ(transform.size(), 3); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "obj3"); + + EXPECT_EQ(metadata[1].get().game_object_id, 1); + EXPECT_EQ(metadata[1].get().name, "obj1"); + + EXPECT_EQ(metadata[2].get().game_object_id, 2); + EXPECT_EQ(metadata[2].get().name, "obj4"); + + EXPECT_EQ(transform[0].get().game_object_id, 0); + EXPECT_EQ(transform[0].get().scale, 5); + + EXPECT_EQ(transform[1].get().game_object_id, 1); + EXPECT_EQ(transform[1].get().scale, 1); + + EXPECT_EQ(transform[2].get().game_object_id, 2); + EXPECT_EQ(transform[2].get().scale, 5); +} + +TEST_F(ECSTest, resetPersistent) { + GameObject obj0 = mgr.new_object("obj0", "obj0", vec2 {0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("obj1", "obj1", vec2 {0, 0}, 0, 1); + obj1.set_persistent(); + GameObject obj2 = mgr.new_object("obj2", "obj2", vec2 {0, 0}, 0, 1); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 3); + EXPECT_EQ(transform.size(), 3); + + mgr.delete_all_components(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + vector<reference_wrapper<Metadata>> metadata_id = mgr.get_components_by_id<Metadata>(1); + + EXPECT_EQ(metadata_id.size(), 1); + EXPECT_EQ(metadata_id[0].get().game_object_id, 1); + EXPECT_EQ(metadata_id[0].get().name, "obj1"); + + mgr.set_persistent(1, false); + mgr.delete_all_components(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 0); + EXPECT_EQ(transform.size(), 0); +} + +TEST_F(ECSTest, IDByName) { + GameObject foo = mgr.new_object("foo"); + GameObject bar = mgr.new_object("bar"); + + { + auto objects = mgr.get_objects_by_name(""); + EXPECT_EQ(objects.size(), 0); + } + + { + auto objects = mgr.get_objects_by_name("foo"); + EXPECT_EQ(objects.size(), 1); + EXPECT_TRUE(objects.contains(foo.id)); + } +} + +TEST_F(ECSTest, IDByTag) { + GameObject foo = mgr.new_object("foo", "common tag"); + GameObject bar = mgr.new_object("bar", "common tag"); + + { + auto objects = mgr.get_objects_by_tag(""); + EXPECT_EQ(objects.size(), 0); + } + + { + auto objects = mgr.get_objects_by_tag("common tag"); + EXPECT_EQ(objects.size(), 2); + EXPECT_TRUE(objects.contains(foo.id)); + EXPECT_TRUE(objects.contains(bar.id)); + } +} + +TEST_F(ECSTest, ComponentsByName) { + GameObject foo = mgr.new_object("foo"); + foo.add_component<TestComponent>(); + GameObject bar = mgr.new_object("bar"); + bar.add_component<TestComponent>(); + bar.add_component<TestComponent>(); + + { + auto objects = mgr.get_components_by_name<TestComponent>(""); + EXPECT_EQ(objects.size(), 0); + } + + { + auto objects = mgr.get_components_by_name<TestComponent>("foo"); + EXPECT_EQ(objects.size(), 1); + } + + { + auto objects = mgr.get_components_by_name<TestComponent>("bar"); + EXPECT_EQ(objects.size(), 2); + } +} + +TEST_F(ECSTest, ComponentsByTag) { + GameObject foo = mgr.new_object("foo", "common tag"); + foo.add_component<TestComponent>(); + GameObject bar = mgr.new_object("bar", "common tag"); + bar.add_component<TestComponent>(); + bar.add_component<TestComponent>(); + + { + auto objects = mgr.get_components_by_tag<TestComponent>(""); + EXPECT_EQ(objects.size(), 0); + } + + { + auto objects = mgr.get_components_by_tag<TestComponent>("common tag"); + EXPECT_EQ(objects.size(), 3); + } +} + +TEST_F(ECSTest, Snapshot) { + GameObject foo = mgr.new_object("foo"); + + foo.transform.position = {1, 1}; + + ComponentManager::Snapshot snapshot = mgr.save(); + + foo.transform.position = {0, 0}; + + mgr.restore(snapshot); + + EXPECT_EQ(foo.transform.position, (vec2 {1, 1})); +} diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp index b0e6c9c..6105679 100644 --- a/src/test/EventTest.cpp +++ b/src/test/EventTest.cpp @@ -1,8 +1,6 @@ - -#include "api/Event.h" -#include "api/EventManager.h" -#include "api/IKeyListener.h" -#include "api/IMouseListener.h" +#include <crepe/api/Event.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/Mediator.h> #include <gmock/gmock.h> #include <gtest/gtest.h> using namespace std; @@ -11,72 +9,56 @@ using namespace crepe; class EventManagerTest : public ::testing::Test { protected: + Mediator mediator; + EventManager event_mgr {mediator}; void SetUp() override { // Clear any existing subscriptions or events before each test - EventManager::get_instance().clear(); + event_mgr.clear(); } void TearDown() override { // Ensure cleanup after each test - EventManager::get_instance().clear(); + event_mgr.clear(); } }; -class MockKeyListener : public IKeyListener { -public: - MOCK_METHOD(bool, on_key_pressed, (const KeyPressEvent & event), (override)); - MOCK_METHOD(bool, on_key_released, (const KeyReleaseEvent & event), (override)); -}; - -class MockMouseListener : public IMouseListener { -public: - MOCK_METHOD(bool, on_mouse_clicked, (const MouseClickEvent & event), (override)); - MOCK_METHOD(bool, on_mouse_pressed, (const MousePressEvent & event), (override)); - MOCK_METHOD(bool, on_mouse_released, (const MouseReleaseEvent & event), (override)); - MOCK_METHOD(bool, on_mouse_moved, (const MouseMoveEvent & event), (override)); -}; - TEST_F(EventManagerTest, EventSubscription) { - EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { - std::cout << "Key Event Triggered" << std::endl; - return true; - }; + EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { return true; }; // Subscribe to KeyPressEvent - EventManager::get_instance().subscribe<KeyPressEvent>(key_handler, 1); + event_mgr.subscribe<KeyPressEvent>(key_handler, 1); // Verify subscription (not directly verifiable; test by triggering event) - EventManager::get_instance().trigger_event<KeyPressEvent>( - KeyPressEvent{ + event_mgr.trigger_event<KeyPressEvent>( + KeyPressEvent { .repeat = true, .key = Keycode::A, }, - 1); - EventManager::get_instance().trigger_event<KeyPressEvent>( - KeyPressEvent{ + 1 + ); + event_mgr.trigger_event<KeyPressEvent>( + KeyPressEvent { .repeat = true, .key = Keycode::A, }, - EventManager::CHANNEL_ALL); + EventManager::CHANNEL_ALL + ); } TEST_F(EventManagerTest, EventManagerTest_trigger_all_channels) { bool triggered = false; EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { triggered = true; - EXPECT_EQ(e.mouse_x, 100); - EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); return false; }; - EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, - EventManager::CHANNEL_ALL); + event_mgr.subscribe<MouseClickEvent>(mouse_handler, EventManager::CHANNEL_ALL); - MouseClickEvent click_event{ - .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; - EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, - EventManager::CHANNEL_ALL); + MouseClickEvent click_event {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}; + event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); EXPECT_TRUE(triggered); } @@ -85,24 +67,21 @@ TEST_F(EventManagerTest, EventManagerTest_trigger_one_channel) { int test_channel = 1; EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { triggered = true; - EXPECT_EQ(e.mouse_x, 100); - EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); return false; }; - EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, test_channel); + event_mgr.subscribe<MouseClickEvent>(mouse_handler, test_channel); - MouseClickEvent click_event{ - .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; - EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, - EventManager::CHANNEL_ALL); + MouseClickEvent click_event {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}; + event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); EXPECT_FALSE(triggered); - EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, test_channel); + event_mgr.trigger_event<MouseClickEvent>(click_event, test_channel); } TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { - EventManager & event_manager = EventManager::get_instance(); // Flags to track handler calls bool triggered_true = false; @@ -111,28 +90,27 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { // Handlers EventHandler<MouseClickEvent> mouse_handler_true = [&](const MouseClickEvent & e) { triggered_true = true; - EXPECT_EQ(e.mouse_x, 100); - EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); return true; // Stops propagation }; EventHandler<MouseClickEvent> mouse_handler_false = [&](const MouseClickEvent & e) { triggered_false = true; - EXPECT_EQ(e.mouse_x, 100); - EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); return false; // Allows propagation }; // Test event - MouseClickEvent click_event{ - .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; - event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); - event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + MouseClickEvent click_event {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}; + event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); // Trigger event - event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); // Check that only the true handler was triggered EXPECT_TRUE(triggered_true); @@ -141,12 +119,12 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { // Reset and clear triggered_true = false; triggered_false = false; - event_manager.clear(); - event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); - event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + event_mgr.clear(); + event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); // Trigger event again - event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); // Check that both handlers were triggered EXPECT_TRUE(triggered_true); @@ -154,39 +132,39 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { } TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) { - EventManager & event_manager = EventManager::get_instance(); bool triggered1 = false; bool triggered2 = false; int test_channel = 1; EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { triggered1 = true; - EXPECT_EQ(e.mouse_x, 100); - EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); return false; // Allows propagation }; EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { triggered2 = true; - EXPECT_EQ(e.mouse_x, 100); - EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); return false; // Allows propagation }; - event_manager.subscribe<MouseClickEvent>(mouse_handler1); - event_manager.subscribe<MouseClickEvent>(mouse_handler2, test_channel); - - event_manager.queue_event<MouseClickEvent>( - MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); - event_manager.queue_event<MouseClickEvent>( - MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}, - test_channel); - event_manager.dispatch_events(); + event_mgr.subscribe<MouseClickEvent>(mouse_handler1); + event_mgr.subscribe<MouseClickEvent>(mouse_handler2, test_channel); + + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE} + ); + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}, + test_channel + ); + event_mgr.dispatch_events(); EXPECT_TRUE(triggered1); EXPECT_TRUE(triggered2); } TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { - EventManager & event_manager = EventManager::get_instance(); // Flags to track if handlers are triggered bool triggered1 = false; @@ -195,29 +173,30 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { // Define EventHandlers EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { triggered1 = true; - EXPECT_EQ(e.mouse_x, 100); - EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); return false; // Allows propagation }; EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { triggered2 = true; - EXPECT_EQ(e.mouse_x, 100); - EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); return false; // Allows propagation }; // Subscribe handlers - subscription_t handler1_id = event_manager.subscribe<MouseClickEvent>(mouse_handler1); - subscription_t handler2_id = event_manager.subscribe<MouseClickEvent>(mouse_handler2); + subscription_t handler1_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler1); + subscription_t handler2_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler2); // Queue events - event_manager.queue_event<MouseClickEvent>( - MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE} + ); // Dispatch events - both handlers should be triggered - event_manager.dispatch_events(); + event_mgr.dispatch_events(); EXPECT_TRUE(triggered1); // Handler 1 should be triggered EXPECT_TRUE(triggered2); // Handler 2 should be triggered @@ -226,14 +205,15 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { triggered2 = false; // Unsubscribe handler1 - event_manager.unsubscribe(handler1_id); + event_mgr.unsubscribe(handler1_id); // Queue the same event again - event_manager.queue_event<MouseClickEvent>( - MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE} + ); // Dispatch events - only handler 2 should be triggered, handler 1 should NOT - event_manager.dispatch_events(); + event_mgr.dispatch_events(); EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered EXPECT_TRUE(triggered2); // Handler 2 should be triggered @@ -241,14 +221,15 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { triggered2 = false; // Unsubscribe handler2 - event_manager.unsubscribe(handler2_id); + event_mgr.unsubscribe(handler2_id); // Queue the event again - event_manager.queue_event<MouseClickEvent>( - MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE} + ); // Dispatch events - no handler should be triggered - event_manager.dispatch_events(); + event_mgr.dispatch_events(); EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered EXPECT_FALSE(triggered2); // Handler 2 should NOT be triggered } diff --git a/src/test/InputTest.cpp b/src/test/InputTest.cpp new file mode 100644 index 0000000..a1fe59a --- /dev/null +++ b/src/test/InputTest.cpp @@ -0,0 +1,337 @@ +#include <gtest/gtest.h> + +#include <crepe/manager/ResourceManager.h> +#include <crepe/system/RenderSystem.h> +#define protected public +#define private public + +#include "api/KeyCodes.h" +#include "manager/ComponentManager.h" +#include "manager/EventManager.h" +#include "manager/Mediator.h" +#include "system/InputSystem.h" +#include <SDL2/SDL.h> +#include <SDL2/SDL_keycode.h> +#include <crepe/api/Button.h> +#include <crepe/api/Camera.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <crepe/facade/SDLContext.h> +#include <gmock/gmock.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class InputTest : public ::testing::Test { +public: + Mediator mediator; + ComponentManager mgr {mediator}; + SDLContext sdl_context {mediator}; + + InputSystem input_system {mediator}; + ResourceManager resman {mediator}; + RenderSystem render {mediator}; + EventManager event_manager {mediator}; + //GameObject camera; + vec2 offset = {100, 200}; + +protected: + void SetUp() override { + GameObject obj = mgr.new_object("camera", "camera", offset, 0, 1); + auto & camera = obj.add_component<Camera>( + ivec2 {500, 500}, vec2 {500, 500}, + Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f} + ); + render.frame_update(); + //mediator.event_manager = event_manager; + //mediator.component_manager = mgr; + //event_manager.clear(); + } + void TearDown() override {} + void simulate_mouse_click(int mouse_x, int mouse_y, Uint8 mouse_button) { + SDL_Event event; + + // Simulate Mouse Button Down event + SDL_zero(event); + event.type = SDL_MOUSEBUTTONDOWN; + event.button.x = mouse_x; + event.button.y = mouse_y; + event.button.button = mouse_button; + SDL_PushEvent(&event); + + // Simulate Mouse Button Up event + SDL_zero(event); + event.type = SDL_MOUSEBUTTONUP; + event.button.x = mouse_x; + event.button.y = mouse_y; + event.button.button = mouse_button; + SDL_PushEvent(&event); + } +}; + +TEST_F(InputTest, MouseDown) { + bool mouse_triggered = false; + EventHandler<MousePressEvent> on_mouse_down = [&](const MousePressEvent & event) { + mouse_triggered = true; + //middle of the screen = 0,0 + EXPECT_EQ(event.mouse_pos, offset); + EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE); + return false; + }; + event_manager.subscribe<MousePressEvent>(on_mouse_down); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_MOUSEBUTTONDOWN; + // middle of the screen of a 500*500 camera = 250*250 + event.button.x = 250; + event.button.y = 250; + event.button.button = SDL_BUTTON_LEFT; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(mouse_triggered); +} + +TEST_F(InputTest, MouseUp) { + bool function_triggered = false; + EventHandler<MouseReleaseEvent> on_mouse_release = [&](const MouseReleaseEvent & e) { + function_triggered = true; + EXPECT_EQ(e.mouse_pos, offset); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; + }; + event_manager.subscribe<MouseReleaseEvent>(on_mouse_release); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_MOUSEBUTTONUP; + event.button.x = 250; + event.button.y = 250; + event.button.button = SDL_BUTTON_LEFT; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseMove) { + bool function_triggered = false; + EventHandler<MouseMoveEvent> on_mouse_move = [&](const MouseMoveEvent & e) { + function_triggered = true; + EXPECT_EQ(e.mouse_pos, offset); + EXPECT_EQ(e.mouse_delta.x, 10); + EXPECT_EQ(e.mouse_delta.y, 10); + return false; + }; + event_manager.subscribe<MouseMoveEvent>(on_mouse_move); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_MOUSEMOTION; + event.motion.x = 250; + event.motion.y = 250; + event.motion.xrel = 10; + event.motion.yrel = 10; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, KeyDown) { + bool function_triggered = false; + + // Define event handler for KeyPressEvent + EventHandler<KeyPressEvent> on_key_press = [&](const KeyPressEvent & event) { + function_triggered = true; + EXPECT_EQ(event.key, Keycode::B); // Validate the key is 'B' + EXPECT_EQ(event.repeat, true); // Validate repeat flag + return false; + }; + + event_manager.subscribe<KeyPressEvent>(on_key_press); + + // Simulate SDL_KEYDOWN event + SDL_Event test_event; + SDL_zero(test_event); + test_event.type = SDL_KEYDOWN; // Key down event + test_event.key.keysym.scancode = SDL_SCANCODE_B; // Set scancode for 'B' + test_event.key.repeat = 1; // Set repeat flag + SDL_PushEvent(&test_event); + + input_system.fixed_update(); // Process the event + event_manager.dispatch_events(); // Dispatch events to handlers + + EXPECT_TRUE(function_triggered); // Check if the handler was triggered +} + +TEST_F(InputTest, KeyUp) { + bool function_triggered = false; + EventHandler<KeyReleaseEvent> on_key_release = [&](const KeyReleaseEvent & event) { + function_triggered = true; + EXPECT_EQ(event.key, Keycode::B); + return false; + }; + event_manager.subscribe<KeyReleaseEvent>(on_key_release); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_KEYUP; + event.key.keysym.scancode = SDL_SCANCODE_B; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseClick) { + bool on_click_triggered = false; + EventHandler<MouseClickEvent> on_mouse_click = [&](const MouseClickEvent & event) { + on_click_triggered = true; + EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE); + EXPECT_EQ(event.mouse_pos, offset); + return false; + }; + event_manager.subscribe<MouseClickEvent>(on_mouse_click); + + this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(on_click_triggered); +} + +TEST_F(InputTest, testButtonClick) { + GameObject button_obj = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); + bool button_clicked = false; + event_manager.subscribe<ButtonPressEvent>([&](const ButtonPressEvent & event) { + button_clicked = true; + EXPECT_EQ(event.metadata.game_object_id, button_obj.id); + return false; + }); + auto & button + = button_obj.add_component<Button>(vec2 {100, 100}, Button::Data {}, vec2 {0, 0}); + + bool hover = false; + button.active = true; + this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_FALSE(button_clicked); + + this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(button_clicked); +} +TEST_F(InputTest, buttonPositionCamera) { + GameObject button_obj = mgr.new_object("body", "person", vec2 {50, 50}, 0, 1); + bool button_clicked = false; + event_manager.subscribe<ButtonPressEvent>([&](const ButtonPressEvent & event) { + button_clicked = true; + EXPECT_EQ(event.metadata.game_object_id, button_obj.id); + return false; + }); + auto & button = button_obj.add_component<Button>( + vec2 {10, 10}, + Button::Data { + .world_space = false, + }, + vec2 {0, 0} + ); + + bool hover = false; + button.active = true; + this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_FALSE(button_clicked); + + this->simulate_mouse_click(300, 300, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(button_clicked); +} +TEST_F(InputTest, buttonPositionWorld) { + GameObject button_obj = mgr.new_object("body", "person", vec2 {50, 50}, 0, 1); + bool button_clicked = false; + event_manager.subscribe<ButtonPressEvent>([&](const ButtonPressEvent & event) { + button_clicked = true; + EXPECT_EQ(event.metadata.game_object_id, button_obj.id); + return false; + }); + auto & button = button_obj.add_component<Button>( + vec2 {10, 10}, + Button::Data { + .world_space = true, + }, + vec2 {0, 0} + ); + bool hover = false; + button.active = true; + this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_FALSE(button_clicked); + + this->simulate_mouse_click(300, 300, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_FALSE(button_clicked); +} +TEST_F(InputTest, testButtonHover) { + GameObject button_obj = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1); + bool button_hover = false; + event_manager.subscribe<ButtonEnterEvent>([&](const ButtonEnterEvent & event) { + button_hover = true; + EXPECT_EQ(event.metadata.game_object_id, button_obj.id); + return false; + }); + event_manager.subscribe<ButtonExitEvent>([&](const ButtonExitEvent & event) { + button_hover = false; + EXPECT_EQ(event.metadata.game_object_id, button_obj.id); + return false; + }); + auto & button = button_obj.add_component<Button>( + vec2 {100, 100}, + Button::Data { + .world_space = true, + }, + vec2 {0, 0} + ); + // Mouse on button + SDL_Event hover_event; + SDL_zero(hover_event); + hover_event.type = SDL_MOUSEMOTION; + hover_event.motion.x = 250; + hover_event.motion.y = 250; + hover_event.motion.xrel = 10; + hover_event.motion.yrel = 10; + SDL_PushEvent(&hover_event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(button.hover); + EXPECT_TRUE(button_hover); + // Mouse not on button + SDL_Event event; + SDL_zero(event); + event.type = SDL_MOUSEMOTION; + event.motion.x = 500; + event.motion.y = 500; + event.motion.xrel = 10; + event.motion.yrel = 10; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_FALSE(button.hover); + EXPECT_FALSE(button_hover); +} diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp new file mode 100644 index 0000000..302d96c --- /dev/null +++ b/src/test/LoopManagerTest.cpp @@ -0,0 +1,78 @@ +#include <chrono> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <thread> +#define private public +#define protected public +#include <crepe/api/Engine.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/LoopTimerManager.h> +using namespace std::chrono; +using namespace crepe; + +class DISABLED_LoopManagerTest : public ::testing::Test { +protected: + class TestGameLoop : public crepe::Engine { + public: + MOCK_METHOD(void, fixed_update, (), (override)); + MOCK_METHOD(void, frame_update, (), (override)); + }; + + TestGameLoop test_loop; + void SetUp() override {} +}; + +TEST_F(DISABLED_LoopManagerTest, FixedUpdate) { + // Arrange + test_loop.loop_timer.set_target_framerate(60); + + // Set expectations for the mock calls + EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65)); + EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52)); + + // Start the loop in a separate thread + std::thread loop_thread([&]() { test_loop.start(); }); + + // Let the loop run for exactly 1 second + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Stop the game loop + test_loop.game_running = false; + // Wait for the loop thread to finish + loop_thread.join(); + + // Test finished +} + +TEST_F(DISABLED_LoopManagerTest, ScaledFixedUpdate) { + // Arrange + test_loop.loop_timer.set_target_framerate(60); + + // Set expectations for the mock calls + EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65)); + EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52)); + + // Start the loop in a separate thread + std::thread loop_thread([&]() { test_loop.start(); }); + + // Let the loop run for exactly 1 second + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Stop the game loop + test_loop.game_running = false; + // Wait for the loop thread to finish + loop_thread.join(); + + // Test finished +} + +TEST_F(DISABLED_LoopManagerTest, ShutDown) { + // Arrange + test_loop.loop_timer.set_target_framerate(60); + // Start the loop in a separate thread + std::thread loop_thread([&]() { test_loop.start(); }); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + test_loop.event_manager.trigger_event<ShutDownEvent>(ShutDownEvent {}); + // Wait for the loop thread to finish + loop_thread.join(); +} diff --git a/src/test/LoopTimerTest.cpp b/src/test/LoopTimerTest.cpp new file mode 100644 index 0000000..52e412e --- /dev/null +++ b/src/test/LoopTimerTest.cpp @@ -0,0 +1,97 @@ +#include <chrono> +#include <gtest/gtest.h> +#include <thread> + +#define private public +#define protected public + +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/Mediator.h> + +using namespace std::chrono; +using namespace crepe; + +class LoopTimerTest : public ::testing::Test { +protected: + Mediator mediator; + LoopTimerManager loop_timer {mediator}; + + void SetUp() override { loop_timer.start(); } +}; + +TEST_F(LoopTimerTest, EnforcesTargetFrameRate) { + // Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) + loop_timer.set_target_framerate(60); + + auto start_time = steady_clock::now(); + loop_timer.enforce_frame_rate(); + + auto elapsed_time = steady_clock::now() - start_time; + auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + + // For 60 FPS, the target frame time is around 16.67ms + ASSERT_NEAR(elapsed_ms, 16.7, 5); +} + +TEST_F(LoopTimerTest, SetTargetFps) { + // Set the target FPS to 120 + loop_timer.set_target_framerate(120); + + // Calculate the expected frame time (~8.33ms per frame) + duration_t expected_frame_time = std::chrono::duration<float>(1.0 / 120.0); + + ASSERT_NEAR(loop_timer.frame_target_time.count(), expected_frame_time.count(), 0.001); +} + +TEST_F(LoopTimerTest, DeltaTimeCalculation) { + // Set the target FPS to 60 (16.67 ms per frame) + loop_timer.set_target_framerate(60); + + auto start_time = steady_clock::now(); + loop_timer.update(); + auto end_time = steady_clock::now(); + + // Check the delta time + duration_t delta_time = loop_timer.get_delta_time(); + + auto elapsed_time = duration_cast<seconds>(end_time - start_time).count(); + + // Assert that delta_time is close to the elapsed time + ASSERT_NEAR(delta_time.count(), elapsed_time, 1); +} + +TEST_F(LoopTimerTest, DISABLED_getCurrentTime) { + // Set the target FPS to 60 (16.67 ms per frame) + loop_timer.set_target_framerate(60); + + auto start_time = steady_clock::now(); + + // Sleep + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + loop_timer.update(); + + auto end_time = steady_clock::now(); + + // Get the elapsed time in seconds as a double + auto elapsed_time + = std::chrono::duration_cast<elapsed_time_t>(end_time - start_time).count(); + + ASSERT_NEAR(loop_timer.get_elapsed_time().count(), elapsed_time, 5); +} +TEST_F(LoopTimerTest, getFPS) { + // Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) + loop_timer.set_target_framerate(60); + + auto start_time = steady_clock::now(); + loop_timer.enforce_frame_rate(); + + auto elapsed_time = steady_clock::now() - start_time; + loop_timer.update(); + unsigned int fps = loop_timer.get_fps(); + auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + + // For 60 FPS, the target frame time is around 16.67ms + ASSERT_NEAR(elapsed_ms, 16.7, 1); + ASSERT_NEAR(fps, 60, 2); +} diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp index 1c69348..83f7b23 100644 --- a/src/test/OptionalRefTest.cpp +++ b/src/test/OptionalRefTest.cpp @@ -18,9 +18,7 @@ TEST(OptionalRefTest, Normal) { ref.clear(); EXPECT_FALSE(ref); - ASSERT_THROW({ - string & value_ref = ref; - }, runtime_error); + ASSERT_THROW({ string & value_ref = ref; }, runtime_error); } TEST(OptionalRefTest, Empty) { @@ -28,9 +26,7 @@ TEST(OptionalRefTest, Empty) { OptionalRef<string> ref; EXPECT_FALSE(ref); - ASSERT_THROW({ - string & value_ref = ref; - }, runtime_error); + ASSERT_THROW({ string & value_ref = ref; }, runtime_error); } TEST(OptionalRefTest, Chain) { @@ -44,4 +40,3 @@ TEST(OptionalRefTest, Chain) { value_ref = "bar"; EXPECT_EQ(value_ref, value); } - diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index eee022f..eee7a73 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -1,56 +1,70 @@ -#include "api/Vector2.h" -#include <crepe/ComponentManager.h> -#include <crepe/Particle.h> +#include "api/Asset.h" #include <crepe/api/Config.h> #include <crepe/api/GameObject.h> -#include <crepe/api/ParticleEmitter.h> #include <crepe/api/Rigidbody.h> #include <crepe/api/Sprite.h> #include <crepe/api/Transform.h> -#include <crepe/system/ParticleSystem.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/LoopTimerManager.h> #include <gtest/gtest.h> #include <math.h> +#define protected public +#define private public +#include <crepe/Particle.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/system/ParticleSystem.h> using namespace std; using namespace std::chrono_literals; using namespace crepe; class ParticlesTest : public ::testing::Test { + Mediator m; + public: - ComponentManager component_manager; - ParticleSystem particle_system{component_manager}; + ComponentManager component_manager {m}; + ParticleSystem particle_system {m}; + LoopTimerManager loop_timer {m}; void SetUp() override { ComponentManager & mgr = this->component_manager; std::vector<std::reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0); if (transforms.empty()) { - GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0); + GameObject game_object = mgr.new_object("", "", vec2 {0, 0}, 0, 0); Color color(0, 0, 0, 0); + auto s1 = Asset("asset/texture/img.png"); Sprite & test_sprite = game_object.add_component<Sprite>( - make_shared<Texture>("asset/texture/img.png"), color, - FlipSettings{true, true}); + s1, + Sprite::Data { + .color = color, + .flip = Sprite::FlipSettings {true, true}, + .size = {10, 10}, + } + ); - game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{ - .position = {0, 0}, - .max_particles = 100, - .emission_rate = 0, - .min_speed = 0, - .max_speed = 0, - .min_angle = 0, - .max_angle = 0, - .begin_lifespan = 0, - .end_lifespan = 0, - .force_over_time = Vector2{0, 0}, - .boundary{ - .width = 0, - .height = 0, - .offset = Vector2{0, 0}, - .reset_on_exit = false, - }, - .sprite = test_sprite, - }); + game_object.add_component<ParticleEmitter>( + test_sprite, + ParticleEmitter::Data { + .offset = {0, 0}, + .max_particles = 100, + .emission_rate = 0, + .min_speed = 0, + .max_speed = 0, + .min_angle = 0, + .max_angle = 0, + .begin_lifespan = 0, + .end_lifespan = 0, + .force_over_time = vec2 {0, 0}, + .boundary { + .width = 0, + .height = 0, + .offset = vec2 {0, 0}, + .reset_on_exit = false, + }, + } + ); } transforms = mgr.get_components_by_id<Transform>(0); Transform & transform = transforms.front().get(); @@ -60,7 +74,7 @@ public: std::vector<std::reference_wrapper<ParticleEmitter>> rigidbodies = mgr.get_components_by_id<ParticleEmitter>(0); ParticleEmitter & emitter = rigidbodies.front().get(); - emitter.data.position = {0, 0}; + emitter.data.offset = {0, 0}; emitter.data.emission_rate = 0; emitter.data.min_speed = 0; emitter.data.max_speed = 0; @@ -68,9 +82,9 @@ public: emitter.data.max_angle = 0; emitter.data.begin_lifespan = 0; emitter.data.end_lifespan = 0; - emitter.data.force_over_time = Vector2{0, 0}; - emitter.data.boundary = {0, 0, Vector2{0, 0}, false}; - for (auto & particle : emitter.data.particles) { + emitter.data.force_over_time = vec2 {0, 0}; + emitter.data.boundary = {0, 0, vec2 {0, 0}, false}; + for (auto & particle : emitter.particles) { particle.active = false; } } @@ -87,21 +101,21 @@ TEST_F(ParticlesTest, spawnParticle) { emitter.data.max_angle = 0.1; emitter.data.max_speed = 10; emitter.data.max_angle = 10; - particle_system.update(); + particle_system.fixed_update(); //check if nothing happend - EXPECT_EQ(emitter.data.particles[0].active, false); - emitter.data.emission_rate = 1; + EXPECT_EQ(emitter.particles[0].active, false); + emitter.data.emission_rate = 50; //check particle spawnes - particle_system.update(); - EXPECT_EQ(emitter.data.particles[0].active, true); - particle_system.update(); - EXPECT_EQ(emitter.data.particles[1].active, true); - particle_system.update(); - EXPECT_EQ(emitter.data.particles[2].active, true); - particle_system.update(); - EXPECT_EQ(emitter.data.particles[3].active, true); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[0].active, true); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[1].active, true); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[2].active, true); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[3].active, true); - for (auto & particle : emitter.data.particles) { + for (auto & particle : emitter.particles) { // Check velocity range EXPECT_GE(particle.velocity.x, emitter.data.min_speed); // Speed should be greater than or equal to min_speed @@ -127,13 +141,13 @@ TEST_F(ParticlesTest, moveParticleHorizontal) { emitter.data.end_lifespan = 100; emitter.data.boundary.height = 100; emitter.data.boundary.width = 100; - emitter.data.min_speed = 1; - emitter.data.max_speed = 1; + emitter.data.min_speed = 50; + emitter.data.max_speed = 50; emitter.data.max_angle = 0; - emitter.data.emission_rate = 1; + emitter.data.emission_rate = 50; for (int a = 1; a < emitter.data.boundary.width / 2; a++) { - particle_system.update(); - EXPECT_EQ(emitter.data.particles[0].position.x, a); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[0].position.x, a); } } @@ -144,14 +158,14 @@ TEST_F(ParticlesTest, moveParticleVertical) { emitter.data.end_lifespan = 100; emitter.data.boundary.height = 100; emitter.data.boundary.width = 100; - emitter.data.min_speed = 1; - emitter.data.max_speed = 1; + emitter.data.min_speed = 50; + emitter.data.max_speed = 50; emitter.data.min_angle = 90; emitter.data.max_angle = 90; - emitter.data.emission_rate = 1; + emitter.data.emission_rate = 50; for (int a = 1; a < emitter.data.boundary.width / 2; a++) { - particle_system.update(); - EXPECT_EQ(emitter.data.particles[0].position.y, a); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[0].position.y, a); } } @@ -169,9 +183,9 @@ TEST_F(ParticlesTest, boundaryParticleReset) { emitter.data.max_angle = 90; emitter.data.emission_rate = 1; for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { - particle_system.update(); + particle_system.fixed_update(); } - EXPECT_EQ(emitter.data.particles[0].active, false); + EXPECT_EQ(emitter.particles[0].active, false); } TEST_F(ParticlesTest, boundaryParticleStop) { @@ -188,15 +202,19 @@ TEST_F(ParticlesTest, boundaryParticleStop) { emitter.data.max_angle = 90; emitter.data.emission_rate = 1; for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { - particle_system.update(); + particle_system.fixed_update(); } const double TOLERANCE = 0.01; - EXPECT_NEAR(emitter.data.particles[0].velocity.x, 0, TOLERANCE); - EXPECT_NEAR(emitter.data.particles[0].velocity.y, 0, TOLERANCE); - if (emitter.data.particles[0].velocity.x != 0) - EXPECT_NEAR(std::abs(emitter.data.particles[0].position.x), - emitter.data.boundary.height / 2, TOLERANCE); - if (emitter.data.particles[0].velocity.y != 0) - EXPECT_NEAR(std::abs(emitter.data.particles[0].position.y), - emitter.data.boundary.width / 2, TOLERANCE); + EXPECT_NEAR(emitter.particles[0].velocity.x, 0, TOLERANCE); + EXPECT_NEAR(emitter.particles[0].velocity.y, 0, TOLERANCE); + if (emitter.particles[0].velocity.x != 0) + EXPECT_NEAR( + std::abs(emitter.particles[0].position.x), emitter.data.boundary.height / 2, + TOLERANCE + ); + if (emitter.particles[0].velocity.y != 0) + EXPECT_NEAR( + std::abs(emitter.particles[0].position.y), emitter.data.boundary.width / 2, + TOLERANCE + ); } diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp index 1e37c26..85eb6d5 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -1,8 +1,10 @@ -#include <crepe/ComponentManager.h> #include <crepe/api/Config.h> #include <crepe/api/GameObject.h> #include <crepe/api/Rigidbody.h> #include <crepe/api/Transform.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/Mediator.h> #include <crepe/system/PhysicsSystem.h> #include <gtest/gtest.h> @@ -11,25 +13,26 @@ using namespace std::chrono_literals; using namespace crepe; class PhysicsTest : public ::testing::Test { + Mediator m; + public: - ComponentManager component_manager; - PhysicsSystem system{component_manager}; + ComponentManager component_manager {m}; + PhysicsSystem system {m}; + LoopTimerManager loop_timer {m}; void SetUp() override { ComponentManager & mgr = this->component_manager; vector<reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0); if (transforms.empty()) { - auto entity = mgr.new_object("", "", Vector2{0, 0}, 0, 0); - entity.add_component<Rigidbody>(Rigidbody::Data{ + auto entity = mgr.new_object("", "", vec2 {0, 0}, 0, 0); + entity.add_component<Rigidbody>(Rigidbody::Data { .mass = 1, .gravity_scale = 1, .body_type = Rigidbody::BodyType::DYNAMIC, - .max_linear_velocity = Vector2{10, 10}, + .max_linear_velocity = 10, .max_angular_velocity = 10, .constraints = {0, 0}, - .use_gravity = true, - .bounce = false, }); } transforms = mgr.get_components_by_id<Transform>(0); @@ -54,40 +57,41 @@ TEST_F(PhysicsTest, gravity) { ASSERT_FALSE(transforms.empty()); EXPECT_EQ(transform.position.y, 0); - system.update(); - EXPECT_EQ(transform.position.y, 1); + system.fixed_update(); + EXPECT_NEAR(transform.position.y, 0.0004, 0.0001); - system.update(); - EXPECT_EQ(transform.position.y, 3); + system.fixed_update(); + EXPECT_NEAR(transform.position.y, 0.002, 0.001); } TEST_F(PhysicsTest, max_velocity) { ComponentManager & mgr = this->component_manager; vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0); Rigidbody & rigidbody = rigidbodies.front().get(); + rigidbody.data.gravity_scale = 0; ASSERT_FALSE(rigidbodies.empty()); EXPECT_EQ(rigidbody.data.linear_velocity.y, 0); rigidbody.add_force_linear({100, 100}); rigidbody.add_force_angular(100); - system.update(); - EXPECT_EQ(rigidbody.data.linear_velocity.y, 10); - EXPECT_EQ(rigidbody.data.linear_velocity.x, 10); + system.fixed_update(); + EXPECT_NEAR(rigidbody.data.linear_velocity.y, 7.07, 0.01); + EXPECT_NEAR(rigidbody.data.linear_velocity.x, 7.07, 0.01); EXPECT_EQ(rigidbody.data.angular_velocity, 10); rigidbody.add_force_linear({-100, -100}); rigidbody.add_force_angular(-100); - system.update(); - EXPECT_EQ(rigidbody.data.linear_velocity.y, -10); - EXPECT_EQ(rigidbody.data.linear_velocity.x, -10); + system.fixed_update(); + EXPECT_NEAR(rigidbody.data.linear_velocity.y, -7.07, 0.01); + EXPECT_NEAR(rigidbody.data.linear_velocity.x, -7.07, 0.01); EXPECT_EQ(rigidbody.data.angular_velocity, -10); } TEST_F(PhysicsTest, movement) { - Config::get_instance().physics.gravity = 0; ComponentManager & mgr = this->component_manager; vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0); Rigidbody & rigidbody = rigidbodies.front().get(); + rigidbody.data.gravity_scale = 0; vector<reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0); const Transform & transform = transforms.front().get(); ASSERT_FALSE(rigidbodies.empty()); @@ -95,32 +99,34 @@ TEST_F(PhysicsTest, movement) { rigidbody.add_force_linear({1, 1}); rigidbody.add_force_angular(1); - system.update(); - EXPECT_EQ(transform.position.x, 1); - EXPECT_EQ(transform.position.y, 1); - EXPECT_EQ(transform.rotation, 1); + system.fixed_update(); + EXPECT_NEAR(transform.position.x, 0.02, 0.001); + EXPECT_NEAR(transform.position.y, 0.02, 0.001); + EXPECT_NEAR(transform.rotation, 0.02, 0.001); rigidbody.data.constraints = {1, 1, 1}; - EXPECT_EQ(transform.position.x, 1); - EXPECT_EQ(transform.position.y, 1); - EXPECT_EQ(transform.rotation, 1); - - rigidbody.data.linear_damping.x = 0.5; - rigidbody.data.linear_damping.y = 0.5; - rigidbody.data.angular_damping = 0.5; - system.update(); - EXPECT_EQ(rigidbody.data.linear_velocity.x, 0.5); - EXPECT_EQ(rigidbody.data.linear_velocity.y, 0.5); - EXPECT_EQ(rigidbody.data.angular_velocity, 0.5); + EXPECT_NEAR(transform.position.x, 0.02, 0.001); + EXPECT_NEAR(transform.position.y, 0.02, 0.001); + EXPECT_NEAR(transform.rotation, 0.02, 0.001); + rigidbody.data.constraints = {0, 0, 0}; + rigidbody.data.linear_velocity_coefficient.x = 0.5; + rigidbody.data.linear_velocity_coefficient.y = 0.5; + rigidbody.data.angular_velocity_coefficient = 0.5; + system.fixed_update(); + EXPECT_NEAR(rigidbody.data.linear_velocity.x, 0.98, 0.01); + EXPECT_NEAR(rigidbody.data.linear_velocity.y, 0.98, 0.01); + EXPECT_NEAR(rigidbody.data.angular_velocity, 0.98, 0.01); rigidbody.data.constraints = {1, 1, 0}; - rigidbody.data.angular_damping = 0; + rigidbody.data.angular_velocity_coefficient = 0; rigidbody.data.max_angular_velocity = 1000; rigidbody.data.angular_velocity = 360; - system.update(); - EXPECT_EQ(transform.rotation, 1); + system.fixed_update(); + EXPECT_NEAR(transform.rotation, 7.24, 0.01); rigidbody.data.angular_velocity = -360; - system.update(); - EXPECT_EQ(transform.rotation, 1); + system.fixed_update(); + EXPECT_NEAR(transform.rotation, 0.04, 0.001); + system.fixed_update(); + EXPECT_NEAR(transform.rotation, 352.84, 0.01); } diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp new file mode 100644 index 0000000..d8bd09d --- /dev/null +++ b/src/test/Profiling.cpp @@ -0,0 +1,262 @@ +#include <chrono> +#include <cmath> +#include <crepe/api/Asset.h> +#include <crepe/manager/Mediator.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/system/ParticleSystem.h> +#include <crepe/system/PhysicsSystem.h> +#include <crepe/system/RenderSystem.h> +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> +#include <crepe/facade/SDLContext.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/types.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class TestScript : public Script { + bool oncollision(const CollisionEvent & test) { + Log::logf("Box {} script on_collision()", test.info.self.transform.game_object_id); + return true; + } + void init() { + subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { + return this->oncollision(ev); + }); + } + void fixed_update() { + // Retrieve component from the same GameObject this script is on + } +}; + +class DISABLED_ProfilingTest : public Test { +public: + // Config for test + // Minimum amount to let test pass + const int min_gameobject_count = 100; + // Maximum amount to stop test + const int max_gameobject_count = 3000; + // Amount of times a test runs to calculate average + const int average = 5; + // Maximum duration to stop test + const std::chrono::microseconds duration = 16000us; + + Mediator m; + SDLContext sdl_context {m}; + ResourceManager resman {m}; + ComponentManager mgr {m}; + // Add system used for profling tests + EventManager evmgr {m}; + LoopTimerManager loopmgr {m}; + CollisionSystem collision_sys {m}; + PhysicsSystem physics_sys {m}; + ParticleSystem particle_sys {m}; + RenderSystem render_sys {m}; + ScriptSystem script_sys {m}; + + // Test data + std::map<std::string, std::chrono::microseconds> timings; + int game_object_count = 0; + std::chrono::microseconds total_time = 0us; + + void SetUp() override { + + GameObject do_not_use = mgr.new_object("DO_NOT_USE", "", {0, 0}); + do_not_use.add_component<Camera>( + ivec2 {1080, 720}, vec2 {2000, 2000}, + Camera::Data { + .bg_color = Color::WHITE, + .zoom = 1.0f, + } + ); + // initialize systems here: + //calls init + script_sys.fixed_update(); + //creates window + render_sys.frame_update(); + } + + // Helper function to time an update call and store its duration + template <typename Func> + std::chrono::microseconds time_function(const std::string & name, Func && func) { + auto start = std::chrono::steady_clock::now(); + func(); + auto end = std::chrono::steady_clock::now(); + std::chrono::microseconds duration + = std::chrono::duration_cast<std::chrono::microseconds>(end - start); + timings[name] += duration; + return duration; + } + + // Run and profile all systems, return the total time in milliseconds + std::chrono::microseconds run_all_systems() { + std::chrono::microseconds total_microseconds = 0us; + total_microseconds + += time_function("PhysicsSystem", [&]() { physics_sys.fixed_update(); }); + total_microseconds + += time_function("CollisionSystem", [&]() { collision_sys.fixed_update(); }); + total_microseconds + += time_function("ParticleSystem", [&]() { particle_sys.fixed_update(); }); + total_microseconds + += time_function("RenderSystem", [&]() { render_sys.frame_update(); }); + return total_microseconds; + } + + // Print timings of all functions + void log_timings() const { + std::string result = "\nFunction timings:\n"; + + for (const auto & [name, duration] : timings) { + result += name + " took " + std::to_string(duration.count() / 1000.0 / average) + + " ms (" + std::to_string(duration.count() / average) + " µs).\n"; + } + + result += "Total time: " + std::to_string(this->total_time.count() / 1000.0 / average) + + " ms (" + std::to_string(this->total_time.count() / average) + " µs)\n"; + + result += "Amount of gameobjects: " + std::to_string(game_object_count) + "\n"; + + GTEST_LOG_(INFO) << result; + } + + void clear_timings() { + for (auto & [key, value] : timings) { + value = std::chrono::microseconds(0); + } + } +}; + +TEST_F(DISABLED_ProfilingTest, Profiling_1) { + while (this->total_time / this->average < this->duration) { + + { + //define gameobject used for testing + GameObject gameobject = mgr.new_object("gameobject", "", {0, 0}); + } + + this->game_object_count++; + + this->total_time = 0us; + clear_timings(); + + for (int amount = 0; amount < this->average; amount++) { + this->total_time += run_all_systems(); + } + + if (this->game_object_count >= this->max_gameobject_count) break; + } + log_timings(); + EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} + +TEST_F(DISABLED_ProfilingTest, Profiling_2) { + while (this->total_time / this->average < this->duration) { + + { + //define gameobject used for testing + GameObject gameobject = mgr.new_object( + "gameobject", "", {static_cast<float>(game_object_count * 2), 0} + ); + gameobject.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 0.0, + .body_type = Rigidbody::BodyType::STATIC, + }); + gameobject.add_component<BoxCollider>(vec2 {0, 0}, vec2 {1, 1}); + + gameobject.add_component<BehaviorScript>().set_script<TestScript>(); + Sprite & test_sprite = gameobject.add_component<Sprite>( + Asset {"asset/texture/square.png"}, + Sprite::Data { + .color = {0, 0, 0, 0}, + .flip = {.flip_x = false, .flip_y = false}, + .sorting_in_layer = 1, + .order_in_layer = 1, + .size = {.y = 500}, + } + ); + } + + this->game_object_count++; + + this->total_time = 0us; + clear_timings(); + for (int amount = 0; amount < this->average; amount++) { + this->total_time += run_all_systems(); + } + + if (this->game_object_count >= this->max_gameobject_count) break; + } + log_timings(); + EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} + +TEST_F(DISABLED_ProfilingTest, Profiling_3) { + while (this->total_time / this->average < this->duration) { + + { + //define gameobject used for testing + GameObject gameobject = mgr.new_object( + "gameobject", "", {static_cast<float>(game_object_count * 2), 0} + ); + gameobject.add_component<Rigidbody>(Rigidbody::Data { + .gravity_scale = 0, + .body_type = Rigidbody::BodyType::STATIC, + }); + gameobject.add_component<BoxCollider>(vec2 {0, 0}, vec2 {1, 1}); + gameobject.add_component<BehaviorScript>().set_script<TestScript>(); + Sprite & test_sprite = gameobject.add_component<Sprite>( + Asset {"asset/texture/square.png"}, + Sprite::Data { + .color = {0, 0, 0, 0}, + .flip = {.flip_x = false, .flip_y = false}, + .sorting_in_layer = 1, + .order_in_layer = 1, + .size = {.y = 500}, + } + ); + auto & test = gameobject.add_component<ParticleEmitter>( + test_sprite, + ParticleEmitter::Data { + .max_particles = 10, + .emission_rate = 100, + .end_lifespan = 100000, + .boundary { + .width = 1000, + .height = 1000, + .offset = vec2 {0, 0}, + .reset_on_exit = false, + }, + + } + ); + } + render_sys.frame_update(); + this->game_object_count++; + + this->total_time = 0us; + clear_timings(); + for (int amount = 0; amount < this->average; amount++) { + this->total_time += run_all_systems(); + } + + if (this->game_object_count >= this->max_gameobject_count) break; + } + log_timings(); + EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp index ac479d3..bdd87ee 100644 --- a/src/test/RenderSystemTest.cpp +++ b/src/test/RenderSystemTest.cpp @@ -1,4 +1,7 @@ -#include "api/Camera.h" +#include "api/Asset.h" +#include "facade/SDLContext.h" +#include "manager/ResourceManager.h" +#include "types.h" #include <functional> #include <gtest/gtest.h> #include <memory> @@ -7,11 +10,11 @@ #define private public #define protected public -#include <crepe/ComponentManager.h> +#include <crepe/api/Camera.h> #include <crepe/api/Color.h> #include <crepe/api/GameObject.h> #include <crepe/api/Sprite.h> -#include <crepe/api/Texture.h> +#include <crepe/manager/ComponentManager.h> #include <crepe/system/RenderSystem.h> @@ -20,65 +23,77 @@ using namespace crepe; using namespace testing; class RenderSystemTest : public Test { + Mediator m; + public: - ComponentManager mgr{}; - RenderSystem sys{mgr}; + ComponentManager mgr {m}; + SDLContext ctx {m}; + ResourceManager resource_manager {m}; + RenderSystem sys {m}; GameObject entity1 = this->mgr.new_object("name"); GameObject entity2 = this->mgr.new_object("name"); GameObject entity3 = this->mgr.new_object("name"); GameObject entity4 = this->mgr.new_object("name"); void SetUp() override { - auto & sprite1 - = entity1.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), - Color(0, 0, 0, 0), FlipSettings{false, false}); - ASSERT_NE(sprite1.sprite_image.get(), nullptr); - sprite1.order_in_layer = 5; - sprite1.sorting_in_layer = 5; - EXPECT_EQ(sprite1.order_in_layer, 5); - EXPECT_EQ(sprite1.sorting_in_layer, 5); - auto & sprite2 - = entity2.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), - Color(0, 0, 0, 0), FlipSettings{false, false}); - ASSERT_NE(sprite2.sprite_image.get(), nullptr); - sprite2.sorting_in_layer = 2; - sprite2.order_in_layer = 1; - - EXPECT_EQ(sprite2.sorting_in_layer, 2); - EXPECT_EQ(sprite2.order_in_layer, 1); - - auto & sprite3 - = entity3.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), - Color(0, 0, 0, 0), FlipSettings{false, false}); - ASSERT_NE(sprite3.sprite_image.get(), nullptr); - sprite3.sorting_in_layer = 1; - sprite3.order_in_layer = 2; - - EXPECT_EQ(sprite3.sorting_in_layer, 1); - EXPECT_EQ(sprite3.order_in_layer, 2); - - auto & sprite4 - = entity4.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), - Color(0, 0, 0, 0), FlipSettings{false, false}); - ASSERT_NE(sprite4.sprite_image.get(), nullptr); - sprite4.sorting_in_layer = 1; - sprite4.order_in_layer = 1; - EXPECT_EQ(sprite4.sorting_in_layer, 1); - EXPECT_EQ(sprite4.order_in_layer, 1); + auto s1 = Asset("asset/texture/img.png"); + auto s2 = Asset("asset/texture/img.png"); + auto s3 = Asset("asset/texture/img.png"); + auto s4 = Asset("asset/texture/img.png"); + auto & sprite1 = entity1.add_component<Sprite>( + s1, + Sprite::Data { + .color = Color(0, 0, 0, 0), + .flip = Sprite::FlipSettings {false, false}, + .sorting_in_layer = 5, + .order_in_layer = 5, + .size = {10, 10}, + } + ); + + EXPECT_EQ(sprite1.data.order_in_layer, 5); + EXPECT_EQ(sprite1.data.sorting_in_layer, 5); + auto & sprite2 = entity2.add_component<Sprite>( + s2, + Sprite::Data { + .color = Color(0, 0, 0, 0), + .flip = Sprite::FlipSettings {false, false}, + .sorting_in_layer = 2, + .order_in_layer = 1, + } + ); + EXPECT_EQ(sprite2.data.sorting_in_layer, 2); + EXPECT_EQ(sprite2.data.order_in_layer, 1); + + auto & sprite3 = entity3.add_component<Sprite>( + s3, + Sprite::Data { + .color = Color(0, 0, 0, 0), + .flip = Sprite::FlipSettings {false, false}, + .sorting_in_layer = 1, + .order_in_layer = 2, + } + ); + EXPECT_EQ(sprite3.data.sorting_in_layer, 1); + EXPECT_EQ(sprite3.data.order_in_layer, 2); + + auto & sprite4 = entity4.add_component<Sprite>( + s4, + Sprite::Data { + .color = Color(0, 0, 0, 0), + .flip = Sprite::FlipSettings {false, false}, + .sorting_in_layer = 1, + .order_in_layer = 1, + } + ); + EXPECT_EQ(sprite4.data.sorting_in_layer, 1); + EXPECT_EQ(sprite4.data.order_in_layer, 1); } }; -TEST_F(RenderSystemTest, expected_throws) { - GameObject entity1 = this->mgr.new_object("NAME"); - - // no texture img - EXPECT_ANY_THROW({ - entity1.add_component<Sprite>(make_shared<Texture>("NO_IMAGE"), Color(0, 0, 0, 0), - FlipSettings{false, false}); - }); - +TEST_F(RenderSystemTest, NoCamera) { // No camera - EXPECT_ANY_THROW({ this->sys.update(); }); + EXPECT_ANY_THROW({ this->sys.frame_update(); }); } TEST_F(RenderSystemTest, make_sprites) {} @@ -96,32 +111,35 @@ TEST_F(RenderSystemTest, sorting_sprites) { // 3. sorting_in_layer: 2, order_in_layer: 1 (entity2) // 4. sorting_in_layer: 5, order_in_layer: 5 (entity1) - EXPECT_EQ(sorted_sprites[0].get().sorting_in_layer, 1); - EXPECT_EQ(sorted_sprites[0].get().order_in_layer, 1); + EXPECT_EQ(sorted_sprites[0].get().data.sorting_in_layer, 1); + EXPECT_EQ(sorted_sprites[0].get().data.order_in_layer, 1); - EXPECT_EQ(sorted_sprites[1].get().sorting_in_layer, 1); - EXPECT_EQ(sorted_sprites[1].get().order_in_layer, 2); + EXPECT_EQ(sorted_sprites[1].get().data.sorting_in_layer, 1); + EXPECT_EQ(sorted_sprites[1].get().data.order_in_layer, 2); - EXPECT_EQ(sorted_sprites[2].get().sorting_in_layer, 2); - EXPECT_EQ(sorted_sprites[2].get().order_in_layer, 1); + EXPECT_EQ(sorted_sprites[2].get().data.sorting_in_layer, 2); + EXPECT_EQ(sorted_sprites[2].get().data.order_in_layer, 1); - EXPECT_EQ(sorted_sprites[3].get().sorting_in_layer, 5); - EXPECT_EQ(sorted_sprites[3].get().order_in_layer, 5); + EXPECT_EQ(sorted_sprites[3].get().data.sorting_in_layer, 5); + EXPECT_EQ(sorted_sprites[3].get().data.order_in_layer, 5); for (size_t i = 1; i < sorted_sprites.size(); ++i) { const Sprite & prev = sorted_sprites[i - 1].get(); const Sprite & curr = sorted_sprites[i].get(); - if (prev.sorting_in_layer == curr.sorting_in_layer) { - EXPECT_LE(prev.order_in_layer, curr.order_in_layer); + if (prev.data.sorting_in_layer == curr.data.sorting_in_layer) { + EXPECT_LE(prev.data.order_in_layer, curr.data.order_in_layer); } else { - EXPECT_LE(prev.sorting_in_layer, curr.sorting_in_layer); + EXPECT_LE(prev.data.sorting_in_layer, curr.data.sorting_in_layer); } } } TEST_F(RenderSystemTest, Update) { - entity1.add_component<Camera>(Color::WHITE); + entity1.add_component<Camera>( + ivec2 {100, 100}, vec2 {100, 100}, + Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f} + ); { vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); ASSERT_EQ(sprites.size(), 4); @@ -131,7 +149,7 @@ TEST_F(RenderSystemTest, Update) { EXPECT_EQ(sprites[2].get().game_object_id, 2); EXPECT_EQ(sprites[3].get().game_object_id, 3); } - this->sys.update(); + this->sys.frame_update(); { vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); ASSERT_EQ(sprites.size(), 4); @@ -149,7 +167,11 @@ TEST_F(RenderSystemTest, Camera) { EXPECT_NE(cameras.size(), 1); } { - entity1.add_component<Camera>(Color::WHITE); + entity1.add_component<Camera>( + ivec2 {100, 100}, vec2 {100, 100}, + Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f} + ); + auto cameras = this->mgr.get_components_by_type<Camera>(); EXPECT_EQ(cameras.size(), 1); } @@ -157,18 +179,22 @@ TEST_F(RenderSystemTest, Camera) { //TODO improve with newer version } TEST_F(RenderSystemTest, Color) { - entity1.add_component<Camera>(Color::WHITE); + entity1.add_component<Camera>( + ivec2 {100, 100}, vec2 {100, 100}, + Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f} + ); + auto & sprite = this->mgr.get_components_by_id<Sprite>(entity1.id).front().get(); - ASSERT_NE(sprite.sprite_image.get(), nullptr); - - sprite.color = Color::GREEN; - EXPECT_EQ(sprite.color.r, Color::GREEN.r); - EXPECT_EQ(sprite.color.g, Color::GREEN.g); - EXPECT_EQ(sprite.color.b, Color::GREEN.b); - EXPECT_EQ(sprite.color.a, Color::GREEN.a); - this->sys.update(); - EXPECT_EQ(sprite.color.r, Color::GREEN.r); - EXPECT_EQ(sprite.color.g, Color::GREEN.g); - EXPECT_EQ(sprite.color.b, Color::GREEN.b); - EXPECT_EQ(sprite.color.a, Color::GREEN.a); + //ASSERT_NE(sprite.texture.texture.get(), nullptr); + + sprite.data.color = Color::GREEN; + EXPECT_EQ(sprite.data.color.r, Color::GREEN.r); + EXPECT_EQ(sprite.data.color.g, Color::GREEN.g); + EXPECT_EQ(sprite.data.color.b, Color::GREEN.b); + EXPECT_EQ(sprite.data.color.a, Color::GREEN.a); + this->sys.frame_update(); + EXPECT_EQ(sprite.data.color.r, Color::GREEN.r); + EXPECT_EQ(sprite.data.color.g, Color::GREEN.g); + EXPECT_EQ(sprite.data.color.b, Color::GREEN.b); + EXPECT_EQ(sprite.data.color.a, Color::GREEN.a); } diff --git a/src/test/ReplayManagerTest.cpp b/src/test/ReplayManagerTest.cpp new file mode 100644 index 0000000..b2619eb --- /dev/null +++ b/src/test/ReplayManagerTest.cpp @@ -0,0 +1,38 @@ +#include <gtest/gtest.h> + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ReplayManager.h> +#include <crepe/system/ReplaySystem.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ReplayManagerTest : public Test { + Mediator mediator; + +public: + ComponentManager component_manager {mediator}; + ReplayManager replay_manager {mediator}; + ReplaySystem replay_system {mediator}; + + GameObject entity = component_manager.new_object("foo"); + Transform & entity_transform + = component_manager.get_components_by_id<Transform>(entity.id).back(); + Metadata & entity_metadata + = component_manager.get_components_by_id<Metadata>(entity.id).back(); +}; + +TEST_F(ReplayManagerTest, Default) { + // replay_manager.record_start(); + + // replay_system.fixed_update(); + // entity_transform.position += {1, 1}; + // replay_system.fixed_update(); + // entity_transform.position += {1, 1}; + // replay_system.fixed_update(); + + // recording_t recording = replay_manager.record_end(); +} diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp new file mode 100644 index 0000000..e5a7fad --- /dev/null +++ b/src/test/ResourceManagerTest.cpp @@ -0,0 +1,84 @@ +#include "manager/Mediator.h" +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/GameObject.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ResourceManagerTest : public Test { + Mediator mediator; + +public: + ResourceManager resource_manager {mediator}; + + class Unrelated : public Resource { + using Resource::Resource; + }; + + Asset asset_a {"asset/texture/img.png"}; + Asset asset_b {"asset/texture/ERROR.png"}; + + class TestResource : public Resource { + public: + static unsigned instances; + + public: + const unsigned instance; + TestResource(const Asset & src, Mediator & mediator) + : Resource(src, mediator), + instance(this->instances++) {} + ~TestResource() { this->instances--; } + bool operator==(const TestResource & other) const { + return this->instance == other.instance; + } + }; + +private: + void SetUp() override { TestResource::instances = 0; } +}; +unsigned ResourceManagerTest::TestResource::instances = 0; + +TEST_F(ResourceManagerTest, Default) { + TestResource & res_1 = resource_manager.get<TestResource>(asset_a); + TestResource & res_2 = resource_manager.get<TestResource>(asset_a); + TestResource & res_3 = resource_manager.get<TestResource>(asset_b); + TestResource & res_4 = resource_manager.get<TestResource>(asset_b); + + ASSERT_EQ(res_1, res_2); + ASSERT_EQ(res_3, res_4); + EXPECT_NE(res_1, res_3); + + EXPECT_EQ(TestResource::instances, 2); + + resource_manager.clear(); +} + +TEST_F(ResourceManagerTest, Persistent) { + resource_manager.set_persistent(asset_a, true); + EXPECT_EQ(TestResource::instances, 0); + + resource_manager.get<TestResource>(asset_a); + resource_manager.get<TestResource>(asset_a); + resource_manager.get<TestResource>(asset_b); + resource_manager.get<TestResource>(asset_b); + EXPECT_EQ(TestResource::instances, 2); + + resource_manager.clear(); + EXPECT_EQ(TestResource::instances, 1); + + resource_manager.clear_all(); + EXPECT_EQ(TestResource::instances, 0); +} + +TEST_F(ResourceManagerTest, UnmatchedType) { + EXPECT_NO_THROW({ resource_manager.get<TestResource>(asset_a); }); + + EXPECT_THROW({ resource_manager.get<Unrelated>(asset_a); }, runtime_error); +} diff --git a/src/test/SaveManagerTest.cpp b/src/test/SaveManagerTest.cpp new file mode 100644 index 0000000..fd53200 --- /dev/null +++ b/src/test/SaveManagerTest.cpp @@ -0,0 +1,51 @@ +#include <gtest/gtest.h> + +#include <crepe/ValueBroker.h> +#include <crepe/facade/DB.h> +#include <crepe/manager/SaveManager.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class SaveManagerTest : public Test { + Mediator m; + class TestSaveManager : public SaveManager { + using SaveManager::SaveManager; + + // in-memory database for testing + DB db {}; + virtual DB & get_db() override { return this->db; } + }; + +public: + TestSaveManager mgr {m}; +}; + +TEST_F(SaveManagerTest, ReadWrite) { + ASSERT_FALSE(mgr.has("foo")); + mgr.set<string>("foo", "bar"); + ASSERT_TRUE(mgr.has("foo")); + + string value = mgr.get<string>("foo"); + EXPECT_EQ(value, "bar"); +} + +TEST_F(SaveManagerTest, DefaultValue) { + ValueBroker value = mgr.get<int>("foo", 3); + + ASSERT_EQ(value.get(), 3); + value.set(5); + EXPECT_EQ(value.get(), 5); +} + +TEST_F(SaveManagerTest, MultipleKeys) { + ValueBroker foo = mgr.get<int>("foo", 1); + ValueBroker bar = mgr.get<int>("bar", 2); + + EXPECT_EQ(foo.get(), 1); + EXPECT_EQ(bar.get(), 2); + + EXPECT_EQ(mgr.get<int>("foo"), 1); + EXPECT_EQ(mgr.get<int>("bar"), 2); +} diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp index dab2ce9..e58ce36 100644 --- a/src/test/SceneManagerTest.cpp +++ b/src/test/SceneManagerTest.cpp @@ -1,48 +1,60 @@ -#include <crepe/ComponentManager.h> +#include <gtest/gtest.h> + #include <crepe/api/GameObject.h> #include <crepe/api/Metadata.h> #include <crepe/api/Scene.h> -#include <crepe/api/SceneManager.h> #include <crepe/api/Transform.h> #include <crepe/api/Vector2.h> -#include <gtest/gtest.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/SceneManager.h> +#include <crepe/types.h> using namespace std; using namespace crepe; class ConcreteScene1 : public Scene { public: - using Scene::Scene; - void load_scene() { - auto & mgr = this->component_manager; - GameObject object1 = mgr.new_object("scene_1", "tag_scene_1", Vector2{0, 0}, 0, 1); - GameObject object2 = mgr.new_object("scene_1", "tag_scene_1", Vector2{1, 0}, 0, 1); - GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", Vector2{2, 0}, 0, 1); + GameObject object1 = new_object("scene_1", "tag_scene_1", vec2 {0, 0}, 0, 1); + GameObject object2 = new_object("scene_1", "tag_scene_1", vec2 {1, 0}, 0, 1); + GameObject object3 = new_object("scene_1", "tag_scene_1", vec2 {2, 0}, 0, 1); } - string get_name() const { return "scene1";} + string get_name() const { return "scene1"; } }; class ConcreteScene2 : public Scene { public: - using Scene::Scene; + void load_scene() { + GameObject object1 = new_object("scene_2", "tag_scene_2", vec2 {0, 0}, 0, 1); + GameObject object2 = new_object("scene_2", "tag_scene_2", vec2 {0, 1}, 0, 1); + GameObject object3 = new_object("scene_2", "tag_scene_2", vec2 {0, 2}, 0, 1); + GameObject object4 = new_object("scene_2", "tag_scene_2", vec2 {0, 3}, 0, 1); + } + + string get_name() const { return "scene2"; } +}; + +class ConcreteScene3 : public Scene { +public: + ConcreteScene3(const string & name) : name(name) {} void load_scene() { - auto & mgr = this->component_manager; - GameObject object1 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 0}, 0, 1); - GameObject object2 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 1}, 0, 1); - GameObject object3 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 2}, 0, 1); - GameObject object4 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 3}, 0, 1); + GameObject object1 = new_object("scene_3", "tag_scene_3", vec2 {0, 0}, 0, 1); } - string get_name() const { return "scene2";} + string get_name() const { return name; } + +private: + const string name; }; class SceneManagerTest : public ::testing::Test { + Mediator m; + public: - ComponentManager component_mgr{}; - SceneManager scene_mgr{component_mgr}; + ComponentManager component_mgr {m}; + SceneManager scene_mgr {m}; }; TEST_F(SceneManagerTest, loadScene) { @@ -124,3 +136,25 @@ TEST_F(SceneManagerTest, loadScene) { EXPECT_EQ(transform[3].get().position.x, 0); EXPECT_EQ(transform[3].get().position.y, 3); } + +TEST_F(SceneManagerTest, perfectForwarding) { + scene_mgr.add_scene<ConcreteScene3>("scene3"); + + scene_mgr.load_next_scene(); + + vector<reference_wrapper<Metadata>> metadata + = component_mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform + = component_mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "scene_3"); + EXPECT_EQ(metadata[0].get().tag, "tag_scene_3"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); +} diff --git a/src/test/ScriptECSTest.cpp b/src/test/ScriptECSTest.cpp new file mode 100644 index 0000000..1ec33ba --- /dev/null +++ b/src/test/ScriptECSTest.cpp @@ -0,0 +1,41 @@ +#include <gtest/gtest.h> + +#define protected public + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/system/ScriptSystem.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptECSTest : public ScriptTest { +public: + class TestComponent : public Component { + using Component::Component; + }; +}; + +TEST_F(ScriptECSTest, GetOwnComponent) { + MyScript & script = this->script; + Metadata & metadata = script.get_component<Metadata>(); + + EXPECT_EQ(metadata.name, OBJ_NAME); +} + +TEST_F(ScriptECSTest, GetOwnComponents) { + const unsigned COUNT = 4; + + for (unsigned i = 0; i < COUNT; i++) entity.add_component<TestComponent>(); + + MyScript & script = this->script; + RefVector<TestComponent> components = script.get_components<TestComponent>(); + + EXPECT_EQ(components.size(), COUNT); +} diff --git a/src/test/ScriptEventTest.cpp b/src/test/ScriptEventTest.cpp new file mode 100644 index 0000000..8b4a72d --- /dev/null +++ b/src/test/ScriptEventTest.cpp @@ -0,0 +1,50 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Script.h> +#include <crepe/api/Vector2.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/system/ScriptSystem.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptEventTest : public ScriptTest { +public: + EventManager & event_manager = mediator.event_manager; + + struct MyEvent : public Event {}; +}; + +TEST_F(ScriptEventTest, Default) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + EventManager & evmgr = this->event_manager; + + unsigned event_count = 0; + script.subscribe<MyEvent>([&](const MyEvent &) { + event_count++; + return true; + }); + + system.fixed_update(); + behaviorscript.active = false; + EXPECT_EQ(0, event_count); + + evmgr.trigger_event<MyEvent>(); + EXPECT_EQ(0, event_count); + + behaviorscript.active = true; + evmgr.trigger_event<MyEvent>(); + EXPECT_EQ(1, event_count); +} diff --git a/src/test/ScriptSaveManagerTest.cpp b/src/test/ScriptSaveManagerTest.cpp new file mode 100644 index 0000000..e2debae --- /dev/null +++ b/src/test/ScriptSaveManagerTest.cpp @@ -0,0 +1,35 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include <crepe/facade/DB.h> +#include <crepe/manager/SaveManager.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptSaveManagerTest : public ScriptTest { +public: + class TestSaveManager : public SaveManager { + using SaveManager::SaveManager; + + // in-memory database for testing + DB db {}; + virtual DB & get_db() override { return this->db; } + }; + + TestSaveManager save_mgr {mediator}; +}; + +TEST_F(ScriptSaveManagerTest, GetSaveManager) { + MyScript & script = this->script; + + SaveManager & mgr = script.get_save_manager(); + + EXPECT_EQ(&mgr, &save_mgr); +} diff --git a/src/test/ScriptSceneTest.cpp b/src/test/ScriptSceneTest.cpp new file mode 100644 index 0000000..7d01f14 --- /dev/null +++ b/src/test/ScriptSceneTest.cpp @@ -0,0 +1,30 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include "ScriptTest.h" +#include <crepe/manager/SceneManager.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptSceneTest : public ScriptTest { +public: + SceneManager scene_manager {mediator}; + + class MyScene : public Scene {}; +}; + +TEST_F(ScriptSceneTest, Default) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + + const char * non_default_value = "foo"; + ASSERT_NE(non_default_value, scene_manager.next_scene); + + script.set_next_scene(non_default_value); + EXPECT_EQ(non_default_value, scene_manager.next_scene); +} diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp index 19fef6d..40aa25c 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -1,72 +1,95 @@ +#include <gmock/gmock.h> #include <gtest/gtest.h> // stupid hack to allow access to private/protected members under test #define private public #define protected public -#include <crepe/ComponentManager.h> -#include <crepe/api/BehaviorScript.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Script.h> -#include <crepe/api/Vector2.h> -#include <crepe/system/ScriptSystem.h> +#include "ScriptTest.h" using namespace std; using namespace crepe; using namespace testing; -class ScriptTest : public Test { -public: - ComponentManager component_manager{}; - ScriptSystem system{component_manager}; - - class MyScript : public Script { - // NOTE: default (private) visibility of init and update shouldn't cause - // issues! - void init() { this->init_count++; } - void update() { this->update_count++; } - - public: - unsigned init_count = 0; - unsigned update_count = 0; - }; - - BehaviorScript * behaviorscript_ref = nullptr; - MyScript * script_ref = nullptr; - - void SetUp() override { - auto & mgr = this->component_manager; - GameObject entity = mgr.new_object("name"); - BehaviorScript & component = entity.add_component<BehaviorScript>(); - - this->behaviorscript_ref = &component; - EXPECT_EQ(this->behaviorscript_ref->script.get(), nullptr); - component.set_script<MyScript>(); - ASSERT_NE(this->behaviorscript_ref->script.get(), nullptr); - - this->script_ref = (MyScript *) this->behaviorscript_ref->script.get(); - ASSERT_NE(this->script_ref, nullptr); - } -}; +void ScriptTest::SetUp() { + auto & mgr = this->component_manager; + BehaviorScript & component = entity.add_component<BehaviorScript>(); + + this->behaviorscript = component; + ASSERT_TRUE(this->behaviorscript); + EXPECT_EQ(component.script.get(), nullptr); + component.set_script<NiceMock<MyScript>>(); + ASSERT_NE(component.script.get(), nullptr); + + this->script = *(MyScript *) component.script.get(); + ASSERT_TRUE(this->script); +} TEST_F(ScriptTest, Default) { - EXPECT_EQ(0, this->script_ref->init_count); - EXPECT_EQ(0, this->script_ref->update_count); + MyScript & script = this->script; + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, fixed_update(_)).Times(0); + EXPECT_CALL(script, frame_update(_)).Times(0); } TEST_F(ScriptTest, UpdateOnce) { - EXPECT_EQ(0, this->script_ref->init_count); - EXPECT_EQ(0, this->script_ref->update_count); + MyScript & script = this->script; + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(1); + EXPECT_CALL(script, fixed_update(_)).Times(1); + system.fixed_update(); + } + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, fixed_update(_)).Times(1); + system.fixed_update(); + } + + { + InSequence seq; - this->system.update(); - EXPECT_EQ(1, this->script_ref->init_count); - EXPECT_EQ(1, this->script_ref->update_count); + EXPECT_CALL(script, frame_update(_)).Times(1); + system.frame_update(); + } } -TEST_F(ScriptTest, ListScripts) { - size_t script_count = 0; - for (auto & _ : this->system.get_scripts()) { - script_count++; +TEST_F(ScriptTest, UpdateInactive) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, fixed_update(_)).Times(0); + behaviorscript.active = false; + system.fixed_update(); + } + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(1); + EXPECT_CALL(script, fixed_update(_)).Times(1); + behaviorscript.active = true; + system.fixed_update(); } - ASSERT_EQ(1, script_count); +} + +TEST_F(ScriptTest, SaveManager) { + MyScript & script = this->script; + + EXPECT_EQ(&script.get_save_manager(), &this->save_manager); +} + +TEST_F(ScriptTest, LoopTimerManager) { + MyScript & script = this->script; + + EXPECT_EQ(&script.get_loop_timer(), &this->loop_timer); } diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h new file mode 100644 index 0000000..f953aab --- /dev/null +++ b/src/test/ScriptTest.h @@ -0,0 +1,40 @@ +#pragma once + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/SaveManager.h> +#include <crepe/system/ScriptSystem.h> + +class ScriptTest : public testing::Test { +protected: + crepe::Mediator mediator; + static constexpr const char * OBJ_NAME = "foo"; + +public: + crepe::ComponentManager component_manager {mediator}; + crepe::ScriptSystem system {mediator}; + crepe::EventManager event_mgr {mediator}; + crepe::LoopTimerManager loop_timer {mediator}; + crepe::SaveManager save_manager {mediator}; + crepe::GameObject entity = component_manager.new_object(OBJ_NAME); + + class MyScript : public crepe::Script { + // NOTE: explicitly stating `public:` is not required on actual scripts + + public: + MOCK_METHOD(void, init, (), (override)); + MOCK_METHOD(void, fixed_update, (crepe::duration_t), (override)); + MOCK_METHOD(void, frame_update, (crepe::duration_t), (override)); + }; + + crepe::OptionalRef<crepe::BehaviorScript> behaviorscript; + crepe::OptionalRef<MyScript> script; + + virtual void SetUp(); +}; diff --git a/src/test/ValueBrokerTest.cpp b/src/test/ValueBrokerTest.cpp index e6bb058..5928c37 100644 --- a/src/test/ValueBrokerTest.cpp +++ b/src/test/ValueBrokerTest.cpp @@ -13,7 +13,7 @@ public: int write_count = 0; int value = 0; - ValueBroker<int> broker{ + ValueBroker<int> broker { [this](const int & target) -> void { this->write_count++; this->value = target; @@ -23,7 +23,7 @@ public: return this->value; }, }; - Proxy<int> proxy{broker}; + Proxy<int> proxy {broker}; void SetUp() override { ASSERT_EQ(read_count, 0); diff --git a/src/test/Vector2Test.cpp b/src/test/Vector2Test.cpp new file mode 100644 index 0000000..b17f95a --- /dev/null +++ b/src/test/Vector2Test.cpp @@ -0,0 +1,542 @@ +#include <gtest/gtest.h> + +#include <crepe/api/Vector2.h> +#include <crepe/types.h> + +using namespace crepe; + +class Vector2Test : public ::testing::Test { +public: + Vector2<int> int_vec1; + Vector2<int> int_vec2; + Vector2<double> double_vec1; + Vector2<double> double_vec2; + Vector2<long> long_vec1; + Vector2<long> long_vec2; + Vector2<float> float_vec1; + Vector2<float> float_vec2; + + void SetUp() override { + int_vec1 = {1, 2}; + int_vec2 = {3, 4}; + double_vec1 = {1.0, 2.0}; + double_vec2 = {3.0, 4.0}; + long_vec1 = {1, 2}; + long_vec2 = {3, 4}; + float_vec1 = {1.0f, 2.0f}; + float_vec2 = {3.0f, 4.0f}; + } +}; + +TEST_F(Vector2Test, Subtract) { + Vector2<int> result = int_vec1 - int_vec2; + EXPECT_EQ(result.x, -2); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = double_vec1 - double_vec2; + EXPECT_FLOAT_EQ(result2.x, -2.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = long_vec1 - long_vec2; + EXPECT_EQ(result3.x, -2); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = float_vec1 - float_vec2; + EXPECT_FLOAT_EQ(result4.x, -2.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, SubtractScalar) { + Vector2<int> result = int_vec1 - 1; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1 - 1.0; + EXPECT_FLOAT_EQ(result2.x, 0.0); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1 - 1; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1 - 1.0f; + EXPECT_FLOAT_EQ(result4.x, 0.0f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, Add) { + Vector2<int> result = int_vec1 + int_vec2; + EXPECT_EQ(result.x, 4); + EXPECT_EQ(result.y, 6); + + Vector2<double> result2 = double_vec1 + double_vec2; + EXPECT_FLOAT_EQ(result2.x, 4.0); + EXPECT_FLOAT_EQ(result2.y, 6.0); + + Vector2<long> result3 = long_vec1 + long_vec2; + EXPECT_EQ(result3.x, 4); + EXPECT_EQ(result3.y, 6); + + Vector2<float> result4 = float_vec1 + float_vec2; + EXPECT_FLOAT_EQ(result4.x, 4.0f); + EXPECT_FLOAT_EQ(result4.y, 6.0f); +} + +TEST_F(Vector2Test, AddScalar) { + Vector2<int> result = int_vec1 + 1; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 3); + + Vector2<double> result2 = double_vec1 + 1.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 3.0); + + Vector2<long> result3 = long_vec1 + 1; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 3); + + Vector2<float> result4 = float_vec1 + 1.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, Multiply) { + Vector2<int> result = int_vec1 * int_vec2; + EXPECT_EQ(result.x, 3); + EXPECT_EQ(result.y, 8); + + Vector2<double> result2 = double_vec1 * double_vec2; + EXPECT_FLOAT_EQ(result2.x, 3.0); + EXPECT_FLOAT_EQ(result2.y, 8.0); + + Vector2<long> result3 = long_vec1 * long_vec2; + EXPECT_EQ(result3.x, 3); + EXPECT_EQ(result3.y, 8); + + Vector2<float> result4 = float_vec1 * float_vec2; + EXPECT_FLOAT_EQ(result4.x, 3.0f); + EXPECT_FLOAT_EQ(result4.y, 8.0f); +} + +TEST_F(Vector2Test, MultiplyScalar) { + Vector2<int> result = int_vec1 * 2; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 4); + + Vector2<double> result2 = double_vec1 * 2.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 4.0); + + Vector2<long> result3 = long_vec1 * 2; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 4); + + Vector2<float> result4 = float_vec1 * 2.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 4.0f); +} + +TEST_F(Vector2Test, Divide) { + Vector2<int> result = int_vec1 / int_vec2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 0); + + Vector2<double> result2 = double_vec1 / double_vec2; + EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331); + EXPECT_FLOAT_EQ(result2.y, 0.5); + + Vector2<long> result3 = long_vec1 / long_vec2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 0); + + Vector2<float> result4 = float_vec1 / float_vec2; + EXPECT_FLOAT_EQ(result4.x, 0.333333343f); + EXPECT_FLOAT_EQ(result4.y, 0.5f); +} + +TEST_F(Vector2Test, DivideScalar) { + Vector2<int> result = int_vec1 / 2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1 / 2.0; + EXPECT_FLOAT_EQ(result2.x, 0.5); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1 / 2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1 / 2.0f; + EXPECT_FLOAT_EQ(result4.x, 0.5f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, AddChain) { + Vector2<int> result = int_vec1; + result += int_vec2; + EXPECT_EQ(result.x, 4); + EXPECT_EQ(result.y, 6); + + Vector2<double> result2 = double_vec1; + result2 += double_vec2; + EXPECT_FLOAT_EQ(result2.x, 4.0); + EXPECT_FLOAT_EQ(result2.y, 6.0); + + Vector2<long> result3 = long_vec1; + result3 += long_vec2; + EXPECT_EQ(result3.x, 4); + EXPECT_EQ(result3.y, 6); + + Vector2<float> result4 = float_vec1; + result4 += float_vec2; + EXPECT_FLOAT_EQ(result4.x, 4.0f); + EXPECT_FLOAT_EQ(result4.y, 6.0f); +} + +TEST_F(Vector2Test, AddScalarChain) { + Vector2<int> result = int_vec1; + result += 1; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 3); + + Vector2<double> result2 = double_vec1; + result2 += 1.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 3.0); + + Vector2<long> result3 = long_vec1; + result3 += 1; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 3); + + Vector2<float> result4 = float_vec1; + result4 += 1.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, SubtractChain) { + Vector2<int> result = int_vec1; + result -= int_vec2; + EXPECT_EQ(result.x, -2); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = double_vec1; + result2 -= double_vec2; + EXPECT_FLOAT_EQ(result2.x, -2.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = long_vec1; + result3 -= long_vec2; + EXPECT_EQ(result3.x, -2); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = float_vec1; + result4 -= float_vec2; + EXPECT_FLOAT_EQ(result4.x, -2.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, SubtractScalarChain) { + Vector2<int> result = int_vec1; + result -= 1; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1; + result2 -= 1.0; + EXPECT_FLOAT_EQ(result2.x, 0.0); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1; + result3 -= 1; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1; + result4 -= 1.0f; + EXPECT_FLOAT_EQ(result4.x, 0.0f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, MultiplyChain) { + Vector2<int> result = int_vec1; + result *= int_vec2; + EXPECT_EQ(result.x, 3); + EXPECT_EQ(result.y, 8); + + Vector2<double> result2 = double_vec1; + result2 *= double_vec2; + EXPECT_FLOAT_EQ(result2.x, 3.0); + EXPECT_FLOAT_EQ(result2.y, 8.0); + + Vector2<long> result3 = long_vec1; + result3 *= long_vec2; + EXPECT_EQ(result3.x, 3); + EXPECT_EQ(result3.y, 8); + + Vector2<float> result4 = float_vec1; + result4 *= float_vec2; + EXPECT_FLOAT_EQ(result4.x, 3.0f); + EXPECT_FLOAT_EQ(result4.y, 8.0f); +} + +TEST_F(Vector2Test, MultiplyScalarChain) { + Vector2<int> result = int_vec1; + result *= 2; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 4); + + Vector2<double> result2 = double_vec1; + result2 *= 2.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 4.0); + + Vector2<long> result3 = long_vec1; + result3 *= 2; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 4); + + Vector2<float> result4 = float_vec1; + result4 *= 2.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 4.0f); +} + +TEST_F(Vector2Test, DivideChain) { + Vector2<int> result = int_vec1; + result /= int_vec2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 0); + + Vector2<double> result2 = double_vec1; + result2 /= double_vec2; + EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331); + EXPECT_FLOAT_EQ(result2.y, 0.5); + + Vector2<long> result3 = long_vec1; + result3 /= long_vec2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 0); + + Vector2<float> result4 = float_vec1; + result4 /= float_vec2; + EXPECT_FLOAT_EQ(result4.x, 0.333333343f); + EXPECT_FLOAT_EQ(result4.y, 0.5f); +} + +TEST_F(Vector2Test, DivideScalarChain) { + Vector2<int> result = int_vec1; + result /= 2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1; + result2 /= 2.0; + EXPECT_FLOAT_EQ(result2.x, 0.5); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1; + result3 /= 2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1; + result4 /= 2.0f; + EXPECT_FLOAT_EQ(result4.x, 0.5f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, Negatation) { + Vector2<int> result = -int_vec1; + EXPECT_EQ(result.x, -1); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = -double_vec1; + EXPECT_FLOAT_EQ(result2.x, -1.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = -long_vec1; + EXPECT_EQ(result3.x, -1); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = -float_vec1; + EXPECT_FLOAT_EQ(result4.x, -1.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, Equals) { + EXPECT_TRUE(int_vec1 == int_vec1); + EXPECT_FALSE(int_vec1 == int_vec2); + EXPECT_TRUE(double_vec1 == double_vec1); + EXPECT_FALSE(double_vec1 == double_vec2); + EXPECT_TRUE(long_vec1 == long_vec1); + EXPECT_FALSE(long_vec1 == long_vec2); +} + +TEST_F(Vector2Test, NotEquals) { + EXPECT_FALSE(int_vec1 != int_vec1); + EXPECT_TRUE(int_vec1 != int_vec2); + EXPECT_FALSE(double_vec1 != double_vec1); + EXPECT_TRUE(double_vec1 != double_vec2); + EXPECT_FALSE(long_vec1 != long_vec1); + EXPECT_TRUE(long_vec1 != long_vec2); +} + +TEST_F(Vector2Test, Truncate) { + Vector2<int> vec = {3, 4}; + vec.truncate(3); + EXPECT_EQ(vec.x, 0); + EXPECT_EQ(vec.y, 0); + + Vector2<double> vec2 = {3.0, 4.0}; + vec2.truncate(3.0); + EXPECT_FLOAT_EQ(vec2.x, 1.8); + EXPECT_FLOAT_EQ(vec2.y, 2.4); + + Vector2<long> vec3 = {3, 4}; + vec3.truncate(3); + EXPECT_EQ(vec3.x, 0); + EXPECT_EQ(vec3.y, 0); + + Vector2<float> vec4 = {3.0f, 4.0f}; + vec4.truncate(3.0f); + EXPECT_FLOAT_EQ(vec4.x, 1.8f); + EXPECT_FLOAT_EQ(vec4.y, 2.4f); +} + +TEST_F(Vector2Test, Normalize) { + Vector2<int> vec = {3, 4}; + vec.normalize(); + EXPECT_EQ(vec.x, 0); + EXPECT_EQ(vec.y, 0); + + Vector2<double> vec2 = {3.0, 4.0}; + vec2.normalize(); + EXPECT_FLOAT_EQ(vec2.x, 0.6); + EXPECT_FLOAT_EQ(vec2.y, 0.8); + + Vector2<long> vec3 = {3, 4}; + vec3.normalize(); + EXPECT_EQ(vec3.x, 0); + EXPECT_EQ(vec3.y, 0); + + Vector2<float> vec4 = {3.0f, 4.0f}; + vec4.normalize(); + EXPECT_FLOAT_EQ(vec4.x, 0.6f); + EXPECT_FLOAT_EQ(vec4.y, 0.8f); +} + +TEST_F(Vector2Test, Length) { + Vector2<int> vec = {3, 4}; + EXPECT_EQ(vec.length(), 5); + + Vector2<double> vec2 = {3.0, 4.0}; + EXPECT_FLOAT_EQ(vec2.length(), 5.0); + + Vector2<long> vec3 = {3, 4}; + EXPECT_EQ(vec3.length(), 5); + + Vector2<float> vec4 = {3.0f, 4.0f}; + EXPECT_FLOAT_EQ(vec4.length(), 5.0f); +} + +TEST_F(Vector2Test, LengthSquared) { + Vector2<int> vec = {3, 4}; + EXPECT_EQ(vec.length_squared(), 25); + + Vector2<double> vec2 = {3.0, 4.0}; + EXPECT_FLOAT_EQ(vec2.length_squared(), 25.0); + + Vector2<long> vec3 = {3, 4}; + EXPECT_EQ(vec3.length_squared(), 25); + + Vector2<float> vec4 = {3.0f, 4.0f}; + EXPECT_FLOAT_EQ(vec4.length_squared(), 25.0f); +} + +TEST_F(Vector2Test, Dot) { + Vector2<int> vec1 = {3, 4}; + Vector2<int> vec2 = {5, 6}; + EXPECT_EQ(vec1.dot(vec2), 39); + + Vector2<double> vec3 = {3.0, 4.0}; + Vector2<double> vec4 = {5.0, 6.0}; + EXPECT_FLOAT_EQ(vec3.dot(vec4), 39.0); + + Vector2<long> vec5 = {3, 4}; + Vector2<long> vec6 = {5, 6}; + EXPECT_EQ(vec5.dot(vec6), 39); + + Vector2<float> vec7 = {3.0f, 4.0f}; + Vector2<float> vec8 = {5.0f, 6.0f}; + EXPECT_FLOAT_EQ(vec7.dot(vec8), 39.0f); +} + +TEST_F(Vector2Test, Distance) { + Vector2<int> vec1 = {1, 1}; + Vector2<int> vec2 = {4, 5}; + EXPECT_EQ(vec1.distance(vec2), 5); + + Vector2<double> vec3 = {1.0, 1.0}; + Vector2<double> vec4 = {4.0, 5.0}; + EXPECT_FLOAT_EQ(vec3.distance(vec4), 5.0); + + Vector2<long> vec5 = {1, 1}; + Vector2<long> vec6 = {4, 5}; + EXPECT_EQ(vec5.distance(vec6), 5); + + Vector2<float> vec7 = {1.0f, 1.0f}; + Vector2<float> vec8 = {4.0f, 5.0f}; + EXPECT_FLOAT_EQ(vec7.distance(vec8), 5.0f); +} + +TEST_F(Vector2Test, DistanceSquared) { + Vector2<int> vec1 = {3, 4}; + Vector2<int> vec2 = {5, 6}; + EXPECT_EQ(vec1.distance_squared(vec2), 8); + + Vector2<double> vec3 = {3.0, 4.0}; + Vector2<double> vec4 = {5.0, 6.0}; + EXPECT_FLOAT_EQ(vec3.distance_squared(vec4), 8.0); + + Vector2<long> vec5 = {3, 4}; + Vector2<long> vec6 = {5, 6}; + EXPECT_EQ(vec5.distance_squared(vec6), 8); + + Vector2<float> vec7 = {3.0f, 4.0f}; + Vector2<float> vec8 = {5.0f, 6.0f}; + EXPECT_FLOAT_EQ(vec7.distance_squared(vec8), 8.0f); +} + +TEST_F(Vector2Test, Perpendicular) { + Vector2<int> vec = {3, 4}; + Vector2<int> result = vec.perpendicular(); + EXPECT_EQ(result.x, -4); + EXPECT_EQ(result.y, 3); + + Vector2<double> vec2 = {3.0, 4.0}; + Vector2<double> result2 = vec2.perpendicular(); + EXPECT_FLOAT_EQ(result2.x, -4.0); + EXPECT_FLOAT_EQ(result2.y, 3.0); + + Vector2<long> vec3 = {3, 4}; + Vector2<long> result3 = vec3.perpendicular(); + EXPECT_EQ(result3.x, -4); + EXPECT_EQ(result3.y, 3); + + Vector2<float> vec4 = {3.0f, 4.0f}; + Vector2<float> result4 = vec4.perpendicular(); + EXPECT_FLOAT_EQ(result4.x, -4.0f); + EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, Rotate) { + vec2 foo {0, 1}; + + foo = foo.rotate(90); + const float GOOD_ENOUGH = 0.001; + EXPECT_NEAR(foo.x, 1, GOOD_ENOUGH); + EXPECT_NEAR(foo.y, 0, GOOD_ENOUGH); +} diff --git a/src/test/main.cpp b/src/test/main.cpp index 241015d..0e1bc75 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -1,15 +1,29 @@ -#include <crepe/api/Config.h> - #include <gtest/gtest.h> +#include <crepe/api/Config.h> + using namespace crepe; using namespace testing; +class GlobalConfigReset : public EmptyTestEventListener { +public: + Config & cfg = Config::get_instance(); + + // This function is called before each test + void OnTestStart(const TestInfo &) override { + cfg = { + .log = { + .level = Log::Level::WARNING, + }, + }; + } +}; + int main(int argc, char ** argv) { InitGoogleTest(&argc, argv); - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::ERROR; + UnitTest & ut = *UnitTest::GetInstance(); + ut.listeners().Append(new GlobalConfigReset); return RUN_ALL_TESTS(); } |