diff options
45 files changed, 1328 insertions, 667 deletions
diff --git a/.editorconfig b/.editorconfig index 191871f..65f5034 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,5 +10,5 @@ max_line_length = 95 indent_style = space indent_size = 2 -[*.md] +[*.{md,dox}] max_line_length = 80 @@ -14,12 +14,20 @@ RECURSIVE = YES GENERATE_LATEX = NO +LAYOUT_FILE = src/doc/layout.xml +TAB_SIZE = 2 + +HTML_INDEX_NUM_ENTRIES = 2 +HTML_EXTRA_STYLESHEET = src/doc/style.css + USE_MDFILE_AS_MAINPAGE = ./readme.md -HTML_INDEX_NUM_ENTRIES = 1 # collapse trees by default REPEAT_BRIEF = NO INTERNAL_DOCS = YES +EXTRACT_PRIVATE = YES EXTRACT_STATIC = YES +HIDE_UNDOC_NAMESPACES = YES +HIDE_UNDOC_CLASSES = YES QUIET = YES diff --git a/asset/texture/ERROR.png b/asset/texture/ERROR.png Binary files differnew file mode 100644 index 0000000..2af3548 --- /dev/null +++ b/asset/texture/ERROR.png diff --git a/contributing.md b/contributing.md index 5b0c79d..9c95851 100644 --- a/contributing.md +++ b/contributing.md @@ -20,7 +20,7 @@ that you can click on to open them. # Code style - Formatting nitty-gritty is handled by clang-format/clang-tidy (run `make - format` in the root folder of this repository to format all sources files) + format` or `make lint`) - <details><summary> ASCII only </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> @@ -798,6 +798,27 @@ that you can click on to open them. resolving merge conflicts when multiple sources were added by different people to the same CMakeLists.txt easier. +## GoogleTest-specific + +- Unit tests are not *required* to follow all code standards +- <details><summary> + Private/protected members may be accessed using preprocessor tricks + </summary> + + ```cpp + // include unrelated headers before + + #define private public + #define protected public + + // headers included after *will* be affected + ``` + </details> +- Each test source file defines tests within a single test suite (first + 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) + # Structure - Files are placed in the appropriate directory: @@ -6,5 +6,6 @@ doxygen: Doxyfile FORCE FMT += $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') format: FORCE clang-format -i $(FMT) - $(MAKE) -C src $@ +lint: FORCE + $(MAKE) -C src $@ diff --git a/src/crepe/Component.h b/src/crepe/Component.h index 5279fb3..dc17721 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -27,6 +27,11 @@ protected: //! Only the ComponentManager can create components friend class ComponentManager; + Component(const Component &) = delete; + Component(Component &&) = delete; + virtual Component & operator=(const Component &) = delete; + virtual Component & operator=(Component &&) = delete; + public: virtual ~Component() = default; diff --git a/src/crepe/api/Color.cpp b/src/crepe/api/Color.cpp index 9e5f187..29bd77a 100644 --- a/src/crepe/api/Color.cpp +++ b/src/crepe/api/Color.cpp @@ -2,32 +2,11 @@ using namespace crepe; -Color Color::white = Color(255, 255, 255, 0); -Color Color::red = Color(255, 0, 0, 0); -Color Color::green = Color(0, 255, 0, 0); -Color Color::blue = Color(0, 0, 255, 0); -Color Color::black = Color(0, 0, 0, 0); -Color Color::cyan = Color(0, 255, 255, 0); -Color Color::yellow = Color(255, 255, 0, 0); -Color Color::magenta = Color(255, 0, 255, 0); - -Color::Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { - this->a = alpha; - this->r = red; - this->g = green; - this->b = blue; -}; - -const Color & Color::get_white() { return Color::white; }; - -const Color & Color::get_red() { return Color::red; }; -const Color & Color::get_green() { return Color::green; }; -const Color & Color::get_blue() { return Color::blue; }; - -const Color & Color::get_black() { return Color::black; }; - -const Color & Color::get_cyan() { return Color::cyan; }; - -const Color & Color::get_yellow() { return Color::yellow; }; - -const Color & Color::get_magenta() { return Color::magenta; }; +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}; diff --git a/src/crepe/api/Color.h b/src/crepe/api/Color.h index aa47bf4..84edb5c 100644 --- a/src/crepe/api/Color.h +++ b/src/crepe/api/Color.h @@ -4,41 +4,20 @@ namespace crepe { -// TODO: make Color a struct w/o constructors/destructors -class Color { - - // FIXME: can't these colors be defined as a `static constexpr const Color` - // instead? - -public: - Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha); - static const Color & get_white(); - static const Color & get_red(); - static const Color & get_green(); - static const Color & get_blue(); - static const Color & get_cyan(); - static const Color & get_magenta(); - static const Color & get_yellow(); - static const Color & get_black(); - -private: - // TODO: why are these private!? - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - - static Color white; - static Color red; - static Color green; - static Color blue; - static Color cyan; - static Color magenta; - static Color yellow; - static Color black; - -private: - friend class SDLContext; +struct Color { + uint8_t r = 0x00; + uint8_t g = 0x00; + uint8_t b = 0x00; + uint8_t a = 0xff; + + static const Color WHITE; + static const Color RED; + static const Color GREEN; + static const Color BLUE; + static const Color CYAN; + static const Color MAGENTA; + static const Color YELLOW; + static const Color BLACK; }; } // namespace crepe diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index 0192793..74a55d4 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -2,8 +2,9 @@ #include <memory> +#include "../Component.h" + #include "Color.h" -#include "Component.h" #include "Texture.h" namespace crepe { diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp index de0d0ea..734a5bb 100644 --- a/src/crepe/api/Texture.cpp +++ b/src/crepe/api/Texture.cpp @@ -35,5 +35,5 @@ int Texture::get_width() const { } int Texture::get_height() const { if (this->texture == nullptr) return 0; - return SDLContext::get_instance().get_width(*this); + return SDLContext::get_instance().get_height(*this); } diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp index d5d19dc..95cf606 100644 --- a/src/crepe/facade/DB.cpp +++ b/src/crepe/facade/DB.cpp @@ -18,8 +18,8 @@ DB::DB(const string & path) { this->db = {db, [](libdb::DB * db) { db->close(db, 0); }}; // load or create database file - ret = this->db->open(this->db.get(), NULL, path.c_str(), NULL, libdb::DB_BTREE, DB_CREATE, - 0); + 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 diff --git a/src/crepe/facade/DB.h b/src/crepe/facade/DB.h index 629b0eb..115c0f1 100644 --- a/src/crepe/facade/DB.h +++ b/src/crepe/facade/DB.h @@ -22,8 +22,10 @@ class DB { public: /** * \param path The path of the database (created if nonexistant) + * + * \note If \p path is empty, the database is entirely in-memory */ - DB(const std::string & path); + DB(const std::string & path = ""); virtual ~DB() = default; public: diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index 9464c31..b8b2bda 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -1,5 +1,6 @@ #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> +#include <SDL2/SDL_keycode.h> #include <SDL2/SDL_rect.h> #include <SDL2/SDL_render.h> #include <SDL2/SDL_surface.h> @@ -7,13 +8,15 @@ #include <cmath> #include <cstddef> #include <functional> -#include <iostream> #include <memory> +#include <stdexcept> #include <string> +#include "../api/Camera.h" #include "../api/Sprite.h" #include "../api/Texture.h" #include "../api/Transform.h" +#include "../api/Vector2.h" #include "../util/Log.h" #include "SDLContext.h" @@ -30,29 +33,21 @@ SDLContext::SDLContext() { dbg_trace(); // FIXME: read window defaults from config manager - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - // FIXME: throw exception - std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; - return; + 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); if (!tmp_window) { - // FIXME: throw exception - std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl; - return; + throw runtime_error(format("SDLContext: SDL_Window error: {}", SDL_GetError())); } this->game_window = {tmp_window, [](SDL_Window * window) { SDL_DestroyWindow(window); }}; SDL_Renderer * tmp_renderer = SDL_CreateRenderer(this->game_window.get(), -1, SDL_RENDERER_ACCELERATED); if (!tmp_renderer) { - // FIXME: throw exception - std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() - << std::endl; - SDL_DestroyWindow(this->game_window.get()); - return; + throw runtime_error(format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError())); } this->game_renderer @@ -60,9 +55,7 @@ SDLContext::SDLContext() { int img_flags = IMG_INIT_PNG; if (!(IMG_Init(img_flags) & img_flags)) { - // FIXME: throw exception - std::cout << "SDL_image could not initialize! SDL_image Error: " << IMG_GetError() - << std::endl; + throw runtime_error("SDLContext: SDL_image could not initialize!"); } } @@ -102,40 +95,63 @@ void SDLContext::handle_events(bool & running) { void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); } void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); } -void SDLContext::draw(const Sprite & sprite, const Transform & transform, const Camera & cam) { - - SDL_RendererFlip render_flip - = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) - | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); - - double adjusted_x = (transform.position.x - cam.x) * cam.zoom; - double adjusted_y = (transform.position.y - cam.y) * cam.zoom; - double adjusted_w = sprite.sprite_rect.w * transform.scale * cam.zoom; - double adjusted_h = sprite.sprite_rect.h * transform.scale * cam.zoom; - - SDL_Rect srcrect = { +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, }; +} +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_Rect dstrect = { + 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), }; +} + +void SDLContext::draw_particle(const Sprite & sprite, const Vector2 & pos, + const double & angle, const double & scale, + const Camera & camera) { + + SDL_RendererFlip render_flip + = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) + | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + + SDL_Rect srcrect = this->get_src_rect(sprite); + SDL_Rect dstrect = this->get_dst_rect(sprite, pos, scale, camera); + + SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect, + &dstrect, angle, NULL, render_flip); +} + +void SDLContext::draw(const Sprite & sprite, const Transform & transform, const Camera & cam) { + + SDL_RendererFlip render_flip + = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) + | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + + SDL_Rect srcrect = this->get_src_rect(sprite); + SDL_Rect dstrect = this->get_dst_rect(sprite, transform.position, transform.scale, cam); SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect, &dstrect, transform.rotation, NULL, render_flip); } -void SDLContext::camera(const Camera & cam) { +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) - (SCREEN_WIDTH / 2); - this->viewport.y = static_cast<int>(cam.y) - (SCREEN_HEIGHT / 2); + 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_SetRenderDrawColor(this->game_renderer.get(), cam.bg_color.r, cam.bg_color.g, cam.bg_color.b, cam.bg_color.a); @@ -147,9 +163,8 @@ std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> SDLContext::texture_from_path(const std::string & path) { SDL_Surface * tmp = IMG_Load(path.c_str()); - if (tmp == nullptr) { - tmp = IMG_Load("../asset/texture/ERROR.png"); - } + if (tmp == nullptr) + throw runtime_error(format("SDLContext: IMG_Load error: {}", SDL_GetError())); std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> img_surface; img_surface = {tmp, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }}; @@ -158,7 +173,7 @@ SDLContext::texture_from_path(const std::string & path) { = SDL_CreateTextureFromSurface(this->game_renderer.get(), img_surface.get()); if (tmp_texture == nullptr) { - throw runtime_error(format("Texture cannot be load from {}", path)); + throw runtime_error(format("SDLContext: Texture cannot be load from {}", path)); } std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> img_texture; diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index 007092b..841ffc9 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -1,8 +1,10 @@ #pragma once #include <SDL2/SDL_keycode.h> +#include <SDL2/SDL_rect.h> #include <SDL2/SDL_render.h> #include <SDL2/SDL_video.h> +#include <cmath> #include <functional> #include <memory> #include <string> @@ -10,10 +12,7 @@ #include "../api/Sprite.h" #include "../api/Transform.h" #include "api/Camera.h" - -// FIXME: this needs to be removed -const int SCREEN_WIDTH = 640; -const int SCREEN_HEIGHT = 480; +#include "api/Vector2.h" namespace crepe { @@ -21,9 +20,6 @@ namespace crepe { // typedef is unusable when crepe is packaged. Wouter will fix this later. typedef SDL_Keycode CREPE_KEYCODES; -class Texture; -class LoopManager; - /** * \class SDLContext * \brief Facade for the SDL library @@ -91,9 +87,6 @@ private: //! Will use the funtions: texture_from_path, get_width,get_height. friend class Texture; - //! Will use the funtions: texture_from_path, get_width,get_height. - friend class Animator; - /** * \brief Loads a texture from a file path. * \param path Path to the image file. @@ -127,6 +120,9 @@ private: */ 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); + //! Clears the screen, preparing for a new frame. void clear_screen(); @@ -134,10 +130,31 @@ private: void present_screen(); /** - * \brief Sets the current camera for rendering. + * \brief sets the background of the camera (will be adjusted in future PR) * \param camera Reference to the Camera object. */ - void camera(const Camera & camera); + void set_camera(const Camera & camera); + +private: + /** + * \brief calculates the sqaure size of the image + * + * \param sprite Reference to the sprite to calculate the rectangle + * \return sdl rectangle to draw a src image + */ + SDL_Rect get_src_rect(const Sprite & sprite) const; + /** + * \brief calculates the sqaure size of the image for an destination + * + * \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 + */ + SDL_Rect get_dst_rect(const Sprite & sprite, const Vector2 & pos, const double & scale, + const Camera & cam) const; private: //! sdl Window diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index fa3d0de..e379771 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -1,44 +1,104 @@ +#include <algorithm> +#include <cassert> +#include <cmath> #include <functional> +#include <iostream> +#include <stdexcept> #include <vector> #include "../ComponentManager.h" +#include "../api/ParticleEmitter.h" #include "../api/Sprite.h" #include "../api/Transform.h" +#include "../api/Vector2.h" #include "../facade/SDLContext.h" -#include "../util/Log.h" #include "RenderSystem.h" using namespace crepe; +using namespace std; -void RenderSystem::clear_screen() const { SDLContext::get_instance().clear_screen(); } +void RenderSystem::clear_screen() { this->context.clear_screen(); } -void RenderSystem::present_screen() const { SDLContext::get_instance().present_screen(); } +void RenderSystem::present_screen() { this->context.present_screen(); } void RenderSystem::update_camera() { ComponentManager & mgr = this->component_manager; std::vector<std::reference_wrapper<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) { - SDLContext::get_instance().camera(cam); - this->curr_cam = &cam; + if (!cam.active) continue; + this->context.set_camera(cam); + this->curr_cam_ref = &cam; } } -void RenderSystem::render_sprites() const { - ComponentManager & mgr = this->component_manager; - std::vector<std::reference_wrapper<Sprite>> sprites = mgr.get_components_by_type<Sprite>(); +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; - SDLContext & render = SDLContext::get_instance(); - for (const Sprite & sprite : sprites) { - auto transforms = mgr.get_components_by_id<Transform>(sprite.game_object_id); - render.draw(sprite, transforms[0], *curr_cam); - } + return false; +} + +std::vector<std::reference_wrapper<Sprite>> +RenderSystem::sort(std::vector<std::reference_wrapper<Sprite>> & objs) const { + + std::vector<std::reference_wrapper<Sprite>> sorted_objs(objs); + std::sort(sorted_objs.begin(), sorted_objs.end(), sorting_comparison); + + return sorted_objs; } void RenderSystem::update() { this->clear_screen(); this->update_camera(); - this->render_sprites(); + this->render(); this->present_screen(); } + +bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { + + ComponentManager & mgr = this->component_manager; + + vector<reference_wrapper<ParticleEmitter>> emitters + = mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); + + bool rendering_particles = false; + + for (const ParticleEmitter & em : emitters) { + if (!(&em.data.sprite == &sprite)) continue; + rendering_particles = true; + if (!em.active) continue; + + for (const Particle & p : em.data.particles) { + if (!p.active) continue; + this->context.draw_particle(sprite, p.position, p.angle, scale, + *this->curr_cam_ref); + } + } + 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() { + + ComponentManager & mgr = this->component_manager; + vector<reference_wrapper<Sprite>> sprites = mgr.get_components_by_type<Sprite>(); + vector<reference_wrapper<Sprite>> sorted_sprites = this->sort(sprites); + + 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); + + if (rendered_particles) continue; + + this->render_normal(sprite, transform); + } +} diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 87ec494..d25a6e3 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -1,18 +1,24 @@ #pragma once -#include "api/Camera.h" +#include <functional> +#include <vector> + +#include "facade/SDLContext.h" #include "System.h" +#include <cmath> namespace crepe { +class Camera; +class Sprite; + /** * \class RenderSystem * \brief Manages rendering operations for all game objects. * - * RenderSystem is responsible for rendering sprites, clearing and presenting the screen, and - * managing the active camera. It functions as a singleton, providing centralized rendering - * services for the application. + * RenderSystem is responsible for rendering, clearing and presenting the screen, and + * managing the active camera. */ class RenderSystem : public System { public: @@ -25,20 +31,45 @@ public: private: //! Clears the screen in preparation for rendering. - void clear_screen() const; + void clear_screen(); //! Presents the rendered frame to the display. - void present_screen() const; + void present_screen(); //! Updates the active camera used for rendering. void update_camera(); - //! Renders all active sprites to the screen. - void render_sprites() const; + //! Renders the whole screen + void render(); + + /** + * \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 + * \return true if particles have been rendered + */ + bool render_particle(const Sprite & sprite, const double & scale); + + /** + * \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 + */ + void render_normal(const Sprite & sprite, const Transform & tm); + + /** + * \brief sort a vector sprite objects with + * + * \param objs the vector that will do a sorting algorithm on + * \return returns a sorted reference vector + */ + std::vector<std::reference_wrapper<Sprite>> + sort(std::vector<std::reference_wrapper<Sprite>> & objs) const; /** * \todo Include color handling for sprites. - * \todo Implement particle emitter rendering with 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. @@ -48,8 +79,10 @@ private: private: //! Pointer to the current active camera for rendering - Camera * curr_cam = nullptr; + Camera * curr_cam_ref = nullptr; // TODO: needs a better solution + + SDLContext & context = SDLContext::get_instance(); }; } // namespace crepe diff --git a/src/crepe/util/Proxy.h b/src/crepe/util/Proxy.h index b34f7c6..789144a 100644 --- a/src/crepe/util/Proxy.h +++ b/src/crepe/util/Proxy.h @@ -16,6 +16,8 @@ template <typename T> class Proxy { public: //! Set operator + Proxy & operator=(Proxy &); + //! Set operator Proxy & operator=(const T &); //! Get operator operator const T &(); diff --git a/src/crepe/util/Proxy.hpp b/src/crepe/util/Proxy.hpp index b9923db..ef2b69f 100644 --- a/src/crepe/util/Proxy.hpp +++ b/src/crepe/util/Proxy.hpp @@ -14,6 +14,12 @@ Proxy<T> & Proxy<T>::operator=(const T & val) { } template <typename T> +Proxy<T> & Proxy<T>::operator=(Proxy & proxy) { + this->broker.set(T(proxy)); + return *this; +} + +template <typename T> Proxy<T>::operator const T &() { return this->broker.get(); } diff --git a/src/doc/feature/script.dox b/src/doc/feature/script.dox new file mode 100644 index 0000000..d25a63b --- /dev/null +++ b/src/doc/feature/script.dox @@ -0,0 +1,62 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_script Scripting +\ingroup feature +\brief User-defined scripts for game objects + +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) + +\see Script +\see BehaviorScript +\see GameObject + +\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*. + +```cpp +#include <crepe/api/Script.h> +#include <crepe/api/BehaviorScript.h> + +class MyScript : public crepe::Script { + void init() { + // called once + } + void update() { + // called on fixed update + } +}; +``` + +Concrete scripts can be instantiated and attached to \ref GameObject +"game objects" using the BehaviorScript \ref Component "component". + +```cpp +using namespace crepe; +GameObject obj = component_manager.new_object("name"); + +// create BehaviorScript instance +BehaviorScript & behavior_script = obj.add_component<BehaviorScript>(); +// attach (and instantiate) MyScript to behavior_script +behavior_script.set_script<MyScript>(); + +// the above can also be done in a single call for convenience: +obj.add_component<BehaviorScript>().set_script<MyScript>(); +``` + +*/ +} diff --git a/src/doc/features.dox b/src/doc/features.dox new file mode 100644 index 0000000..4786bed --- /dev/null +++ b/src/doc/features.dox @@ -0,0 +1,10 @@ +// vim:ft=doxygen +/** + +\defgroup feature Features +\brief Engine components + +This page lists engine features and contains usage instructions for each +feature. + +*/ diff --git a/src/doc/index.dox b/src/doc/index.dox new file mode 100644 index 0000000..5ec7889 --- /dev/null +++ b/src/doc/index.dox @@ -0,0 +1,10 @@ +// vim:ft=doxygen +/** + +\mainpage crêpe game engine + +Welcome to the documentation for the crêpe game engine. + +\see feature + +*/ diff --git a/src/doc/installing.dox b/src/doc/installing.dox new file mode 100644 index 0000000..48b27d7 --- /dev/null +++ b/src/doc/installing.dox @@ -0,0 +1,9 @@ +// vim:ft=doxygen +/** + +\defgroup install Installation +\brief Engine installation instructions + +\todo This entire page + +*/ diff --git a/src/doc/layout.xml b/src/doc/layout.xml new file mode 100644 index 0000000..2244fa7 --- /dev/null +++ b/src/doc/layout.xml @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="UTF-8"?> +<doxygenlayout version="1.0"> + <navindex> + <tab type="mainpage" visible="yes" title=""/> + <tab type="pages" visible="no" title="" intro=""/> + <tab type="topics" visible="yes" title="" intro=""/> + <tab type="modules" visible="yes" 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="namespacelist" visible="yes" title="" intro=""/> + <tab type="namespacemembers" visible="yes" title="" intro=""/> + </tab> + <tab type="concepts" visible="yes" title=""> + </tab> + <tab type="interfaces" visible="yes" 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="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="structlist" visible="yes" title="" intro=""/> + <tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/> + </tab> + <tab type="exceptions" visible="yes" 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="filelist" visible="yes" title="" intro=""/> + <tab type="globals" visible="yes" title="" intro=""/> + </tab> + <tab type="examples" visible="yes" title="" intro=""/> + </navindex> + <class> + <briefdescription visible="yes"/> + <includes visible="$SHOW_HEADERFILE"/> + <inheritancegraph visible="yes"/> + <collaborationgraph visible="yes"/> + <memberdecl> + <nestedclasses visible="yes" title=""/> + <publictypes title=""/> + <services title=""/> + <interfaces title=""/> + <publicslots title=""/> + <signals title=""/> + <publicmethods title=""/> + <publicstaticmethods title=""/> + <publicattributes title=""/> + <publicstaticattributes title=""/> + <protectedtypes title=""/> + <protectedslots title=""/> + <protectedmethods title=""/> + <protectedstaticmethods title=""/> + <protectedattributes title=""/> + <protectedstaticattributes title=""/> + <packagetypes title=""/> + <packagemethods title=""/> + <packagestaticmethods title=""/> + <packageattributes title=""/> + <packagestaticattributes title=""/> + <properties title=""/> + <events title=""/> + <privatetypes title=""/> + <privateslots title=""/> + <privatemethods title=""/> + <privatestaticmethods title=""/> + <privateattributes title=""/> + <privatestaticattributes title=""/> + <friends title=""/> + <related title="" subtitle=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <services title=""/> + <interfaces title=""/> + <constructors title=""/> + <functions title=""/> + <related title=""/> + <variables title=""/> + <properties title=""/> + <events title=""/> + </memberdef> + <allmemberslink visible="yes"/> + <usedfiles visible="$SHOW_USED_FILES"/> + <authorsection visible="yes"/> + </class> + <namespace> + <briefdescription visible="yes"/> + <memberdecl> + <nestednamespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <concepts visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <properties title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <properties title=""/> + </memberdef> + <authorsection visible="yes"/> + </namespace> + <concept> + <briefdescription visible="yes"/> + <includes visible="$SHOW_HEADERFILE"/> + <definition visible="yes" title=""/> + <detaileddescription title=""/> + <authorsection visible="yes"/> + </concept> + <file> + <briefdescription visible="yes"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <includegraph visible="yes"/> + <includedbygraph visible="yes"/> + <sourcelink visible="yes"/> + <memberdecl> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <concepts visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <properties title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <properties title=""/> + </memberdef> + <authorsection/> + </file> + <group> + <detaileddescription title=""/> + <groupgraph visible="yes"/> + <memberdecl> + <nestedgroups visible="yes" title=""/> + <modules visible="yes" title=""/> + <dirs visible="yes" title=""/> + <files visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <concepts visible="yes" title=""/> + <classes visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <pagedocs/> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + </memberdef> + <authorsection visible="yes"/> + </group> + <module> + <briefdescription visible="yes"/> + <exportedmodules visible="yes"/> + <memberdecl> + <concepts visible="yes" title=""/> + <classes visible="yes" title=""/> + <enums title=""/> + <typedefs title=""/> + <functions title=""/> + <variables title=""/> + <membergroups title=""/> + </memberdecl> + <detaileddescription title=""/> + <memberdecl> + <files visible="yes"/> + </memberdecl> + </module> + <directory> + <briefdescription visible="yes"/> + <directorygraph visible="yes"/> + <memberdecl> + <dirs visible="yes"/> + <files visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + </directory> +</doxygenlayout> diff --git a/src/doc/style.css b/src/doc/style.css new file mode 100644 index 0000000..08bc9f5 --- /dev/null +++ b/src/doc/style.css @@ -0,0 +1,4 @@ +#titlearea, +address { + display: none; +} diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index ddb0262..7b4cc43 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -16,13 +16,7 @@ function(add_example target_name) add_dependencies(examples ${target_name}) endfunction() -add_example(audio_internal) -# add_example(components_internal) -add_example(script) -add_example(log) -add_example(rendering) add_example(asset_manager) -add_example(physics) add_example(savemgr) add_example(proxy) add_example(db) @@ -30,5 +24,6 @@ add_example(ecs) add_example(scene_manager) add_example(events) add_example(particles) +add_example(rendering_particle) add_example(gameloop) diff --git a/src/example/audio_internal.cpp b/src/example/audio_internal.cpp deleted file mode 100644 index 661161a..0000000 --- a/src/example/audio_internal.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/** \file - * - * Standalone example for usage of the internal \c Sound class. - */ - -#include <crepe/api/Config.h> -#include <crepe/facade/Sound.h> -#include <crepe/util/Log.h> - -#include <thread> - -using namespace crepe; -using namespace std; -using namespace std::chrono_literals; -using std::make_unique; - -// Unrelated stuff that is not part of this POC -int _ = []() { - // Show dbg_trace() output - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::TRACE; - - return 0; // satisfy compiler -}(); - -int main() { - // Load a background track (Ogg Vorbis) - auto bgm = Sound("../mwe/audio/bgm.ogg"); - // Load three short samples (WAV) - auto sfx1 = Sound("../mwe/audio/sfx1.wav"); - auto sfx2 = Sound("../mwe/audio/sfx2.wav"); - auto sfx3 = Sound("../mwe/audio/sfx3.wav"); - - // Start the background track - bgm.play(); - - // Play each sample sequentially while pausing and resuming the background track - this_thread::sleep_for(500ms); - sfx1.play(); - this_thread::sleep_for(500ms); - sfx2.play(); - bgm.pause(); - this_thread::sleep_for(500ms); - sfx3.play(); - bgm.play(); - this_thread::sleep_for(500ms); - - // Play all samples simultaniously - sfx1.play(); - sfx2.play(); - sfx3.play(); - this_thread::sleep_for(1000ms); - - // Stop all audio and exit - return EXIT_SUCCESS; -} diff --git a/src/example/components_internal.cpp b/src/example/components_internal.cpp deleted file mode 100644 index 2a232a9..0000000 --- a/src/example/components_internal.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/** \file - * - * Standalone example for usage of the internal ECS - */ - -#include <cassert> -#include <chrono> - -#include <crepe/Component.h> -#include <crepe/ComponentManager.h> - -#include <crepe/api/GameObject.h> -#include <crepe/api/Rigidbody.h> -#include <crepe/api/Sprite.h> - -#include <crepe/util/Log.h> - -using namespace crepe; -using namespace std; - -#define OBJ_COUNT 100000 - -int main() { - dbg_trace(); - - ComponentManager mgr{}; - - auto start_adding = chrono::high_resolution_clock::now(); - - for (int i = 0; i < OBJ_COUNT; ++i) { - GameObject obj = mgr.new_object("Name", "Tag"); - obj.add_component<Sprite>("test"); - obj.add_component<Rigidbody>(0, 0, i); - } - - auto stop_adding = chrono::high_resolution_clock::now(); - - auto sprites = mgr.get_components_by_type<Sprite>(); - for (auto sprite : sprites) { - assert(true); - } - - auto stop_looping = chrono::high_resolution_clock::now(); - - auto add_time = chrono::duration_cast<chrono::microseconds>(stop_adding - start_adding); - auto loop_time = chrono::duration_cast<chrono::microseconds>(stop_looping - stop_adding); - printf("add time: %ldus\n", add_time.count()); - printf("loop time: %ldus\n", loop_time.count()); - - return 0; -} diff --git a/src/example/db.cpp b/src/example/db.cpp deleted file mode 100644 index ee4e8fc..0000000 --- a/src/example/db.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include <crepe/api/Config.h> -#include <crepe/facade/DB.h> -#include <crepe/util/Log.h> - -using namespace crepe; -using namespace std; - -// run before main -static auto _ = []() { - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::TRACE; - return 0; -}(); - -int main() { - dbg_trace(); - - DB db("file.db"); - - const char * test_key = "test-key"; - string test_data = "Hello world!"; - - dbg_logf("DB has key = {}", db.has(test_key)); - - db.set(test_key, test_data); - - dbg_logf("key = \"{}\"", db.get(test_key)); - - return 0; -} diff --git a/src/example/ecs.cpp b/src/example/ecs.cpp deleted file mode 100644 index d5ba51b..0000000 --- a/src/example/ecs.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include <iostream> - -#include <crepe/ComponentManager.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Metadata.h> -#include <crepe/api/Transform.h> - -using namespace crepe; -using namespace std; - -int main() { - ComponentManager mgr{}; - - // Create a few GameObjects - try { - 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); - - // Set the parent of each GameObject - right_foot.set_parent(right_leg); - left_foot.set_parent(left_leg); - right_leg.set_parent(body); - left_leg.set_parent(body); - - // Adding a second Transform component is not allowed and will invoke an exception - body.add_component<Transform>(Vector2{10, 10}, 0, 1); - } catch (const exception & e) { - cerr << e.what() << endl; - } - - // Get the Metadata and Transform components of each GameObject - vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); - vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); - - // Print the Metadata and Transform components - for (auto & m : metadata) { - cout << "Id: " << m.get().game_object_id << " Name: " << m.get().name - << " Tag: " << m.get().tag << " Parent: " << m.get().parent << " Children: "; - for (auto & c : m.get().children) { - cout << c << " "; - } - cout << endl; - } - for (auto & t : transform) { - cout << "Id: " << t.get().game_object_id << " Position: [" << t.get().position.x - << ", " << t.get().position.y << "]" << endl; - } - - return 0; -} diff --git a/src/example/log.cpp b/src/example/log.cpp deleted file mode 100644 index 5baa021..0000000 --- a/src/example/log.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** \file - * - * Standalone example for usage of the logging functions - */ - -#include <crepe/api/Config.h> -#include <crepe/util/Log.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() { - dbg_trace(); - dbg_log("debug message"); - Log::logf("info message with variable: {}", 3); - Log::logf(Log::Level::WARNING, "warning"); - Log::logf(Log::Level::ERROR, "error"); - - return 0; -} diff --git a/src/example/particles.cpp b/src/example/particles.cpp deleted file mode 100644 index 3d5f676..0000000 --- a/src/example/particles.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include <crepe/ComponentManager.h> -#include <crepe/api/AssetManager.h> - -#include <crepe/Component.h> -#include <crepe/api/Color.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> - -using namespace crepe; -using namespace std; - -int main(int argc, char * argv[]) { - ComponentManager mgr{}; - GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0); - Color color(0, 0, 0, 0); - Sprite test_sprite = game_object.add_component<Sprite>( - make_shared<Texture>("../asset/texture/img.png"), color, FlipSettings{true, true}); - 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, - }); - - return 0; -} diff --git a/src/example/physics.cpp b/src/example/physics.cpp deleted file mode 100644 index ad663a0..0000000 --- a/src/example/physics.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include <crepe/Component.h> -#include <crepe/ComponentManager.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Rigidbody.h> -#include <crepe/api/Transform.h> -#include <crepe/system/PhysicsSystem.h> - -using namespace crepe; -using namespace std; - -int main(int argc, char * argv[]) { - ComponentManager mgr{}; - - GameObject game_object = mgr.new_object("Name", "Tag", Vector2{0, 0}, 0, 0); - game_object.add_component<Rigidbody>(Rigidbody::Data{ - .mass = 1, - .gravity_scale = 1, - .body_type = Rigidbody::BodyType::DYNAMIC, - .constraints = {0, 0, 0}, - .use_gravity = true, - .bounce = false, - }); - return 0; -} diff --git a/src/example/proxy.cpp b/src/example/proxy.cpp deleted file mode 100644 index 69451f8..0000000 --- a/src/example/proxy.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/** \file - * - * Standalone example for usage of the proxy type - */ - -#include <crepe/ValueBroker.h> -#include <crepe/api/Config.h> -#include <crepe/util/Log.h> -#include <crepe/util/Proxy.h> - -using namespace std; -using namespace crepe; - -void test_ro_ref(const int & val) {} -void test_rw_ref(int & val) {} -void test_ro_val(int val) {} - -int main() { - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::DEBUG; - - int real_value = 0; - - ValueBroker<int> broker{ - [&real_value](const int & target) { - dbg_logf("set {} to {}", real_value, target); - real_value = target; - }, - [&real_value]() -> const int & { - dbg_logf("get {}", real_value); - return real_value; - }, - }; - - Proxy<int> proxy{broker}; - - broker.set(54); - proxy = 84; - - test_ro_ref(proxy); // this is allowed - // test_rw_ref(proxy); // this should throw a compile error - test_ro_val(proxy); - - return 0; -} diff --git a/src/example/rendering.cpp b/src/example/rendering.cpp deleted file mode 100644 index c9e62f1..0000000 --- a/src/example/rendering.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "api/Camera.h" -#include <crepe/ComponentManager.h> -#include <crepe/api/GameObject.h> -#include <crepe/system/RenderSystem.h> -#include <crepe/util/Log.h> - -#include <crepe/api/AssetManager.h> -#include <crepe/api/Color.h> -#include <crepe/api/Sprite.h> -#include <crepe/api/Texture.h> -#include <crepe/api/Transform.h> -#include <crepe/api/Vector2.h> - -#include <chrono> -#include <memory> - -using namespace std; -using namespace crepe; - -int main() { - dbg_trace(); - - ComponentManager mgr{}; - RenderSystem sys{mgr}; - - GameObject obj = mgr.new_object("name", "tag", Vector2{0, 0}, 1, 1); - GameObject obj1 = mgr.new_object("name", "tag", Vector2{500, 0}, 1, 0.1); - GameObject obj2 = mgr.new_object("name", "tag", Vector2{800, 0}, 1, 0.1); - - // Normal adding components - { - Color color(0, 0, 0, 0); - obj.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), color, - FlipSettings{false, false}); - obj.add_component<Camera>(Color::get_red()); - } - { - Color color(0, 0, 0, 0); - obj1.add_component<Sprite>(make_shared<Texture>("../asset/texture/second.png"), color, - FlipSettings{true, true}); - } - - /* - { - Color color(0, 0, 0, 0); - auto img = mgr.cache<Texture>("../asset/texture/second.png"); - obj2.add_component<Sprite>(img, color, FlipSettings{true, true}); - } - */ - - auto start = std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { - sys.update(); - } -} diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp new file mode 100644 index 0000000..4571afb --- /dev/null +++ b/src/example/rendering_particle.cpp @@ -0,0 +1,71 @@ +#include "api/Camera.h" +#include "system/ParticleSystem.h" +#include <SDL2/SDL_timer.h> +#include <crepe/ComponentManager.h> + +#include <crepe/Component.h> +#include <crepe/api/Color.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> + +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}; + + 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; + + 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); + + game_object + .add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), color, + FlipSettings{false, false}) + .order_in_layer + = 6; + + 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); + } + + return 0; +} diff --git a/src/example/scene_manager.cpp b/src/example/scene_manager.cpp deleted file mode 100644 index accec7d..0000000 --- a/src/example/scene_manager.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include <iostream> - -#include <crepe/ComponentManager.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Metadata.h> -#include <crepe/api/Scene.h> -#include <crepe/api/SceneManager.h> -#include <crepe/api/Vector2.h> - -using namespace crepe; -using namespace std; - -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); - } -}; - -class ConcreteScene2 : public Scene { -public: - using Scene::Scene; - - 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); - } -}; - -int main() { - ComponentManager component_mgr{}; - SceneManager scene_mgr{component_mgr}; - - // Add the scenes to the scene manager - scene_mgr.add_scene<ConcreteScene1>("scene1"); - scene_mgr.add_scene<ConcreteScene2>("scene2"); - - // There is no need to call set_next_scene() at the beginnen, because the first scene will be - // automatically set as the next scene - - // Load scene1 (the first scene added) - scene_mgr.load_next_scene(); - - // Get the Metadata components of each GameObject of Scene1 - vector<reference_wrapper<Metadata>> metadata - = component_mgr.get_components_by_type<Metadata>(); - - cout << "Metadata components of Scene1:" << endl; - // Print the Metadata - for (auto & m : metadata) { - cout << "Id: " << m.get().game_object_id << " Name: " << m.get().name - << " Tag: " << m.get().tag << endl; - } - - // Set scene2 as the next scene - scene_mgr.set_next_scene("scene2"); - // Load scene2 - scene_mgr.load_next_scene(); - - // Get the Metadata components of each GameObject of Scene2 - metadata = component_mgr.get_components_by_type<Metadata>(); - - cout << "Metadata components of Scene2:" << endl; - // Print the Metadata - for (auto & m : metadata) { - cout << "Id: " << m.get().game_object_id << " Name: " << m.get().name - << " Tag: " << m.get().tag << endl; - } - - return 0; -} diff --git a/src/example/script.cpp b/src/example/script.cpp deleted file mode 100644 index a23295b..0000000 --- a/src/example/script.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** \file - * - * Standalone example for usage of the script component and system - */ - -#include <crepe/ComponentManager.h> -#include <crepe/system/ScriptSystem.h> -#include <crepe/util/Log.h> - -#include <crepe/api/BehaviorScript.h> -#include <crepe/api/Config.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Script.h> -#include <crepe/api/Transform.h> - -using namespace crepe; -using namespace std; - -// Unrelated stuff that is not part of this POC -int _ = []() { - // Show dbg_trace() output - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::TRACE; - - return 0; // satisfy compiler -}(); - -// User-defined script: -class MyScript : public Script { - void update() { - // Retrieve component from the same GameObject this script is on - Transform & test = get_component<Transform>(); - dbg_logf("Transform({:.2f}, {:.2f})", test.position.x, test.position.y); - } -}; - -int main() { - ComponentManager component_manager{}; - ScriptSystem system{component_manager}; - - // Create game object with Transform and BehaviorScript components - GameObject obj = component_manager.new_object("name"); - obj.add_component<BehaviorScript>().set_script<MyScript>(); - - // Update all scripts. This should result in MyScript::update being called - system.update(); - - return EXIT_SUCCESS; -} diff --git a/src/makefile b/src/makefile index 5f80204..a0e8f02 100644 --- a/src/makefile +++ b/src/makefile @@ -1,6 +1,9 @@ .PHONY: FORCE -FMT := $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') format: FORCE - clang-tidy -p build/compile_commands.json --fix-errors $(FMT) + $(MAKE) -C .. $@ + +LINT := $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') +lint: FORCE + clang-tidy -p build/compile_commands.json --fix-errors $(LINT) diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 6f6ad79..dc985a3 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -4,5 +4,9 @@ target_sources(test_main PUBLIC ScriptTest.cpp ParticleTest.cpp EventTest.cpp + ECSTest.cpp + SceneManagerTest.cpp + ValueBrokerTest.cpp + DBTest.cpp ) diff --git a/src/test/DBTest.cpp b/src/test/DBTest.cpp new file mode 100644 index 0000000..b57eba9 --- /dev/null +++ b/src/test/DBTest.cpp @@ -0,0 +1,29 @@ +#include <gtest/gtest.h> +#include <crepe/facade/DB.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class DBTest : public Test { +public: + DB db; +}; + +TEST_F(DBTest, ReadWrite) { + db.set("foo", "bar"); + EXPECT_EQ(db.get("foo"), "bar"); +} + +TEST_F(DBTest, Nonexistant) { + EXPECT_THROW(db.get("foo"), std::out_of_range); + db.set("foo", "bar"); + EXPECT_NO_THROW(db.get("foo")); +} + +TEST_F(DBTest, Has) { + EXPECT_EQ(db.has("foo"), false); + db.set("foo", "bar"); + EXPECT_EQ(db.has("foo"), true); +} + diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp new file mode 100644 index 0000000..d5a5826 --- /dev/null +++ b/src/test/ECSTest.cpp @@ -0,0 +1,236 @@ +#include <gtest/gtest.h> + +#define protected 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> + +using namespace std; +using namespace crepe; + +class ECSTest : public ::testing::Test { +public: + ComponentManager mgr{}; +}; + +TEST_F(ECSTest, createGameObject) { + GameObject obj = mgr.new_object("body", "person", Vector2{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(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[0].get().tag, "person"); + 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); + EXPECT_EQ(transform[0].get().rotation, 0); + EXPECT_EQ(transform[0].get().scale, 1); +} + +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); + + mgr.delete_all_components(); + + 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(), 0); + EXPECT_EQ(transform.size(), 0); + + GameObject obj2 = mgr.new_object("body2", "person2", Vector2{1, 0}, 5, 1); + + 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, 0); + EXPECT_EQ(metadata[0].get().name, "body2"); + EXPECT_EQ(metadata[0].get().tag, "person2"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + + EXPECT_EQ(transform[0].get().game_object_id, 0); + EXPECT_EQ(transform[0].get().position.x, 1); + EXPECT_EQ(transform[0].get().position.y, 0); + EXPECT_EQ(transform[0].get().rotation, 5); + EXPECT_EQ(transform[0].get().scale, 1); +} + +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); + + mgr.delete_all_components_of_id(0); + + 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(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 1); + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[0].get().tag, "person"); + 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); + EXPECT_EQ(transform[0].get().rotation, 0); + EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, manyGameObjects) { + for (int i = 0; i < 5000; i++) { + GameObject obj = mgr.new_object("body", "person", Vector2{0, 0}, 0, i); + } + + 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(), 5000); + EXPECT_EQ(transform.size(), 5000); + for (int i = 0; i < 5000; i++) { + EXPECT_EQ(metadata[i].get().game_object_id, i); + EXPECT_EQ(metadata[i].get().name, "body"); + 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, i); + } + + mgr.delete_components<Metadata>(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 0); + EXPECT_EQ(transform.size(), 5000); + + 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); + } + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 10000 - 5000); + EXPECT_EQ(transform.size(), 10000); +} + +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); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_id<Metadata>(0); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_id<Transform>(1); + + 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, "body"); + EXPECT_EQ(metadata[0].get().tag, "person"); + 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); + EXPECT_EQ(transform[0].get().rotation, 0); + EXPECT_EQ(transform[0].get().scale, 1); +} + +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); + } catch (const exception & e) { + 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); + obj1.add_component<Metadata>("body", "person"); + } catch (const exception & e) { + 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>(); + + EXPECT_EQ(metadata.size(), 2); + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[1].get().name, "body"); +} + +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); + + // Set the parent of each GameObject + right_foot.set_parent(right_leg); + left_foot.set_parent(left_leg); + right_leg.set_parent(body); + left_leg.set_parent(body); + } + + // Get the Metadata and Transform components of each GameObject + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + + // Check IDs + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[1].get().game_object_id, 1); + EXPECT_EQ(metadata[2].get().game_object_id, 2); + EXPECT_EQ(metadata[3].get().game_object_id, 3); + EXPECT_EQ(metadata[4].get().game_object_id, 4); + + // Check the parent-child relationships + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[1].get().name, "rightLeg"); + EXPECT_EQ(metadata[2].get().name, "leftLeg"); + EXPECT_EQ(metadata[3].get().name, "rightFoot"); + EXPECT_EQ(metadata[4].get().name, "leftFoot"); + + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[1].get().parent, 0); + EXPECT_EQ(metadata[2].get().parent, 0); + EXPECT_EQ(metadata[3].get().parent, 1); + EXPECT_EQ(metadata[4].get().parent, 2); + + EXPECT_EQ(metadata[0].get().children.size(), 2); + EXPECT_EQ(metadata[1].get().children.size(), 1); + EXPECT_EQ(metadata[2].get().children.size(), 1); + EXPECT_EQ(metadata[3].get().children.size(), 0); + EXPECT_EQ(metadata[4].get().children.size(), 0); + + EXPECT_EQ(metadata[0].get().children[0], 1); + EXPECT_EQ(metadata[0].get().children[1], 2); + EXPECT_EQ(metadata[1].get().children[0], 3); + EXPECT_EQ(metadata[2].get().children[0], 4); +} diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 4e655a9..d9bbba0 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -28,7 +28,7 @@ public: GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0); Color color(0, 0, 0, 0); - Sprite test_sprite = game_object.add_component<Sprite>( + Sprite & test_sprite = game_object.add_component<Sprite>( make_shared<Texture>("../asset/texture/img.png"), color, FlipSettings{true, true}); diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp new file mode 100644 index 0000000..ac479d3 --- /dev/null +++ b/src/test/RenderSystemTest.cpp @@ -0,0 +1,174 @@ +#include "api/Camera.h" +#include <functional> +#include <gtest/gtest.h> +#include <memory> +#include <vector> + +#define private public +#define protected public + +#include <crepe/ComponentManager.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> + +#include <crepe/system/RenderSystem.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class RenderSystemTest : public Test { +public: + ComponentManager mgr{}; + RenderSystem sys{mgr}; + 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); + } +}; + +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}); + }); + + // No camera + EXPECT_ANY_THROW({ this->sys.update(); }); +} + +TEST_F(RenderSystemTest, make_sprites) {} + +TEST_F(RenderSystemTest, sorting_sprites) { + vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); + ASSERT_EQ(sprites.size(), 4); + + vector<reference_wrapper<Sprite>> sorted_sprites = this->sys.sort(sprites); + ASSERT_EQ(sorted_sprites.size(), 4); + + // Expected order after sorting: + // 1. sorting_in_layer: 1, order_in_layer: 1 (entity4) + // 2. sorting_in_layer: 1, order_in_layer: 2 (entity3) + // 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[1].get().sorting_in_layer, 1); + EXPECT_EQ(sorted_sprites[1].get().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[3].get().sorting_in_layer, 5); + EXPECT_EQ(sorted_sprites[3].get().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); + } else { + EXPECT_LE(prev.sorting_in_layer, curr.sorting_in_layer); + } + } +} + +TEST_F(RenderSystemTest, Update) { + entity1.add_component<Camera>(Color::WHITE); + { + vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); + ASSERT_EQ(sprites.size(), 4); + + EXPECT_EQ(sprites[0].get().game_object_id, 0); + EXPECT_EQ(sprites[1].get().game_object_id, 1); + EXPECT_EQ(sprites[2].get().game_object_id, 2); + EXPECT_EQ(sprites[3].get().game_object_id, 3); + } + this->sys.update(); + { + vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); + ASSERT_EQ(sprites.size(), 4); + + EXPECT_EQ(sprites[0].get().game_object_id, 0); + EXPECT_EQ(sprites[1].get().game_object_id, 1); + EXPECT_EQ(sprites[2].get().game_object_id, 2); + EXPECT_EQ(sprites[3].get().game_object_id, 3); + } +} + +TEST_F(RenderSystemTest, Camera) { + { + auto cameras = this->mgr.get_components_by_type<Camera>(); + EXPECT_NE(cameras.size(), 1); + } + { + entity1.add_component<Camera>(Color::WHITE); + auto cameras = this->mgr.get_components_by_type<Camera>(); + EXPECT_EQ(cameras.size(), 1); + } + + //TODO improve with newer version +} +TEST_F(RenderSystemTest, Color) { + entity1.add_component<Camera>(Color::WHITE); + 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); +} diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp new file mode 100644 index 0000000..69e1171 --- /dev/null +++ b/src/test/SceneManagerTest.cpp @@ -0,0 +1,122 @@ +#include <crepe/ComponentManager.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> + +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); + } +}; + +class ConcreteScene2 : public Scene { +public: + using Scene::Scene; + + 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); + } +}; + +class SceneManagerTest : public ::testing::Test { +public: + ComponentManager component_mgr{}; + SceneManager scene_mgr{component_mgr}; +}; + +TEST_F(SceneManagerTest, loadScene) { + scene_mgr.add_scene<ConcreteScene1>("scene1"); + scene_mgr.add_scene<ConcreteScene2>("scene2"); + + 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(), 3); + EXPECT_EQ(transform.size(), 3); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "scene_1"); + EXPECT_EQ(metadata[0].get().tag, "tag_scene_1"); + 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); + + EXPECT_EQ(metadata[1].get().game_object_id, 1); + EXPECT_EQ(metadata[1].get().name, "scene_1"); + EXPECT_EQ(metadata[1].get().tag, "tag_scene_1"); + EXPECT_EQ(metadata[1].get().parent, -1); + EXPECT_EQ(metadata[1].get().children.size(), 0); + EXPECT_EQ(transform[1].get().position.x, 1); + EXPECT_EQ(transform[1].get().position.y, 0); + + EXPECT_EQ(metadata[2].get().game_object_id, 2); + EXPECT_EQ(metadata[2].get().name, "scene_1"); + EXPECT_EQ(metadata[2].get().tag, "tag_scene_1"); + EXPECT_EQ(metadata[2].get().parent, -1); + EXPECT_EQ(metadata[2].get().children.size(), 0); + EXPECT_EQ(transform[2].get().position.x, 2); + EXPECT_EQ(transform[2].get().position.y, 0); + + scene_mgr.set_next_scene("scene2"); + scene_mgr.load_next_scene(); + + metadata = component_mgr.get_components_by_type<Metadata>(); + transform = component_mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 4); + EXPECT_EQ(transform.size(), 4); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "scene_2"); + EXPECT_EQ(metadata[0].get().tag, "tag_scene_2"); + 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); + + EXPECT_EQ(metadata[1].get().game_object_id, 1); + EXPECT_EQ(metadata[1].get().name, "scene_2"); + EXPECT_EQ(metadata[1].get().tag, "tag_scene_2"); + EXPECT_EQ(metadata[1].get().parent, -1); + EXPECT_EQ(metadata[1].get().children.size(), 0); + EXPECT_EQ(transform[1].get().position.x, 0); + EXPECT_EQ(transform[1].get().position.y, 1); + + EXPECT_EQ(metadata[2].get().game_object_id, 2); + EXPECT_EQ(metadata[2].get().name, "scene_2"); + EXPECT_EQ(metadata[2].get().tag, "tag_scene_2"); + EXPECT_EQ(metadata[2].get().parent, -1); + EXPECT_EQ(metadata[2].get().children.size(), 0); + EXPECT_EQ(transform[2].get().position.x, 0); + EXPECT_EQ(transform[2].get().position.y, 2); + + EXPECT_EQ(metadata[3].get().game_object_id, 3); + EXPECT_EQ(metadata[3].get().name, "scene_2"); + EXPECT_EQ(metadata[3].get().tag, "tag_scene_2"); + EXPECT_EQ(metadata[3].get().parent, -1); + EXPECT_EQ(metadata[3].get().children.size(), 0); + EXPECT_EQ(transform[3].get().position.x, 0); + EXPECT_EQ(transform[3].get().position.y, 3); +} diff --git a/src/test/ValueBrokerTest.cpp b/src/test/ValueBrokerTest.cpp new file mode 100644 index 0000000..10a4654 --- /dev/null +++ b/src/test/ValueBrokerTest.cpp @@ -0,0 +1,64 @@ +#include <gtest/gtest.h> + +#include <crepe/ValueBroker.h> +#include <crepe/util/Proxy.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ValueBrokerTest : public Test { +public: + int read_count = 0; + int write_count = 0; + int value = 0; + + ValueBroker<int> broker { + [this](const int & target) -> void { + this->write_count++; + this->value = target; + }, + [this]() -> const int & { + this->read_count++; + return this->value; + }, + }; + Proxy<int> proxy{broker}; + + void SetUp() override { + ASSERT_EQ(read_count, 0); + ASSERT_EQ(write_count, 0); + } +}; + +TEST_F(ValueBrokerTest, BrokerWrite) { + broker.set(0); + EXPECT_EQ(read_count, 0); + EXPECT_EQ(write_count, 1); +} + +TEST_F(ValueBrokerTest, BrokerRead) { + broker.get(); + EXPECT_EQ(read_count, 1); + EXPECT_EQ(write_count, 0); +} + +TEST_F(ValueBrokerTest, ProxyWrite) { + proxy = 0; + EXPECT_EQ(read_count, 0); + EXPECT_EQ(write_count, 1); +} + +void dummy(int) { } +TEST_F(ValueBrokerTest, ProxyRead) { + dummy(proxy); + EXPECT_EQ(read_count, 1); + EXPECT_EQ(write_count, 0); +} + +TEST_F(ValueBrokerTest, ProxyReadWrite) { + proxy = proxy; + ASSERT_EQ(read_count, 1); + ASSERT_EQ(write_count, 1); +} + |