Merge pull request #116 from AnonymusRaccoon/ecs

Views of entities and components
This commit is contained in:
Zoe Roux
2021-06-07 15:54:23 +02:00
committed by GitHub
45 changed files with 868 additions and 466 deletions

View File

@@ -85,8 +85,9 @@ add_executable(unit_tests EXCLUDE_FROM_ALL
tests/EngineTests.cpp
tests/CallbackTest.cpp
tests/MoveTests.cpp
tests/ViewTest.cpp
tests/CollisionTest.cpp
)
)
target_include_directories(unit_tests PUBLIC sources)
target_link_libraries(unit_tests PUBLIC wal ray)

View File

@@ -7,7 +7,6 @@ add_library(wal
sources/Entity/Entity.hpp
sources/Component/Component.hpp
sources/System/System.hpp
sources/Wal.cpp
sources/Wal.hpp
sources/Scene/Scene.cpp
sources/Scene/Scene.hpp
@@ -15,8 +14,10 @@ add_library(wal
sources/Exception/WalError.hpp
sources/Entity/Entity.cpp
sources/Component/Component.cpp
sources/System/System.cpp
sources/Models/Callback.hpp
sources/Models/TypeHolder.hpp)
sources/View/View.hpp
sources/System/ISystem.hpp
sources/Models/TypeHolder.hpp
)
target_include_directories(wal PUBLIC sources)

View File

@@ -3,6 +3,7 @@
//
#include "Entity/Entity.hpp"
#include "Scene/Scene.hpp"
#include <string>
#include <utility>
@@ -10,18 +11,20 @@ namespace WAL
{
unsigned Entity::nextID = 0;
Entity::Entity(std::string name)
Entity::Entity(Scene &scene, std::string name)
: _uid(Entity::nextID++),
_scene(scene),
_name(std::move(name))
{ }
Entity::Entity(const Entity &other)
: _uid(Entity::nextID++),
_scene(other._scene),
_name(other._name),
_disabled(other._disabled)
{
for (const auto &cmp : other._components)
this->addComponent(*cmp);
this->addComponent(*cmp.second);
}
unsigned Entity::getUid() const
@@ -46,25 +49,31 @@ namespace WAL
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));
const std::type_index &type = typeid(component);
if (this->hasComponent(type))
throw DuplicateError("A component of the type \"" + std::string(type.name()) + "\" already exists.");
this->_components.emplace(type, component.clone(*this));
this->_scene._componentAdded(*this, type);
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();
return this->hasComponent(static_cast<const std::type_index &>(type));
}
bool Entity::hasComponent(const std::type_index &type) const
{
auto existing = std::find_if(this->_components.begin(), this->_components.end(), [&type] (const auto &cmp) {
return std::type_index(typeid(*cmp)) == type;
});
return existing != this->_components.end();
return this->_components.contains(type);
}
void Entity::_componentAdded(const std::type_index &type)
{
this->_scene._componentAdded(*this, type);
}
void Entity::_componentRemoved(const std::type_index &type)
{
this->_scene._componentRemoved(*this, type);
}
} // namespace WAL

View File

@@ -5,7 +5,7 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <typeinfo>
#include <memory>
#include "Component/Component.hpp"
@@ -14,6 +14,8 @@
namespace WAL
{
class Scene;
//! @brief An entity of the WAL's ECS.
class Entity
{
@@ -25,10 +27,20 @@ namespace WAL
//! @brief Is this entity enabled?
bool _disabled = false;
//! @brief The list of the components of this entity
std::vector<std::unique_ptr<Component>> _components = {};
std::unordered_map<std::type_index, std::unique_ptr<Component>> _components = {};
//! @brief This ID will be the one of the next entity created.
static unsigned nextID;
//! @brief Callback called when a component is added
//! @param type The type of component
void _componentAdded(const std::type_index &type);
//! @brief Callback called when a component is removed
//! @param type The type of component
void _componentRemoved(const std::type_index &type);
protected:
//! @brief A reference to the ECS.
Scene &_scene;
public:
//! @brief Get the ID of the entity.
unsigned getUid() const;
@@ -42,17 +54,55 @@ namespace WAL
void setDisable(bool disabled);
//! @brief Get a component of a specific type
//! @tparam The type of the component
//! @throw NotFoundError if the component could not be found
//! @return The component of the requested type.
template<typename T>
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;
});
T *ret = this->tryGetComponent<T>();
if (ret == nullptr)
throw NotFoundError("No component could be found with the type \"" + std::string(typeid(T).name()) + "\".");
return *ret;
}
//! @brief Get a component of a specific type or null if not found.
//! @tparam The type of the component
//! @return The component or nullptr if not found.
template<typename T>
T *tryGetComponent()
{
const std::type_index &type = typeid(T);
auto existing = this->_components.find(type);
if (existing == this->_components.end())
throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\".");
return *static_cast<T *>(existing->get());
return nullptr;
return static_cast<T *>(existing->second.get());
}
//! @brief Get a component of a specific type
//! @tparam The type of the component
//! @throw NotFoundError if the component could not be found
//! @return The component of the requested type.
template<typename T>
const T &getComponent() const
{
const T *ret = this->tryGetComponent<T>();
if (ret == nullptr)
throw NotFoundError("No component could be found with the type \"" + std::string(typeid(T).name()) + "\".");
return *ret;
}
//! @brief Get a component of a specific type or null if not found.
//! @tparam The type of the component
//! @return The component or nullptr if not found.
template<typename T>
const T *tryGetComponent() const
{
const std::type_index &type = typeid(T);
auto existing = this->_components.find(type);
if (existing == this->_components.end())
return nullptr;
return static_cast<T *>(existing->second.get());
}
//! @brief Check if this entity has a component.
@@ -75,24 +125,14 @@ namespace WAL
//! @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<typename T, typename ...Types>
template<typename T, typename ...TNested, typename ...Types>
Entity &addComponent(Types &&...params)
{
if (this->hasComponent<T>())
throw DuplicateError("A component of the type \"" + std::string(typeid(T).name()) + "\" already exists.");
this->_components.push_back(std::make_unique<T>(*this, std::forward<Types>(params)...));
return *this;
}
//! @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<typename T, typename TNested, typename ...Types>
Entity &addComponent(Types &&...params)
{
if (this->hasComponent<T>())
throw DuplicateError("A component of the type \"" + std::string(typeid(T).name()) + "\" already exists.");
this->_components.push_back(std::make_unique<T>(*this, TypeHolder<TNested>(), std::forward<Types>(params)...));
const std::type_index &type = typeid(T);
if (this->hasComponent(type))
throw DuplicateError("A component of the type \"" + std::string(type.name()) + "\" already exists.");
this->_components[type] = std::make_unique<T>(*this, TypeHolder<TNested>()..., std::forward<Types>(params)...);
this->_componentAdded(type);
return *this;
}
@@ -107,17 +147,16 @@ namespace WAL
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;
});
auto existing = this->_components.find(type);
if (existing == this->_components.end())
throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\".");
this->_components.erase(existing);
this->_componentRemoved(type);
return *this;
}
//! @brief A default constructor
explicit Entity(std::string name);
explicit Entity(Scene &wal, std::string name);
//! @brief An entity is copyable
Entity(const Entity &);
//! @brief An entity is movable.

View File

@@ -24,10 +24,14 @@ namespace WAL
//! @brief Add a method to be called when this callback is invoked.
//! @param callback The list of arguments of the callback method
//! @return A unique ID for this callback. That can be used to remove the callback later.
int addCallback(std::function<void (Types...)> callback)
template<typename Func>
int addCallback(Func callback)
{
int id = this->_nextID++;
if constexpr(std::is_same_v<Func, std::function<void (Types...)>>)
this->_functions[id] = std::move(callback);
else
this->_functions[id] = std::function<void (Types...)>(callback);
return id;
}
@@ -53,8 +57,9 @@ namespace WAL
//! @brief A default assignment operator
Callback &operator=(const Callback &) = default;
//! @brief Implicitly transform a function into a callback.
Callback(std::function<void (Types...)> callback) // NOLINT(google-explicit-constructor)
//! @brief Implicitly transform a callable into a callback.
template<typename Func>
Callback(Func callback) // NOLINT(google-explicit-constructor)
{
this->addCallback(callback);
}

View File

@@ -3,15 +3,46 @@
//
#include "Scene.hpp"
#include <algorithm>
namespace WAL
{
std::vector<Entity> &Scene::getEntities()
int Scene::_nextID = 0;
std::list<Entity> &Scene::getEntities()
{
return this->_entities;
}
Scene &Scene::operator=(const Scene &)
{
return *this;
}
Entity &Scene::addEntity(const std::string &name)
{
return this->_entities.emplace_back(*this, name);
}
void Scene::_componentAdded(Entity &entity, const std::type_index &type)
{
for (auto &view : this->_views) {
if (std::find(view->getTypes().begin(), view->getTypes().end(), type) == view->getTypes().end())
continue;
bool valid = std::all_of(view->getTypes().begin(), view->getTypes().end(), [&entity](const auto &type){
return entity.hasComponent(type);
});
if (valid)
view->emplace_back(entity);
}
}
void Scene::_componentRemoved(const Entity &entity, const std::type_index &type)
{
for (auto &view : this->_views) {
if (std::find(view->getTypes().begin(), view->getTypes().end(), type) == view->getTypes().end())
continue;
view->erase(entity);
}
}
} // namespace WAL

View File

