diff --git a/CMakeLists.txt b/CMakeLists.txt index 16e7c617..434d1261 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,14 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.11) +cmake_minimum_required(VERSION 3.11) project(Bomberman) + set(CMAKE_CXX_STANDARD 20) +add_subdirectory(${PROJECT_SOURCE_DIR}/lib/wal) +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/lib/raylib) + add_executable(bomberman - sources/main.cpp + sources/main.cpp ) -target_link_libraries(bomberman raylib) \ No newline at end of file +find_package(raylib REQUIRED) +target_link_libraries(bomberman wal raylib) \ No newline at end of file diff --git a/lib/raylib/Findraylib.cmake b/lib/raylib/Findraylib.cmake new file mode 100644 index 00000000..4baf8bd8 --- /dev/null +++ b/lib/raylib/Findraylib.cmake @@ -0,0 +1,20 @@ +# Usage: +# FIND_PACKAGE(raylib REQUIRED) +# [...] +# TARGET_LINK_LIBRARIES(target_name raylib) + +CMAKE_MINIMUM_REQUIRED(VERSION 3.11) + +if (NOT raylib_FOUND) + INCLUDE(FetchContent) + + FetchContent_Declare(raylib URL https://github.com/raysan5/raylib/archive/master.tar.gz) + FetchContent_GetProperties(raylib) + if (NOT raylib_POPULATED) + SET(FETCHCONTENT_QUIET NO) + FetchContent_Populate(raylib) + SET(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + ADD_SUBDIRECTORY(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) + SET(raylib_FOUND TRUE) + endif() +endif() diff --git a/lib/wal/CMakeLists.txt b/lib/wal/CMakeLists.txt new file mode 100644 index 00000000..81d730f2 --- /dev/null +++ b/lib/wal/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.19) +project(wal) + +set(CMAKE_CXX_STANDARD 20) + +add_library(wal + sources/Entity/Entity.hpp + sources/Component/Component.hpp + sources/System/System.hpp + sources/Wal.cpp + sources/Wal.hpp + sources/Scene/SceneManager.cpp + sources/Scene/SceneManager.hpp + sources/Scene/Scene.cpp + sources/Scene/Scene.hpp + sources/Events/EventManager.cpp + sources/Events/EventManager.hpp + sources/Exception/WalError.cpp + sources/Exception/WalError.hpp + sources/Entity/Entity.cpp + sources/Component/Component.cpp + sources/Component/Position/PositionComponent.cpp + sources/Component/Position/PositionComponent.hpp + sources/Models/Vector3.hpp + sources/Component/Movable/MovableComponent.cpp + sources/Component/Movable/MovableComponent.hpp + sources/System/Movable/MovableSystem.cpp + sources/System/Movable/MovableSystem.hpp + sources/System/System.cpp +) + +target_include_directories(wal PUBLIC sources) + +add_executable(wal_tests EXCLUDE_FROM_ALL + tests/EntityTests.cpp + tests/MainTest.cpp + tests/EngineTests.cpp +) + +target_link_libraries(wal_tests PRIVATE wal) +find_package(Catch2 REQUIRED) +target_link_libraries(wal_tests PRIVATE Catch2::Catch2) \ No newline at end of file diff --git a/lib/wal/Entity/Entity.hpp b/lib/wal/Entity/Entity.hpp deleted file mode 100644 index a0c90acc..00000000 --- a/lib/wal/Entity/Entity.hpp +++ /dev/null @@ -1,64 +0,0 @@ - - -#pragma once - -#include -#include -#include "Component/Component.hpp" - -//! @brief I'm deeply sorry for this name -//! @note I am not, he is a liar -namespace WAL -{ - //! @brief An entity of the WAL's ECS. - class Entity - { - private: - //! @brief The unique ID of the entity - unsigned _uid; - //! @brief An entity name (this is useful for debugging) - std::string _name; - //! @brief Is this entity enabled? - bool _disabled; - //! @brief The list of the components of this entity - std::vector _components; - public: - //! @brief Get the ID of the entity. - unsigned getUid() const; - //! @brief Get the name fo the entity - std::string getName() const; - - //! @brief Used if the entity is disabled - bool isDisable() const; - - //! @brief Disable this entity. - void setDisable(bool disabled); - - //! @brief Get a component of a specific type - //! @throw ComponentNotFoundError if the component could not be found - template - T getComponent(); - - //! @brief Add a component to this entity. The component is constructed in place. - //! @return This entity is returned - template - Entity &addComponent(...params); - - //! @brief Copy a component to this entity. - //! @return This entity is returned. - Entity &addComponent(const Component &component); - - //! @brief Remove a specific component (by type). - template - Entity &removeComponent(); - - //! @brief A default constructor - Entity(const std::string &name); - //! @brief An entity is copyable - Entity(const Entity &); - //! @brief A default destructor - ~Entity() = default; - //! @brief An entity is assignable - Entity &operator=(const Entity &); - }; -} \ No newline at end of file diff --git a/lib/wal/System/System.hpp b/lib/wal/System/System.hpp deleted file mode 100644 index de57d374..00000000 --- a/lib/wal/System/System.hpp +++ /dev/null @@ -1,30 +0,0 @@ - - -#pragma once - -#include "Entity/Entity.hpp" - -namespace WAL -{ - //! @brief A base system of WAL - class System - { - public: - //! @brief A virtual, default, destructor - virtual ~System() = default; - - //! @brief Get the name of the component corresponding to this system. - virtual std::string getComponentName() const = 0; - - //! @brief Update the corresponding component of the given entity - //! @param entity The entity to update. - virtual void onUpdate(Entity &entity) = 0; - protected: - //! @brief A system can't be instantiated, it should be derived. - System() = default; - //! @brief A system can't be instantiated, it should be derived. - System(const System &) = default; - //! @brief A system can't be instantiated, it should be derived. - System &operator=(const System &) = default; - }; -} \ No newline at end of file diff --git a/lib/wal/sources/Component/Component.cpp b/lib/wal/sources/Component/Component.cpp new file mode 100644 index 00000000..f891352b --- /dev/null +++ b/lib/wal/sources/Component/Component.cpp @@ -0,0 +1,37 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#include "Component/Component.hpp" + +namespace WAL +{ + Component::Component(Entity &entity) + : _entity(entity) + { } + + bool Component::isDisabled() const + { + return this->_disabled; + } + + void Component::setDisable(bool disabled) + { + this->_disabled = disabled; + } + + const std::vector &Component::getDependencies() const + { + return this->_dependencies; + } + + void Component::onStart() + { + //TODO handle events here + } + + void Component::onStop() + { + //TODO handle events here + } +} \ No newline at end of file diff --git a/lib/wal/Component/Component.hpp b/lib/wal/sources/Component/Component.hpp similarity index 50% rename from lib/wal/Component/Component.hpp rename to lib/wal/sources/Component/Component.hpp index 7589ae6a..410a026c 100644 --- a/lib/wal/Component/Component.hpp +++ b/lib/wal/sources/Component/Component.hpp @@ -1,10 +1,12 @@ - - +// +// Created by Zoe Roux on 2021-05-14. +// #pragma once #include #include +#include namespace WAL { @@ -15,38 +17,40 @@ namespace WAL class Component { private: - //! @brief The name of this component - std::string _name; //! @brief Is this component disabled? - bool _disabled; + bool _disabled = false; + protected: + //! @brief The entity that own this component + Entity &_entity; //! @brief The list of dependencies of this component. - // TODO check if there is a better type than strings - std::vector _dependencies; + std::vector _dependencies; + + //! @brief A component can't be instantiated, it should be derived. + explicit Component(Entity &entity); + //! @brief A component can't be instantiated, it should be derived. + Component(const Component &) = default; public: - //! @brief Get the name of this component - std::string _getName() const; + //! @brief A component can't be assigned + Component &operator=(const Component &) = delete; + //! @brief A virtual destructor + virtual ~Component() = default; + + //! @brief Clone a component for another or the same entity. + //! @param entity The entity that owns the ne component. + virtual Component *clone(Entity &entity) const = 0; //! @brief Used if the component is disabled - bool isDisable() const; + bool isDisabled() const; //! @brief Disable this component. void setDisable(bool disabled); + //! @brief Get the dependencies of this component. + const std::vector &getDependencies() const; + //! @brief The entity or this component has just been enabled. - //! @param entity The entity that has this component - virtual void onStart(Entity &entity); + virtual void onStart(); //! @brief The entity or this component has just been disable. - //! @param entity The entity that has this component - virtual void onStop(Entity &entity); - - //! @brief A virtual destructor (that also calls onStop) - virtual ~Component(); - protected: - //! @brief A component can't be instantiated, it should be derived. - Component() = default; - //! @brief A component can't be instantiated, it should be derived. - Component(const Component &) = default; - //! @brief A component can't be instantiated, it should be derived. - Component &operator=(const Component &) = default; + virtual void onStop(); }; } \ No newline at end of file diff --git a/lib/wal/sources/Component/Movable/MovableComponent.cpp b/lib/wal/sources/Component/Movable/MovableComponent.cpp new file mode 100644 index 00000000..a86a69ab --- /dev/null +++ b/lib/wal/sources/Component/Movable/MovableComponent.cpp @@ -0,0 +1,22 @@ +// +// Created by Zoe Roux on 5/17/21. +// + +#include "MovableComponent.hpp" + +namespace WAL +{ + MovableComponent::MovableComponent(Entity &entity) + : Component(entity) + {} + + Component *MovableComponent::clone(Entity &entity) const + { + return new MovableComponent(entity); + } + + void MovableComponent::addForce(Vector3f force) + { + this->_acceleration += force; + } +} \ No newline at end of file diff --git a/lib/wal/sources/Component/Movable/MovableComponent.hpp b/lib/wal/sources/Component/Movable/MovableComponent.hpp new file mode 100644 index 00000000..0693121a --- /dev/null +++ b/lib/wal/sources/Component/Movable/MovableComponent.hpp @@ -0,0 +1,39 @@ +// +// Created by Zoe Roux on 5/17/21. +// + +#pragma once + +#include "Models/Vector3.hpp" +#include "Entity/Entity.hpp" + +namespace WAL +{ + //! @brief A component to place on entities that can move or be moved. + class MovableComponent : public Component + { + private: + //! @brief The acceleration of this entity. + Vector3f _acceleration; + //! @brief The velocity of the entity. + Vector3f _velocity; + public: + //! @brief Add an instant force to this entity. + //! @param force The force to add to this entity's acceleration. The force is added instantly and in one go. + void addForce(Vector3f force); + + //! @inherit + Component *clone(Entity &entity) const override; + + //! @brief Create a new movable component. + explicit MovableComponent(Entity &entity); + //! @brief A movable component is copy constructable. + MovableComponent(const MovableComponent &) = default; + //! @brief A default destructor + ~MovableComponent() override = default; + //! @brief A movable component is not assignable. + MovableComponent &operator=(const MovableComponent &) = delete; + + friend class MovableSystem; + }; +} \ No newline at end of file diff --git a/lib/wal/sources/Component/Position/PositionComponent.cpp b/lib/wal/sources/Component/Position/PositionComponent.cpp new file mode 100644 index 00000000..1e470254 --- /dev/null +++ b/lib/wal/sources/Component/Position/PositionComponent.cpp @@ -0,0 +1,43 @@ +// +// Created by Zoe Roux on 5/17/21. +// + +#include "PositionComponent.hpp" + +namespace WAL +{ + PositionComponent::PositionComponent(Entity &entity) + : Component(entity), + position() + {} + + PositionComponent::PositionComponent(Entity &entity, Vector3f pos) + : Component(entity), + position(pos) + {} + + PositionComponent::PositionComponent(Entity &entity, float x, float y, float z) + : Component(entity), + position(x, y, z) + {} + + Component *PositionComponent::clone(WAL::Entity &entity) const + { + return new PositionComponent(entity, this->position); + } + + float PositionComponent::getX() const + { + return this->position.x; + } + + float PositionComponent::getY() const + { + return this->position.y; + } + + float PositionComponent::getZ() const + { + return this->position.z; + } +} \ No newline at end of file diff --git a/lib/wal/sources/Component/Position/PositionComponent.hpp b/lib/wal/sources/Component/Position/PositionComponent.hpp new file mode 100644 index 00000000..8e11a2e3 --- /dev/null +++ b/lib/wal/sources/Component/Position/PositionComponent.hpp @@ -0,0 +1,42 @@ +// +// Created by Zoe Roux on 5/17/21. +// + +#pragma once + +#include "Models/Vector3.hpp" +#include "Component/Component.hpp" + +namespace WAL +{ + //! @brief A basic position component + class PositionComponent : public Component + { + public: + //! @brief Get the editable position of this entity + Vector3f position; + + //! @brief Get the X position of this entity. + float getX() const; + //! @brief Get the Y position of this entity. + float getY() const; + //! @brief Get the Z position of this entity. + float getZ() const; + + //! @inherit + Component *clone(Entity &entity) const override; + + //! @brief Create a new PositionComponent linked to a specific entity + explicit PositionComponent(Entity &entity); + //! @brief Create a new PositionComponent at a certain position + PositionComponent(Entity &entity, Vector3f pos); + //! @brief Create a new PositionComponent at a certain position + PositionComponent(Entity &entity, float x, float y, float z); + //! @brief A position component is copy constructable + PositionComponent(const PositionComponent &) = default; + //! @brief A default destructor + ~PositionComponent() override = default; + //! @brief A position component is not assignable + PositionComponent &operator=(const PositionComponent &) = delete; + }; +} diff --git a/lib/wal/sources/Entity/Entity.cpp b/lib/wal/sources/Entity/Entity.cpp new file mode 100644 index 00000000..e5ca8b29 --- /dev/null +++ b/lib/wal/sources/Entity/Entity.cpp @@ -0,0 +1,62 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#include "Entity/Entity.hpp" + +#include + +namespace WAL +{ + unsigned Entity::nextID = 0; + + Entity::Entity(std::string name) + : _uid(Entity::nextID++), + _name(std::move(name)) + { } + + Entity::Entity(const Entity &other) + : _uid(Entity::nextID++), + _name(other._name), + _disabled(other._disabled) + { + for (const auto &cmp : other._components) + this->addComponent(*cmp); + } + + unsigned Entity::getUid() const + { + return this->_uid; + } + + std::string Entity::getName() const + { + return this->_name; + } + + bool Entity::isDisable() const + { + return this->_disabled; + } + + void Entity::setDisable(bool disabled) + { + this->_disabled = disabled; + } + + Entity &Entity::addComponent(const Component &component) + { + if (this->hasComponent(typeid(component))) + throw DuplicateError("A component of the type \"" + std::string(typeid(component).name()) + "\" already exists."); + this->_components.emplace_back(component.clone(*this)); + return *this; + } + + bool Entity::hasComponent(const std::type_info &type) const + { + auto existing = std::find_if(this->_components.begin(), this->_components.end(), [&type] (const auto &cmp) { + return typeid(*cmp) == type; + }); + return existing != this->_components.end(); + } +} \ No newline at end of file diff --git a/lib/wal/sources/Entity/Entity.hpp b/lib/wal/sources/Entity/Entity.hpp new file mode 100644 index 00000000..7a902ecd --- /dev/null +++ b/lib/wal/sources/Entity/Entity.hpp @@ -0,0 +1,113 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#pragma once + +#include +#include +#include +#include +#include "Component/Component.hpp" +#include "Exception/WalError.hpp" + +namespace WAL +{ + //! @brief An entity of the WAL's ECS. + class Entity + { + private: + //! @brief The unique ID of the entity + unsigned _uid; + //! @brief An entity name (this is useful for debugging) + std::string _name; + //! @brief Is this entity enabled? + bool _disabled = false; + //! @brief The list of the components of this entity + std::vector> _components = {}; + + //! @brief This ID will be the one of the next entity created. + static unsigned nextID; + public: + //! @brief Get the ID of the entity. + unsigned getUid() const; + //! @brief Get the name fo the entity + std::string getName() const; + + //! @brief Used if the entity is disabled + bool isDisable() const; + + //! @brief Disable this entity. + void setDisable(bool disabled); + + //! @brief Get a component of a specific type + //! @throw NotFoundError if the component could not be found + template + T &getComponent() + { + const std::type_info &type = typeid(T); + auto existing = std::find_if(this->_components.begin(), this->_components.end(), [&type] (const auto &cmp) { + return typeid(*cmp) == type; + }); + if (existing == this->_components.end()) + throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\"."); + return *static_cast(existing->get()); + } + + //! @brief Check if this entity has a component. + //! @tparam T The type of the component + template + bool hasComponent() const + { + const std::type_info &type = typeid(T); + return this->hasComponent(type); + } + + //! @brief Check if this entity has a component. + //! @param type The type of the component + bool hasComponent(const std::type_info &type) const; + + //! @brief Add a component to this entity. The component is constructed in place. + //! @throw DuplicateError is thrown if a component with the same type already exist. + //! @return This entity is returned + template + Entity &addComponent(Types ...params) + { + if (this->hasComponent()) + throw DuplicateError("A component of the type \"" + std::string(typeid(T).name()) + "\" already exists."); + this->_components.push_back(std::make_unique(*this, params...)); + return *this; + } + + //! @brief Copy a component to this entity. + //! @return This entity is returned. + Entity &addComponent(const Component &component); + + //! @brief Remove a specific component (by type). + //! @throw NotFoundError is thrown if the component could not be found. + //! @return This entity is returned. + template + Entity &removeComponent() + { + const std::type_info &type = typeid(T); + auto existing = std::find_if(this->_components.begin(), this->_components.end(), [&type] (const auto &cmp) { + return typeid(*cmp) == type; + }); + if (existing == this->_components.end()) + throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\"."); + this->_components.erase(existing); + return *this; + } + + //! @brief A default constructor + explicit Entity(std::string name); + //! @brief An entity is copyable + Entity(const Entity &); + //! @brief An entity is movable. + Entity(Entity &&) = default; + //! @brief A default destructor + ~Entity() = default; + //! @brief An entity is assignable + Entity &operator=(const Entity &) = default; + }; +} \ No newline at end of file diff --git a/lib/wal/sources/Events/EventManager.cpp b/lib/wal/sources/Events/EventManager.cpp new file mode 100644 index 00000000..69b939b7 --- /dev/null +++ b/lib/wal/sources/Events/EventManager.cpp @@ -0,0 +1,5 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#include "EventManager.hpp" diff --git a/lib/wal/sources/Events/EventManager.hpp b/lib/wal/sources/Events/EventManager.hpp new file mode 100644 index 00000000..a1f608e1 --- /dev/null +++ b/lib/wal/sources/Events/EventManager.hpp @@ -0,0 +1,15 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + + +#pragma once + +namespace WAL +{ + //! @brief A class to handle events. + class EventManager + { + + }; +} diff --git a/lib/wal/sources/Exception/WalError.cpp b/lib/wal/sources/Exception/WalError.cpp new file mode 100644 index 00000000..53581300 --- /dev/null +++ b/lib/wal/sources/Exception/WalError.cpp @@ -0,0 +1,20 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#include "WalError.hpp" + +namespace WAL +{ + WalError::WalError(const std::string &what) + : std::runtime_error(what) + {} + + DuplicateError::DuplicateError(const std::string &what) + : WalError(what) + {} + + NotFoundError::NotFoundError(const std::string &what) + : WalError(what) + {} +} \ No newline at end of file diff --git a/lib/wal/sources/Exception/WalError.hpp b/lib/wal/sources/Exception/WalError.hpp new file mode 100644 index 00000000..0c791647 --- /dev/null +++ b/lib/wal/sources/Exception/WalError.hpp @@ -0,0 +1,54 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + + +#pragma once + +#include +#include + +namespace WAL +{ + //! @brief The base class for WAL's exceptions. + class WalError : public std::runtime_error + { + public: + //! @brief Create a new wal exception + explicit WalError(const std::string &what); + //! @brief A wal exception is copy constructable + WalError(const WalError &) = default; + //! @brief A default destructor + ~WalError() override = default; + //! @brief A default assignment operator + WalError &operator=(const WalError &) = default; + }; + + //! @brief An exception informing the user that something already exists. + class DuplicateError : public WalError + { + public: + //! @brief Create a new wal exception + explicit DuplicateError(const std::string &what); + //! @brief A wal exception is copy constructable + DuplicateError(const DuplicateError &) = default; + //! @brief A default destructor + ~DuplicateError() override = default; + //! @brief A default assignment operator + DuplicateError &operator=(const DuplicateError &) = default; + }; + + //! @brief An exception informing the user that something could not be found + class NotFoundError : public WalError + { + public: + //! @brief Create a new wal exception + explicit NotFoundError(const std::string &what); + //! @brief A wal exception is copy constructable + NotFoundError(const NotFoundError &) = default; + //! @brief A default destructor + ~NotFoundError() override = default; + //! @brief A default assignment operator + NotFoundError &operator=(const NotFoundError &) = default; + }; +} diff --git a/lib/wal/sources/Models/Vector3.hpp b/lib/wal/sources/Models/Vector3.hpp new file mode 100644 index 00000000..05391c25 --- /dev/null +++ b/lib/wal/sources/Models/Vector3.hpp @@ -0,0 +1,167 @@ +// +// Created by Zoe Roux on 5/17/21. +// + + +#pragma once + +#include +#include + +namespace WAL +{ + //! @brief A Vector3 data type. (templated to allow any kind of vector3) + template + class Vector3 + { + public: + //! @brief The x value of the vector + T x; + //! @brief The y value of the vector + T y; + //! @brief The y value of the vector + T z; + + //! @brief Create a new nil vector3. + Vector3() + : x(0), y(0), z(0) + {} + + //! @brief Create a new vector3 representing a specific coordinate. + Vector3(T x, T y, T z) + : x(x), y(y), z(z) + {} + + //! @brief A default destructor + ~Vector3() = default; + + bool operator==(const Vector3 &other) const + { + return this->x == other.x && this->y == other.y && this->z == other.z; + } + + bool operator!=(const Vector3 &other) const + { + return !this->operator==(other); + } + + template + Vector3 &operator+=(const Vector3 &vec) + { + this->x += vec.x; + this->y += vec.y; + this->z += vec.z; + return *this; + } + + template + Vector3 operator+(const Vector3 &vec) const + { + return Vector3(this->x + vec.x, this->y + vec.y, this->z + vec.z); + } + + template + Vector3 &operator-=(const Vector3 &vec) + { + this->x -= vec.x; + this->y -= vec.y; + this->z -= vec.z; + return *this; + } + + template + Vector3 &operator*=(T2 d) + { + this->x *= d; + this->y *= d; + this->z *= d; + return *this; + } + + template + Vector3 operator*(T2 d) const + { + return Vector3(this->x * d, this->y * d, this->z * d); + } + + template + Vector3 operator*(Vector3 &b) const + { + return Vector3(this->x * b.x, this->y * b.y, this->z * b.z); + } + + template + Vector3 operator/=(Vector3 &b) + { + this->x /= b.x; + this->y /= b.y; + this->z /= b.z; + return this; + } + + template + Vector3 operator/(Vector3 &b) const + { + return Vector3(this->x / b.x, this->y / b.y, this->z / b.z); + } + + template + Vector3 operator/=(T2 b) + { + this->x /= b; + this->y /= b; + this->z /= b; + return this; + } + + template + Vector3 operator/(T2 b) const + { + return Vector3(this->x / b, this->y / b, this->z / b); + } + + template + double distance(const Vector3 &o) const + { + return std::sqrt(std::pow(this->x - o.x, 2) + std::pow(this->y - o.y, 2) + std::pow(this->z - o.z, 2)); + } + + double magnitude() const + { + return (std::sqrt(std::pow(this->x, 2) + std::pow(this->y, 2), std::pow(this->z, 2))); + } + + Vector3 normalize() + { + double mag = this->magnitude(); + + this->x /= mag; + this->y /= mag; + this->z /= mag; + return *this; + } + + Vector3 normalized() const + { + T mag = this->magnitude(); + + return Vector3(this->x / mag, this->y / mag, this->z / mag); + } + + Vector3 projection(const Vector3 &point) const + { + return (point * this) / std::pow(this->magnitude(), 2) * this; + } + }; + + typedef Vector3 Vector3f; + typedef Vector3 Vector3u; + typedef Vector3 Vector3i; +} + +template +std::ostream &operator<<(std::ostream &s, const WAL::Vector3 &v) +{ + s << "Vector3<" << typeid(T).name() << ">("<< v.x << ", " << v.y << ", " << v.z << ")"; + return s; +} \ No newline at end of file diff --git a/lib/wal/sources/Scene/Scene.cpp b/lib/wal/sources/Scene/Scene.cpp new file mode 100644 index 00000000..b0a3b3a7 --- /dev/null +++ b/lib/wal/sources/Scene/Scene.cpp @@ -0,0 +1,14 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#include "Scene.hpp" + +namespace WAL +{ + std::vector &Scene::getEntities() + { + return this->_entities; + } +} + diff --git a/lib/wal/sources/Scene/Scene.hpp b/lib/wal/sources/Scene/Scene.hpp new file mode 100644 index 00000000..bf86de44 --- /dev/null +++ b/lib/wal/sources/Scene/Scene.hpp @@ -0,0 +1,23 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + + +#pragma once + +#include +#include "Entity/Entity.hpp" + +namespace WAL +{ + //! @brief Represent a single scene that contains entities. + class Scene + { + private: + //! @brief The list of registered entities + std::vector _entities; + public: + //! @brief Get the list of entities. + std::vector &getEntities(); + }; +} diff --git a/lib/wal/sources/Scene/SceneManager.cpp b/lib/wal/sources/Scene/SceneManager.cpp new file mode 100644 index 00000000..21c38221 --- /dev/null +++ b/lib/wal/sources/Scene/SceneManager.cpp @@ -0,0 +1,33 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#include "SceneManager.hpp" + +namespace WAL +{ + SceneManager &WAL::SceneManager::addScene(WAL::Scene &&scene) + { + this->_scenes.push_front(scene); + return *this; + } + + SceneManager &SceneManager::addBackScene(Scene &&scene) + { + this->_scenes.insert(++this->_scenes.begin(), scene); + return *this; + } + + Scene &SceneManager::getCurrent() + { + if (this->_scenes.empty()) + throw NotFoundError("No scene exists."); + return this->_scenes.front(); + } + + SceneManager &SceneManager::closeCurrent() + { + this->_scenes.pop_front(); + return *this; + } +} \ No newline at end of file diff --git a/lib/wal/sources/Scene/SceneManager.hpp b/lib/wal/sources/Scene/SceneManager.hpp new file mode 100644 index 00000000..16268e75 --- /dev/null +++ b/lib/wal/sources/Scene/SceneManager.hpp @@ -0,0 +1,43 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + + +#pragma once + +#include +#include "Scene/Scene.hpp" + +namespace WAL +{ + //! @brief A class to manage scenes + class SceneManager + { + private: + std::deque _scenes = {}; + public: + //! @brief Add a scene to the container and move to it. + //! @return The manager instance used to call this function is returned. This allow method chaining. + SceneManager &addScene(Scene &&scene); + + //! @brief Add a scene before the current scene. This could be useful for lobbies or scene where the next scene can be constructed. + //! @return The manager instance used to call this function is returned. This allow method chaining. + SceneManager &addBackScene(Scene &&scene); + + //! @breif Get the current scene + Scene &getCurrent(); + + //! @brief Remove the current scene and switch to the previous scene on the stack. + //! @return The manager instance used to call this function is returned. This allow method chaining. + SceneManager &closeCurrent(); + + //! @brief A default constructor + SceneManager() = default; + //! @brief A scene manager is copy constructable + SceneManager(const SceneManager &) = default; + //! @brief A default destructor. + ~SceneManager() = default; + //! @brief A scene manager is assignable + SceneManager &operator=(const SceneManager &) = default; + }; +} diff --git a/lib/wal/sources/System/Movable/MovableSystem.cpp b/lib/wal/sources/System/Movable/MovableSystem.cpp new file mode 100644 index 00000000..600f7c8e --- /dev/null +++ b/lib/wal/sources/System/Movable/MovableSystem.cpp @@ -0,0 +1,26 @@ +// +// Created by Zoe Roux on 5/17/21. +// + +#include "Component/Position/PositionComponent.hpp" +#include "System/Movable/MovableSystem.hpp" +#include "Component/Movable/MovableComponent.hpp" +#include "Wal.hpp" + +namespace WAL +{ + const std::type_info &MovableSystem::getComponent() const + { + return typeid(MovableComponent); + } + + void MovableSystem::onFixedUpdate(Entity &entity) + { + auto &movable = entity.getComponent(); + auto &position = entity.getComponent(); + + position.position += movable._velocity * Wal::timestep.count(); + movable._velocity = movable._acceleration * Wal::timestep.count(); + movable._acceleration = Vector3f(); + } +} \ No newline at end of file diff --git a/lib/wal/sources/System/Movable/MovableSystem.hpp b/lib/wal/sources/System/Movable/MovableSystem.hpp new file mode 100644 index 00000000..400c7830 --- /dev/null +++ b/lib/wal/sources/System/Movable/MovableSystem.hpp @@ -0,0 +1,30 @@ + +// +// Created by Zoe Roux on 5/17/21. +// + +#pragma once + +#include "System/System.hpp" + +namespace WAL +{ + //! @brief A system to handle movable entities. This system update velocity based on accelerations and positions based on velocity. + class MovableSystem : public System + { + public: + //! @inherit + const std::type_info &getComponent() const override; + //! @inherit + void onFixedUpdate(Entity &entity) override; + + //! @brief A default constructor + MovableSystem() = default; + //! @brief A movable system is copy constructable + MovableSystem(const MovableSystem &) = default; + //! @brief A default destructor + ~MovableSystem() override = default; + //! @brief A movable system is assignable. + MovableSystem &operator=(const MovableSystem &) = default; + }; +} diff --git a/lib/wal/sources/System/System.cpp b/lib/wal/sources/System/System.cpp new file mode 100644 index 00000000..82f7fe11 --- /dev/null +++ b/lib/wal/sources/System/System.cpp @@ -0,0 +1,18 @@ +// +// Created by Zoe Roux on 5/17/21. +// + +#include "System.hpp" + +namespace WAL +{ + + void System::onUpdate(Entity &entity, std::chrono::nanoseconds dtime) + {} + + void System::onFixedUpdate(Entity &entity) + {} + + void System::onSelfUpdate() + {} +} \ No newline at end of file diff --git a/lib/wal/sources/System/System.hpp b/lib/wal/sources/System/System.hpp new file mode 100644 index 00000000..3813e216 --- /dev/null +++ b/lib/wal/sources/System/System.hpp @@ -0,0 +1,44 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#pragma once + +#include +#include "Entity/Entity.hpp" + +namespace WAL +{ + //! @brief A base system of WAL + class System + { + public: + //! @brief A virtual, default, destructor + virtual ~System() = default; + //! @brief A system can be moved + System(System &&) = default; + + //! @brief Get the name of the component corresponding to this system. + virtual const std::type_info &getComponent() const = 0; + + //! @brief Update the corresponding component of the given entity + //! @param entity The entity to update. + //! @param dtime The delta time. + virtual void onUpdate(Entity &entity, std::chrono::nanoseconds dtime); + + //! @brief An alternative of onUpdate that is called every 8ms (120 times per seconds). If the system slow down, it will try to catch up. + //! @remark This should be used for Physics, AI and everything that could be imprecise due to float rounding. + //! @param entity The entity to update. + virtual void onFixedUpdate(Entity &entity); + + //! @brief A method called after all entities that this system manage has been updated. + virtual void onSelfUpdate(); + protected: + //! @brief A system can't be instantiated, it should be derived. + System() = default; + //! @brief A system can't be instantiated, it should be derived. + System(const System &) = default; + //! @brief A system can't be instantiated, it should be derived. + System &operator=(const System &) = default; + }; +} \ No newline at end of file diff --git a/lib/wal/sources/Wal.cpp b/lib/wal/sources/Wal.cpp new file mode 100644 index 00000000..569743aa --- /dev/null +++ b/lib/wal/sources/Wal.cpp @@ -0,0 +1,68 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + +#include +#include "Wal.hpp" + +using namespace std::chrono_literals; + +namespace WAL +{ + std::chrono::nanoseconds Wal::timestep = 8ms; + + SceneManager &Wal::getSceneManager() + { + return this->_sceneManager; + } + + void Wal::run() + { + auto lastTick = std::chrono::steady_clock::now(); + std::chrono::nanoseconds fBehind(0); + + while (!this->_shouldClose) { + auto now = std::chrono::steady_clock::now(); + std::chrono::nanoseconds dtime = now - lastTick; + fBehind += dtime; + lastTick = now; + + while (fBehind > Wal::timestep) { + fBehind -= Wal::timestep; + this->_fixedUpdate(); + } + this->_update(dtime); + } + } + + void Wal::_update(std::chrono::nanoseconds dtime) + { + auto &entities = this->_sceneManager.getCurrent().getEntities(); + + for (auto &system : this->_systems) { + for (auto &entity : entities) { + const auto &cmp = system->getComponent(); + if (!entity.hasComponent(cmp)) + continue; + // TODO handle dependencies. + system->onUpdate(entity, dtime); + } + system->onSelfUpdate(); + } + } + + void Wal::_fixedUpdate() + { + auto &entities = this->_sceneManager.getCurrent().getEntities(); + + for (auto &system : this->_systems) { + for (auto &entity : entities) { + auto &cmp = system->getComponent(); + if (!entity.hasComponent(cmp)) + continue; + // TODO handle dependencies. + system->onFixedUpdate(entity); + } + } + } +} \ No newline at end of file diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp new file mode 100644 index 00000000..ea72eb05 --- /dev/null +++ b/lib/wal/sources/Wal.hpp @@ -0,0 +1,115 @@ +// +// Created by Zoe Roux on 2021-05-14. +// + + +#pragma once + +#include +#include +#include +#include +#include "Events/EventManager.hpp" +#include "Scene/SceneManager.hpp" +#include "Entity/Entity.hpp" +#include "System/System.hpp" + +namespace WAL +{ + //! @brief The main WAL class, it is used to setup and run the ECS. + class Wal + { + private: + //! @brief The scene manager that allow multiple scene to work together. + SceneManager _sceneManager; + //! @brief The event manager + EventManager _eventManager; + //! @brief The list of registered systems + std::vector> _systems = {}; + //! @brief True if the engine should close after the end of the current tick. + bool _shouldClose = false; + + //! @brief Call the onUpdate of every system with every component + void _update(std::chrono::nanoseconds dtime); + + //! @brief Call the onFixedUpdate of every system with every component + void _fixedUpdate(); + public: + //! @brief The time between each fixed update. + static std::chrono::nanoseconds timestep; + + //! @brief Create a new system in place. + //! @return The wal instance used to call this function is returned. This allow method chaining. + template + Wal &addSystem(Types ...params) + { + const std::type_info &type = typeid(T); + auto existing = std::find_if(this->_systems.begin(), this->_systems.end(), [&type] (auto &sys) { + return typeid(*sys) == type; + }); + if (existing != this->_systems.end()) + throw DuplicateError("A system of the type \"" + std::string(type.name()) + "\" already exists."); + this->_systems.push_back(std::make_unique(params...)); + return *this; + } + + //! @brief Add a system by copy. + //! @return The wal instance used to call this function is returned. This allow method chaining. + template + Wal &addSystem(const T &system) + { + const std::type_info &type = typeid(T); + auto existing = std::find_if(this->_systems.begin(), this->_systems.end(), [&type] (auto &sys) { + return typeid(*sys) == type; + }); + if (existing != this->_systems.end()) + throw DuplicateError("A system of the type \"" + std::string(type.name()) + "\" already exists."); + this->_systems.push_back(std::make_unique(system)); + return *this; + } + + //! @brief Get a system of a specific type + //! @tparam T the type of the system. + template + T &getSystem() + { + const std::type_info &type = typeid(T); + auto existing = std::find_if(this->_systems.begin(), this->_systems.end(), [&type] (auto &sys) { + return typeid(*sys) == type; + }); + if (existing == this->_systems.end()) + throw NotFoundError("A system of the type \"" + std::string(type.name()) + "\" could not be found."); + return *static_cast(existing->get()); + + } + + //! @brief Remove a system using it's type. + template + Wal &removeSystem() + { + const std::type_info &type = typeid(T); + auto existing = std::find_if(this->_systems.begin(), this->_systems.end(), [&type] (auto &sys) { + return typeid(*sys) == type; + }); + if (existing == this->_systems.end()) + throw NotFoundError("No system could be found with the type \"" + std::string(type.name()) + "\"."); + this->_systems.erase(existing); + return *this; + } + + //! @brief Get the scene manager. + SceneManager &getSceneManager(); + + //! @brief Start the game loop + void run(); + + //! @brief A default constructor + Wal() = default; + //! @brief A WAL can't be copy constructed + Wal(const Wal &) = delete; + //! @brief A default destructor + ~Wal() = default; + //! @brief A WAL can't be assigned. + Wal &operator=(const Wal &) = delete; + }; +} diff --git a/lib/wal/tests/EngineTests.cpp b/lib/wal/tests/EngineTests.cpp new file mode 100644 index 00000000..c6074138 --- /dev/null +++ b/lib/wal/tests/EngineTests.cpp @@ -0,0 +1,36 @@ +// +// Created by Zoe Roux on 5/17/21. +// + + +#include "Wal.hpp" +#include "System/Movable/MovableSystem.hpp" +#include + +using namespace WAL; + +TEST_CASE("Create system", "[Engine][System]") +{ + Wal wal; + wal.addSystem(); + + SECTION("Check existence") { + REQUIRE_NOTHROW(wal.getSystem()); + } + SECTION("Duplicate check") { + REQUIRE_THROWS_AS(wal.addSystem(), DuplicateError); + } + SECTION("Remove system") { + wal.removeSystem(); + REQUIRE_THROWS_AS(wal.getSystem(), NotFoundError); + REQUIRE_THROWS_AS(wal.removeSystem(), NotFoundError); + } +} + +TEST_CASE("Create system by reference", "[Engine][System]") +{ + Wal wal; + MovableSystem system; + wal.addSystem(system); + REQUIRE_THROWS_AS(wal.addSystem(), DuplicateError); +} \ No newline at end of file diff --git a/lib/wal/tests/EntityTests.cpp b/lib/wal/tests/EntityTests.cpp new file mode 100644 index 00000000..6c6c4edf --- /dev/null +++ b/lib/wal/tests/EntityTests.cpp @@ -0,0 +1,44 @@ +// +// Created by Zoe Roux on 5/17/21. +// + +#include "Entity/Entity.hpp" +#include "Component/Position/PositionComponent.hpp" +#include + +using namespace WAL; + +TEST_CASE("Component", "[Entity]") +{ + Entity entity("Bob"); + entity.addComponent(2, 3, 4); + + SECTION("Check value") { + auto &pos = entity.getComponent(); + REQUIRE(entity.hasComponent()); + REQUIRE(pos.position == Vector3f(2, 3, 4)); + } + SECTION("Prevent duplicates") { + REQUIRE_THROWS_AS(entity.addComponent(), DuplicateError); + } + SECTION("Remove component") { + entity.removeComponent(); + REQUIRE_THROWS_AS(entity.getComponent(), NotFoundError); + REQUIRE_THROWS_AS(entity.removeComponent(), NotFoundError); + } +} + +TEST_CASE("ComponentNotFound", "[Entity]") +{ + Entity entity("Bob"); + REQUIRE_THROWS_AS(entity.getComponent(), NotFoundError); +} + +TEST_CASE("Add component by reference", "[Entity]") +{ + Entity entity("Bob"); + PositionComponent component(entity, 4, 5, 6); + + REQUIRE(&entity.addComponent(component) == &entity); + REQUIRE(entity.getComponent().position == Vector3f(4, 5, 6)); +} \ No newline at end of file diff --git a/lib/wal/tests/MainTest.cpp b/lib/wal/tests/MainTest.cpp new file mode 100644 index 00000000..6f2c3690 --- /dev/null +++ b/lib/wal/tests/MainTest.cpp @@ -0,0 +1,6 @@ +// +// Created by Zoe Roux on 5/17/21. +// + +#define CATCH_CONFIG_MAIN +#include \ No newline at end of file diff --git a/sources/main.cpp b/sources/main.cpp index 11d982c5..70491566 100644 --- a/sources/main.cpp +++ b/sources/main.cpp @@ -1,7 +1,15 @@ #include +#include int main() { - std::cout << "Hello, World!" << std::endl; - return 0; + WAL::Wal wal; + + try { + wal.run(); + return 0; + } catch (const std::exception &ex) { + std::cerr << ex.what() << std::endl; + return 84; + } }