From 4339f4b1e3ace21a807b4f30ed13ae04ae33db7f Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Mar 2026 12:18:10 +0200 Subject: [PATCH 01/10] Add app level logging and solve platform bug Signed-off-by: Mihai Dumitrescu --- engine/include/velos/core/log.h | 5 ++ engine/include/velos/core/platform.h | 12 ++- engine/include/velos/graphics/camera/camera.h | 5 +- engine/include/velos/graphics/renderer.h | 2 + engine/include/velos/input/input.h | 10 +-- engine/include/velos/math/math.h | 73 ++++++++++++++++- engine/include/velos/math/transform.h | 79 ++++--------------- .../velos/physics/collision_manifold.h | 2 +- engine/src/core/platform.cpp | 15 ---- engine/src/graphics/camera/camera.cpp | 2 +- engine/src/graphics/renderer.cpp | 2 + engine/src/input/input.cpp | 5 ++ 12 files changed, 121 insertions(+), 91 deletions(-) delete mode 100644 engine/src/core/platform.cpp diff --git a/engine/include/velos/core/log.h b/engine/include/velos/core/log.h index a7e8ad7..044971e 100644 --- a/engine/include/velos/core/log.h +++ b/engine/include/velos/core/log.h @@ -19,3 +19,8 @@ namespace velos { #define VELOS_INFO(...) ::velos::Log::engine()->info(__VA_ARGS__) #define VELOS_WARN(...) ::velos::Log::engine()->warn(__VA_ARGS__) #define VELOS_ERROR(...) ::velos::Log::engine()->error(__VA_ARGS__) + +#define APP_TRACE(...) ::velos::Log::app()->trace(__VA_ARGS__) +#define APP_INFO(...) ::velos::Log::app()->info(__VA_ARGS__) +#define APP_WARN(...) ::velos::Log::app()->warn(__VA_ARGS__) +#define APP_ERROR(...) ::velos::Log::app()->error(__VA_ARGS__) diff --git a/engine/include/velos/core/platform.h b/engine/include/velos/core/platform.h index 691f727..177a99b 100644 --- a/engine/include/velos/core/platform.h +++ b/engine/include/velos/core/platform.h @@ -8,5 +8,15 @@ namespace velos { Unknown }; - Platform current_platform(); + constexpr Platform current_platform() { +#if defined(_WIN32) + return Platform::Windows; +#elif defined(__linux__) + return Platform::Linux; +#elif defined(__APPLE__) + return Platform::MacOS; +#else + return Platform::Unknown; +#endif + } } diff --git a/engine/include/velos/graphics/camera/camera.h b/engine/include/velos/graphics/camera/camera.h index 88fb8c8..d64c5e7 100644 --- a/engine/include/velos/graphics/camera/camera.h +++ b/engine/include/velos/graphics/camera/camera.h @@ -9,10 +9,11 @@ namespace velos { Camera() = default; Transform& transform() { return m_transform; } + const Transform& transform() const { return m_transform; } void follow(const Transform* target); void unfollow(); - bool isFollowing(); + bool isFollowing() const; void setFollowMode(CameraFollowMode mode); void setSmoothSpeed(float speed); @@ -22,7 +23,7 @@ namespace velos { void update(float dt); - float zoom() { return m_zoom; } + float zoom() const { return m_zoom; } void setZoom(float zoom) { m_zoom = std::max(0.01f, zoom); } Vec2 worldToScreen(const Vec2& world, const Vec2& screenCenter) const; diff --git a/engine/include/velos/graphics/renderer.h b/engine/include/velos/graphics/renderer.h index c84930d..0f7555a 100644 --- a/engine/include/velos/graphics/renderer.h +++ b/engine/include/velos/graphics/renderer.h @@ -11,6 +11,8 @@ namespace velos { explicit Renderer(SDL_Window* window); ~Renderer(); + Renderer(const Renderer&) = delete; + void beginFrame(); void endFrame(); diff --git a/engine/include/velos/input/input.h b/engine/include/velos/input/input.h index d9fde4b..67fc694 100644 --- a/engine/include/velos/input/input.h +++ b/engine/include/velos/input/input.h @@ -5,7 +5,7 @@ namespace velos { enum class MouseButton { - Left, Right, Middle + Left, Right, Middle, Unknown }; class Input { @@ -18,10 +18,10 @@ namespace velos { static void getMousePosition(float& x, float& y); private: - inline static std::unordered_map s_keyStates; + static std::unordered_map s_keyStates; + static std::unordered_map s_mouseStates; - inline static std::unordered_map s_mouseStates; - inline static float s_mouseX; - inline static float s_mouseY; + static float s_mouseX; + static float s_mouseY; }; } diff --git a/engine/include/velos/math/math.h b/engine/include/velos/math/math.h index 20b453f..8359f89 100644 --- a/engine/include/velos/math/math.h +++ b/engine/include/velos/math/math.h @@ -1,8 +1,77 @@ #pragma once -#include "transform.h" +#include namespace velos { - static Vec2 lerp(const Vec2& a, const Vec2& b, float t) { + struct Vec2{ + float x = 0.f; + float y = 0.f; + + Vec2() = default; + Vec2(float x_, float y_): x(x_), y(y_) {} + + Vec2 operator+(const Vec2& o) const { + return { x + o.x, y + o.y }; + } + + Vec2 operator-(const Vec2& o) const { + return { x - o.x, y - o.y }; + } + + Vec2 operator*(const Vec2& o) const { + return { x * o.x, y * o.y }; + } + + Vec2 operator*(float s) const { + return { x * s, y * s }; + } + + Vec2 operator/(const Vec2& o) { + return { x / o.x, y / o.y}; + } + + Vec2 operator/(float s) const { + return { x / s, y / s }; + } + + void operator+=(const Vec2& o) { + x += o.x; + y += o.y; + } + + void operator-=(const Vec2& o) { + x -= o.x; + y -= o.y; + } + + bool operator==(const Vec2& o) const { + return std::abs(x - o.x) < 0.0001f && std::abs(y - o.y) < 0.0001f; + } + + float length() const { + return std::sqrt(x * x + y * y); + } + + float lengthSquared() const { + return x * x + y * y; + } + + Vec2 normalized() const { + float len = length(); + if (len < 0.00001f) + return {0.f, 0.f}; + return {x / len, y / len}; + } + + void normalize() { + float len = length(); + if (len < 0.00001f) + return; + x /= len; + y /= len; + } + }; + + inline Vec2 lerp(const Vec2& a, const Vec2& b, float t) { return a + (b - a) * t; } } diff --git a/engine/include/velos/math/transform.h b/engine/include/velos/math/transform.h index 6e23706..a3ff00e 100644 --- a/engine/include/velos/math/transform.h +++ b/engine/include/velos/math/transform.h @@ -1,72 +1,12 @@ #pragma once +#include "math.h" #include namespace velos { - struct Vec2{ - float x = 0.f; - float y = 0.f; - - Vec2() = default; - Vec2(float x_, float y_): x(x_), y(y_) {} - - Vec2 operator+(const Vec2& o) const { - 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 }; - } - - Vec2 operator*(const Vec2& o) { - return { x * o.x, y * o.y }; - } - - Vec2 operator-(const Vec2& o) const { - return { x - o.x, y - o.y }; - } - - Vec2 operator/(float s) const { - return { x / s, y / s }; - } - - float length() const { - return std::sqrt(x * x + y * y); - } - - float lengthSquared() const { - return x * x + y * y; - } - - Vec2 normalized() const { - float len = length(); - if (len < 0.00001f) - return {0.f, 0.f}; - return {x / len, y / len}; - } - - void normalize() { - float len = length(); - if (len < 0.00001f) - return; - x /= len; - y /= len; - } - }; - class Transform { public: Vec2 position{0.f, 0.f}; - float rotation = 0.f; + float rotation = 0.f; // radians Vec2 scale{1.f, 1.f}; Vec2 apply(const Vec2& local) const { @@ -92,14 +32,25 @@ namespace velos { float x = sx * cos - sy * sin; float y = sx * sin + sy * cos; - return { x / scale.x, y / scale.y }; + float scaleX, scaleY; + if (std::abs(scale.x) < 0.0001f) + scaleX = 0.0001; + else + scaleX = scale.x;; + + if (std::abs(scale.y) < 0.0001f) + scaleY = 0.0001; + else + scaleY = scale.y; + + return { x / scaleX, y / scaleY }; } Transform combine(const Transform& o) const { Transform transform; transform.scale.x = scale.x * o.scale.x; - transform.scale.y = scale.y + o.scale.y; + transform.scale.y = scale.y * o.scale.y; transform.rotation = rotation + o.rotation; diff --git a/engine/include/velos/physics/collision_manifold.h b/engine/include/velos/physics/collision_manifold.h index ac664e0..c7cdc8b 100644 --- a/engine/include/velos/physics/collision_manifold.h +++ b/engine/include/velos/physics/collision_manifold.h @@ -6,7 +6,7 @@ namespace velos { struct CollisionManifold { bool isColliding = false; float depth = 0.f; - Vec2 normal = Vec2{0.f, 0.f}; + Vec2 normal{0.f, 0.f}; Collider* c1 = nullptr; Collider* c2 = nullptr; diff --git a/engine/src/core/platform.cpp b/engine/src/core/platform.cpp deleted file mode 100644 index 0d8ca7a..0000000 --- a/engine/src/core/platform.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "velos/core/platform.h" - -namespace velos { - Platform current_platform() { -#if defined(_WIN32) - return Platform::Windows; -#elif defined(__linux__) - return Platform::Linux; -#elif defined(__APPLE__) - return Platform::MacOS; -#elif - return Platform::Unknown; -#endif - } -} diff --git a/engine/src/graphics/camera/camera.cpp b/engine/src/graphics/camera/camera.cpp index 151be6a..d66b9a2 100644 --- a/engine/src/graphics/camera/camera.cpp +++ b/engine/src/graphics/camera/camera.cpp @@ -11,7 +11,7 @@ namespace velos { m_target = nullptr; } - bool Camera::isFollowing() { + bool Camera::isFollowing() const { return m_target != nullptr; } diff --git a/engine/src/graphics/renderer.cpp b/engine/src/graphics/renderer.cpp index 8f3ea2c..26f5216 100644 --- a/engine/src/graphics/renderer.cpp +++ b/engine/src/graphics/renderer.cpp @@ -7,6 +7,8 @@ namespace velos { m_renderer = SDL_CreateRenderer(window, nullptr); if (!m_renderer) VELOS_ERROR("Failed to create SDL renderer: {}", SDL_GetError()); + + SDL_SetRenderDrawBlendMode(m_renderer, SDL_BLENDMODE_BLEND); } Renderer::~Renderer() { diff --git a/engine/src/input/input.cpp b/engine/src/input/input.cpp index 0a747b7..2bd09d3 100644 --- a/engine/src/input/input.cpp +++ b/engine/src/input/input.cpp @@ -7,6 +7,11 @@ #include namespace velos { + std::unordered_map Input::s_keyStates; + std::unordered_map Input::s_mouseStates; + float Input::s_mouseX = 0.f; + float Input::s_mouseY = 0.f; + void Input::update() { SDL_Event event; while (SDL_PollEvent(&event)) { From 335d09775673f0a848fd544a1aeac57059c2b7e0 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Mar 2026 12:21:26 +0200 Subject: [PATCH 02/10] Add target fps Signed-off-by: Mihai Dumitrescu --- engine/include/velos/app/engine_config.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/include/velos/app/engine_config.h b/engine/include/velos/app/engine_config.h index 6874e93..32fdaa6 100644 --- a/engine/include/velos/app/engine_config.h +++ b/engine/include/velos/app/engine_config.h @@ -7,5 +7,7 @@ namespace velos { int window_width = 1280; int window_height = 720; bool enable_vsync = true; + + int target_fps = 60; }; } From 81525c10a573d76f9f1dc717d27c106f71e6b74a Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Mar 2026 12:42:16 +0200 Subject: [PATCH 03/10] Add default colors Signed-off-by: Mihai Dumitrescu --- engine/include/velos/graphics/color.h | 20 +++++++++++++++++++- engine/src/graphics/color.cpp | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/engine/include/velos/graphics/color.h b/engine/include/velos/graphics/color.h index 417951c..283aa96 100644 --- a/engine/include/velos/graphics/color.h +++ b/engine/include/velos/graphics/color.h @@ -3,9 +3,27 @@ namespace velos { struct Color { - uint8_t r, g, b, a; + uint8_t r = 255; + uint8_t g = 255; + uint8_t b = 255; + uint8_t a = 255; + Color() = default; Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255) : r(red), g(green), b(blue), a(alpha) {} + + static const Color White; + static const Color Black; + static const Color Red; + static const Color Green; + static const Color Blue; + static const Color Yellow; + static const Color Grey; + static const Color Brown; + static const Color Pink; + static const Color Cyan; + static const Color Orange; + static const Color Magenta; + static const Color Transparent; }; } diff --git a/engine/src/graphics/color.cpp b/engine/src/graphics/color.cpp index e69de29..ef74cfb 100644 --- a/engine/src/graphics/color.cpp +++ b/engine/src/graphics/color.cpp @@ -0,0 +1,17 @@ +#include "velos/graphics/color.h" + +namespace velos { + const Color Color::White{255, 255, 255, 255}; + const Color Color::Black{0, 0, 0, 255}; + const Color Color::Red{255, 0, 0, 255}; + const Color Color::Green{0, 255, 0, 255}; + const Color Color::Blue{0, 0, 255, 255}; + const Color Color::Yellow{255, 255, 0, 255}; + const Color Color::Grey{128, 128, 128, 255}; + const Color Color::Brown{139, 69, 19, 255}; + const Color Color::Pink{255, 0, 155, 255}; + const Color Color::Cyan{0, 255, 255, 255}; + const Color Color::Orange{255, 165, 0, 255}; + const Color Color::Magenta{255, 0, 255, 255}; + const Color Color::Transparent{0, 0, 0, 0}; +} From 8fd1dd11102d877885db3cad288d268c39f0e09c Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Mar 2026 12:44:49 +0200 Subject: [PATCH 04/10] Implement static entities Signed-off-by: Mihai Dumitrescu --- engine/include/velos/entity/entity.h | 53 ++++++++++++++++++++++++--- engine/src/entity/entity.cpp | 20 +++++----- engine/src/physics/physics_system.cpp | 6 ++- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/engine/include/velos/entity/entity.h b/engine/include/velos/entity/entity.h index d56c36b..ff68aa8 100644 --- a/engine/include/velos/entity/entity.h +++ b/engine/include/velos/entity/entity.h @@ -1,13 +1,13 @@ #pragma once #include #include +#include "../physics/collider.h" #include "../graphics/shapes/shape.h" #include "../graphics/renderer.h" #include "../math/transform.h" +#include "../graphics/color.h" namespace velos { - class Collider; - class Entity { public: Entity() = default; @@ -19,10 +19,29 @@ namespace velos { void debugRender(Renderer& renderer); bool containsPoint(const Vec2& worldPos) const; + template + std::shared_ptr addShape(Args&&... args) { + auto shape = std::make_shared(std::forward(args)...); + m_shapes.push_back(shape); + return shape; + } + + template + T* addCollider(Args&&... args) { + auto collider = std::make_unique(std::forward(args)...); + T* raw = collider.get(); + collider->setOwner(this); + m_colliders.push_back(std::move(collider)); + return raw; + } + void addShape(std::shared_ptr shape) { m_shapes.push_back(shape); } + void addCollider(std::unique_ptr collider) { + collider->setOwner(this); + m_colliders.push_back(std::move(collider)); + } - void addCollider(std::unique_ptr collider); - const std::vector>& colliders() const; + const std::vector>& colliders() const { return m_colliders; } void updateColliders(); void setVelocity(Vec2 velocity) { m_velocity = velocity; } @@ -31,11 +50,35 @@ namespace velos { Vec2& velocity() { return m_velocity; } Transform& transform() { return m_transform; } + void setPosition(const Vec2& position) { m_transform.position = position; } + void setRotation(float rot) { m_transform.rotation = rot; } + void setScale(const Vec2& scale) { m_transform.scale = scale; } + + // set color for all entity's shapes + void setColor(const Color& color); + + void move(const Vec2& offset) { m_transform.position += offset; } + void rotate(float angle) { m_transform.rotation += angle; } + void addScale(float scale) { m_transform.scale += Vec2{scale, scale}; } + + void setAngularVelocity(float value) { m_angularVelocity = value; } + float angularVelocity() const { return m_angularVelocity; } + + void setScaleVelocity(const Vec2& scale) { m_scaleVelocity = scale; } + Vec2 scaleVelocity() const { return m_scaleVelocity; } + + bool isStatic() const { return m_isStatic; } + void setStatic(bool state) { m_isStatic = state; } + private: Vec2 m_velocity; - Transform m_transform; + + float m_angularVelocity = 0.f; + Vec2 m_scaleVelocity{0.f, 0.f}; + std::vector> m_shapes; std::vector> m_colliders; + bool m_isStatic = false; }; } diff --git a/engine/src/entity/entity.cpp b/engine/src/entity/entity.cpp index 204a8ce..0baa64d 100644 --- a/engine/src/entity/entity.cpp +++ b/engine/src/entity/entity.cpp @@ -3,7 +3,11 @@ namespace velos { void Entity::update(float dt) { - m_transform.position += m_velocity * dt; + if (!m_isStatic) { + m_transform.position += m_velocity * dt; + m_transform.rotation += m_angularVelocity * dt; + m_transform.scale += m_scaleVelocity * dt; + } updateColliders(); } @@ -25,17 +29,13 @@ namespace velos { return false; } - void Entity::addCollider(std::unique_ptr collider) { - collider->setOwner(this); - m_colliders.push_back(std::move(collider)); - } - - const std::vector>& Entity::colliders() const { - return m_colliders; - } - void Entity::updateColliders() { for (auto& collider: m_colliders) collider->computeWorld(m_transform); } + + void Entity::setColor(const Color& color) { + for (auto& shape: m_shapes) + shape->setColor(color); + } } diff --git a/engine/src/physics/physics_system.cpp b/engine/src/physics/physics_system.cpp index 54d0e56..f4602b8 100644 --- a/engine/src/physics/physics_system.cpp +++ b/engine/src/physics/physics_system.cpp @@ -1,4 +1,6 @@ #include "velos/physics/physics_system.h" +#include +#include namespace velos { PhysicsSystem::CollisionPair PhysicsSystem::makeOrderedPair(Collider* c1, Collider* c2) { @@ -28,8 +30,8 @@ namespace velos { Entity* e1 = c1->owner(); Entity* e2 = c2->owner(); - bool e1_isStatic = (e1->velocity().lengthSquared() == 0.f); - bool e2_isStatic = (e2->velocity().lengthSquared() == 0.f); + bool e1_isStatic = (e1->isStatic() && e1->velocity().lengthSquared() == 0.f); + bool e2_isStatic = (e2->isStatic() && e2->velocity().lengthSquared() == 0.f); if (e1_isStatic && !e2_isStatic) { e2->transform().position += manifold.normal * manifold.depth; From e26b597dccdabb5e8497ca483ee45588897f48e2 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Mar 2026 13:15:49 +0200 Subject: [PATCH 05/10] Implement multiple APIs Signed-off-by: Mihai Dumitrescu --- engine/include/velos/app/engine.h | 27 +++- engine/include/velos/entity/entity_manager.h | 9 +- engine/include/velos/graphics/camera/camera.h | 8 ++ engine/include/velos/graphics/shapes/shape.h | 4 + engine/include/velos/physics/aabb_collider.h | 6 +- engine/include/velos/physics/collider.h | 7 +- engine/include/velos/velos.h | 39 +++++- engine/src/app/engine.cpp | 79 +++++++++++ engine/src/entity/entity_manager.cpp | 6 +- engine/src/graphics/camera/camera.cpp | 9 +- engine/src/physics/aabb_collider.cpp | 21 ++- engine/src/physics/circle_collider.cpp | 2 +- examples/example-camera/src/main.cpp | 36 ++--- examples/sandbox/src/main.cpp | 126 ++++++------------ 14 files changed, 251 insertions(+), 128 deletions(-) diff --git a/engine/include/velos/app/engine.h b/engine/include/velos/app/engine.h index 6dd30f2..fe5591e 100644 --- a/engine/include/velos/app/engine.h +++ b/engine/include/velos/app/engine.h @@ -22,21 +22,38 @@ namespace velos { void stop(); void onEvent(Event& e); - using EventCallback = std::function; void addEventListener(EventType type, EventCallback callback); + void setAction(Key key, const ActionId& action, ActionType type, float holdThreshold, std::function callback); - void processActions(float dt); + + std::shared_ptr createEntity() { return m_entityManager->createEntity(); } + const std::vector>& entities() const { return m_entityManager->entities(); } + EntityManager* entityManager() { return m_entityManager.get(); } void attachCamera(Entity& entity); void detachCamera(); - void setDebugRender(bool state); + Camera* camera() { return m_camera.get(); } + + // APIs + void setTargetFPS(int fps) { m_targetFPS = fps; } + int targetFPS() const { return m_targetFPS; } + + void addMovement(std::shared_ptr entity, float speed = 200.f, Key up = Key::W, Key left = Key::A, Key down = Key::S, Key right = Key::D); + void addRotationControl(std::shared_ptr entity, float speed = 3.f, Key left = Key::Q, Key right = Key::E); + void addScaleControl(std::shared_ptr entity, float speed = 1.f, Key down = Key::Z, Key up = Key::X); + void addCameraControl(float speed = 250.f, Key up = Key::ArrowUp, Key left = Key::ArrowLeft, Key down = Key::ArrowDown, Key right = Key::ArrowRight); + void addCameraZoomControl(float speed = 1.5f, Key zoomOut = Key::Q, Key zoomIn = Key::E); + + void onEntityClick(std::function)>); + void onHoldAction(Key key, const ActionId& action, float threshold, std::function callback); static Engine* instance() { return s_instance; } - Camera* camera() { return m_camera.get(); } - EntityManager* entityManager() { return m_entityManager.get(); } private: + void processActions(float dt); + int m_targetFPS; + std::unique_ptr m_window; std::unique_ptr m_camera; std::unique_ptr m_renderer; diff --git a/engine/include/velos/entity/entity_manager.h b/engine/include/velos/entity/entity_manager.h index 58e10cb..dc3b916 100644 --- a/engine/include/velos/entity/entity_manager.h +++ b/engine/include/velos/entity/entity_manager.h @@ -7,13 +7,20 @@ namespace velos { class EntityManager { public: + std::shared_ptr createEntity() { + auto entity = std::make_shared(); + m_entities.push_back(entity); + return entity; + } + void addEntity(std::shared_ptr entity) { m_entities.push_back(entity); } + void update(float dt); void render(Renderer& renderer); void setDebugRender(bool state); - std::vector> entities() { return m_entities; } + const std::vector>& entities() const { return m_entities; } private: std::vector> m_entities; PhysicsSystem m_physics; diff --git a/engine/include/velos/graphics/camera/camera.h b/engine/include/velos/graphics/camera/camera.h index d64c5e7..85ac3e9 100644 --- a/engine/include/velos/graphics/camera/camera.h +++ b/engine/include/velos/graphics/camera/camera.h @@ -23,8 +23,13 @@ namespace velos { void update(float dt); + void setVelocity(const Vec2& vel) { m_velocity = vel; } + void setVelocityX(float x) { m_velocity.x = x; } + void setVelocityY(float y) { m_velocity.y = y; } + float zoom() const { return m_zoom; } void setZoom(float zoom) { m_zoom = std::max(0.01f, zoom); } + void setZoomVelocity(float zoomVelocity) { m_zoomVelocity = zoomVelocity; } Vec2 worldToScreen(const Vec2& world, const Vec2& screenCenter) const; Vec2 screenToWorld(const Vec2& screen, const Vec2& windowCenter) const; @@ -33,6 +38,9 @@ namespace velos { Transform m_transform; const Transform* m_target = nullptr; + Vec2 m_velocity{0.f, 0.f}; + float m_zoomVelocity; + CameraFollowMode m_mode = CameraFollowMode::Instant; float m_smoothSpeed = 8.f; diff --git a/engine/include/velos/graphics/shapes/shape.h b/engine/include/velos/graphics/shapes/shape.h index 9765c3c..e04f0e3 100644 --- a/engine/include/velos/graphics/shapes/shape.h +++ b/engine/include/velos/graphics/shapes/shape.h @@ -12,6 +12,10 @@ namespace velos { void setColor(const Color& color) { m_color = color; } Transform& transform() { return m_localTransform; } + void setPosition(const Vec2& position) { m_localTransform.position = position; } + void setRotation(float rot) { m_localTransform.rotation = rot; } + void setScale(const Vec2& scale) { m_localTransform.scale = scale; } + protected: Transform m_localTransform; Color m_color{255, 255, 255, 255}; diff --git a/engine/include/velos/physics/aabb_collider.h b/engine/include/velos/physics/aabb_collider.h index e63f28b..97896d7 100644 --- a/engine/include/velos/physics/aabb_collider.h +++ b/engine/include/velos/physics/aabb_collider.h @@ -4,8 +4,8 @@ namespace velos { class AABBCollider: public Collider { public: - AABBCollider(const Vec2& size) - : m_size(size), m_worldSize(size) {} + explicit AABBCollider(const Vec2& size) + : m_size(size) {} ColliderType type() const override; void computeWorld(const Transform& entityTransform) override; @@ -19,6 +19,6 @@ namespace velos { private: Vec2 m_size; - Vec2 m_worldSize; + Vec2 m_worldSize{0.f, 0.f}; }; } diff --git a/engine/include/velos/physics/collider.h b/engine/include/velos/physics/collider.h index 3d86422..8f03f67 100644 --- a/engine/include/velos/physics/collider.h +++ b/engine/include/velos/physics/collider.h @@ -3,9 +3,10 @@ #include "../graphics/renderer.h" #include -class Entity; namespace velos { + class Entity; + enum class ColliderType { AABB, Circle @@ -25,6 +26,10 @@ namespace velos { bool isSolidCollision() { return m_isSolid; } Transform& transform() { return m_localTransform; } + void setPosition(const Vec2& position) { m_localTransform.position = position; } + void setRotation(float rot) { m_localTransform.rotation = rot; } + void setScale(const Vec2& scale) { m_localTransform.scale = scale; } + Entity* owner() const { return m_owner; } void setOwner(Entity* owner) { m_owner = owner; } diff --git a/engine/include/velos/velos.h b/engine/include/velos/velos.h index c554f44..cd1c5dc 100644 --- a/engine/include/velos/velos.h +++ b/engine/include/velos/velos.h @@ -1,4 +1,41 @@ #pragma once -#include +// core +#include #include +#include +#include + +// math +#include +#include + +// entity +#include +#include + +// graphics +#include +#include +#include + +// shapes +#include +#include +#include +#include +#include + +// physics +#include +#include +#include + +// input +#include +#include +#include +#include + +// time +#include diff --git a/engine/src/app/engine.cpp b/engine/src/app/engine.cpp index e9ace6a..19d0e44 100644 --- a/engine/src/app/engine.cpp +++ b/engine/src/app/engine.cpp @@ -13,8 +13,10 @@ namespace velos { m_window = std::make_unique( config.app_name, config.window_width, config.window_height ); + m_targetFPS = config.target_fps; m_camera = std::make_unique(); + m_camera->transform().position = velos::Vec2{config.window_width / 2.f, config.window_height / 2.f}; m_renderer = std::make_unique(m_window->native()); m_renderer->setCamera(m_camera.get()); @@ -30,6 +32,8 @@ namespace velos { void Engine::run() { while (m_running && !m_window->shouldClose()) { + uint64_t frameStart = SDL_GetTicks(); + Clock::tick(); velos::Input::update(); @@ -42,6 +46,14 @@ namespace velos { m_renderer->beginFrame(); m_entityManager->render(*m_renderer); m_renderer->endFrame(); + + if (m_targetFPS > 0) { + float targetFrameTime = 1000.f / m_targetFPS; + uint64_t frameTimeTaken = SDL_GetTicks() - frameStart; + + if (frameTimeTaken < targetFrameTime) + SDL_Delay(static_cast(targetFrameTime - frameTimeTaken)); + } } } @@ -111,4 +123,71 @@ namespace velos { void Engine::setDebugRender(bool state) { entityManager()->setDebugRender(state); } + + void Engine::addMovement(std::shared_ptr entity, float speed, Key up, Key left, Key down, Key right) { + setAction(up, "MoveUp", ActionType::Hold, 0.f, [entity, speed](float dt){ entity->setVelocityY(-speed); }); + setAction(down, "MoveDown", ActionType::Hold, 0.f, [entity, speed](float dt){ entity->setVelocityY(speed); }); + setAction(left, "MoveLeft", ActionType::Hold, 0.f, [entity, speed](float dt){ entity->setVelocityX(-speed); }); + setAction(right, "MoveRight", ActionType::Hold, 0.f, [entity, speed](float dt){ entity->setVelocityX(speed); }); + + setAction(up, "StopUp", ActionType::Release, 0.f, [entity](float dt){ entity->setVelocityY(0.f); }); + setAction(down, "StopDown", ActionType::Release, 0.f, [entity](float dt){ entity->setVelocityY(0.f); }); + setAction(left, "StopLeft", ActionType::Release, 0.f, [entity](float dt){ entity->setVelocityX(0.f); }); + setAction(right, "StopRight", ActionType::Release, 0.f, [entity](float dt){ entity->setVelocityX(0.f); }); + } + + void Engine::addRotationControl(std::shared_ptr entity, float speed, Key left, Key right) { + setAction(left, "RotateLeft", ActionType::Hold, 0.f, [entity, speed](float){ entity->setAngularVelocity(-speed); }); + setAction(right, "RotateRight", ActionType::Hold, 0.f, [entity, speed](float){ entity->setAngularVelocity(speed); }); + + setAction(left, "StopRotateLeft", ActionType::Release, 0.f, [entity, speed](float){ entity->setAngularVelocity(0.f); }); + setAction(right, "StopRotateRight", ActionType::Release, 0.f, [entity, speed](float){ entity->setAngularVelocity(0.f); }); + } + + void Engine::addScaleControl(std::shared_ptr entity, float speed, Key down, Key up) { + setAction(down, "ScaleDown", ActionType::Hold, 0.f, [entity, speed](float){ entity->setScaleVelocity({-speed, -speed}); }); + setAction(up, "ScaleUp", ActionType::Hold, 0.f, [entity, speed](float){ entity->setScaleVelocity({speed, speed}); }); + + setAction(down, "StopScaleDown", ActionType::Release, 0.f, [entity, speed](float){ entity->setScaleVelocity({0.f, 0.f}); }); + setAction(up, "StopScaleUp", ActionType::Release, 0.f, [entity, speed](float){ entity->setScaleVelocity({0.f, 0.f}); }); + } + + void Engine::addCameraControl(float speed, Key up, Key left, Key down, Key right) { + setAction(up, "CamUp", ActionType::Hold, 0.f, [this, speed](float){ m_camera->setVelocityY(-speed); }); + setAction(left, "CamLeft", ActionType::Hold, 0.f, [this, speed](float){ m_camera->setVelocityX(-speed); }); + setAction(down, "CamDown", ActionType::Hold, 0.f, [this, speed](float){ m_camera->setVelocityY(speed); }); + setAction(right, "CamRight", ActionType::Hold, 0.f, [this, speed](float){ m_camera->setVelocityX(speed); }); + + setAction(up, "StopCamUp", ActionType::Release, 0.f, [this, speed](float){ m_camera->setVelocityY(0.f); }); + setAction(left, "StopCamLeft", ActionType::Release, 0.f, [this, speed](float){ m_camera->setVelocityX(0.f); }); + setAction(down, "StopCamDown", ActionType::Release, 0.f, [this, speed](float){ m_camera->setVelocityY(0.f); }); + setAction(right, "StopCamRight", ActionType::Release, 0.f, [this, speed](float){ m_camera->setVelocityX(0.f); }); + } + + void Engine::addCameraZoomControl(float speed, Key zoomOut, Key zoomIn) { + setAction(zoomOut, "ZoomOut", ActionType::Hold, 0.f, [this, speed](float){ m_camera->setZoomVelocity(speed); }); + setAction(zoomIn, "ZoomIn", ActionType::Hold, 0.f, [this, speed](float){ m_camera->setZoomVelocity(-speed); }); + + setAction(zoomOut, "StopZoomOut", ActionType::Release, 0.f, [this, speed](float){ m_camera->setZoomVelocity(0.f); }); + setAction(zoomIn, "StopZoomIn", ActionType::Release, 0.f, [this, speed](float){ m_camera->setZoomVelocity(0.f); }); + } + + void Engine::onEntityClick(std::function)> callback) { + setAction(Key::MouseLeft, "ClickDetection", ActionType::Press, 0.f, [this, callback](float dt){ + float x, y; + velos::Input::getMousePosition(x, y); + + velos::Vec2 worldPos = m_camera->screenToWorld(velos::Vec2{x, y}, m_renderer->screenCenter()); + + for (auto& entity: entities()) + if (entity->containsPoint(worldPos)) { + callback(entity); + break; + } + }); + } + + void Engine::onHoldAction(Key key, const ActionId& action, float threshold, std::function callback) { + setAction(key, action, ActionType::HoldCompleted, threshold, [callback](float dt){ callback(); }); + } } diff --git a/engine/src/entity/entity_manager.cpp b/engine/src/entity/entity_manager.cpp index bd9d0fb..b7e3e32 100644 --- a/engine/src/entity/entity_manager.cpp +++ b/engine/src/entity/entity_manager.cpp @@ -4,14 +4,16 @@ namespace velos { void EntityManager::update(float dt) { for (auto& entity: m_entities) entity->update(dt); + m_physics.update(m_entities); } void EntityManager::render(Renderer& renderer) { - for (auto& entity: m_entities) { + for (auto& entity: m_entities) entity->render(renderer); - if (m_debugRender) + if (m_debugRender) { + for (auto& entity:m_entities) entity->debugRender(renderer); } } diff --git a/engine/src/graphics/camera/camera.cpp b/engine/src/graphics/camera/camera.cpp index d66b9a2..0ebaf23 100644 --- a/engine/src/graphics/camera/camera.cpp +++ b/engine/src/graphics/camera/camera.cpp @@ -32,8 +32,15 @@ namespace velos { } void Camera::update(float dt) { - if (!m_target) + if (m_zoomVelocity != 0.f) { + m_zoom *= (1.f + m_zoomVelocity * dt); + m_zoom = std::max(0.01f, m_zoom); + } + + if (!m_target) { + m_transform.position += m_velocity * dt; return; + } Vec2 delta = m_target->position - m_lastTargetPos; m_lastTargetPos = m_target->position; diff --git a/engine/src/physics/aabb_collider.cpp b/engine/src/physics/aabb_collider.cpp index dbf1d6d..b71c496 100644 --- a/engine/src/physics/aabb_collider.cpp +++ b/engine/src/physics/aabb_collider.cpp @@ -1,4 +1,5 @@ #include "velos/physics/aabb_collider.h" +#include namespace velos { ColliderType AABBCollider::type() const { @@ -7,8 +8,22 @@ namespace velos { void AABBCollider::computeWorld(const Transform& entityTransform) { Transform transform = m_localTransform.combine(entityTransform); - m_worldSize = m_size * transform.scale; - m_worldPosition = transform.position - (m_worldSize / 2.f); + + float x = m_size.x / 2.f; + float y = m_size.y / 2.f; + + Vec2 p1 = transform.apply({-x, -y}); + Vec2 p2 = transform.apply({x, -y}); + Vec2 p3 = transform.apply({x, y}); + Vec2 p4 = transform.apply({-x, y}); + + float minX = std::min({p1.x, p2.x, p3.x, p4.x}); + float maxX = std::max({p1.x, p2.x, p3.x, p4.x}); + float minY = std::min({p1.y, p2.y, p3.y, p4.y}); + float maxY = std::max({p1.y, p2.y, p3.y, p4.y}); + + m_worldPosition = {minX, minY}; + m_worldSize = {maxX - minX, maxY - minY}; } bool AABBCollider::containsPoint(const Vec2& point) const { @@ -23,6 +38,6 @@ namespace velos { } void AABBCollider::debugRender(Renderer& renderer) const { - renderer.drawRect(m_worldPosition, m_worldSize, Color{117, 145, 166, 100}); + renderer.drawRect(m_worldPosition, m_worldSize, Color{0, 255, 0, 100}); } } diff --git a/engine/src/physics/circle_collider.cpp b/engine/src/physics/circle_collider.cpp index 6e0093a..9e6da2a 100644 --- a/engine/src/physics/circle_collider.cpp +++ b/engine/src/physics/circle_collider.cpp @@ -18,6 +18,6 @@ namespace velos { } void CircleCollider::debugRender(Renderer& renderer) const { - renderer.drawCircle(m_worldPosition, m_worldRadius, Color{117, 145, 166, 100}); + renderer.drawCircle(m_worldPosition, m_worldRadius, Color{0, 255, 0, 100}); } } diff --git a/examples/example-camera/src/main.cpp b/examples/example-camera/src/main.cpp index 71db194..97d9471 100644 --- a/examples/example-camera/src/main.cpp +++ b/examples/example-camera/src/main.cpp @@ -17,39 +17,25 @@ int main() { velos::EngineConfig config; config.app_name = "Velos Sandbox"; - velos::Engine engine(config); - auto rect = std::make_shared(velos::Vec2{70.f, 70.f}, velos::Color{0, 0, 255}); - auto circle = std::make_shared(50, velos::Color{255, 255, 0}); - circle->transform().position = velos::Vec2{100.f, 100.f}; + auto entity = engine.createEntity(); + entity->setPosition(velos::Vec2{500.f, 300.f}); + + auto circle = entity->addShape(50.f, velos::Color::Yellow); + circle->setPosition({100.f, 100.f}); - auto entity = std::make_shared(); - entity->addShape(rect); - entity->addShape(circle); + auto rectangle = entity->addShape(velos::Vec2{70.f, 70.f}, velos::Color::Blue); float cam_speed = 250.f; - engine.setAction(velos::Key::W, "CamUp", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->transform().position.y -= cam_speed * dt; }); - engine.setAction(velos::Key::A, "CamLeft", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->transform().position.x -= cam_speed * dt; }); - engine.setAction(velos::Key::S, "CamDown", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->transform().position.y += cam_speed * dt; }); - engine.setAction(velos::Key::D, "CamRight", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->transform().position.x += cam_speed * dt; }); + engine.addCameraControl(cam_speed, velos::Key::W, velos::Key::A, velos::Key::S, velos::Key::D); - float zoom_speed = 1.5f; - engine.setAction(velos::Key::Q, "ZoomDown", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->setZoom(engine.camera()->zoom() * (1.f + zoom_speed * dt)); }); - engine.setAction(velos::Key::E, "ZoomUp", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->setZoom(engine.camera()->zoom() * (1.f - zoom_speed * dt)); }); - - engine.setAction(velos::Key::MouseLeft, "ClickDetection", velos::ActionType::Press, 0.f, [&](float dt){ - float x, y; - velos::Input::getMousePosition(x, y); - velos::Vec2 worldPos = engine.camera()->screenToWorld(velos::Vec2{x, y}, - velos::Vec2{config.window_width / 2.f, config.window_height / 2.f}); - - for (auto& entity: engine.entityManager()->entities()) - if (entity->containsPoint(worldPos)) - VELOS_INFO("Clicked entity at {}, {}", worldPos.x, worldPos.y); + engine.onEntityClick([](std::shared_ptr clickedEntity) { + APP_INFO("Entity clicked!"); }); - engine.entityManager()->addEntity(entity); + float zoom_speed = 1.5f; + engine.addCameraZoomControl(zoom_speed); VELOS_INFO("W/A/S/D to move camera"); VELOS_INFO("Q/E to zoom in and out"); diff --git a/examples/sandbox/src/main.cpp b/examples/sandbox/src/main.cpp index 53afcda..8127a31 100644 --- a/examples/sandbox/src/main.cpp +++ b/examples/sandbox/src/main.cpp @@ -1,106 +1,62 @@ -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include int main() { velos::EngineConfig config; config.app_name = "Velos Sandbox"; - velos::Engine engine(config); - engine.camera()->transform().position = velos::Vec2{config.window_width / 2.f, config.window_height / 2.f}; velos::Vec2 size{50.f, 50.f}; - auto rect = std::make_shared(size, velos::Color{255, 0, 0}); - auto circle = std::make_shared(50, velos::Color{255, 255, 0}); - circle->transform().position = velos::Vec2{100.f, 0.f}; - - auto rect2 = std::make_shared(size * 1.5f, velos::Color{0, 0, 255}); - rect2->transform().position = velos::Vec2{100.f, 150.f}; - - auto entity = std::make_shared(); - entity->addShape(rect); - entity->transform().position = {200.f, 200.f}; - - auto collider = std::make_unique(size); - entity->addCollider(std::move(collider)); - - auto stationary_entity = std::make_shared(); - stationary_entity->transform().position = {300.f, 300.f}; - stationary_entity->addShape(circle); - stationary_entity->addShape(rect2); - - auto stationary_collider = std::make_unique(50.f); - stationary_collider->transform().position = circle->transform().position; - auto stationary_collider2 = std::make_unique(size * 1.5f); - stationary_collider2->transform().position = rect2->transform().position; - /* - stationary_collider->setOnCollisionEnter([](velos::Collider& other) { - VELOS_INFO("Entered collision!"); - }); - 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); }); + auto player = engine.createEntity(); + player->transform().position = {200.f, 200.f}; - float speed = 200.f; - 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); }); + player->addShape(size, velos::Color::Red); + player->addCollider(size); - 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; }); + auto obstacle = engine.createEntity(); + obstacle->setPosition({300.f, 300.f}); + obstacle->setStatic(true); - engine.setAction(velos::Key::Z, "ScaleDown", velos::ActionType::Hold, 0.05f, [entity](float dt){ entity->transform().scale.x -= dt; entity->transform().scale.y -= dt; }); - engine.setAction(velos::Key::X, "ScaleUp", velos::ActionType::Hold, 0.05f, [entity](float dt){ entity->transform().scale.x += dt; entity->transform().scale.y += dt; }); + auto circle = obstacle->addShape(50.f, velos::Color::Yellow); + circle->setPosition({100.f, 0.f}); - float cam_speed = 250.f; - engine.setAction(velos::Key::ArrowUp, "CamUp", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->transform().position.y -= cam_speed * dt; }); - engine.setAction(velos::Key::ArrowLeft, "CamLeft", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->transform().position.x -= cam_speed * dt; }); - engine.setAction(velos::Key::ArrowDown, "CamDown", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->transform().position.y += cam_speed * dt; }); - engine.setAction(velos::Key::ArrowRight, "CamRight", velos::ActionType::Hold, 0.05f, [&](float dt){ engine.camera()->transform().position.x += cam_speed * dt; }); - - engine.setAction(velos::Key::MouseLeft, "ClickDetection", velos::ActionType::Press, 0.f, [&](float dt){ - float x, y; - velos::Input::getMousePosition(x, y); - velos::Vec2 worldPos = engine.camera()->screenToWorld(velos::Vec2{x, y}, - velos::Vec2{config.window_width / 2.f, config.window_height / 2.f}); - - for (auto& entity: engine.entityManager()->entities()) - if (entity->containsPoint(worldPos)) - VELOS_INFO("Clicked entity at {}, {}", worldPos.x, worldPos.y); + auto circleCollider = obstacle->addCollider(50.f); + circleCollider->setPosition(circle->transform().position); + + auto rectangle = obstacle->addShape(size * 2.f, velos::Color::Blue); + rectangle->setPosition({100.f, 200.f}); + + auto rectangleCollider = obstacle->addCollider(size * 2.f); + rectangleCollider->setPosition(rectangle->transform().position); + + circleCollider->setOnCollisionEnter([](velos::Collider& other) { + APP_INFO("Entered collision zone!"); + }); + + circleCollider->setOnCollisionExit([](velos::Collider& other) { + APP_INFO("Exited collision zone!"); }); - engine.setAction(velos::Key::Space, "Charge", velos::ActionType::HoldCompleted, 1.f, [](float dt){ VELOS_INFO("Charged!"); }); + float speed = 200.f; + engine.addMovement(player, speed); + engine.addRotationControl(player); + engine.addScaleControl(player); - engine.entityManager()->addEntity(entity); - engine.entityManager()->addEntity(stationary_entity); + float cam_speed = 250.f; + engine.addCameraControl(cam_speed); - engine.attachCamera(*entity); - engine.camera()->setFollowMode(velos::CameraFollowMode::Smooth); + engine.onEntityClick([](std::shared_ptr clickedEntity) { + APP_INFO("Entity clicked!"); + }); - // For debugging collision zones - // engine.setDebugRender(true); + engine.onHoldAction(velos::Key::Space, "Charge", 1.f, [](){ + APP_INFO("Charged!"); + }); + + engine.attachCamera(*player); + engine.camera()->setFollowMode(velos::CameraFollowMode::Smooth); engine.run(); + + return 0; } From 380c078fb3f3bfa3ef27ff3c890e8ca036f948a6 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Mar 2026 13:43:52 +0200 Subject: [PATCH 06/10] Implement entity destruction Signed-off-by: Mihai Dumitrescu --- engine/include/velos/app/engine.h | 2 +- engine/include/velos/entity/entity.h | 4 ++++ engine/src/entity/entity_manager.cpp | 8 +++++++- examples/sandbox/src/main.cpp | 4 +--- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/engine/include/velos/app/engine.h b/engine/include/velos/app/engine.h index fe5591e..03eb538 100644 --- a/engine/include/velos/app/engine.h +++ b/engine/include/velos/app/engine.h @@ -15,7 +15,7 @@ namespace velos { class Engine { public: - explicit Engine(const EngineConfig& config); + explicit Engine(const EngineConfig& config = EngineConfig{}); ~Engine(); void run(); diff --git a/engine/include/velos/entity/entity.h b/engine/include/velos/entity/entity.h index ff68aa8..8b958f0 100644 --- a/engine/include/velos/entity/entity.h +++ b/engine/include/velos/entity/entity.h @@ -70,6 +70,9 @@ namespace velos { bool isStatic() const { return m_isStatic; } void setStatic(bool state) { m_isStatic = state; } + bool isDestroyed() const { return m_isDistroyed; } + void destroy() { m_isDistroyed = true; } + private: Vec2 m_velocity; Transform m_transform; @@ -80,5 +83,6 @@ namespace velos { std::vector> m_shapes; std::vector> m_colliders; bool m_isStatic = false; + bool m_isDistroyed = false; }; } diff --git a/engine/src/entity/entity_manager.cpp b/engine/src/entity/entity_manager.cpp index b7e3e32..828b326 100644 --- a/engine/src/entity/entity_manager.cpp +++ b/engine/src/entity/entity_manager.cpp @@ -3,9 +3,15 @@ namespace velos { void EntityManager::update(float dt) { for (auto& entity: m_entities) - entity->update(dt); + if (!entity->isDestroyed()) + entity->update(dt); m_physics.update(m_entities); + + m_entities.erase(std::remove_if(m_entities.begin(), m_entities.end(), + [](const std::shared_ptr& e) { return e->isDestroyed(); }), + m_entities.end() + ); } void EntityManager::render(Renderer& renderer) { diff --git a/examples/sandbox/src/main.cpp b/examples/sandbox/src/main.cpp index 8127a31..b42868f 100644 --- a/examples/sandbox/src/main.cpp +++ b/examples/sandbox/src/main.cpp @@ -1,9 +1,7 @@ #include int main() { - velos::EngineConfig config; - config.app_name = "Velos Sandbox"; - velos::Engine engine(config); + velos::Engine engine; velos::Vec2 size{50.f, 50.f}; From 4ffa47b9ce7ba5156ad5879489cf28c0c969eb66 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Mon, 2 Mar 2026 18:39:25 +0200 Subject: [PATCH 07/10] Implement timer Signed-off-by: Mihai Dumitrescu --- engine/include/velos/app/engine.h | 7 +++++++ engine/include/velos/time/timer.h | 9 +++++++++ engine/include/velos/velos.h | 1 + engine/src/app/engine.cpp | 20 ++++++++++++++++++++ 4 files changed, 37 insertions(+) create mode 100644 engine/include/velos/time/timer.h diff --git a/engine/include/velos/app/engine.h b/engine/include/velos/app/engine.h index 03eb538..a5f37ef 100644 --- a/engine/include/velos/app/engine.h +++ b/engine/include/velos/app/engine.h @@ -10,6 +10,7 @@ #include "velos/events/event.h" #include "velos/input/key.h" #include "velos/actions/actions.h" +#include "velos/time/timer.h" #include "velos/entity/entity_manager.h" namespace velos { @@ -49,11 +50,17 @@ namespace velos { void onEntityClick(std::function)>); void onHoldAction(Key key, const ActionId& action, float threshold, std::function callback); + void setTimeout(float seconds, std::function callback); + static Engine* instance() { return s_instance; } private: void processActions(float dt); int m_targetFPS; + // timer variables + std::vector m_timers; + void updateTimers(float dt); + std::unique_ptr m_window; std::unique_ptr m_camera; std::unique_ptr m_renderer; diff --git a/engine/include/velos/time/timer.h b/engine/include/velos/time/timer.h new file mode 100644 index 0000000..d4fcfc5 --- /dev/null +++ b/engine/include/velos/time/timer.h @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace velos { + struct Timer { + float timeLeft; + std::function callback; + }; +} diff --git a/engine/include/velos/velos.h b/engine/include/velos/velos.h index cd1c5dc..0b6dfb5 100644 --- a/engine/include/velos/velos.h +++ b/engine/include/velos/velos.h @@ -39,3 +39,4 @@ // time #include +#include diff --git a/engine/src/app/engine.cpp b/engine/src/app/engine.cpp index 19d0e44..323ea55 100644 --- a/engine/src/app/engine.cpp +++ b/engine/src/app/engine.cpp @@ -38,6 +38,7 @@ namespace velos { velos::Input::update(); float dt = Clock::delta(); + updateTimers(dt); processActions(dt); m_entityManager->update(dt); @@ -190,4 +191,23 @@ namespace velos { void Engine::onHoldAction(Key key, const ActionId& action, float threshold, std::function callback) { setAction(key, action, ActionType::HoldCompleted, threshold, [callback](float dt){ callback(); }); } + + void Engine::setTimeout(float seconds, std::function callback) { + m_timers.push_back({seconds, std::move(callback)}); + } + + void Engine::updateTimers(float dt) { + for (size_t i = 0; i < m_timers.size(); ) { + m_timers[i].timeLeft -= dt; + + if (m_timers[i].timeLeft <= 0.f) { + auto callback = m_timers[i].callback; + + m_timers.erase(m_timers.begin() + i); + callback(); + } else{ + ++i; + } + } + } } From 0c21250b6fbbf66f256d05228819dd5c70eee1c0 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Mon, 2 Mar 2026 18:41:40 +0200 Subject: [PATCH 08/10] Implement random float generator Signed-off-by: Mihai Dumitrescu --- engine/include/velos/math/random.h | 15 +++++++++++++++ engine/include/velos/velos.h | 1 + 2 files changed, 16 insertions(+) create mode 100644 engine/include/velos/math/random.h diff --git a/engine/include/velos/math/random.h b/engine/include/velos/math/random.h new file mode 100644 index 0000000..b91d00f --- /dev/null +++ b/engine/include/velos/math/random.h @@ -0,0 +1,15 @@ +#pragma once +#include + +namespace velos { + class Random { + public: + static float getFloat(float min, float max) { + std::uniform_real_distribution dist(min, max); + return dist(s_rng); + } + + private: + inline static std::mt19937 s_rng{std::random_device{}()}; + }; +} diff --git a/engine/include/velos/velos.h b/engine/include/velos/velos.h index 0b6dfb5..2b24df6 100644 --- a/engine/include/velos/velos.h +++ b/engine/include/velos/velos.h @@ -9,6 +9,7 @@ // math #include #include +#include // entity #include From 292a0c3f3e0e078a247a9d22553c1bfb74fec908 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Mon, 2 Mar 2026 18:44:38 +0200 Subject: [PATCH 09/10] Remove entity's colliders from physics system once it's destroyed Signed-off-by: Mihai Dumitrescu --- engine/include/velos/entity/entity.h | 2 ++ engine/include/velos/physics/physics_system.h | 2 ++ engine/src/entity/entity_manager.cpp | 4 ++++ engine/src/physics/physics_system.cpp | 8 ++++++++ 4 files changed, 16 insertions(+) diff --git a/engine/include/velos/entity/entity.h b/engine/include/velos/entity/entity.h index 8b958f0..8cc6cfb 100644 --- a/engine/include/velos/entity/entity.h +++ b/engine/include/velos/entity/entity.h @@ -50,7 +50,9 @@ namespace velos { Vec2& velocity() { return m_velocity; } Transform& transform() { return m_transform; } + const Vec2& position() const { return m_transform.position; } void setPosition(const Vec2& position) { m_transform.position = position; } + void setRotation(float rot) { m_transform.rotation = rot; } void setScale(const Vec2& scale) { m_transform.scale = scale; } diff --git a/engine/include/velos/physics/physics_system.h b/engine/include/velos/physics/physics_system.h index 755ee3b..494d951 100644 --- a/engine/include/velos/physics/physics_system.h +++ b/engine/include/velos/physics/physics_system.h @@ -14,6 +14,8 @@ namespace velos { public: void update(const std::vector>& entities); + void removeEntity(Entity* entity); + private: using CollisionPair = std::pair; diff --git a/engine/src/entity/entity_manager.cpp b/engine/src/entity/entity_manager.cpp index 828b326..5a508c7 100644 --- a/engine/src/entity/entity_manager.cpp +++ b/engine/src/entity/entity_manager.cpp @@ -8,6 +8,10 @@ namespace velos { m_physics.update(m_entities); + for (auto& entity: m_entities) + if (entity->isDestroyed()) + m_physics.removeEntity(entity.get()); + m_entities.erase(std::remove_if(m_entities.begin(), m_entities.end(), [](const std::shared_ptr& e) { return e->isDestroyed(); }), m_entities.end() diff --git a/engine/src/physics/physics_system.cpp b/engine/src/physics/physics_system.cpp index f4602b8..49ebe86 100644 --- a/engine/src/physics/physics_system.cpp +++ b/engine/src/physics/physics_system.cpp @@ -84,6 +84,14 @@ namespace velos { m_previous = m_current; } + void PhysicsSystem::removeEntity(Entity* entity) { + for (auto colliderPair = m_previous.begin(); colliderPair != m_previous.end(); ) + if (colliderPair->first->owner() == entity || colliderPair->second->owner() == entity) + colliderPair = m_previous.erase(colliderPair); + else + ++colliderPair; + } + void PhysicsSystem::checkCollision(Entity& e1, Entity& e2) { for (const auto& c1: e1.colliders()) { for (const auto& c2: e2.colliders()) { From c3f730fdd4c39c2a6748fcf2e74feae4ff22a69f Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Mon, 2 Mar 2026 21:32:32 +0200 Subject: [PATCH 10/10] Add a game example Signed-off-by: Mihai Dumitrescu --- examples/CMakeLists.txt | 1 + examples/example-game/CMakeLists.txt | 9 +++ examples/example-game/src/main.cpp | 107 +++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 examples/example-game/CMakeLists.txt create mode 100644 examples/example-game/src/main.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c467b21..75c8fde 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(sandbox) add_subdirectory(example-camera) +add_subdirectory(example-game) diff --git a/examples/example-game/CMakeLists.txt b/examples/example-game/CMakeLists.txt new file mode 100644 index 0000000..28fd52b --- /dev/null +++ b/examples/example-game/CMakeLists.txt @@ -0,0 +1,9 @@ +project(velos-example-game) + +file(GLOB_RECURSE EXAMPLE_GAME_SOURCES "src/*.cpp") + +add_executable(velos-example_game ${EXAMPLE_GAME_SOURCES}) + +target_link_libraries(velos-example_game PRIVATE velos) + +target_compile_features(velos-example_game PRIVATE cxx_std_20) diff --git a/examples/example-game/src/main.cpp b/examples/example-game/src/main.cpp new file mode 100644 index 0000000..e67e010 --- /dev/null +++ b/examples/example-game/src/main.cpp @@ -0,0 +1,107 @@ +#include +using namespace velos; + +int main() { + EngineConfig config; + config.app_name = "Game"; + Engine engine(config); + + Vec2 playerSize{50.f, 50.f}; + int points = 0; + + auto player = engine.createEntity(); + player->addShape(playerSize, Color::Red); + player->setPosition({140.f, (config.window_height - playerSize.y) / 2.f}); + + auto playerCollider = player->addCollider(playerSize); + + // player Y axis movement + float speed = 280.f; + engine.addMovement(player, speed, Key::W, Key::Unknown, Key::S, Key::Unknown); + + // top and bottom barriers + auto topWall = engine.createEntity(); + topWall->setPosition({0.f, -10.f}); + auto topWallCollider = topWall->addCollider(Vec2{config.window_width + 20.f, 10.f}); + topWall->setStatic(true); + + auto bottomWall = engine.createEntity(); + bottomWall->setPosition({0.f, static_cast(config.window_height)}); + auto bottomWallCollider = bottomWall->addCollider(Vec2{config.window_width + 20.f, 10.f}); + bottomWall->setStatic(true); + + // death zone to know when enemies pass the player + auto deathZone = engine.createEntity(); + auto deathZoneCollider = deathZone->addCollider(Vec2{10.f, static_cast(config.window_height)}); + deathZone->setStatic(true); + deathZoneCollider->setOnCollisionEnter([&engine, &points](Collider& other) { + if (other.owner()->isStatic() || other.owner()->velocity().x >= 0.f) + return; + + APP_INFO("Enemy passed the player!"); + APP_INFO("Points earned: {}", points); + engine.stop(); + }); + + // bullet shooting mechanic + engine.setAction(Key::Space, "Shoot", ActionType::Press, 0.f, [&](float) { + if (player->isDestroyed()) + return; + + auto bullet = engine.createEntity(); + bullet->setPosition(player->position() + Vec2{40.f, 0.f}); + bullet->addShape(10.f, Color::Yellow); + + auto bulletCollider = bullet->addCollider(10.f); + bulletCollider->setSolidCollision(false); + bullet->setVelocityX(500.f); + + bulletCollider->setOnCollisionEnter([bullet, &points](Collider& other) { + if (other.owner()->isDestroyed() || bullet->isDestroyed()) + return; + + bullet->destroy(); + other.owner()->destroy(); + points++; + }); + + engine.setTimeout(2.f, [bullet]() { + if (!bullet->isDestroyed()) + bullet->destroy(); + }); + }); + + std::function spawnEnemy; + spawnEnemy = [&engine, &spawnEnemy, player, playerSize, &points, config]() { + if (player->isDestroyed()) + return; + + float randomY = Random::getFloat(50.f, config.window_height - 50.f); + + auto enemy = engine.createEntity(); + enemy->setPosition({static_cast(config.window_width), randomY}); + enemy->addShape(playerSize, Color::Blue); + + auto enemyCollider = enemy->addCollider(playerSize / 1.2f); + enemyCollider->setSolidCollision(false); + enemy->setVelocityX(-200.f); + + enemyCollider->setOnCollisionEnter([player, enemy, &engine, &points](Collider& other) { + if (other.owner() == player.get()) { + player->destroy(); + engine.stop(); + APP_INFO("Player died!"); + APP_INFO("Points earned: {}", points); + } + }); + + float spawnTime = std::max(0.4f, 1.2f - (points * 0.05f)); + engine.setTimeout(spawnTime, spawnEnemy); + }; + + engine.setTimeout(1.f, spawnEnemy); + + engine.run(); + + return 0; +}