@@ -6,7 +6,9 @@
#pragma once
#include <vector>
#include <list>
#include <functional>
#include <View/View.hpp>
#include "Entity/Entity.hpp"
namespace WAL
@@ -15,18 +17,43 @@ namespace WAL
class Scene
{
private:
static int _nextID;
//! @brief An ID representing this scene.
int _id = _nextID++;
//! @brief The list of registered entities
std::vector<Entity> _entities = {};
std::list<Entity> _entities = {};
//! @brief The list of cached views to update.
std::vector<std::shared_ptr<IView>> _views = {};
//! @brief Notify this scene that a component has been added to the given entity.
//! @param entity The entity with the new component
//! @param type The type of the component added.
void _componentAdded(Entity &entity, const std::type_index &type);
//! @brief Notify this scene that a component has been removed to the given entity.
//! @param entity The entity with the removed component
//! @param type The type of the component removed.
void _componentRemoved(const Entity &entity, const std::type_index &type);
public:
//! @brief Get the list of entities.
std::vector<Entity> &getEntities();
std::list<Entity> &getEntities();
//! @brief Add a new entity to the scene, you can use this method with the same arguments as the entity's constructor.
//! @return The current scene is returned to allow you to chain call.
template <class ...Params>
Entity &addEntity(Params &&...params)
//! @brief Add a new entity to the scene.
//! @param name The name of the created entity.
//! @return The created entity is returned.
Entity &addEntity(const std::string &name);
template<typename ...Components>
View<Components...> &view()
{
return this->_entities.emplace_back(std::forward<Params>(params)...);
static std::unordered_map<int, std::weak_ptr<View<Components...>>> cache;
auto existing = cache.find(this->_id);
if (existing != cache.end() && !existing->second.expired())
return *existing->second.lock();
auto view = std::make_shared<View<Components...>>(this->_entities);
this->_views.emplace_back(view);
cache.emplace(this->_id, view);
return *view;
}
//! @brief A default constructor
@@ -38,5 +65,7 @@ namespace WAL
//! @brief A scene is assignable
Scene &operator=(const Scene &);
Scene(Scene &&) = default;
friend Entity;
};
} // namespace WAL

View File

@@ -0,0 +1,32 @@
//
// Created by Zoe Roux on 2021-06-04.
//
#pragma once
#include "Entity/Entity.hpp"
#include "View/View.hpp"
#include <chrono>
namespace WAL
{
//! @brief A base class that represent a system.
class ISystem
{
public:
//! @brief Update the whole system (every entities that this system is responsible can be updated.
//! @param dtime The delta time since the last call to this method.
virtual void update(std::chrono::nanoseconds dtime) = 0;
//! @brief An alternative of update 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.
virtual void fixedUpdate() = 0;
//! @brief Get a view containing every entity this system should update.
virtual IView &getView() = 0;
//! @brief A virtual default destructor.
virtual ~ISystem() = default;
};
}

View File

@@ -1,28 +0,0 @@
//
// Created by Zoe Roux on 5/17/21.
//
#include "System.hpp"
#include <vector>
#include <utility>
namespace WAL
{
System::System(std::vector<std::type_index> dependencies)
: _dependencies(std::move(dependencies))
{}
void System::onUpdate(Entity &entity, std::chrono::nanoseconds dtime)
{}
void System::onFixedUpdate(Entity &entity)
{}
void System::onSelfUpdate()
{}
const std::vector<std::type_index> &System::getDependencies() const
{
return this->_dependencies;
}
} // namespace WAL

View File

