diff --git a/include/game/agame/sampletextbox.hpp b/include/game/agame/sampletextbox.hpp index 01ea79b..bed7973 100644 --- a/include/game/agame/sampletextbox.hpp +++ b/include/game/agame/sampletextbox.hpp @@ -15,6 +15,7 @@ namespace Game::AGame { setText("Hello, World!"); mIsActive = false; + mIsVisible = false; } void update(float deltaTime) override { // Call the base class update to handle input and text refreshing diff --git a/include/game/agame/trash.hpp b/include/game/agame/trash.hpp new file mode 100644 index 0000000..7c143c7 --- /dev/null +++ b/include/game/agame/trash.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Game::AGame { + GAME_ENTITY(Trash) + END_GAME_ENTITY() +} \ No newline at end of file diff --git a/include/game/gamemanager.hpp b/include/game/gamemanager.hpp index 54fd402..d9d13e0 100644 --- a/include/game/gamemanager.hpp +++ b/include/game/gamemanager.hpp @@ -50,6 +50,13 @@ namespace Game { static GameStateEnum getCurrentGameState() { return mCurrentGameState; } static void setCurrentGameState(GameStateEnum newState) { mCurrentGameState = newState; } + template + static bool instantiateEntity(std::unique_ptr entity); + template + static T* getEntityByName(const std::string& name); + template + static void destroyEntity(T* entity); + private: int mTargetUpdatesPerSecond = TARGET_UPDATE_RATE; clock::time_point mLastUpdate; @@ -114,4 +121,26 @@ namespace Game { return T{}; } + + template + bool GameManager::instantiateEntity(std::unique_ptr entity) { + static_assert(std::is_base_of_v, "T must derive from Object::Entity"); + State::GameState::getInstance().addEntity(std::move(entity)); + return true; + } + + template + T* GameManager::getEntityByName(const std::string& name) { + static_assert(std::is_base_of_v, "T must derive from Object::Entity"); + Object::Entity* entity = State::GameState::getInstance().getEntityByName(name); + return dynamic_cast(entity); + } + + template + void GameManager::destroyEntity(T* entity) { + static_assert(std::is_base_of_v, "T must derive from Object::Entity"); + if (entity) { + State::GameState::getInstance().removeEntity(entity->getName()); + } + } } \ No newline at end of file diff --git a/include/object/components/component.hpp b/include/object/components/component.hpp new file mode 100644 index 0000000..67f7722 --- /dev/null +++ b/include/object/components/component.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +namespace Game::Object { + class Entity; +} + +namespace Game::Object::Components { + class Component { + public: + Component() = default; + + // Declare copy and move; in case components declare some other things, we want to make sure those are properly copied/moved as well, so we'll just define these as virtual and implement them in the .cpp file + Component(const Component&); + Component& operator=(const Component&); + Component(Component&&) noexcept; + Component& operator=(Component&&) noexcept; + + virtual ~Component() = 0; + virtual void start() = 0; + virtual void update(float deltaTime, Object::Entity* thisEntity) = 0; + + // Getters and setters + void setName(const std::string& name); + std::string getName(); + void setActive(bool active); + bool isActive(); + + protected: + // Uniqueness is NOT enforced; be careful when naming! + std::string mName; + bool mIsActive = true; + }; +} \ No newline at end of file diff --git a/include/object/entity.hpp b/include/object/entity.hpp index 658833d..396625c 100644 --- a/include/object/entity.hpp +++ b/include/object/entity.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace Game::Renderer { class Renderer; @@ -44,14 +46,57 @@ namespace Game::Object { bool isActive() { return mIsActive; } int getZIndex() const { return mZIndex; } + // Component management + template + T* addComponent(Args&&... args); + template + T* getComponent(); + template + bool removeComponent(); + void updateComponents(float deltaTime); + protected: std::string mName; std::shared_ptr mTex; Transform mTransform; bool mIsFlipped = false; // Whether the texture should be rendered flipped horizontally or not - bool mIsActive; + bool mIsActive = true; + bool mIsVisible = true; int mZIndex = 0; // For rendering order; higher zIndex renders on top of lower zIndex float mTiledScale = 1.f; // Only used if the texture is tiled, to determine how much to scale the texture when rendering (since the entire texture is rendered as a single tile, this is necessary to be able to have different sized tiles) + std::vector> mComponents; // Components attached to this entity; TODO private: }; + + template + T* Entity::addComponent(Args&&... args) { + static_assert(std::is_base_of_v, "T must derive from Component"); + auto component = std::make_unique(std::forward(args)...); + T* componentPtr = component.get(); + mComponents.push_back(std::move(component)); + return componentPtr; + } + + template + T* Entity::getComponent() { + static_assert(std::is_base_of_v, "T must derive from Component"); + for (const auto& component : mComponents) { + if (auto casted = dynamic_cast(component.get())) { + return casted; + } + } + return nullptr; + } + + template + bool Entity::removeComponent() { + static_assert(std::is_base_of_v, "T must derive from Component"); + for (auto it = mComponents.begin(); it != mComponents.end(); it++) { + if (dynamic_cast(it->get())) { + mComponents.erase(it); + return true; + } + } + return false; + } } \ No newline at end of file diff --git a/src/game/agame/background.cpp b/src/game/agame/background.cpp index a8dc957..e4b520d 100644 --- a/src/game/agame/background.cpp +++ b/src/game/agame/background.cpp @@ -24,8 +24,7 @@ namespace Game::AGame { } void Background::update(float deltaTime) { - if (!mIsActive) return; - + return; /*const bool* state = SDL_GetKeyboardState(nullptr); if (state[SDL_SCANCODE_P]) { mTransform.scaleX *= 2.f; diff --git a/src/game/agame/player.cpp b/src/game/agame/player.cpp index 16e376c..d26bcad 100644 --- a/src/game/agame/player.cpp +++ b/src/game/agame/player.cpp @@ -26,7 +26,6 @@ namespace Game::AGame { } void Player::update(float deltaTime) { - if (!mIsActive) return; //mTransform.rotation += 1.f; // Rotate clockwise for testing //mTransform.scaleX = 1.f + 1.f * std::sin(RUNNING_TIME() / 0.5f); // Pulsate scale for testing //mTransform.scaleY = 1.f + 0.5f * std::cos(RUNNING_TIME() / 0.5f); // Pulsate scale for testing diff --git a/src/game/agame/trash.cpp b/src/game/agame/trash.cpp new file mode 100644 index 0000000..8f7c327 --- /dev/null +++ b/src/game/agame/trash.cpp @@ -0,0 +1,11 @@ +#include + +namespace Game::AGame { + void Trash::start() { + mZIndex = 50; + } + + void Trash::update(float deltaTime) { + return; + } +} \ No newline at end of file diff --git a/src/game/gamemanager.cpp b/src/game/gamemanager.cpp index 1deb681..0ddbd2f 100644 --- a/src/game/gamemanager.cpp +++ b/src/game/gamemanager.cpp @@ -29,9 +29,14 @@ namespace Game { Input::update(); // Update input states at the start of each frame auto entities = State::GameState::getInstance().getEntitiesSnapshot(); for (auto* entity : entities) { - if (entity) { - entity->update(seconds); + if (!entity || !entity->isActive()) { + continue; } + + // Update components first + entity->updateComponents(seconds); + + entity->update(seconds); } } catch (const std::exception& e) { ERROR("Exception in GameManager thread: " << e.what()); @@ -69,4 +74,5 @@ namespace Game { mSharedBools.erase(key); } } + } \ No newline at end of file diff --git a/src/object/components/component.cpp b/src/object/components/component.cpp new file mode 100644 index 0000000..c8198bd --- /dev/null +++ b/src/object/components/component.cpp @@ -0,0 +1,43 @@ +#include + +namespace Game::Object::Components { + Component::Component(const Component& other) : mName(other.mName), mIsActive(other.mIsActive) { + LOG("Copied Component: " << mName); + } + + Component& Component::operator=(const Component& other) { + if (this != &other) { + mName = other.mName; + mIsActive = other.mIsActive; + } + return *this; + } + + Component::Component(Component&& other) noexcept : mName(std::move(other.mName)), mIsActive(other.mIsActive) { + LOG("Moved Component: " << mName); + } + + Component& Component::operator=(Component&& other) noexcept { + if (this != &other) { + mName = std::move(other.mName); + mIsActive = other.mIsActive; + } + return *this; + } + + void Component::setName(const std::string& name) { + mName = name; + } + + std::string Component::getName() { + return mName; + } + + void Component::setActive(bool active) { + mIsActive = active; + } + + bool Component::isActive() { + return mIsActive; + } +} \ No newline at end of file diff --git a/src/object/entity.cpp b/src/object/entity.cpp index bb7029f..0b6c701 100644 --- a/src/object/entity.cpp +++ b/src/object/entity.cpp @@ -39,7 +39,7 @@ namespace Game::Object { } void Entity::render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) { - if (!mIsActive || !mTex) return; // Don't render if not active or if there's no texture + if (!mIsVisible || !mTex) return; // Don't render if not visible or if there's no texture if (!mTex->isTiled()) { float w, h; @@ -98,4 +98,13 @@ namespace Game::Object { } } } + + void Entity::updateComponents(float deltaTime) { + for (const auto& component : mComponents) { + if (!component || !component->isActive()) { + continue; + } + component->update(deltaTime, this); + } + } } \ No newline at end of file