diff --git a/engine/include/velos/actions/actions.h b/engine/include/velos/actions/actions.h index 3a60960..0bbc3d4 100644 --- a/engine/include/velos/actions/actions.h +++ b/engine/include/velos/actions/actions.h @@ -20,15 +20,15 @@ namespace velos { public: static void bind(Key key, const ActionId& action, ActionType type = ActionType::Press, float holdThreshold = 0.f); static void on(const ActionId& action, std::function callback); - static void trigger(Key key, float dt); + static void trigger(ActionId actionId, float dt); - static std::unordered_map& getKeyBindings() { return s_keyBindings; } + static std::unordered_map>& getKeyBindings() { return s_keyBindings; } static float getHoldTime(Key key) {return s_keyHoldTime[key]; } static void addHoldTime(Key key, float dt) { s_keyHoldTime[key] += dt; } static void resetHoldTime(Key key) { s_keyHoldTime[key] = 0.f; } private: - inline static std::unordered_map s_keyBindings; + inline static std::unordered_map> s_keyBindings; inline static std::unordered_map>> s_callbacks; inline static std::unordered_map s_keyHoldTime; }; diff --git a/engine/include/velos/entity/entity.h b/engine/include/velos/entity/entity.h index 6632539..d56c36b 100644 --- a/engine/include/velos/entity/entity.h +++ b/engine/include/velos/entity/entity.h @@ -1,53 +1,41 @@ #pragma once #include #include -#include #include "../graphics/shapes/shape.h" -#include "../physics/collider.h" #include "../graphics/renderer.h" #include "../math/transform.h" namespace velos { + class Collider; + class Entity { public: Entity() = default; virtual ~Entity() = default; - virtual void update(float dt) { updateColliders(); } + virtual void update(float dt); virtual void render(Renderer& renderer); - using CollisionCallback = std::function; - - virtual void onCollisionEnter(Entity& other) {}; - virtual void onCollisionStay(Entity& other) {}; - virtual void onCollisionExit(Entity& other) {}; - - void setOnCollisionEnter(CollisionCallback cb) { m_onEnter = std::move(cb); } - void setOnCollisionStay(CollisionCallback cb) { m_onStay = std::move(cb); } - void setOnCollisionExit(CollisionCallback cb) { m_onExit = std::move(cb); } - - void dispatchCollisionEnter(Entity& other); - void dispatchCollisionStay(Entity& other); - void dispatchCollisionExit(Entity& other); - void debugRender(Renderer& renderer); bool containsPoint(const Vec2& worldPos) const; void addShape(std::shared_ptr shape) { m_shapes.push_back(shape); } - void move(float dx, float dy); void addCollider(std::unique_ptr collider); const std::vector>& colliders() const; void updateColliders(); + void setVelocity(Vec2 velocity) { m_velocity = velocity; } + void setVelocityX(float velocityX) { m_velocity.x = velocityX; } + void setVelocityY(float velocityY) { m_velocity.y = velocityY; } + Vec2& velocity() { return m_velocity; } + Transform& transform() { return m_transform; } private: + Vec2 m_velocity; + Transform m_transform; std::vector> m_shapes; std::vector> m_colliders; - - CollisionCallback m_onEnter; - CollisionCallback m_onStay; - CollisionCallback m_onExit; }; } diff --git a/engine/include/velos/math/transform.h b/engine/include/velos/math/transform.h index 227e196..191010d 100644 --- a/engine/include/velos/math/transform.h +++ b/engine/include/velos/math/transform.h @@ -13,6 +13,16 @@ namespace velos { return { x + o.x, y + o.y }; } + void operator+=(const Vec2& o) { + x += o.x; + y += o.y; + } + + void operator-=(const Vec2& o) { + x -= o.x; + y -= o.y; + } + Vec2 operator*(float s) const { return { x * s, y * s }; } diff --git a/engine/include/velos/physics/collider.h b/engine/include/velos/physics/collider.h index d596ac4..91b423a 100644 --- a/engine/include/velos/physics/collider.h +++ b/engine/include/velos/physics/collider.h @@ -1,6 +1,9 @@ #pragma once #include "../math/transform.h" #include "../graphics/renderer.h" +#include + +class Entity; namespace velos { enum class ColliderType { @@ -17,7 +20,35 @@ namespace velos { virtual bool containsPoint(const Vec2& worldPoint) const = 0; virtual void debugRender(Renderer& renderer) const = 0; + + void setSolidCollision(bool state) { m_isSolid = state; } + bool isSolidCollision() { return m_isSolid; } + + Entity* owner() const { return m_owner; } + void setOwner(Entity* owner) { m_owner = owner; } + + using CollisionCallback = std::function; + + virtual void onCollisionEnter(Collider& other) {}; + virtual void onCollisionStay(Collider& other) {}; + virtual void onCollisionExit(Collider& other) {}; + + void setOnCollisionEnter(CollisionCallback cb) { m_onEnter = std::move(cb); m_isSolid = false;} + void setOnCollisionStay(CollisionCallback cb) { m_onStay = std::move(cb); m_isSolid = false; } + void setOnCollisionExit(CollisionCallback cb) { m_onExit = std::move(cb); m_isSolid = false; } + + void dispatchCollisionEnter(Collider& other); + void dispatchCollisionStay(Collider& other); + void dispatchCollisionExit(Collider& other); + protected: Vec2 m_worldPosition{0.f, 0.f}; + bool m_isSolid = true; + + Entity* m_owner = nullptr; + + CollisionCallback m_onEnter; + CollisionCallback m_onStay; + CollisionCallback m_onExit; }; } diff --git a/engine/include/velos/physics/collision_manifold.h b/engine/include/velos/physics/collision_manifold.h new file mode 100644 index 0000000..ac664e0 --- /dev/null +++ b/engine/include/velos/physics/collision_manifold.h @@ -0,0 +1,14 @@ +#pragma once +#include "../math/transform.h" +#include "collider.h" + +namespace velos { + struct CollisionManifold { + bool isColliding = false; + float depth = 0.f; + Vec2 normal = Vec2{0.f, 0.f}; + + Collider* c1 = nullptr; + Collider* c2 = nullptr; + }; +} diff --git a/engine/include/velos/physics/physics_system.h b/engine/include/velos/physics/physics_system.h index d5e6daa..755ee3b 100644 --- a/engine/include/velos/physics/physics_system.h +++ b/engine/include/velos/physics/physics_system.h @@ -7,6 +7,7 @@ #include "aabb_collider.h" #include "circle_collider.h" #include "collider.h" +#include "collision_manifold.h" namespace velos { class PhysicsSystem { @@ -14,17 +15,18 @@ namespace velos { void update(const std::vector>& entities); private: - using CollisionPair = std::pair; + using CollisionPair = std::pair; std::set m_previous; std::set m_current; + std::vector m_manifolds; - bool checkCollision(Entity& e1, Entity& e2); - static CollisionPair makeOrderedPair(Entity* e1, Entity* e2); + void checkCollision(Entity& e1, Entity& e2); + static CollisionPair makeOrderedPair(Collider* c1, Collider* c2); - bool intersects(const Collider& c1, const Collider& c2); - bool aabbVsAabb(const AABBCollider& c1, const AABBCollider& c2); - bool circleVsCircle(const CircleCollider& c1, const CircleCollider& c2); - bool aabbVsCircle(const AABBCollider& c1, const CircleCollider& c2); + CollisionManifold intersects(Collider& c1, Collider& c2); + CollisionManifold aabbVsAabb(AABBCollider& c1, AABBCollider& c2); + CollisionManifold circleVsCircle(CircleCollider& c1, CircleCollider& c2); + CollisionManifold aabbVsCircle(AABBCollider& c1, CircleCollider& c2); }; } diff --git a/engine/src/actions/actions.cpp b/engine/src/actions/actions.cpp index 2f3b4ff..004df62 100644 --- a/engine/src/actions/actions.cpp +++ b/engine/src/actions/actions.cpp @@ -2,7 +2,7 @@ namespace velos { void Actions::bind(Key key, const ActionId& action, ActionType type, float holdThreshold) { - s_keyBindings[key] = ActionBinding{action, type, holdThreshold}; + s_keyBindings[key].push_back(ActionBinding{action, type, holdThreshold}); s_keyHoldTime[key] = 0.f; } @@ -10,13 +10,8 @@ namespace velos { s_callbacks[action].push_back(std::move(callback)); } - void Actions::trigger(Key key, float dt) { - auto binding = s_keyBindings.find(key); - if (binding == s_keyBindings.end()) - return; - - const ActionId& action = binding->second.action; - auto callbacks = s_callbacks.find(action); + void Actions::trigger(ActionId actionId, float dt) { + auto callbacks = s_callbacks.find(actionId); if (callbacks == s_callbacks.end()) return; diff --git a/engine/src/app/engine.cpp b/engine/src/app/engine.cpp index 9f78b2e..e9ace6a 100644 --- a/engine/src/app/engine.cpp +++ b/engine/src/app/engine.cpp @@ -60,26 +60,31 @@ namespace velos { } void Engine::processActions(float dt) { - for (auto& [key, binding]: Actions::getKeyBindings()) { + for (auto& [key, bindings]: Actions::getKeyBindings()) { bool pressed = velos::Input::isKeyPressed(key); - if (pressed) { + if (pressed) Actions::addHoldTime(key, dt); - if (binding.type == ActionType::Press && Actions::getHoldTime(key) <= dt) - Actions::trigger(key, dt); - if (binding.type == ActionType::Hold && Actions::getHoldTime(key) >= binding.holdThreshold) - Actions::trigger(key, dt); - if (binding.type == ActionType::HoldCompleted && !binding.completed && Actions::getHoldTime(key) >= binding.holdThreshold) { - Actions::trigger(key, dt); - binding.completed = true; + for (auto& binding: bindings) { + if (pressed) { + if (binding.type == ActionType::Press && Actions::getHoldTime(key) <= dt) + Actions::trigger(binding.action, dt); + if (binding.type == ActionType::Hold && Actions::getHoldTime(key) >= binding.holdThreshold) + Actions::trigger(binding.action, dt); + if (binding.type == ActionType::HoldCompleted && !binding.completed && Actions::getHoldTime(key) >= binding.holdThreshold) { + Actions::trigger(binding.action, dt); + binding.completed = true; + } + } else { + if (binding.type == ActionType::Release && Actions::getHoldTime(key) > 0.f) + Actions::trigger(binding.action, dt); + binding.completed = false; } - } else { - if (binding.type == ActionType::Release && Actions::getHoldTime(key) > 0.f) - Actions::trigger(key, dt); - Actions::resetHoldTime(key); - binding.completed = false; } + + if (!pressed) + Actions::resetHoldTime(key); } } diff --git a/engine/src/entity/entity.cpp b/engine/src/entity/entity.cpp index 9d25797..86d85d3 100644 --- a/engine/src/entity/entity.cpp +++ b/engine/src/entity/entity.cpp @@ -1,6 +1,12 @@ #include "velos/entity/entity.h" +#include "velos/physics/collider.h" namespace velos { + void Entity::update(float dt) { + updateColliders(); + m_transform.position += m_velocity * dt; + } + void Entity::render(Renderer& renderer) { for (auto& shape: m_shapes) shape->render(renderer, m_transform); @@ -20,12 +26,8 @@ namespace velos { return false; } - void Entity::move(float dx, float dy) { - m_transform.position.x += dx; - m_transform.position.y += dy; - } - void Entity::addCollider(std::unique_ptr collider) { + collider->setOwner(this); m_colliders.push_back(std::move(collider)); } @@ -37,22 +39,4 @@ namespace velos { for (auto& collider: m_colliders) collider->computeWorld(m_transform); } - - void Entity::dispatchCollisionEnter(Entity& other) { - onCollisionEnter(other); - if (m_onEnter) - m_onEnter(other); - } - - void Entity::dispatchCollisionStay(Entity& other) { - onCollisionStay(other); - if (m_onStay) - m_onStay(other); - } - - void Entity::dispatchCollisionExit(Entity& other) { - onCollisionExit(other); - if (m_onExit) - m_onExit(other); - } } diff --git a/engine/src/physics/collider.cpp b/engine/src/physics/collider.cpp new file mode 100644 index 0000000..d8e4e15 --- /dev/null +++ b/engine/src/physics/collider.cpp @@ -0,0 +1,21 @@ +#include "velos/physics/collider.h" + +namespace velos { + void Collider::dispatchCollisionEnter(Collider& other) { + onCollisionEnter(other); + if (m_onEnter) + m_onEnter(other); + } + + void Collider::dispatchCollisionStay(Collider& other) { + onCollisionStay(other); + if (m_onStay) + m_onStay(other); + } + + void Collider::dispatchCollisionExit(Collider& other) { + onCollisionExit(other); + if (m_onExit) + m_onExit(other); + } +} diff --git a/engine/src/physics/physics_system.cpp b/engine/src/physics/physics_system.cpp index 99bbf1e..54d0e56 100644 --- a/engine/src/physics/physics_system.cpp +++ b/engine/src/physics/physics_system.cpp @@ -1,101 +1,194 @@ #include "velos/physics/physics_system.h" namespace velos { - PhysicsSystem::CollisionPair PhysicsSystem::makeOrderedPair(Entity* e1, Entity* e2) { - if (e1 < e2) - return std::make_pair(e1, e2); - return std::make_pair(e2, e1); + PhysicsSystem::CollisionPair PhysicsSystem::makeOrderedPair(Collider* c1, Collider* c2) { + if (c1 < c2) + return std::make_pair(c1, c2); + return std::make_pair(c2, c1); } void PhysicsSystem::update(const std::vector>& entities) { m_current.clear(); + m_manifolds.clear(); for (size_t i = 0; i < entities.size(); ++i) { for (size_t j = i + 1; j < entities.size(); ++j) { Entity& e1 = *entities[i]; Entity& e2 = *entities[j]; - if (checkCollision(e1, e2)) - m_current.insert(makeOrderedPair(&e1, &e2)); + checkCollision(e1, e2); + } + } + + for (const auto& manifold: m_manifolds) { + Collider* c1 = manifold.c1; + Collider* c2 = manifold.c2; + + if (c1->isSolidCollision() && c2->isSolidCollision()) { + Entity* e1 = c1->owner(); + Entity* e2 = c2->owner(); + + bool e1_isStatic = (e1->velocity().lengthSquared() == 0.f); + bool e2_isStatic = (e2->velocity().lengthSquared() == 0.f); + + if (e1_isStatic && !e2_isStatic) { + e2->transform().position += manifold.normal * manifold.depth; + } else if (e2_isStatic && !e1_isStatic) { + e1->transform().position -= manifold.normal * manifold.depth; + } else if (!e1_isStatic && !e2_isStatic) { + e1->transform().position -= manifold.normal * (manifold.depth / 2.f); + e2->transform().position += manifold.normal * (manifold.depth / 2.f); + } + + if (std::abs(manifold.normal.x) > 0.1f) { + if (!e1_isStatic) + e1->setVelocityX(0.f); + if (!e2_isStatic) + e2->setVelocityX(0.f); + } + + if (std::abs(manifold.normal.y) > 0.1f) { + if (!e1_isStatic) + e1->setVelocityY(0.f); + if (!e2_isStatic) + e2->setVelocityY(0.f); + } } } for (const auto& pair: m_current) { - Entity* e1 = pair.first; - Entity* e2 = pair.second; + Collider* c1 = pair.first; + Collider* c2 = pair.second; if (m_previous.find(pair) == m_previous.end()) { - e1->dispatchCollisionEnter(*e2); - e2->dispatchCollisionEnter(*e1); + c1->dispatchCollisionEnter(*c2); + c2->dispatchCollisionEnter(*c1); } else { - e1->dispatchCollisionStay(*e2); - e2->dispatchCollisionStay(*e1); + c1->dispatchCollisionStay(*c2); + c2->dispatchCollisionStay(*c1); } } for (const auto& pair: m_previous) { if (m_current.find(pair) == m_current.end()) { - Entity* e1 = pair.first; - Entity* e2 = pair.second; + Collider* c1 = pair.first; + Collider* c2 = pair.second; - e1->dispatchCollisionExit(*e2); - e2->dispatchCollisionExit(*e1); + c1->dispatchCollisionExit(*c2); + c2->dispatchCollisionExit(*c1); } } m_previous = m_current; } - bool PhysicsSystem::checkCollision(Entity& e1, Entity& e2) { - for (const auto& c1: e1.colliders()) - for (const auto& c2: e2.colliders()) - if (intersects(*c1, *c2)) - return true; - return false; + void PhysicsSystem::checkCollision(Entity& e1, Entity& e2) { + for (const auto& c1: e1.colliders()) { + for (const auto& c2: e2.colliders()) { + CollisionManifold manifold = intersects(*c1, *c2); + if (manifold.isColliding) { + m_current.insert(makeOrderedPair(c1.get(), c2.get())); + m_manifolds.push_back(manifold); + } + } + } } - bool PhysicsSystem::intersects(const Collider& c1, const Collider& c2) { + CollisionManifold PhysicsSystem::intersects(Collider& c1, Collider& c2) { if (c1.type() == ColliderType::AABB && c2.type() == ColliderType::AABB) return aabbVsAabb( - static_cast(c1), - static_cast(c2) + static_cast(c1), + static_cast(c2) ); if (c1.type() == ColliderType::Circle && c2.type() == ColliderType::Circle) return circleVsCircle( - static_cast(c1), - static_cast(c2) + static_cast(c1), + static_cast(c2) ); if (c1.type() == ColliderType::AABB && c2.type() == ColliderType::Circle) return aabbVsCircle( - static_cast(c1), - static_cast(c2) + static_cast(c1), + static_cast(c2) ); if (c1.type() == ColliderType::Circle && c2.type() == ColliderType::AABB) return aabbVsCircle( - static_cast(c2), - static_cast(c1) + static_cast(c2), + static_cast(c1) ); - return false; + CollisionManifold manifold; + manifold.isColliding = false; + return manifold; } - bool PhysicsSystem::aabbVsAabb(const AABBCollider& c1, const AABBCollider& c2) { - return c1.worldPosition().x < c2.worldPosition().x + c2.size().x && + CollisionManifold PhysicsSystem::aabbVsAabb(AABBCollider& c1, AABBCollider& c2) { + CollisionManifold manifold; + manifold.isColliding = c1.worldPosition().x < c2.worldPosition().x + c2.size().x && c1.worldPosition().x + c1.size().x > c2.worldPosition().x && c1.worldPosition().y < c2.worldPosition().y + c2.size().y && c1.worldPosition().y + c1.size().y > c2.worldPosition().y; + + if (manifold.isColliding) { + Vec2 center1 = c1.worldPosition() + (c1.size() / 2.f); + Vec2 center2 = c2.worldPosition() + (c2.size() / 2.f); + + float overlapX = ((c1.size() / 2.f).x + (c2.size() / 2.f).x) - std::abs((center2 - center1).x); + float overlapY = ((c1.size() / 2.f).y + (c2.size() / 2.f).y) - std::abs((center2 - center1).y); + + if (overlapX < overlapY) { + manifold.depth = overlapX; + + if ((center2 - center1).x > 0.f) + manifold.normal = Vec2{1.f, 0.f}; + else + manifold.normal = Vec2{-1.f, 0.f}; + } else { + manifold.depth = overlapY; + + if ((center2 - center1).y > 0.f) + manifold.normal = Vec2{0.f, 1.f}; + else + manifold.normal = Vec2{0.f, -1.f}; + } + + manifold.c1 = &c1; + manifold.c2 = &c2; + } + + return manifold; } - bool PhysicsSystem::circleVsCircle(const CircleCollider& c1, const CircleCollider& c2) { + CollisionManifold PhysicsSystem::circleVsCircle(CircleCollider& c1, CircleCollider& c2) { + CollisionManifold manifold; Vec2 d = c1.worldPosition() - c2.worldPosition(); float r = c1.radius() + c2.radius(); - return d.lengthSquared() <= r * r; + + manifold.isColliding = d.lengthSquared() <= r * r; + + if (manifold.isColliding) { + Vec2 dir = c2.worldPosition() - c1.worldPosition(); + float dist = dir.length(); + + if (dist == 0.f) { + manifold.depth = c1.radius() + c2.radius(); + manifold.normal = Vec2{1.f, 0.f}; + } else { + manifold.depth = (c1.radius() + c2.radius()) - dist; + manifold.normal = dir / dist; + } + + manifold.c1 = &c1; + manifold.c2 = &c2; + } + + return manifold; } - bool PhysicsSystem::aabbVsCircle(const AABBCollider& c1, const CircleCollider& c2) { + CollisionManifold PhysicsSystem::aabbVsCircle(AABBCollider& c1, CircleCollider& c2) { + CollisionManifold manifold; Vec2 closest{ std::clamp(c2.worldPosition().x, c1.worldPosition().x, @@ -106,6 +199,40 @@ namespace velos { }; Vec2 delta = c2.worldPosition() - closest; - return delta.lengthSquared() <= c2.radius() * c2.radius(); + + manifold.isColliding = delta.lengthSquared() <= c2.radius() * c2.radius(); + + if (manifold.isColliding) { + float dist = delta.length(); + + if (dist == 0.f) { + Vec2 center = c2.worldPosition(); + float left = center.x - c1.worldPosition().x; + float right = (c1.worldPosition().x + c1.size().x) - center.x; + float top = center.y - c1.worldPosition().y; + float bottom = (c1.worldPosition().y + c1.size().y) - center.y; + + float min = std::min({left, right, top, bottom}); + manifold.depth = min + c2.radius(); + + if (min == left) + manifold.normal = Vec2{-1.f, 0.f}; + else if (min == right) + manifold.normal = Vec2{1.f, 0.f}; + else if (min == top) + manifold.normal = Vec2{0.f, -1.f}; + else if (min == bottom) + manifold.normal = Vec2{0.f, 1.f}; + + } else { + manifold.depth = c2.radius() - dist; + manifold.normal = delta / dist; + } + + manifold.c1 = &c1; + manifold.c2 = &c2; + } + + return manifold; } } diff --git a/examples/sandbox/src/main.cpp b/examples/sandbox/src/main.cpp index aac2a94..f0ab2f9 100644 --- a/examples/sandbox/src/main.cpp +++ b/examples/sandbox/src/main.cpp @@ -25,6 +25,7 @@ int main() { velos::Vec2 size{50.f, 50.f}; auto rect = std::make_shared(velos::Vec2{0.f, 0.f} - size / 2.f, size, velos::Color{255, 0, 0}); auto circle = std::make_shared(velos::Vec2{100.f, 100.f}, 50, velos::Color{255, 255, 0}); + auto rect2 = std::make_shared(velos::Vec2{100.f, 220.f}, size * 1.5f, velos::Color{0, 0, 255}); auto entity = std::make_shared(); entity->addShape(rect); @@ -35,22 +36,31 @@ int main() { auto stationary_entity = std::make_shared(); stationary_entity->addShape(circle); + stationary_entity->addShape(rect2); auto stationary_collider = std::make_unique(50, velos::Vec2{100.f, 100.f}); - stationary_entity->addCollider(std::move(stationary_collider)); - - stationary_entity->setOnCollisionEnter([](velos::Entity& other) { + auto stationary_collider2 = std::make_unique(size * 1.5f, velos::Vec2{100.f, 220.f}); + /* + stationary_collider->setOnCollisionEnter([](velos::Collider& other) { VELOS_INFO("Entered collision!"); }); - stationary_entity->setOnCollisionExit([](velos::Entity& other) { + stationary_collider->setOnCollisionExit([](velos::Collider& other) { VELOS_INFO("Exited collision!"); }); + */ + stationary_entity->addCollider(std::move(stationary_collider)); + stationary_entity->addCollider(std::move(stationary_collider2)); + + engine.setAction(velos::Key::W, "StopMoveForward", velos::ActionType::Release, 0.f, [entity](float dt){ entity->setVelocityY(0.f); }); + engine.setAction(velos::Key::S, "StopMoveBackward", velos::ActionType::Release, 0.f, [entity](float dt){ entity->setVelocityY(0.f); }); + engine.setAction(velos::Key::A, "StopMoveLeft", velos::ActionType::Release, 0.f, [entity](float dt){ entity->setVelocityX(0.f); }); + engine.setAction(velos::Key::D, "StopMoveRight", velos::ActionType::Release, 0.f, [entity](float dt){ entity->setVelocityX(0.f); }); float speed = 200.f; - engine.setAction(velos::Key::W, "MoveForward", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->move(0, -speed * dt); }); - engine.setAction(velos::Key::S, "MoveBackward", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->move(0, speed * dt); }); - engine.setAction(velos::Key::A, "MoveLeft", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->move(-speed * dt, 0); }); - engine.setAction(velos::Key::D, "MoveRight", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->move(speed * dt, 0); }); + engine.setAction(velos::Key::W, "MoveForward", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->setVelocityY(-speed); }); + engine.setAction(velos::Key::S, "MoveBackward", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->setVelocityY(speed); }); + engine.setAction(velos::Key::A, "MoveLeft", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->setVelocityX(-speed); }); + engine.setAction(velos::Key::D, "MoveRight", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->setVelocityX(speed); }); engine.setAction(velos::Key::Q, "RotateLeft", velos::ActionType::Hold, 0.05f, [entity](float dt){ entity->transform().rotation -= 3.f * dt; }); engine.setAction(velos::Key::E, "RotateRight", velos::ActionType::Hold, 0.05f, [entity](float dt){ entity->transform().rotation += 3.f * dt; });