@@ -6,40 +6,71 @@
#include <chrono>
#include <vector>
#include <typeinfo>
#include <typeindex>
#include "Entity/Entity.hpp"
#include "Wal.hpp"
#include "View/View.hpp"
#include "ISystem.hpp"
#include <iostream>
namespace WAL
{
//! @brief A base system of WAL
class System
//! @tparam Dependencies The list of dependencies this system has.
template<typename ...Dependencies>
class System : public ISystem
{
private:
//! @brief The list of dependencies of this system
std::vector<std::type_index> _dependencies = {};
public:
//! @brief A virtual, default, destructor
virtual ~System() = default;
~System() override = default;
//! @brief A system can be moved
System(System &&) = default;
System(System &&) noexcept = default;
//! @brief Get the name of the component corresponding to this system.
const std::vector<std::type_index> &getDependencies() const;
//! @brief Get a view of all entities containing every dependencies of this system.
View<Dependencies...> &getView() override
{
return this->_wal.scene->template view<Dependencies...>();
}
//! @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);
virtual void onUpdate(ViewEntity<Dependencies...> &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);
virtual void onFixedUpdate(ViewEntity<Dependencies...> &entity) {}
//! @brief A method called after all entities that this system manage has been updated.
virtual void onSelfUpdate();
virtual void onSelfUpdate() {}
//! @brief Update the whole system (every entities that this system is responsible can be updated.
//! @param dtime The delta time since the last call to this method.
void update(std::chrono::nanoseconds dtime) final
{
for (auto &entity : this->getView())
this->onUpdate(entity, dtime);
this->onSelfUpdate();
}
//! @brief An alternative of update 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.
void fixedUpdate() final
{
for (auto &entity : this->getView())
this->onFixedUpdate(entity);
}
protected:
//! @brief A reference to the ECS.
Wal &_wal;
//! @brief A system can't be instantiated, it should be derived.
explicit System(std::vector<std::type_index> dependencies);
explicit System(Wal &wal)
: _wal(wal)
{}
//! @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.

View File

@@ -0,0 +1,222 @@
//
// Created by Zoe Roux on 2021-06-03.
//
#pragma once
#include <list>
#include <tuple>
#include <typeindex>
#include <functional>
#include <utility>
#include <optional>
#include "Entity/Entity.hpp"
namespace WAL
{
template<typename ...Components>
class ViewEntity
{
private:
std::tuple<std::reference_wrapper<Entity>, std::reference_wrapper<Components>...> &_value;
public:
explicit ViewEntity(std::tuple<std::reference_wrapper<Entity>, std::reference_wrapper<Components>...> &value)
: _value(value)
{}
Entity *operator->()
{
return &(std::get<0>(this->_value).get());
}
Entity &operator*()
{
return std::get<0>(this->_value);
}
operator Entity &()
{
return std::get<0>(this->_value);
}
template<typename T>
T &get()
{
return std::get<std::reference_wrapper<T>>(this->_value);
}
template<std::size_t I>
auto &get()
{
return std::get<I>(this->_value);
}
};
template<typename It, typename ...Components>
class ViewIterator
{
private:
It _it;
std::optional<ViewEntity<Components...>> _entity;
public:
ViewEntity<Components...> &operator*()
{
if (!this->_entity)
this->_entity.emplace(*this->_it);
return *this->_entity;
}
ViewEntity<Components...> *operator->()
{
if (!this->_entity)
this->_entity =(*this->_it);
return &this->_entity;
}
ViewIterator &operator++()
{
this->_it++;
this->_entity = std::nullopt;
return *this;
}
ViewIterator operator++(int)
{
ViewIterator copy = *this;
this->_it++;
this->_entity = std::nullopt;
return *this;
}
bool operator==(const ViewIterator &other) const
{
return this->_it == other._it;
}
bool operator!=(const ViewIterator &other) const
{
return !this->operator==(other);
}
explicit ViewIterator(It current)
: _it(current),
_entity(std::nullopt)
{}
};
//! @brief A basic view used to manipulate view without knowing their type at compile time.
class IView
{
public:
//! @brief The list of types that every entity of the view has.
virtual const std::vector<std::type_index> &getTypes() const = 0;
virtual void emplace_back(Entity &) = 0;
virtual void erase(const Entity &) = 0;
//! @brief A default destructor
virtual ~IView() = default;
};
//! @brief A view allowing one to easily access entities containing a set list of component.
//! A view is always updated and only references to entities are kept.
template<typename ...Components>
class View : public IView
{
private:
using entity_type = std::tuple<std::reference_wrapper<Entity>, std::reference_wrapper<Components>...>;
//! @brief The list of entities in the view.
std::vector<entity_type> _entities = {};
//! @brief The list of types that every entity of the view has.
std::vector<std::type_index> _types = {};
public:
using iterator = ViewIterator<typename std::vector<entity_type>::iterator, Components...>;
iterator begin()
{
return iterator(this->_entities.begin());
}
iterator end()
{
return iterator(this->_entities.end());
}
std::size_t size() const
{
return this->_entities.size();
}
ViewEntity<Components...> front()
{
return *iterator(this->_entities.begin());
}
ViewEntity<Components...> back()
{
return *iterator(--this->_entities.end());
}
const std::vector<std::type_index> &getTypes() const override
{
return this->_types;
}
void emplace_back(Entity &entity) override
{
auto tuple = std::make_tuple<Components *...>(entity.tryGetComponent<Components>()...);
if (std::apply([](const auto *...component) {return ((component == nullptr) || ...);}, tuple))
return;
std::apply([&](auto *...component) {
this->_entities.emplace_back(entity, *component...);
}, tuple);
}
void erase(const Entity &entity) override
{
this->_entities.erase(std::remove_if(this->_entities.begin(), this->_entities.end(), [&entity](const auto &ref){
return &std::get<0>(ref).get() == &entity;
}));
}
//! @brief Construct a view from a list of entities.
//! Those entities are never copied but references to them are kept internally.
explicit View(std::list<Entity> &scene)
{
this->_types = {typeid(Components)...};
for (auto &entity : scene)
this->emplace_back(entity);
}
//! @brief Copying a view is not possible since a view must be managed by a scene.
View(const View &) = delete;
//! @brief A default destructor
~View() override = default;
//! @brief A view is not assignable.
View &operator=(const View &) = delete;
};
}
namespace std
{
template<typename ...Components>
struct tuple_size<::WAL::ViewEntity<Components...>>
: public std::integral_constant<std::size_t, 1 + sizeof...(Components)>
{};
template<typename ...Components>
struct tuple_element<0, ::WAL::ViewEntity<Components...>>
{
using type = WAL::Entity &;
};
template<std::size_t N, typename ...Components>
struct tuple_element<N, ::WAL::ViewEntity<Components...>>
{
using type = typename std::tuple_element<N - 1, std::tuple<Components...>>::type;
};
}

View File

@@ -1,50 +0,0 @@
//
// Created by Zoe Roux on 2021-05-14.
//
#include <chrono>
#include <algorithm>
#include "Wal.hpp"
namespace WAL
{
std::chrono::nanoseconds Wal::timestep = std::chrono::milliseconds(8);
void Wal::_update(std::chrono::nanoseconds dtime)
{
auto &entities = this->scene->getEntities();
for (auto &system : this->_systems) {
for (auto &entity : entities) {
if (!Wal::_hasDependencies(entity, *system))
continue;
system->onUpdate(entity, dtime);
}
system->onSelfUpdate();
}
}
void Wal::_fixedUpdate()
{
auto &entities = this->scene->getEntities();
for (auto &system : this->_systems) {
for (auto &entity : entities) {
if (!Wal::_hasDependencies(entity, *system))
continue;
system->onFixedUpdate(entity);
}
}
}
bool Wal::_hasDependencies(const Entity &entity, const System &system)
{
// TODO use an hashmap to cache results.
const auto &dependency = system.getDependencies();
for (const auto &dependency : system.getDependencies()) {
if (!entity.hasComponent(dependency))
return false;
}
return true;
}
} // namespace WAL

View File

@@ -10,10 +10,9 @@
#include <memory>
#include <typeinfo>
#include "Exception/WalError.hpp"
#include "Scene/Scene.hpp"
#include "Entity/Entity.hpp"
#include "System/System.hpp"
#include "System/ISystem.hpp"
#include "Models/Callback.hpp"
#include "Scene/Scene.hpp"
#if defined(PLATFORM_WEB)
#include <emscripten/emscripten.h>
@@ -21,31 +20,71 @@
namespace WAL
{
class Entity;
//! @brief The main WAL class, it is used to setup and run the ECS.
class Wal
{
private:
//! @brief The list of registered systems
std::vector<std::unique_ptr<System>> _systems = {};
std::vector<std::unique_ptr<ISystem>> _systems = {};
//! @brief Call the onUpdate of every system with every component
void _update(std::chrono::nanoseconds dtime);
//! @brief Start the game loop
//! @param callback A callback called after each update of the game. It allow you to update the engine based on a specific game state. (you can also update the game state here)
//! @param state An initial game state. If not specified, it will be defaulted.
//! @tparam T A type used to track your game state. It must be default constructable.
template<typename T>
void _run(const Callback<Wal &, T &> &callback, T state = T())
{
auto lastTick = std::chrono::steady_clock::now();
std::chrono::nanoseconds fBehind(0);
//! @brief Call the onFixedUpdate of every system with every component
void _fixedUpdate();
while (!this->shouldClose) {
auto now = std::chrono::steady_clock::now();
std::chrono::nanoseconds dtime = now - lastTick;
fBehind += dtime;
lastTick = now;
//! @brief Check if an entity met a system's dependencies.
//! @param entity The entity to check
//! @param system The system that will list dependencies
//! @return True if all dependencies are met, false otherwise.
static bool _hasDependencies(const Entity &entity, const System &system);
while (fBehind > Wal::timestep) {
fBehind -= Wal::timestep;
for (auto &system : this->_systems)
system->fixedUpdate();
}
for (auto &system : this->_systems)
system->update(dtime);
callback(*this, state);
}
}
#if defined(PLATFORM_WEB)
template<typename T>
static void _runIteration(void *param)
{
static auto [wal, callback, state] = *reinterpret_cast<std::tuple<Wal &, const Callback<Wal &, T &> &, T &> *>(param);
static auto lastTick = std::chrono::steady_clock::now();
static std::chrono::nanoseconds fBehind(0);
auto now = std::chrono::steady_clock::now();
std::chrono::nanoseconds dtime = now - lastTick;
fBehind += dtime;
lastTick = now;
while (fBehind > Wal::timestep) {
fBehind -= Wal::timestep;
for (auto &system : wal._systems)
system->fixedUpdate();
}
for (auto &system : wal._systems)
system->update(dtime);
callback(wal, state);
}
#endif
public:
//! @brief The scene that contains entities.
std::shared_ptr<Scene> scene;
//! @brief True if the engine should close after the end of the current tick.
bool shouldClose = false;
//! @brief The time between each fixed update.
static std::chrono::nanoseconds timestep;
static constexpr std::chrono::nanoseconds timestep = std::chrono::milliseconds(32);
//! @brief Create a new system in place.
//! @return The wal instance used to call this function is returned. This allow method chaining.
@@ -58,7 +97,7 @@ namespace WAL
});
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<T>(std::forward<Types>(params)...));
this->_systems.push_back(std::make_unique<T>(*this, std::forward<Types>(params)...));
return *this;
}
@@ -105,23 +144,6 @@ namespace WAL
return *this;
}
//! @brief Start the game loop
//! @param callback A callback called after each update of the game. It allow you to update the engine based on a specific game state. (you can also update the game state here)
//! @param state An initial game state. If not specified, it will be defaulted.
//! @tparam T A type used to track your game state. It must be default constructable.
template<typename T>
void run(const std::function<void (Wal &, T &)> &callback, T state = T())
{
Callback<Wal &, T &> update(callback);
#if defined(PLATFORM_WEB)
std::tuple iterationParams(this, &update, &state);
return emscripten_set_main_loop_arg((em_arg_callback_func)runIteration<T>, (void *)&iterationParams, 0, 1);
#else
return this->run(update, state);
#endif
}
//! @brief Start the game loop
//! @param callback A callback called after each update of the game. It allow you to update the engine based on a specific game state. (you can also update the game state here)
//! @param state An initial game state. If not specified, it will be defaulted.
@@ -129,47 +151,13 @@ namespace WAL
template<typename T>
void run(const Callback<Wal &, T &> &callback, T state = T())
{
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);
callback(*this, state);
}
}
#if defined(PLATFORM_WEB)
template<typename T>
static void runIteration(void *param)
{
static auto iterationParams = reinterpret_cast<std::tuple<Wal *, Callback<Wal &, T &> *, T *> *>(param);
static const Callback<Wal &, T &> callback = *((Callback<Wal &, T &> *)std::get<1>(*iterationParams));
static T *state = (T *)std::get<2>(*iterationParams);
static Wal *wal = (Wal *)std::get<0>(*iterationParams);
static auto lastTick = std::chrono::steady_clock::now();
static std::chrono::nanoseconds fBehind(0);
auto now = std::chrono::steady_clock::now();
std::chrono::nanoseconds dtime = now - lastTick;
fBehind += dtime;
lastTick = now;
while (fBehind > Wal::timestep) {
fBehind -= Wal::timestep;
wal->_fixedUpdate();
}
wal->_update(dtime);
callback(*wal, *state);
}
std::tuple<Wal &, const Callback<Wal &, T &> &, T &> iterationParams(*this, callback, state);
return emscripten_set_main_loop_arg((em_arg_callback_func)_runIteration<T>, (void *)&iterationParams, 0, 1);
#else
return this->_run(callback, state);
#endif
}
//! @brief A default constructor
Wal() = default;

View File

@@ -16,14 +16,6 @@ namespace BBM
return new CollisionComponent(entity);
}
CollisionComponent::CollisionComponent(WAL::Entity &entity, std::function<void (WAL::Entity &, const WAL::Entity &)> onCollide, std::function<void (WAL::Entity &, const WAL::Entity &)> onCollided, Vector3f bound)
: WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound(bound)
{ }
CollisionComponent::CollisionComponent(WAL::Entity &entity, std::function<void (WAL::Entity &, const WAL::Entity &)> onCollide, std::function<void (WAL::Entity &, const WAL::Entity &)> onCollided, float boundSize)
: WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound({boundSize, boundSize, boundSize})
{ }
CollisionComponent::CollisionComponent(WAL::Entity &entity, WAL::Callback<WAL::Entity &, const WAL::Entity &> onCollide, WAL::Callback<WAL::Entity &, const WAL::Entity &> onCollided, Vector3f bound)
: WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound(bound)
{ }

View File

@@ -27,12 +27,6 @@ namespace BBM
//! @brief A component can't be instantiated, it should be derived.
explicit CollisionComponent(WAL::Entity &entity);
//! @brief Constructor with a callback function
CollisionComponent(WAL::Entity &entity, std::function<void (WAL::Entity &, const WAL::Entity &)> onCollide, std::function<void (WAL::Entity &, const WAL::Entity &)> onCollided, Vector3f bound);
//! @brief Constructor with a callback function, same boundSize for all axis
CollisionComponent(WAL::Entity &entity, std::function<void (WAL::Entity &, const WAL::Entity &)> onCollide, std::function<void (WAL::Entity &, const WAL::Entity &)> onCollided, float boundSize = 0);
//! @brief Constructor with a WAL::Callback
CollisionComponent(WAL::Entity &entity, WAL::Callback<WAL::Entity &, const WAL::Entity &> onCollide, WAL::Callback<WAL::Entity &, const WAL::Entity &> onCollided,Vector3f bound);

View File

@@ -20,11 +20,6 @@ namespace BBM
this->_acceleration += force;
}
void MovableComponent::resetVelocity(void)
{
this->_velocity = {0, 0, 0};
}
const Vector3f &MovableComponent::getVelocity(void) const
{
return _velocity;

View File

@@ -23,9 +23,6 @@ namespace BBM
//! @param force The force to add to this entity's acceleration. The force is added instantly and in one go.
void addForce(Vector3f force);
//! @brief Set velocity to 0
void resetVelocity(void);
//! @brief Get velocity
const Vector3f &getVelocity(void) const;
@@ -42,5 +39,6 @@ namespace BBM
MovableComponent &operator=(const MovableComponent &) = delete;
friend class MovableSystem;
friend class MapGenerator;
};
} // namespace WAL

View File

@@ -3,12 +3,33 @@
// Edited by Benjamin Henry on 5/26/21.
//
#include <Component/Collision/CollisionComponent.hpp>
#include "Map.hpp"
#include <iostream>
namespace RAY3D = RAY::Drawables::Drawables3D;
namespace BBM
{
void MapGenerator::wallCollide(WAL::Entity &entity, const WAL::Entity &wall)
{
auto *mov = entity.tryGetComponent<MovableComponent>();
if (!mov)
return;
auto &pos = entity.getComponent<PositionComponent>();
const auto &wallPos = wall.getComponent<PositionComponent>();
auto diff = pos.position + mov->getVelocity() - wallPos.position;
// mov->_velocity = Vector3f();
if (diff.x <= 0 && mov->_velocity.x < 0)
mov->_velocity.x = 0;
if (diff.x >= 0 && mov->_velocity.x > 0)
mov->_velocity.x = 0;
if (diff.z <= 0 && mov->_velocity.z < 0)
mov->_velocity.z = 0;
if (diff.z >= 0 && mov->_velocity.z > 0)
mov->_velocity.z = 0;
}
const std::string MapGenerator::assetsPath = "./assets/";
const std::string MapGenerator::wallAssetsPath = MapGenerator::assetsPath + "map/";
const std::string MapGenerator::imageExtension = ".png";
@@ -24,17 +45,16 @@ namespace BBM
void MapGenerator::generateUnbreakableBlock(int width, int height, std::shared_ptr<WAL::Scene> scene)
{
static const std::string UnbreakableObj = unbreakableWallPath + objExtension;
static const std::string UnbreakablePng = unbreakableWallPath + imageExtension;
static const std::string unbreakableObj = unbreakableWallPath + objExtension;
static const std::string unbreakablePng = unbreakableWallPath + imageExtension;
for (int i = 0; i < width + 1; i++) {
for (int j = 0; j < height + 1; j++) {
if (!(i % 2) && !(j % 2)) {
scene->addEntity("Unbreakable Wall")
.addComponent<PositionComponent>(Vector3f(i, 0, j))
//.addComponent<CollisionComponent>(1)
.addComponent<Drawable3DComponent, RAY3D::Model>(UnbreakableObj,
std::make_pair(MAP_DIFFUSE, UnbreakablePng));
.addComponent<PositionComponent>(i, 0, j)
.addComponent<CollisionComponent>(WAL::Callback<WAL::Entity &, const WAL::Entity &>(), &MapGenerator::wallCollide, .75)
.addComponent<Drawable3DComponent, RAY3D::Model>(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePng));
}
}
}
@@ -47,25 +67,25 @@ namespace BBM
scene->addEntity("Bottom Wall")
.addComponent<PositionComponent>(Vector3f((width + 1) / 2, 0, -1))
//.addComponent<CollisionComponent>(1)
.addComponent<CollisionComponent>(WAL::Callback<WAL::Entity &, const WAL::Entity &>(), &MapGenerator::wallCollide, .75)
.addComponent<Drawable3DComponent, RAY3D::Model>(unbreakableObj,
std::make_pair(MAP_DIFFUSE, unbreakablePnj),
RAY::Vector3(width + 3, 1, 1));
scene->addEntity("Upper Wall")
.addComponent<PositionComponent>(Vector3f((width + 1) / 2, 0, height + 1))
//.addComponent<CollisionComponent>(1)
.addComponent<CollisionComponent>(WAL::Callback<WAL::Entity &, const WAL::Entity &>(), &MapGenerator::wallCollide, .75)
.addComponent<Drawable3DComponent, RAY3D::Model>(unbreakableObj,
std::make_pair(MAP_DIFFUSE, unbreakablePnj),
RAY::Vector3(width + 3, 1, 1));
scene->addEntity("Left Wall")
.addComponent<PositionComponent>(Vector3f(width + 1, 0, height / 2))
//.addComponent<CollisionComponent>(1)
.addComponent<CollisionComponent>(WAL::Callback<WAL::Entity &, const WAL::Entity &>(), &MapGenerator::wallCollide, .75)
.addComponent<Drawable3DComponent, RAY3D::Model>(unbreakableObj,
std::make_pair(MAP_DIFFUSE, unbreakablePnj),
RAY::Vector3(1, 1, height + 1));
scene->addEntity("Right Wall")
.addComponent<PositionComponent>(Vector3f(-1, 0, height / 2))
//.addComponent<CollisionComponent>(1)
.addComponent<CollisionComponent>(WAL::Callback<WAL::Entity &, const WAL::Entity &>(), &MapGenerator::wallCollide, .75)
.addComponent<Drawable3DComponent, RAY3D::Model>(unbreakableObj,
std::make_pair(MAP_DIFFUSE, unbreakablePnj),
RAY::Vector3(1, 1, height + 1));
@@ -81,7 +101,6 @@ namespace BBM
if (map[std::make_tuple(i, 0, j)] != HOLE && map[std::make_tuple(i, -1, j)] != BUMPER)
scene->addEntity("Unbreakable Wall")
.addComponent<PositionComponent>(Vector3f(i, -1, j))
//.addComponent<CollisionComponent>(1)
.addComponent<Drawable3DComponent, RAY3D::Model>(floorObj,
std::make_pair(MAP_DIFFUSE, floorPng));
}
@@ -116,7 +135,7 @@ namespace BBM
scene->addEntity("Breakable Block")
.addComponent<PositionComponent>(coords)
.addComponent<HealthComponent>(1)
//.addComponent<CollisionComponent>(1)
.addComponent<CollisionComponent>(WAL::Callback<WAL::Entity &, const WAL::Entity &>(), &MapGenerator::wallCollide, .75)
.addComponent<Drawable3DComponent, RAY3D::Model>(breakableObj, std::make_pair(MAP_DIFFUSE, breakablePng));
}
@@ -138,7 +157,6 @@ namespace BBM
scene->addEntity("Upper Floor")
.addComponent<PositionComponent>(Vector3f(coords))
//.addComponent<CollisionComponent>(1)
.addComponent<Drawable3DComponent, RAY3D::Model>(floorObj, std::make_pair(MAP_DIFFUSE, floorPng));
}
@@ -150,7 +168,7 @@ namespace BBM
scene->addEntity("Unbreakable Block")
.addComponent<PositionComponent>(coords)
//.addComponent<CollisionComponent>(1)
.addComponent<CollisionComponent>(WAL::Callback<WAL::Entity &, const WAL::Entity &>(), &MapGenerator::wallCollide, .75)
.addComponent<Drawable3DComponent, RAY3D::Model>(UnbreakableObj,
std::make_pair(MAP_DIFFUSE, UnbreakablePng));
}

View File

@@ -14,7 +14,6 @@
#include <tuple>
#include <algorithm>
#include "Component/Renderer/Drawable3DComponent.hpp"
#include "System/Renderer/RenderSystem.hpp"
#include "Scene/Scene.hpp"
#include "Model/Model.hpp"
#include "Component/Component.hpp"
@@ -157,6 +156,8 @@ namespace BBM
static const std::string secondFloorHolePath;
public:
static void wallCollide(WAL::Entity &entity, const WAL::Entity &wall);
//! @param width Width of the map
//! @param height Height of the map

View File

@@ -71,7 +71,13 @@ namespace BBM
}
template<typename T2>
Vector3<T> &operator*=(T2 d)
Vector3<T> operator-(const Vector3<T2> &vec) const
{
return Vector3<T>(this->x - vec.x, this->y - vec.y, this->z - vec.z);
}
template<typename T2>
Vector3<T> &operator*=(const T2 d)
{
this->x *= d;
this->y *= d;
@@ -80,7 +86,7 @@ namespace BBM
}
template<typename T2>
Vector3<T> operator*(T2 d) const
Vector3<T> operator*(const T2 d) const
{
return Vector3<T>(this->x * d, this->y * d, this->z * d);
}

View File

@@ -8,8 +8,6 @@
#include "System/Renderer/RenderSystem.hpp"
#include <Model/Model.hpp>
#include <Drawables/3D/Cube.hpp>
#include <Drawables/2D/Rectangle.hpp>
#include <Drawables/3D/Cube.hpp>
#include <TraceLog.hpp>
#include <System/Keyboard/KeyboardSystem.hpp>
#include <System/Controllable/ControllableSystem.hpp>
@@ -19,9 +17,7 @@
#include <Component/Controllable/ControllableComponent.hpp>
#include <Component/Keyboard/KeyboardComponent.hpp>
#include <System/Gamepad/GamepadSystem.hpp>
#include "Models/Vector2.hpp"
#include "Component/Renderer/CameraComponent.hpp"
#include "Component/Renderer/Drawable2DComponent.hpp"
#include "Component/Renderer/Drawable3DComponent.hpp"
#include "Runner.hpp"
#include "Models/GameState.hpp"
@@ -30,7 +26,6 @@
#include "System/Animation/AnimationsSystem.hpp"
#include "Map/Map.hpp"
namespace RAY2D = RAY::Drawables::Drawables2D;
namespace RAY3D = RAY::Drawables::Drawables3D;
namespace BBM
@@ -49,9 +44,8 @@ namespace BBM
{
wal.addSystem<KeyboardSystem>()
.addSystem<GamepadSystem>()
.addSystem<AnimationsSystem>()
.addSystem<ControllableSystem>()
.addSystem<CollisionSystem>(wal)
.addSystem<CollisionSystem>()
.addSystem<MovableSystem>();
}
@@ -59,7 +53,8 @@ namespace BBM
{
RAY::TraceLog::setLevel(LOG_WARNING);
RAY::Window &window = RAY::Window::getInstance(600, 400, "Bomberman", FLAG_WINDOW_RESIZABLE);
wal.addSystem<RenderSystem>(wal, window);
wal.addSystem<AnimationsSystem>()
.addSystem<RenderSystem>(window);
}
std::shared_ptr<WAL::Scene> loadGameScene()
@@ -71,24 +66,18 @@ namespace BBM
.addComponent<ControllableComponent>()
.addComponent<KeyboardComponent>()
.addComponent<AnimationsComponent>(RAY::ModelAnimations("assets/player/player.iqm"), 3)
.addComponent<CollisionComponent>(2)
.addComponent<CollisionComponent>(1)
.addComponent<MovableComponent>();
scene->addEntity("cube")
.addComponent<PositionComponent>(-5, 0, -5)
.addComponent<Drawable3DComponent, RAY3D::Cube>(Vector3f(-5, 0, -5), Vector3f(3, 3, 3), RED)
.addComponent<ControllableComponent>()
.addComponent<KeyboardComponent>()
.addComponent<CollisionComponent>([](WAL::Entity &, const WAL::Entity &){},
[](WAL::Entity &actual, const WAL::Entity &) {
try {
auto &mov = actual.getComponent<MovableComponent>();
mov.resetVelocity();
} catch (std::exception &e) { };
}, 3);
scene->addEntity("camera")
.addComponent<PositionComponent>(8, 20, 7)
.addComponent<CameraComponent>(Vector3f(8, 0, 8));
// scene->addEntity("cube")
// .addComponent<PositionComponent>(5, 0, 5)
// .addComponent<Drawable3DComponent, RAY3D::Cube>(Vector3f(-5, 0, -5), Vector3f(3, 3, 3), RED)
// .addComponent<ControllableComponent>()
// .addComponent<KeyboardComponent>()
// .addComponent<CollisionComponent>(WAL::Callback<WAL::Entity &, const WAL::Entity &>(), &MapGenerator::wallCollide, 3);
std::srand(std::time(nullptr));
MapGenerator::loadMap(16, 16, MapGenerator::createMap(16, 16), scene);
return scene;

View File

@@ -11,18 +11,14 @@
namespace BBM
{
AnimationsSystem::AnimationsSystem()
: WAL::System({
typeid(Drawable3DComponent),
typeid(AnimationsComponent)
})
{
}
AnimationsSystem::AnimationsSystem(WAL::Wal &wal)
: System(wal)
{}
void AnimationsSystem::onUpdate(WAL::Entity &entity, std::chrono::nanoseconds)
void AnimationsSystem::onUpdate(WAL::ViewEntity<Drawable3DComponent, AnimationsComponent> &entity, std::chrono::nanoseconds)
{
auto &model = entity.getComponent<Drawable3DComponent>();
auto &anim = entity.getComponent<AnimationsComponent>();
auto &model = entity.get<Drawable3DComponent>();
auto &anim = entity.get<AnimationsComponent>();
if (anim.isDisabled())
return;

View File

@@ -5,22 +5,24 @@
#pragma once
#include <System/System.hpp>
#include "Component/Renderer/Drawable3DComponent.hpp"
#include "Component/Animation/AnimationsComponent.hpp"
namespace BBM
{
class AnimationsSystem : public WAL::System
class AnimationsSystem : public WAL::System<Drawable3DComponent, AnimationsComponent>
{
public:
//! @inherit
void onUpdate(WAL::Entity &entity, std::chrono::nanoseconds) override;
void onUpdate(WAL::ViewEntity<Drawable3DComponent, AnimationsComponent> &entity, std::chrono::nanoseconds) override;
//! @brief A default constructor
AnimationsSystem();
explicit AnimationsSystem(WAL::Wal &wal);
//! @brief A Controllable system is copy constructable
AnimationsSystem(const AnimationsSystem &) = default;
//! @brief A default destructor
~AnimationsSystem() override = default;
//! @brief A Controllable system is assignable.
AnimationsSystem &operator=(const AnimationsSystem &) = default;
//! @brief A system is not assignable.
AnimationsSystem &operator=(const AnimationsSystem &) = delete;
};
}

View File

@@ -6,14 +6,13 @@
#include "Component/Position/PositionComponent.hpp"
#include "Component/Collision/CollisionComponent.hpp"
#include "System/Collision/CollisionSystem.hpp"
#include "Scene/Scene.hpp"
namespace BBM
{
CollisionSystem::CollisionSystem(WAL::Wal &wal)
: WAL::System({typeid(PositionComponent), typeid(CollisionComponent)}), _wal(wal)
{
}
: System(wal)
{ }
bool CollisionSystem::collide(Vector3f minA, Vector3f maxA, Vector3f minB, Vector3f maxB)
{
@@ -24,25 +23,20 @@ namespace BBM
return (overlapX && overlapY && overlapZ);
}
void CollisionSystem::onFixedUpdate(WAL::Entity &entity)
void CollisionSystem::onFixedUpdate(WAL::ViewEntity<PositionComponent, CollisionComponent> &entity)
{
auto &posA = entity.getComponent<PositionComponent>();
auto &col = entity.getComponent<CollisionComponent>();
auto &posA = entity.get<PositionComponent>();
auto &col = entity.get<CollisionComponent>();
Vector3f position = posA.position;
if (entity.hasComponent(typeid(MovableComponent)))
position += entity.getComponent<MovableComponent>().getVelocity();
if (auto *movable = entity->tryGetComponent<MovableComponent>())
position += movable->getVelocity();
Vector3f minA = Vector3f::min(position, position + col.bound);
Vector3f maxA = Vector3f::max(position, position + col.bound);
for (auto &other : _wal.scene->getEntities()) {
if (&other == &entity)
for (auto &[other, posB, colB] : this->getView()) {
if (other.getUid() == entity->getUid())
continue;
if (!other.hasComponent<CollisionComponent>() ||
!other.hasComponent<PositionComponent>())
continue;
auto colB = other.getComponent<CollisionComponent>();
auto posB = other.getComponent<PositionComponent>().position;
Vector3f minB = Vector3f::min(posB, posB + colB.bound);
Vector3f maxB = Vector3f::max(posB, posB + colB.bound);
Vector3f minB = Vector3f::min(posB.position, posB.position + colB.bound);
Vector3f maxB = Vector3f::max(posB.position, posB.position + colB.bound);
if (collide(minA, maxA, minB, maxB)) {
col.onCollide(entity, other);
colB.onCollided(entity, other);

View File

@@ -9,27 +9,26 @@
#include "Wal.hpp"
#include "System/System.hpp"
#include "Models/Vector3.hpp"
#include "Component/Collision/CollisionComponent.hpp"
#include "Component/Position/PositionComponent.hpp"
namespace BBM
{
//! @brief A system to handle collisions.
class CollisionSystem : public WAL::System
class CollisionSystem : public WAL::System<PositionComponent, CollisionComponent>
{
private:
//! @brief reference to the ECS engine to get other entities
WAL::Wal &_wal;
public:
//! @inherit
void onFixedUpdate(WAL::Entity &entity) override;
void onFixedUpdate(WAL::ViewEntity<PositionComponent, CollisionComponent> &entity) override;
//! @brief A default constructor
CollisionSystem(WAL::Wal &wal);
explicit CollisionSystem(WAL::Wal &wal);
//! @brief A Collision system is copy constructable
CollisionSystem(const CollisionSystem &) = default;
//! @brief A default destructor
~CollisionSystem() override = default;
//! @brief A Collision system is assignable.
CollisionSystem &operator=(const CollisionSystem &) = default;
//! @brief A system is not assignable.
CollisionSystem &operator=(const CollisionSystem &) = delete;
//! @brief check AABB collision
static bool collide(Vector3f minA, Vector3f maxA, Vector3f minB, Vector3f maxB);

View File

@@ -10,17 +10,14 @@
namespace BBM
{
ControllableSystem::ControllableSystem()
: WAL::System({
typeid(ControllableComponent),
typeid(MovableComponent)
})
ControllableSystem::ControllableSystem(WAL::Wal &wal)
: System(wal)
{}
void ControllableSystem::onFixedUpdate(WAL::Entity &entity)
void ControllableSystem::onFixedUpdate(WAL::ViewEntity<ControllableComponent, MovableComponent> &entity)
{
auto &controllable = entity.getComponent<ControllableComponent>();
auto &movable = entity.getComponent<MovableComponent>();
auto &controllable = entity.get<ControllableComponent>();
auto &movable = entity.get<MovableComponent>();
Vector2f move = controllable.move.normalized() * ControllableSystem::speed;
movable.addForce(Vector3f(move.x, controllable.jump, move.y));

View File

@@ -5,27 +5,29 @@
#pragma once
#include "Component/Movable/MovableComponent.hpp"
#include "Component/Controllable/ControllableComponent.hpp"
#include "System/System.hpp"
namespace BBM
{
//! @brief A system to handle Controllable entities.
class ControllableSystem : public WAL::System
class ControllableSystem : public WAL::System<ControllableComponent, MovableComponent>
{
public:
//! @brief The speed applied to every controllable entities.
static constexpr const float speed = .25f;
//! @inherit
void onFixedUpdate(WAL::Entity &entity) override;
void onFixedUpdate(WAL::ViewEntity<ControllableComponent, MovableComponent> &entity) override;
//! @brief A default constructor
ControllableSystem();
explicit ControllableSystem(WAL::Wal &wal);
//! @brief A Controllable system is copy constructable
ControllableSystem(const ControllableSystem &) = default;
//! @brief A default destructor
~ControllableSystem() override = default;
//! @brief A Controllable system is assignable.
ControllableSystem &operator=(const ControllableSystem &) = default;
//! @brief A system is not assignable.
ControllableSystem &operator=(const ControllableSystem &) = delete;
};
}

View File

@@ -13,17 +13,14 @@ using Gamepad = RAY::Controller::GamePad;
namespace BBM
{
GamepadSystem::GamepadSystem()
: WAL::System({
typeid(GamepadComponent),
typeid(ControllableComponent)
})
GamepadSystem::GamepadSystem(WAL::Wal &wal)
: System(wal)
{}
void GamepadSystem::onFixedUpdate(WAL::Entity &entity)
void GamepadSystem::onFixedUpdate(WAL::ViewEntity<GamepadComponent, ControllableComponent> &entity)
{
const auto &gamepadComponent = entity.getComponent<GamepadComponent>();
auto &controllable = entity.getComponent<ControllableComponent>();
const auto &gamepadComponent = entity.get<GamepadComponent>();
auto &controllable = entity.get<ControllableComponent>();
Gamepad gamepad(gamepadComponent.getID());
const std::map<Button, bool &> keyPressedMap = {

View File

@@ -6,23 +6,25 @@
#include "System/System.hpp"
#include <map>
#include "Component/Gamepad/GamepadComponent.hpp"
#include "Component/Controllable/ControllableComponent.hpp"
namespace BBM
{
//! @brief A system to handle Gamepad entities.
class GamepadSystem : public WAL::System
class GamepadSystem : public WAL::System<GamepadComponent, ControllableComponent>
{
public:
//! @inherit
void onFixedUpdate(WAL::Entity &entity) override;
void onFixedUpdate(WAL::ViewEntity<GamepadComponent, ControllableComponent> &entity) override;
//! @brief A default constructor
GamepadSystem();
explicit GamepadSystem(WAL::Wal &wal);
//! @brief A Gamepad system is copy constructable
GamepadSystem(const GamepadSystem &) = default;
//! @brief A default destructor
~GamepadSystem() override = default;
//! @brief A Gamepad system is assignable.
GamepadSystem &operator=(const GamepadSystem &) = default;
//! @brief A system is not assignable.
GamepadSystem &operator=(const GamepadSystem &) = delete;
};
}

View File

@@ -8,18 +8,14 @@
namespace BBM
{
GridCenteredSystem::GridCenteredSystem()
: WAL::System({
typeid(GridCenteredComponent),
typeid(MovableComponent),
// typeid(PositionComponent)
})
GridCenteredSystem::GridCenteredSystem(WAL::Wal &wal)
: System(wal)
{}
void GridCenteredSystem::onFixedUpdate(WAL::Entity &entity)
void GridCenteredSystem::onFixedUpdate(WAL::ViewEntity<GridCenteredComponent, MovableComponent, PositionComponent> &entity)
{
auto &grid = entity.getComponent<GridCenteredComponent>();
auto &movement = entity.getComponent<MovableComponent>();
auto &grid = entity.get<GridCenteredComponent>();
auto &movement = entity.get<MovableComponent>();
// movement.addForce(grid.force * )
}
}

View File

@@ -5,22 +5,23 @@
#pragma once
#include <System/System.hpp>
#include "Component/Position/PositionComponent.hpp"
namespace BBM
{
//! @brief The system handling GridCenteredComponent
class GridCenteredSystem : public WAL::System
class GridCenteredSystem : public WAL::System<GridCenteredComponent, MovableComponent, PositionComponent>
{
public:
void onFixedUpdate(WAL::Entity &entity) override;
void onFixedUpdate(WAL::ViewEntity<GridCenteredComponent, MovableComponent, PositionComponent> &entity) override;
//! @brief A default constructor
GridCenteredSystem();
explicit GridCenteredSystem(WAL::Wal &wal);
//! @brief A GridCenteredSystem is copyable.
GridCenteredSystem(const GridCenteredSystem &) = default;
//! @brief A default destructor
~GridCenteredSystem() override = default;
//! @brief A GridCenteredSystem is assignable
GridCenteredSystem &operator=(const GridCenteredSystem &) = default;
//! @brief A system is not assignable
GridCenteredSystem &operator=(const GridCenteredSystem &) = delete;
};
}

View File

@@ -10,15 +10,13 @@
namespace BBM
{
HealthSystem::HealthSystem()
: WAL::System({
typeid(HealthComponent)
})
HealthSystem::HealthSystem(WAL::Wal &wal)
: System(wal)
{}
void HealthSystem::onFixedUpdate(WAL::Entity &entity)
void HealthSystem::onFixedUpdate(WAL::ViewEntity<HealthComponent> &entity)
{
auto &health = entity.getComponent<HealthComponent>();
auto &health = entity.get<HealthComponent>();
if (health.getHealthPoint() == 0)
health.onDeath(entity);

View File

@@ -5,24 +5,25 @@
#pragma once
#include "Component/Health/HealthComponent.hpp"
#include "System/System.hpp"
namespace BBM
{
//! @brief A system to handle Health entities.
class HealthSystem : public WAL::System
class HealthSystem : public WAL::System<HealthComponent>
{
public:
//! @inherit
void onFixedUpdate(WAL::Entity &entity) override;
void onFixedUpdate(WAL::ViewEntity<HealthComponent> &entity) override;
//! @brief A default constructor
HealthSystem();
explicit HealthSystem(WAL::Wal &wal);
//! @brief A Health system is copy constructable
HealthSystem(const HealthSystem &) = default;
//! @brief A default destructor
~HealthSystem() override = default;
//! @brief A Health system is assignable.
HealthSystem &operator=(const HealthSystem &) = default;
//! @brief A system is not assignable.
HealthSystem &operator=(const HealthSystem &) = delete;
};
}

View File

@@ -3,28 +3,23 @@
// Edited by Benjamin Henry on 2021-05-20.
//
#include <iostream>
#include "KeyboardSystem.hpp"
#include "Component/Keyboard/KeyboardComponent.hpp"
#include "Component/Controllable/ControllableComponent.hpp"
#include "Entity/Entity.hpp"
#include "Controllers/Keyboard.hpp"
using Keyboard = RAY::Controller::Keyboard;
namespace BBM
{
KeyboardSystem::KeyboardSystem()
: WAL::System({
typeid(KeyboardComponent),
typeid(ControllableComponent)
})
KeyboardSystem::KeyboardSystem(WAL::Wal &wal)
: System(wal)
{}
void KeyboardSystem::onFixedUpdate(WAL::Entity &entity)
void KeyboardSystem::onFixedUpdate(WAL::ViewEntity<KeyboardComponent, ControllableComponent> &entity)
{
const auto &keyboard = entity.getComponent<KeyboardComponent>();
auto &controllable = entity.getComponent<ControllableComponent>();
const auto &keyboard = entity.get<KeyboardComponent>();
auto &controllable = entity.get<ControllableComponent>();
const std::map<KeyboardKey, bool &> keyPressedMap = {
{keyboard.keyJump, controllable.jump},

View File

@@ -7,23 +7,25 @@
#include "System/System.hpp"
#include <map>
#include "Component/Keyboard/KeyboardComponent.hpp"
#include "Component/Controllable/ControllableComponent.hpp"
namespace BBM
{
//! @brief A system to handle keyboard entities.
class KeyboardSystem : public WAL::System
class KeyboardSystem : public WAL::System<KeyboardComponent, ControllableComponent>
{
public:
//! @inherit
void onFixedUpdate(WAL::Entity &entity) override;
void onFixedUpdate(WAL::ViewEntity<KeyboardComponent, ControllableComponent> &entity) override;
//! @brief A default constructor
KeyboardSystem();
explicit KeyboardSystem(WAL::Wal &wal);
//! @brief A keyboard system is copy constructable
KeyboardSystem(const KeyboardSystem &) = default;
//! @brief A default destructor
~KeyboardSystem() override = default;
//! @brief A keyboard system is assignable.
KeyboardSystem &operator=(const KeyboardSystem &) = default;
//! @brief A system is not assignable.
KeyboardSystem &operator=(const KeyboardSystem &) = delete;
};
}

View File

@@ -8,17 +8,14 @@
namespace BBM
{
MovableSystem::MovableSystem()
: WAL::System({
typeid(MovableComponent),
typeid(PositionComponent)
})
MovableSystem::MovableSystem(WAL::Wal &wal)
: System(wal)
{}
void MovableSystem::onFixedUpdate(WAL::Entity &entity)
void MovableSystem::onFixedUpdate(WAL::ViewEntity<MovableComponent, PositionComponent> &entity)
{
auto &movable = entity.getComponent<MovableComponent>();
auto &position = entity.getComponent<PositionComponent>();
auto &movable = entity.get<MovableComponent>();
auto &position = entity.get<PositionComponent>();
position.position += movable._velocity;
movable._velocity = movable._acceleration;

View File

@@ -5,25 +5,27 @@
#pragma once
#include "Component/Movable/MovableComponent.hpp"
#include "Component/Position/PositionComponent.hpp"
#include "System/System.hpp"
#include "Entity/Entity.hpp"
namespace BBM
{
//! @brief A system to handle movable entities. This system update velocity based on accelerations and positions based on velocity.
class MovableSystem : public WAL::System
class MovableSystem : public WAL::System<MovableComponent, PositionComponent>
{
public:
//! @inherit
void onFixedUpdate(WAL::Entity &entity) override;
void onFixedUpdate(WAL::ViewEntity<MovableComponent, PositionComponent> &entity) override;
//! @brief A default constructor
MovableSystem();
explicit MovableSystem(WAL::Wal &wal);
//! @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;
//! @brief A system is not assignable.
MovableSystem &operator=(const MovableSystem &) = delete;
};
} // namespace WAL

View File

@@ -8,17 +8,12 @@
#include "Component/Renderer/CameraComponent.hpp"
#include "Component/Position/PositionComponent.hpp"
#include "Component/Renderer/Drawable2DComponent.hpp"
#include "Drawables/ADrawable2D.hpp"
#include "Drawables/ADrawable3D.hpp"
namespace BBM
{
RenderSystem::RenderSystem(WAL::Wal &wal, RAY::Window &window, bool debugMode)
: WAL::System({
typeid(CameraComponent),
typeid(PositionComponent)
}),
_wal(wal),
: System(wal),
_window(window),
_camera(Vector3f(), Vector3f(), Vector3f(0, 1, 0), 50, CAMERA_PERSPECTIVE),
_debugMode(debugMode)
@@ -33,26 +28,14 @@ namespace BBM
this->_window.clear();
this->_window.useCamera(this->_camera);
for (auto &entity : this->_wal.scene->getEntities()) {
if (!entity.hasComponent<Drawable3DComponent>()
|| !entity.hasComponent<PositionComponent>())
continue;
auto &drawable = entity.getComponent<Drawable3DComponent>();
auto &pos = entity.getComponent<PositionComponent>();
for (auto &[_, pos, drawable] : this->_wal.scene->view<PositionComponent, Drawable3DComponent>()) {
drawable.drawable->setPosition(pos.position);
drawable.drawable->drawOn(this->_window);
}
this->_window.unuseCamera();
// TODO sort entities based on the Z axis
for (auto &entity : this->_wal.scene->getEntities()) {
if (!entity.hasComponent<Drawable2DComponent>()
|| !entity.hasComponent<PositionComponent>())
continue;
auto &drawable = entity.getComponent<Drawable2DComponent>();
auto &pos = entity.getComponent<PositionComponent>();
for (auto &[_, pos, drawable] : this->_wal.scene->view<PositionComponent, Drawable2DComponent>()) {
drawable.drawable->setPosition(Vector2f(pos.position.x, pos.position.y));
drawable.drawable->drawOn(this->_window);
}
@@ -61,10 +44,10 @@ namespace BBM
this->_window.endDrawing();
}
void RenderSystem::onUpdate(WAL::Entity &entity, std::chrono::nanoseconds dtime)
void RenderSystem::onUpdate(WAL::ViewEntity<CameraComponent, PositionComponent> &entity, std::chrono::nanoseconds dtime)
{
const auto &pos = entity.getComponent<PositionComponent>();
const auto &cam = entity.getComponent<CameraComponent>();
const auto &pos = entity.get<PositionComponent>();
const auto &cam = entity.get<CameraComponent>();
_camera.setPosition(pos.position);
_camera.setTarget(cam.target);
}

View File

@@ -4,6 +4,8 @@
#pragma once
#include "Component/Renderer/CameraComponent.hpp"
#include "Component/Position/PositionComponent.hpp"
#include "System/System.hpp"
#include "Camera/Camera2D.hpp"
#include "Window.hpp"
@@ -11,10 +13,8 @@
namespace BBM
{
class RenderSystem : public WAL::System
class RenderSystem : public WAL::System<CameraComponent, PositionComponent>
{
//! @brief The ECS to update.
WAL::Wal &_wal;
//! @brief The window to render on
RAY::Window &_window;
@@ -34,7 +34,7 @@ namespace BBM
void onSelfUpdate() override;
//! @inherit
void onUpdate(WAL::Entity &entity, std::chrono::nanoseconds dtime) override;
void onUpdate(WAL::ViewEntity<CameraComponent, PositionComponent> &entity, std::chrono::nanoseconds dtime) override;
//! @brief ctor
RenderSystem(WAL::Wal &wal, RAY::Window &window, bool debugMode = true);

View File

@@ -4,6 +4,8 @@
#include <catch2/catch.hpp>
#include <stdexcept>
#include <Wal.hpp>
#include <Scene/Scene.hpp>
#include "Entity/Entity.hpp"
#include "Models/Callback.hpp"
@@ -36,6 +38,7 @@ TEST_CASE("Callback multiple arguments", "[Callback]")
callback.addCallback([](const std::string& str, int a, unsigned *value, Entity &entity) {
throw std::runtime_error("");
});
Entity entity("name");
Scene scene;
Entity entity(scene, "name");
REQUIRE_THROWS_AS(callback("1", 0, nullptr, entity), std::runtime_error);
}

View File

@@ -5,12 +5,12 @@
#include <catch2/catch.hpp>
#include "Entity/Entity.hpp"
#include "Component/Position/PositionComponent.hpp"
#include "Component/Movable/MovableComponent.hpp"
#include "System/Movable/MovableSystem.hpp"
#include "System/Collision/CollisionSystem.hpp"
#include "Wal.hpp"
#define private public
#include "System/Collision/CollisionSystem.hpp"
#include "System/Movable/MovableSystem.hpp"
#include "Component/Movable/MovableComponent.hpp"
#include "Component/Collision/CollisionComponent.hpp"
using namespace WAL;
@@ -21,7 +21,7 @@ TEST_CASE("Collision test", "[Component][System]")
{
Wal wal;
CollisionSystem collision(wal);
wal.scene = std::shared_ptr<Scene>(new Scene);
wal.scene = std::make_shared<Scene>();
wal.scene->addEntity("player")
.addComponent<PositionComponent>()
.addComponent<CollisionComponent>([](Entity &actual, const Entity &) {
@@ -32,15 +32,15 @@ TEST_CASE("Collision test", "[Component][System]")
pos.position.z = 1;
} catch (std::exception &e) {};
}, [](Entity &, const Entity &){}, 5.0);
Entity &entity = wal.scene->getEntities()[0];
Entity &entity = wal.scene->getEntities().front();
REQUIRE(entity.getComponent<PositionComponent>().position == Vector3f());
entity.getComponent<CollisionComponent>().bound.x = 5;
entity.getComponent<CollisionComponent>().bound.y = 5;
entity.getComponent<CollisionComponent>().bound.z = 5;
collision.onUpdate(entity, std::chrono::nanoseconds(1));
collision.onFixedUpdate(entity);
collision.update(std::chrono::nanoseconds(1));
collision.fixedUpdate();
REQUIRE(entity.getComponent<PositionComponent>().position.x == 0.0);
REQUIRE(entity.getComponent<PositionComponent>().position.y == 0.0);
REQUIRE(entity.getComponent<PositionComponent>().position.z == 0.0);
@@ -48,10 +48,10 @@ TEST_CASE("Collision test", "[Component][System]")
wal.scene->addEntity("block")
.addComponent<PositionComponent>(2,2,2)
.addComponent<CollisionComponent>(1);
Entity &player = wal.scene->getEntities()[0];
collision.onUpdate(entity, std::chrono::nanoseconds(1));
Entity &player = wal.scene->getEntities().front();
collision.update(std::chrono::nanoseconds(1));
REQUIRE(player.hasComponent(typeid(PositionComponent)));
collision.onFixedUpdate(player);
collision.fixedUpdate();
REQUIRE(wal.scene->getEntities().size() == 2);
REQUIRE(player.hasComponent(typeid(PositionComponent)));
REQUIRE(player.getComponent<PositionComponent>().position.x == 1.0);
@@ -64,8 +64,8 @@ TEST_CASE("Collsion test with movable", "[Component][System]")
{
Wal wal;
CollisionSystem collision(wal);
MovableSystem movable;
wal.scene = std::shared_ptr<Scene>(new Scene);
MovableSystem movable(wal);
wal.scene = std::make_shared<Scene>();
wal.scene->addEntity("player")
.addComponent<PositionComponent>()
.addComponent<CollisionComponent>([](Entity &actual, const Entity &) {}, [](Entity &actual, const Entity &) {}, 5.0)
@@ -76,10 +76,10 @@ TEST_CASE("Collsion test with movable", "[Component][System]")
.addComponent<CollisionComponent>([](Entity &actual, const Entity &){}, [](Entity &actual, const Entity &) {
try {
auto &mov = actual.getComponent<MovableComponent>();
mov.resetVelocity();
mov._velocity = Vector3f();
} catch (std::exception &e) {};
}, 1);
Entity &entity = wal.scene->getEntities()[0];
Entity &entity = wal.scene->getEntities().front();
REQUIRE(entity.getComponent<PositionComponent>().position == Vector3f());
entity.getComponent<CollisionComponent>().bound.x = 5;
@@ -87,10 +87,10 @@ TEST_CASE("Collsion test with movable", "[Component][System]")
entity.getComponent<CollisionComponent>().bound.z = 5;
entity.getComponent<MovableComponent>().addForce({1, 1, 1});
collision.onUpdate(entity, std::chrono::nanoseconds(1));
collision.onFixedUpdate(entity);
movable.onUpdate(entity, std::chrono::nanoseconds(1));
movable.onFixedUpdate(entity);
collision.update(std::chrono::nanoseconds(1));
collision.fixedUpdate();
movable.update(std::chrono::nanoseconds(1));
movable.fixedUpdate();
REQUIRE(entity.getComponent<PositionComponent>().position.x == 0.0);
REQUIRE(entity.getComponent<PositionComponent>().position.y == 0.0);
REQUIRE(entity.getComponent<PositionComponent>().position.z == 0.0);

View File

@@ -31,7 +31,7 @@ TEST_CASE("Create system", "[Engine][System]")
TEST_CASE("Create system by reference", "[Engine][System]")
{
Wal wal;
MovableSystem system;
MovableSystem system(wal);
wal.addSystem(system);
REQUIRE_THROWS_AS(wal.addSystem<MovableSystem>(), DuplicateError);
}

View File

@@ -5,13 +5,16 @@
#include "Entity/Entity.hpp"
#include "Component/Position/PositionComponent.hpp"
#include <catch2/catch.hpp>
#include <Wal.hpp>
#include <Scene/Scene.hpp>
using namespace WAL;
using namespace BBM;
TEST_CASE("Component", "[Entity]")
{
Entity entity("Bob");
Scene scene;
Entity entity(scene, "Bob");
entity.addComponent<PositionComponent>(2, 3, 4);
SECTION("Check value") {
@@ -31,13 +34,15 @@ TEST_CASE("Component", "[Entity]")
TEST_CASE("ComponentNotFound", "[Entity]")
{
Entity entity("Bob");
Scene scene;
Entity entity(scene, "Bob");
REQUIRE_THROWS_AS(entity.getComponent<PositionComponent>(), NotFoundError);
}
TEST_CASE("Add component by reference", "[Entity]")
{
Entity entity("Bob");
Scene scene;
Entity entity(scene, "Bob");
PositionComponent component(entity, 4, 5, 6);
REQUIRE(&entity.addComponent(component) == &entity);

View File

@@ -4,13 +4,13 @@
#include "Entity/Entity.hpp"
#include "Component/Position/PositionComponent.hpp"
#include "System/Movable/MovableSystem.hpp"
#include "System/Controllable/ControllableSystem.hpp"
#include <catch2/catch.hpp>
#include <Wal.hpp>
#include <Component/Controllable/ControllableComponent.hpp>
#define private public
#include "System/Controllable/ControllableSystem.hpp"
#include "System/Movable/MovableSystem.hpp"
#include <Component/Controllable/ControllableComponent.hpp>
#include <Component/Movable/MovableComponent.hpp>
using namespace WAL;
@@ -19,32 +19,33 @@ using namespace BBM;
TEST_CASE("Move test", "[Component][System]")
{
Scene scene;
scene.addEntity("player")
Wal wal;
wal.scene = std::make_shared<Scene>();
wal.scene->addEntity("player")
.addComponent<ControllableComponent>()
.addComponent<MovableComponent>()
.addComponent<PositionComponent>();
Entity &entity = scene.getEntities()[0];
Entity &entity = wal.scene->getEntities().front();
REQUIRE(entity.getComponent<PositionComponent>().position == Vector3f());
entity.getComponent<ControllableComponent>().move = Vector2f(1, 1);
ControllableSystem controllable;
controllable.onUpdate(entity, std::chrono::nanoseconds(1));
controllable.onFixedUpdate(entity);
ControllableSystem controllable(wal);
controllable.update(std::chrono::nanoseconds(1));
controllable.fixedUpdate();
REQUIRE(entity.getComponent<MovableComponent>()._acceleration.x > 0);
REQUIRE(entity.getComponent<MovableComponent>()._acceleration.z > 0);
MovableSystem movable;
movable.onUpdate(entity, std::chrono::nanoseconds(1));
movable.onFixedUpdate(entity);
MovableSystem movable(wal);
movable.update(std::chrono::nanoseconds(1));
movable.fixedUpdate();
REQUIRE(entity.getComponent<MovableComponent>()._velocity.x > 0);
REQUIRE(entity.getComponent<MovableComponent>()._velocity.z > 0);
REQUIRE(entity.getComponent<MovableComponent>()._acceleration.x == 0);
REQUIRE(entity.getComponent<MovableComponent>()._acceleration.z == 0);
movable.onUpdate(entity, std::chrono::nanoseconds(1));
movable.onFixedUpdate(entity);
movable.update(std::chrono::nanoseconds(1));
movable.fixedUpdate();
REQUIRE(entity.getComponent<PositionComponent>().position.x > 0);
REQUIRE(entity.getComponent<PositionComponent>().position.z > 0);

126
tests/ViewTest.cpp Normal file
View File

@@ -0,0 +1,126 @@
//
// Created by Zoe Roux on 6/3/21.
//
#include "Entity/Entity.hpp"
#include "Component/Position/PositionComponent.hpp"
#include <catch2/catch.hpp>
#include <Wal.hpp>
#include <Component/Controllable/ControllableComponent.hpp>
using namespace WAL;
using namespace BBM;
TEST_CASE("View creation", "[View]")
{
Scene scene;
scene.addEntity("player")
.addComponent<PositionComponent>()
.addComponent<ControllableComponent>();
scene.addEntity("Box")
.addComponent<PositionComponent>();
REQUIRE(scene.view<PositionComponent>().size() == 2);
REQUIRE(scene.view<PositionComponent, ControllableComponent>().size() == 1);
Entity &entity = *scene.getEntities().begin();
Entity &firstView = scene.view<PositionComponent, ControllableComponent>().front();
REQUIRE(&entity == &firstView);
}
TEST_CASE("View update", "[View]")
{
Scene scene;
scene.addEntity("player")
.addComponent<PositionComponent>()
.addComponent<ControllableComponent>();
auto &view = scene.view<PositionComponent>();
auto &entity = scene.addEntity("Box")
.addComponent<PositionComponent>();
REQUIRE(view.size() == 2);
entity.removeComponent<PositionComponent>();
REQUIRE(view.size() == 1);
}
TEST_CASE("View cache", "[View]")
{
Scene scene;
scene.addEntity("player")
.addComponent<PositionComponent>()
.addComponent<ControllableComponent>();
auto &view = scene.view<PositionComponent>();
REQUIRE(&view == &scene.view<PositionComponent>());
}
TEST_CASE("View cache switch", "[View]")
{
Scene scene;
scene.addEntity("player")
.addComponent<PositionComponent>()
.addComponent<ControllableComponent>();
auto &view = scene.view<PositionComponent>();
Scene scene2;
scene2.addEntity("box")
.addComponent<PositionComponent>();
REQUIRE(&view == &scene.view<PositionComponent>());
REQUIRE(view.front()->getName() == "player");
REQUIRE(scene2.view<PositionComponent>().front()->getName() == "box");
}
TEST_CASE("View entity iteration", "[View]")
{
Scene scene;
scene.addEntity("player")
.addComponent<PositionComponent>()
.addComponent<ControllableComponent>();
scene.addEntity("Box")
.addComponent<PositionComponent>();
int i = 0;
for (Entity &entity : scene.view<PositionComponent>()) {
if (i == 0)
REQUIRE(entity.getName() == "player");
else
REQUIRE(entity.getName() == "Box");
i++;
}
REQUIRE(i == 2);
}
TEST_CASE("ViewEntity<> iteration", "[View]")
{
Scene scene;
scene.addEntity("player")
.addComponent<PositionComponent>(1, 1, 1)
.addComponent<ControllableComponent>();
scene.addEntity("Box")
.addComponent<PositionComponent>(1, 1, 1);
int i = 0;
for (auto entity : scene.view<PositionComponent>()) {
if (i == 0)
REQUIRE(entity->getName() == "player");
else
REQUIRE(entity->getName() == "Box");
REQUIRE(entity.get<PositionComponent>().position == Vector3f(1, 1, 1));
i++;
}
REQUIRE(i == 2);
}
TEST_CASE("View [entity, component] iteration", "[View]")
{
Scene scene;
scene.addEntity("player")
.addComponent<PositionComponent>(1, 1, 1)
.addComponent<ControllableComponent>();
scene.addEntity("Box")
.addComponent<PositionComponent>(1, 1, 1);
int i = 0;
for (auto &[entity, position] : scene.view<PositionComponent>()) {
if (i == 0)
REQUIRE(entity.getName() == "player");
else
REQUIRE(entity.getName() == "Box");
REQUIRE(position.position == Vector3f(1, 1, 1));
i++;
}
REQUIRE(i == 2);
}