aboutsummaryrefslogtreecommitdiff
path: root/src/crepe
diff options
context:
space:
mode:
Diffstat (limited to 'src/crepe')
-rw-r--r--src/crepe/Component.cpp12
-rw-r--r--src/crepe/Component.h28
-rw-r--r--src/crepe/Particle.cpp13
-rw-r--r--src/crepe/Particle.h14
-rw-r--r--src/crepe/api/Animator.cpp2
-rw-r--r--src/crepe/api/Asset.h4
-rw-r--r--src/crepe/api/BehaviorScript.cpp4
-rw-r--r--src/crepe/api/BehaviorScript.hpp3
-rw-r--r--src/crepe/api/BoxCollider.cpp4
-rw-r--r--src/crepe/api/BoxCollider.h2
-rw-r--r--src/crepe/api/Button.cpp3
-rw-r--r--src/crepe/api/Button.h12
-rw-r--r--src/crepe/api/CMakeLists.txt5
-rw-r--r--src/crepe/api/Camera.cpp6
-rw-r--r--src/crepe/api/Camera.h11
-rw-r--r--src/crepe/api/Config.h37
-rw-r--r--src/crepe/api/Engine.cpp63
-rw-r--r--src/crepe/api/Engine.h78
-rw-r--r--src/crepe/api/Engine.hpp12
-rw-r--r--src/crepe/api/Event.h96
-rw-r--r--src/crepe/api/GameObject.cpp21
-rw-r--r--src/crepe/api/GameObject.h25
-rw-r--r--src/crepe/api/GameObject.hpp2
-rw-r--r--src/crepe/api/KeyCodes.h7
-rw-r--r--src/crepe/api/LoopManager.cpp1
-rw-r--r--src/crepe/api/LoopManager.h122
-rw-r--r--src/crepe/api/ParticleEmitter.cpp21
-rw-r--r--src/crepe/api/ParticleEmitter.h55
-rw-r--r--src/crepe/api/Rigidbody.h4
-rw-r--r--src/crepe/api/Scene.h2
-rw-r--r--src/crepe/api/Script.cpp37
-rw-r--r--src/crepe/api/Script.h117
-rw-r--r--src/crepe/api/Script.hpp34
-rw-r--r--src/crepe/api/Sprite.cpp2
-rw-r--r--src/crepe/api/Transform.cpp11
-rw-r--r--src/crepe/api/Transform.h6
-rw-r--r--src/crepe/api/Vector2.h7
-rw-r--r--src/crepe/api/Vector2.hpp6
-rw-r--r--src/crepe/facade/DB.cpp2
-rw-r--r--src/crepe/facade/SDLContext.cpp290
-rw-r--r--src/crepe/facade/SDLContext.h168
-rw-r--r--src/crepe/facade/Sound.cpp2
-rw-r--r--src/crepe/facade/SoundContext.cpp2
-rw-r--r--src/crepe/facade/Texture.cpp2
-rw-r--r--src/crepe/manager/CMakeLists.txt5
-rw-r--r--src/crepe/manager/ComponentManager.cpp29
-rw-r--r--src/crepe/manager/ComponentManager.h48
-rw-r--r--src/crepe/manager/LoopTimerManager.cpp2
-rw-r--r--src/crepe/manager/LoopTimerManager.h4
-rw-r--r--src/crepe/manager/Mediator.h4
-rw-r--r--src/crepe/manager/ReplayManager.cpp70
-rw-r--r--src/crepe/manager/ReplayManager.h94
-rw-r--r--src/crepe/manager/ResourceManager.cpp2
-rw-r--r--src/crepe/manager/SystemManager.cpp67
-rw-r--r--src/crepe/manager/SystemManager.h93
-rw-r--r--src/crepe/manager/SystemManager.hpp (renamed from src/crepe/api/LoopManager.hpp)18
-rw-r--r--src/crepe/system/AISystem.cpp2
-rw-r--r--src/crepe/system/AISystem.h2
-rw-r--r--src/crepe/system/AnimatorSystem.cpp4
-rw-r--r--src/crepe/system/AnimatorSystem.h2
-rw-r--r--src/crepe/system/AudioSystem.cpp2
-rw-r--r--src/crepe/system/AudioSystem.h2
-rw-r--r--src/crepe/system/CMakeLists.txt4
-rw-r--r--src/crepe/system/CollisionSystem.cpp2
-rw-r--r--src/crepe/system/CollisionSystem.h2
-rw-r--r--src/crepe/system/EventSystem.cpp9
-rw-r--r--src/crepe/system/EventSystem.h21
-rw-r--r--src/crepe/system/InputSystem.cpp257
-rw-r--r--src/crepe/system/InputSystem.h44
-rw-r--r--src/crepe/system/ParticleSystem.cpp87
-rw-r--r--src/crepe/system/ParticleSystem.h24
-rw-r--r--src/crepe/system/PhysicsSystem.cpp3
-rw-r--r--src/crepe/system/PhysicsSystem.h2
-rw-r--r--src/crepe/system/RenderSystem.cpp6
-rw-r--r--src/crepe/system/RenderSystem.h2
-rw-r--r--src/crepe/system/ReplaySystem.cpp54
-rw-r--r--src/crepe/system/ReplaySystem.h44
-rw-r--r--src/crepe/system/ScriptSystem.cpp8
-rw-r--r--src/crepe/system/ScriptSystem.h2
-rw-r--r--src/crepe/system/System.h10
-rw-r--r--src/crepe/util/Log.cpp1
-rw-r--r--src/crepe/util/Log.h21
-rw-r--r--src/crepe/util/dbg.h18
83 files changed, 1674 insertions, 752 deletions
diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp
index acfd35c..ae76e65 100644
--- a/src/crepe/Component.cpp
+++ b/src/crepe/Component.cpp
@@ -1,5 +1,17 @@
#include "Component.h"
using namespace crepe;
+using namespace std;
Component::Component(game_object_id_t id) : game_object_id(id) {}
+
+Component & Component::operator=(const Component & other) {
+ this->active = other.active;
+ return *this;
+}
+
+unique_ptr<Component> Component::save() const {
+ return unique_ptr<Component>(new Component(*this));
+}
+
+void Component::restore(const Component & snapshot) { *this = snapshot; }
diff --git a/src/crepe/Component.h b/src/crepe/Component.h
index eff5a58..52e06d5 100644
--- a/src/crepe/Component.h
+++ b/src/crepe/Component.h
@@ -1,5 +1,7 @@
#pragma once
+#include <memory>
+
#include "types.h"
namespace crepe {
@@ -32,11 +34,33 @@ protected:
//! Only ComponentManager can create components
friend class ComponentManager;
- Component(const Component &) = delete;
+ // components are never moved
Component(Component &&) = delete;
- virtual Component & operator=(const Component &) = delete;
virtual Component & operator=(Component &&) = delete;
+protected:
+ /**
+ * \name ReplayManager (Memento) functions
+ * \{
+ */
+ /**
+ * \brief Save a snapshot of this component's state
+ * \note This function should only be implemented on components that should be saved/restored
+ * by ReplayManager.
+ * \returns Unique pointer to a deep copy of this component
+ */
+ virtual std::unique_ptr<Component> save() const;
+ //! Copy constructor (used by \c save())
+ Component(const Component &) = default;
+ /**
+ * \brief Restore this component from a snapshot
+ * \param snapshot Data to fill this component with (as returned by \c save())
+ */
+ virtual void restore(const Component & snapshot);
+ //! Copy assignment operator (used by \c restore())
+ virtual Component & operator=(const Component &);
+ //! \}
+
public:
virtual ~Component() = default;
diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp
index 485a0d4..b340826 100644
--- a/src/crepe/Particle.cpp
+++ b/src/crepe/Particle.cpp
@@ -2,8 +2,8 @@
using namespace crepe;
-void Particle::reset(uint32_t lifespan, const vec2 & position, const vec2 & velocity,
- double angle) {
+void Particle::reset(unsigned int lifespan, const vec2 & position, const vec2 & velocity,
+ float angle) {
// Initialize the particle state
this->time_in_life = 0;
this->lifespan = lifespan;
@@ -15,16 +15,17 @@ void Particle::reset(uint32_t lifespan, const vec2 & position, const vec2 & velo
this->force_over_time = {0, 0};
}
-void Particle::update() {
+void Particle::update(double dt) {
// Deactivate particle if it has exceeded its lifespan
- if (++time_in_life >= lifespan) {
+ time_in_life += dt;
+ if (time_in_life >= lifespan) {
this->active = false;
return;
}
// Update velocity based on accumulated force and update position
- this->velocity += force_over_time;
- this->position += velocity;
+ this->velocity += force_over_time * dt;
+ this->position += velocity * dt;
}
void Particle::stop_movement() {
diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h
index d0397c9..ee0cd66 100644
--- a/src/crepe/Particle.h
+++ b/src/crepe/Particle.h
@@ -14,8 +14,6 @@ namespace crepe {
* can also be reset or stopped.
*/
class Particle {
- // TODO: add friend particleSsytem and rendersystem. Unit test will fail.
-
public:
//! Position of the particle in 2D space.
vec2 position;
@@ -24,13 +22,13 @@ public:
//! Accumulated force affecting the particle over time.
vec2 force_over_time;
//! Total lifespan of the particle in milliseconds.
- uint32_t lifespan;
+ float lifespan;
//! Active state of the particle; true if it is in use, false otherwise.
bool active = false;
//! The time the particle has been alive, in milliseconds.
- uint32_t time_in_life = 0;
+ float time_in_life = 0;
//! The angle at which the particle is oriented or moving.
- double angle = 0;
+ float angle = 0;
/**
* \brief Resets the particle with new properties.
@@ -43,14 +41,16 @@ public:
* \param velocity The initial velocity of the particle.
* \param angle The angle of the particle's trajectory or orientation.
*/
- void reset(uint32_t lifespan, const vec2 & position, const vec2 & velocity, double angle);
+ void reset(unsigned int lifespan, const vec2 & position, const vec2 & velocity,
+ float angle);
/**
* \brief Updates the particle's state.
*
* Advances the particle's position based on its velocity and applies accumulated forces.
* Deactivates the particle if its lifespan has expired.
+ * \param dt The amount of fixed delta time that has passed.
*/
- void update();
+ void update(double dt);
/**
* \brief Stops the particle's movement.
*
diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp
index 4ce4bf0..203cef3 100644
--- a/src/crepe/api/Animator.cpp
+++ b/src/crepe/api/Animator.cpp
@@ -1,5 +1,5 @@
-#include "util/Log.h"
+#include "util/dbg.h"
#include "Animator.h"
#include "Component.h"
diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h
index bfd0ac7..d802e83 100644
--- a/src/crepe/api/Asset.h
+++ b/src/crepe/api/Asset.h
@@ -43,13 +43,13 @@ private:
/**
* \brief Locate asset path, or throw exception if it cannot be found
*
- * This function resolves asset locations relative to crepe::Config::root_pattern if it is
+ * This function resolves asset locations relative to Config::asset::root_pattern if it is
* set and \p src is a relative path. If \p src is an absolute path, it is canonicalized.
* This function only returns if the file can be found.
*
* \param src Arbitrary path to resource file
*
- * \returns \p src if crepe::Config::root_pattern is empty
+ * \returns \p src if Config::asset::root_pattern is empty
* \returns Canonical path to \p src
*
* \throws std::runtime_error if root_pattern cannot be found
diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp
index d22afdf..af7572c 100644
--- a/src/crepe/api/BehaviorScript.cpp
+++ b/src/crepe/api/BehaviorScript.cpp
@@ -10,6 +10,6 @@ BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator)
template <>
BehaviorScript & GameObject::add_component<BehaviorScript>() {
- ComponentManager & mgr = this->component_manager;
- return mgr.add_component<BehaviorScript>(this->id, mgr.mediator);
+ ComponentManager & mgr = this->mediator.component_manager;
+ return mgr.add_component<BehaviorScript>(this->id, this->mediator);
}
diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp
index b9bb1e2..353d5e2 100644
--- a/src/crepe/api/BehaviorScript.hpp
+++ b/src/crepe/api/BehaviorScript.hpp
@@ -2,8 +2,6 @@
#include <type_traits>
-#include "../util/Log.h"
-
#include "BehaviorScript.h"
#include "Script.h"
@@ -11,7 +9,6 @@ namespace crepe {
template <class T, typename... Args>
BehaviorScript & BehaviorScript::set_script(Args &&... args) {
- dbg_trace();
static_assert(std::is_base_of<Script, T>::value);
this->script = std::unique_ptr<Script>(new T(std::forward<Args>(args)...));
diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp
index c097a24..a893d41 100644
--- a/src/crepe/api/BoxCollider.cpp
+++ b/src/crepe/api/BoxCollider.cpp
@@ -4,7 +4,7 @@
using namespace crepe;
-BoxCollider::BoxCollider(game_object_id_t game_object_id, const vec2 & offset,
- const vec2 & dimensions)
+BoxCollider::BoxCollider(game_object_id_t game_object_id, const vec2 & dimensions,
+ const vec2 & offset)
: Collider(game_object_id, offset),
dimensions(dimensions) {}
diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h
index 1ac4d46..3835e2c 100644
--- a/src/crepe/api/BoxCollider.h
+++ b/src/crepe/api/BoxCollider.h
@@ -13,7 +13,7 @@ namespace crepe {
*/
class BoxCollider : public Collider {
public:
- BoxCollider(game_object_id_t game_object_id, const vec2 & offset, const vec2 & dimensions);
+ BoxCollider(game_object_id_t game_object_id, const vec2 & dimensions, const vec2 & offset = { 0, 0 });
//! Width and height of the box collider
vec2 dimensions;
diff --git a/src/crepe/api/Button.cpp b/src/crepe/api/Button.cpp
index 76f74f0..305922c 100644
--- a/src/crepe/api/Button.cpp
+++ b/src/crepe/api/Button.cpp
@@ -3,9 +3,8 @@
namespace crepe {
Button::Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset,
- const std::function<void()> & on_click, bool is_toggle)
+ const std::function<void()> & on_click)
: UIObject(id, dimensions, offset),
- is_toggle(is_toggle),
on_click(on_click) {}
} // namespace crepe
diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h
index 61b18d7..08f5dec 100644
--- a/src/crepe/api/Button.h
+++ b/src/crepe/api/Button.h
@@ -15,19 +15,11 @@ public:
* \param id The unique ID of the game object associated with this button.
* \param dimensions The width and height of the UIObject
* \param offset The offset relative this GameObjects Transform
- * \param is_toggle Optional flag to indicate if the button is a toggle button. Defaults to false.
* \param on_click callback function that will be invoked when the button is clicked.
*/
Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset,
- const std::function<void()> & on_click, bool is_toggle = false);
+ const std::function<void()> & on_click);
- /**
- * \brief Indicates if the button is a toggle button (can be pressed and released).
- *
- * A toggle button allows for a pressed/released state, whereas a regular button
- * typically only has an on-click state.
- */
- bool is_toggle = false;
// TODO: create separate toggle button class
/**
* \brief The callback function to be executed when the button is clicked.
@@ -56,8 +48,6 @@ public:
private:
//! friend relation for is_pressed and hover variables
friend class InputSystem;
- //! Indicates whether the toggle button is pressed
- bool is_pressed = false;
//! Indicates whether the mouse is currently hovering over the button
bool hover = false;
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index 8f84f06..18d6942 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -13,7 +13,7 @@ target_sources(crepe PUBLIC
Animator.cpp
BoxCollider.cpp
CircleCollider.cpp
- LoopManager.cpp
+ Engine.cpp
Asset.cpp
EventHandler.cpp
Script.cpp
@@ -46,7 +46,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
EventHandler.h
EventHandler.hpp
Event.h
- LoopManager.h
+ Engine.h
+ Engine.hpp
Asset.h
Button.h
UIObject.h
diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp
index 179dc18..9befc3f 100644
--- a/src/crepe/api/Camera.cpp
+++ b/src/crepe/api/Camera.cpp
@@ -1,4 +1,4 @@
-#include "util/Log.h"
+#include "util/dbg.h"
#include "Camera.h"
#include "Component.h"
@@ -6,10 +6,8 @@
using namespace crepe;
-Camera::Camera(game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size,
- const Data & data)
+Camera::Camera(game_object_id_t id, const vec2 & viewport_size, const Data & data)
: Component(id),
- screen(screen),
viewport_size(viewport_size),
data(data) {
dbg_trace();
diff --git a/src/crepe/api/Camera.h b/src/crepe/api/Camera.h
index 54d9a73..2fea9f3 100644
--- a/src/crepe/api/Camera.h
+++ b/src/crepe/api/Camera.h
@@ -30,7 +30,7 @@ public:
* zoom < 1 --> zoom out
* zoom > 1 --> zoom in
*/
- double zoom = 1;
+ float zoom = 1.0;
//! offset postion from the game object transform component
vec2 postion_offset;
@@ -40,19 +40,14 @@ public:
/**
* \brief Constructs a Camera with the specified ID and background color.
* \param id Unique identifier for the camera component.
- * \param screen is the actual screen size in pixels
* \param viewport_size is the view of the world in game units
* \param data the camera component data
*/
- Camera(game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size,
- const Camera::Data & data);
+ Camera(game_object_id_t id, const vec2 & viewport_size, const Data & data);
~Camera(); // dbg_trace only
public:
- Camera::Data data;
-
- //! screen the display size in pixels ( output resolution )
- const ivec2 screen;
+ Data data;
//! viewport is the area of the world visible through the camera (in world units)
const vec2 viewport_size;
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
index ca2d3f1..3cc67c2 100644
--- a/src/crepe/api/Config.h
+++ b/src/crepe/api/Config.h
@@ -3,24 +3,21 @@
#include <string>
#include "../util/Log.h"
-
-#include "types.h"
+#include "../types.h"
namespace crepe {
/**
* \brief Global configuration interface
*
- * This class stores engine default settings. Properties on this class are only supposed to be
- * modified *before* execution is handed over from the game programmer to the engine (i.e. the
- * main loop is started).
+ * This struct stores both engine default settings and global configuration parameters.
*/
struct Config final {
//! Retrieve handle to global Config instance
static Config & get_instance();
//! Logging-related settings
- struct {
+ struct log { // NOLINT
/**
* \brief Log level
*
@@ -28,7 +25,7 @@ struct Config final {
*/
Log::Level level = Log::Level::INFO;
/**
- * \brief Colored log output
+ * \brief Enable colored log output
*
* Enables log coloring using ANSI escape codes.
*/
@@ -36,7 +33,7 @@ struct Config final {
} log;
//! Save manager
- struct {
+ struct savemgr { // NOLINT
/**
* \brief Save file location
*
@@ -46,8 +43,8 @@ struct Config final {
std::string location = "save.crepe.db";
} savemgr;
- //! physics-related settings
- struct {
+ //! Physics-related settings
+ struct physics { // NOLINT
/**
* \brief gravity value of physics system
*
@@ -56,15 +53,16 @@ struct Config final {
float gravity = 10;
} physics;
- //! default window settings
- struct {
- //! default screen size in pixels
- ivec2 default_size = {1280, 720};
- std::string window_title = "Jetpack joyride clone";
- } window_settings;
+ //! Default window settings
+ struct window { // NOLINT
+ //! Default window size (in pixels)
+ ivec2 size = {1280, 720};
+ //! Default window title
+ std::string title = "Jetpack joyride clone";
+ } window;
//! Asset loading options
- struct {
+ struct asset { // NOLINT
/**
* \brief Pattern to match for Asset base directory
*
@@ -76,6 +74,11 @@ struct Config final {
*/
std::string root_pattern = ".crepe-root";
} asset;
+ //! Configuration for click tolerance.
+ struct {
+ //! The maximum number of pixels the mouse can move between MouseDown and MouseUp events to be considered a click.
+ int click_tolerance = 5;
+ } input;
//! Audio system settings
struct {
diff --git a/src/crepe/api/Engine.cpp b/src/crepe/api/Engine.cpp
new file mode 100644
index 0000000..bbb4494
--- /dev/null
+++ b/src/crepe/api/Engine.cpp
@@ -0,0 +1,63 @@
+#include "../util/Log.h"
+
+#include "Engine.h"
+
+using namespace crepe;
+using namespace std;
+
+int Engine::main() noexcept {
+ try {
+ this->setup();
+ } catch (const exception & e) {
+ Log::logf(Log::Level::ERROR, "Uncaught exception in setup: {}\n", e.what());
+ return EXIT_FAILURE;
+ }
+
+ try {
+ this->loop();
+ } catch (const exception & e) {
+ Log::logf(Log::Level::ERROR, "Uncaught exception in main loop: {}\n", e.what());
+ this->event_manager.trigger_event<ShutDownEvent>();
+ }
+
+ return EXIT_SUCCESS;
+}
+
+void Engine::setup() {
+ this->loop_timer.start();
+ this->scene_manager.load_next_scene();
+
+ this->event_manager.subscribe<ShutDownEvent>([this](const ShutDownEvent & event) {
+ this->game_running = false;
+
+ // propagate to possible user ShutDownEvent listeners
+ return false;
+ });
+}
+
+void Engine::loop() {
+ LoopTimerManager & timer = this->loop_timer;
+ SystemManager & systems = this->system_manager;
+
+ while (game_running) {
+ timer.update();
+
+ while (timer.get_lag() >= timer.get_fixed_delta_time()) {
+ try {
+ systems.fixed_update();
+ } catch (const exception & e) {
+ Log::logf(Log::Level::WARNING,
+ "Uncaught exception in fixed update function: {}\n", e.what());
+ }
+ timer.advance_fixed_elapsed_time();
+ }
+
+ try {
+ systems.frame_update();
+ } catch (const exception & e) {
+ Log::logf(Log::Level::WARNING, "Uncaught exception in frame update function: {}\n",
+ e.what());
+ }
+ timer.enforce_frame_rate();
+ }
+}
diff --git a/src/crepe/api/Engine.h b/src/crepe/api/Engine.h
new file mode 100644
index 0000000..3145723
--- /dev/null
+++ b/src/crepe/api/Engine.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "../facade/SDLContext.h"
+#include "../manager/ComponentManager.h"
+#include "../manager/EventManager.h"
+#include "../manager/LoopTimerManager.h"
+#include "../manager/ReplayManager.h"
+#include "../manager/ResourceManager.h"
+#include "../manager/SaveManager.h"
+#include "../manager/SceneManager.h"
+#include "../manager/SystemManager.h"
+
+namespace crepe {
+
+/**
+ * \brief Main game entrypoint
+ *
+ * This class is responsible for managing the game loop, including initialization and updating.
+ */
+class Engine {
+public:
+ /**
+ * \brief Engine entrypoint
+ *
+ * This function is called by the game programmer after registering all scenes
+ *
+ * \returns process exit code
+ */
+ int main() noexcept;
+
+ //! \copydoc SceneManager::add_scene
+ template <typename T>
+ void add_scene();
+
+private:
+ /**
+ * \brief Setup function for one-time initialization.
+ *
+ * This function initializes necessary components for the game.
+ */
+ void setup();
+ /**
+ * \brief Main game loop function.
+ *
+ * This function runs the main loop, handling game updates and rendering.
+ */
+ void loop();
+
+ //! Game loop condition
+ bool game_running = true;
+
+private:
+ //! Global context
+ Mediator mediator;
+
+ //! Component manager instance
+ ComponentManager component_manager{mediator};
+ //! Scene manager instance
+ SceneManager scene_manager{mediator};
+ //! LoopTimerManager instance
+ LoopTimerManager loop_timer{mediator};
+ //! EventManager instance
+ EventManager event_manager{mediator};
+ //! Resource manager instance
+ ResourceManager resource_manager{mediator};
+ //! Save manager instance
+ SaveManager save_manager{mediator};
+ //! SDLContext instance
+ SDLContext sdl_context{mediator};
+ //! ReplayManager instance
+ ReplayManager replay_manager{mediator};
+ //! SystemManager
+ SystemManager system_manager{mediator};
+};
+
+} // namespace crepe
+
+#include "Engine.hpp"
diff --git a/src/crepe/api/Engine.hpp b/src/crepe/api/Engine.hpp
new file mode 100644
index 0000000..f2fdc0a
--- /dev/null
+++ b/src/crepe/api/Engine.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "Engine.h"
+
+namespace crepe {
+
+template <class T>
+void Engine::add_scene() {
+ this->scene_manager.add_scene<T>();
+}
+
+} // namespace crepe
diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h
index f2f3daf..17ae809 100644
--- a/src/crepe/api/Event.h
+++ b/src/crepe/api/Event.h
@@ -4,6 +4,7 @@
#include <string>
#include "KeyCodes.h"
+#include "types.h"
namespace crepe {
@@ -38,11 +39,8 @@ public:
*/
class MousePressEvent : public Event {
public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
-
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
+ //! mouse position
+ vec2 mouse_pos = {0, 0};
//! The mouse button that was pressed.
MouseButton button = MouseButton::NONE;
@@ -53,11 +51,8 @@ public:
*/
class MouseClickEvent : public Event {
public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
-
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
+ //! mouse position
+ vec2 mouse_pos = {0, 0};
//! The mouse button that was clicked.
MouseButton button = MouseButton::NONE;
@@ -68,11 +63,8 @@ public:
*/
class MouseReleaseEvent : public Event {
public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
-
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
+ //! mouse position
+ vec2 mouse_pos = {0, 0};
//! The mouse button that was released.
MouseButton button = MouseButton::NONE;
@@ -83,17 +75,10 @@ public:
*/
class MouseMoveEvent : public Event {
public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
-
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
-
- // Movement since last event in x
- int delta_x = 0;
-
- // Movement since last event in y
- int delta_y = 0;
+ //! new mouse position
+ vec2 mouse_pos = {0, 0};
+ //! The change in mouse position relative to the last position (in pixels).
+ ivec2 mouse_delta = {0, 0};
};
/**
@@ -101,12 +86,8 @@ public:
*/
class MouseScrollEvent : public Event {
public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
-
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
-
+ //! mouse position when the scroll happened.
+ vec2 mouse_pos = {0, 0};
//! scroll direction (-1 = down, 1 = up)
int scroll_direction = 0;
//! scroll amount in y axis (from and away from the person).
@@ -127,4 +108,55 @@ public:
*/
class ShutDownEvent : public Event {};
+/**
+ * \brief Event triggered to indicate the window is overlapped by another window.
+ *
+ * When two windows overlap the bottom window gets distorted and that window has to be redrawn.
+ */
+class WindowExposeEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window is resized.
+ */
+class WindowResizeEvent : public Event {
+public:
+ //! new window dimensions
+ ivec2 dimensions = {0, 0};
+};
+
+/**
+ * \brief Event triggered to indicate the window is moved.
+ */
+class WindowMoveEvent : public Event {
+public:
+ //! The change in position relative to the last position (in pixels).
+ ivec2 delta_move = {0, 0};
+};
+
+/**
+ * \brief Event triggered to indicate the window is minimized.
+ */
+class WindowMinimizeEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window is maximized
+ */
+class WindowMaximizeEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window gained focus
+ *
+ * This event is triggered when the window receives focus, meaning it becomes the active window
+ * for user interaction.
+ */
+class WindowFocusGainEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window lost focus
+ *
+ * This event is triggered when the window loses focus, meaning it is no longer the active window
+ * for user interaction.
+ */
+class WindowFocusLostEvent : public Event {};
+
} // namespace crepe
diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp
index 9ef4682..9b94cad 100644
--- a/src/crepe/api/GameObject.cpp
+++ b/src/crepe/api/GameObject.cpp
@@ -7,20 +7,17 @@
using namespace crepe;
using namespace std;
-GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id,
- const std::string & name, const std::string & tag,
- const vec2 & position, double rotation, double scale)
+GameObject::GameObject(Mediator & mediator, game_object_id_t id, const std::string & name,
+ const std::string & tag, const vec2 & position, double rotation,
+ double scale)
: id(id),
- component_manager(component_manager) {
-
- // Add Transform and Metadata components
- ComponentManager & mgr = this->component_manager;
- mgr.add_component<Transform>(this->id, position, rotation, scale);
- mgr.add_component<Metadata>(this->id, name, tag);
-}
+ mediator(mediator),
+ transform(mediator.component_manager->add_component<Transform>(this->id, position,
+ rotation, scale)),
+ metadata(mediator.component_manager->add_component<Metadata>(this->id, name, tag)) {}
void GameObject::set_parent(const GameObject & parent) {
- ComponentManager & mgr = this->component_manager;
+ ComponentManager & mgr = this->mediator.component_manager;
// Set parent on own Metadata component
RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id);
@@ -32,7 +29,7 @@ void GameObject::set_parent(const GameObject & parent) {
}
void GameObject::set_persistent(bool persistent) {
- ComponentManager & mgr = this->component_manager;
+ ComponentManager & mgr = this->mediator.component_manager;
mgr.set_persistent(this->id, persistent);
}
diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h
index ff80f49..572ce3a 100644
--- a/src/crepe/api/GameObject.h
+++ b/src/crepe/api/GameObject.h
@@ -6,7 +6,9 @@
namespace crepe {
-class ComponentManager;
+class Mediator;
+class Transform;
+class Metadata;
/**
* \brief Represents a GameObject
@@ -20,7 +22,7 @@ private:
* This constructor creates a new GameObject. It creates a new Transform and Metadata
* component and adds them to the ComponentManager.
*
- * \param component_manager Reference to component_manager
+ * \param mediator Reference to mediator
* \param id The id of the GameObject
* \param name The name of the GameObject
* \param tag The tag of the GameObject
@@ -28,13 +30,20 @@ private:
* \param rotation The rotation of the GameObject
* \param scale The scale of the GameObject
*/
- GameObject(ComponentManager & component_manager, game_object_id_t id,
- const std::string & name, const std::string & tag, const vec2 & position,
- double rotation, double scale);
+ GameObject(Mediator & mediator, game_object_id_t id, const std::string & name,
+ const std::string & tag, const vec2 & position, double rotation, double scale);
//! ComponentManager instances GameObject
friend class ComponentManager;
public:
+ //! The id of the GameObject
+ const game_object_id_t id;
+ //! This entity's transform
+ Transform & transform;
+ //! This entity's metadata
+ Metadata & metadata;
+
+public:
/**
* \brief Set the parent of this GameObject
*
@@ -68,12 +77,8 @@ public:
*/
void set_persistent(bool persistent = true);
-public:
- //! The id of the GameObject
- const game_object_id_t id;
-
protected:
- ComponentManager & component_manager;
+ Mediator & mediator;
};
} // namespace crepe
diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp
index a6b45b0..69f7d73 100644
--- a/src/crepe/api/GameObject.hpp
+++ b/src/crepe/api/GameObject.hpp
@@ -8,7 +8,7 @@ namespace crepe {
template <typename T, typename... Args>
T & GameObject::add_component(Args &&... args) {
- ComponentManager & mgr = this->component_manager;
+ ComponentManager & mgr = this->mediator.component_manager;
return mgr.add_component<T>(this->id, std::forward<Args>(args)...);
}
diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h
index fcfc080..748e8f6 100644
--- a/src/crepe/api/KeyCodes.h
+++ b/src/crepe/api/KeyCodes.h
@@ -1,5 +1,9 @@
#pragma once
+
+#include <unordered_map>
+
namespace crepe {
+
//! Enumeration for mouse button inputs, including standard and extended buttons.
enum class MouseButton {
NONE = 0, //!< No mouse button input.
@@ -151,4 +155,7 @@ enum class Keycode {
/// \}
MENU = 348, //!< Menu key.
};
+
+typedef std::unordered_map<Keycode, bool> keyboard_state_t;
+
} // namespace crepe
diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp
index b5e5ff7..7a78019 100644
--- a/src/crepe/api/LoopManager.cpp
+++ b/src/crepe/api/LoopManager.cpp
@@ -65,6 +65,7 @@ void LoopManager::fixed_update() {
this->get_system<InputSystem>().update();
this->event_manager.dispatch_events();
this->get_system<ScriptSystem>().update();
+ this->get_system<ParticleSystem>().update();
this->get_system<AISystem>().update();
this->get_system<PhysicsSystem>().update();
this->get_system<CollisionSystem>().update();
diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h
deleted file mode 100644
index 40e6b38..0000000
--- a/src/crepe/api/LoopManager.h
+++ /dev/null
@@ -1,122 +0,0 @@
-#pragma once
-
-#include <memory>
-
-#include "../facade/SDLContext.h"
-#include "../manager/ComponentManager.h"
-#include "../manager/EventManager.h"
-#include "../manager/LoopTimerManager.h"
-#include "../manager/Mediator.h"
-#include "../manager/ResourceManager.h"
-#include "../manager/SaveManager.h"
-#include "../manager/SceneManager.h"
-#include "../system/System.h"
-
-namespace crepe {
-/**
- * \brief Main game loop manager
- *
- * This class is responsible for managing the game loop, including initialization and updating.
- */
-class LoopManager {
-public:
- LoopManager();
- /**
- * \brief Start the gameloop
- *
- * This is the start of the engine where the setup is called and then the loop keeps running until the game stops running.
- * The Game programmer needs to call this function to run the game. This should be done after creating and adding all scenes.
- */
- void start();
-
- /**
- * \brief Add a new concrete scene to the scene manager
- *
- * \tparam T Type of concrete scene
- */
- template <typename T>
- void add_scene();
-
-private:
- /**
- * \brief Setup function for one-time initialization.
- *
- * This function initializes necessary components for the game.
- */
- void setup();
- /**
- * \brief Main game loop function.
- *
- * This function runs the main loop, handling game updates and rendering.
- */
- void loop();
-
- /**
- * \brief Per-frame update.
- *
- * Updates the game state based on the elapsed time since the last frame.
- */
- virtual void frame_update();
-
- /**
- * \brief Fixed update executed at a fixed rate.
- *
- * This function updates physics and game logic based on LoopTimer's fixed_delta_time.
- */
- virtual void fixed_update();
-
- //! Indicates whether the game is running.
- bool game_running = false;
-
-private:
- //! Global context
- Mediator mediator;
-
- //! Component manager instance
- ComponentManager component_manager{mediator};
- //! Scene manager instance
- SceneManager scene_manager{mediator};
- //! LoopTimerManager instance
- LoopTimerManager loop_timer{mediator};
- //! EventManager instance
- EventManager event_manager{mediator};
- //! Resource manager instance
- ResourceManager resource_manager{mediator};
- //! Save manager instance
- SaveManager save_manager{mediator};
- //! SDLContext instance
- SDLContext sdl_context{mediator};
-
-private:
- /**
- * \brief Callback function for ShutDownEvent
- *
- * This function sets the game_running variable to false, stopping the gameloop and therefor quitting the game.
- */
- bool on_shutdown(const ShutDownEvent & e);
- /**
- * \brief Collection of System instances
- *
- * This map holds System instances indexed by the system's class typeid. It is filled in the
- * constructor of LoopManager using LoopManager::load_system.
- */
- std::unordered_map<std::type_index, std::unique_ptr<System>> systems;
- /**
- * \brief Initialize a system
- * \tparam T System type (must be derivative of \c System)
- */
- template <class T>
- void load_system();
- /**
- * \brief Retrieve a reference to ECS system
- * \tparam T System type
- * \returns Reference to system instance
- * \throws std::runtime_error if the System is not initialized
- */
- template <class T>
- T & get_system();
-};
-
-} // namespace crepe
-
-#include "LoopManager.hpp"
diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp
index 90b77a0..9a70334 100644
--- a/src/crepe/api/ParticleEmitter.cpp
+++ b/src/crepe/api/ParticleEmitter.cpp
@@ -1,11 +1,28 @@
#include "ParticleEmitter.h"
+#include "api/Sprite.h"
using namespace crepe;
+using namespace std;
-ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Data & data)
+ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite,
+ const Data & data)
: Component(game_object_id),
+ sprite(sprite),
data(data) {
for (size_t i = 0; i < this->data.max_particles; i++) {
- this->data.particles.emplace_back();
+ this->particles.emplace_back();
}
}
+
+unique_ptr<Component> ParticleEmitter::save() const {
+ return unique_ptr<Component>{new ParticleEmitter(*this)};
+}
+
+void ParticleEmitter::restore(const Component & snapshot) {
+ *this = static_cast<const ParticleEmitter &>(snapshot);
+}
+
+ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter & other) {
+ this->particles = other.particles;
+ return *this;
+}
diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h
index b83fd61..626b356 100644
--- a/src/crepe/api/ParticleEmitter.h
+++ b/src/crepe/api/ParticleEmitter.h
@@ -1,7 +1,11 @@
#pragma once
+#include <cmath>
#include <vector>
+#include "system/ParticleSystem.h"
+#include "system/RenderSystem.h"
+
#include "Component.h"
#include "Particle.h"
#include "types.h"
@@ -26,15 +30,18 @@ public:
*/
struct Boundary {
//! boundary width (midpoint is emitter location)
- double width = 0.0;
+ float width = INFINITY;
//! boundary height (midpoint is emitter location)
- double height = 0.0;
+ float height = INFINITY;
//! boundary offset from particle emitter location
vec2 offset;
//! reset on exit or stop velocity and set max postion
bool reset_on_exit = false;
};
+ //! sprite reference of displayed sprite
+ const Sprite & sprite;
+
/**
* \brief Holds parameters that control particle emission.
*
@@ -45,29 +52,25 @@ public:
//! position of the emitter
vec2 position;
//! maximum number of particles
- const unsigned int max_particles = 0;
- //! rate of particle emission per update (Lowest value = 0.001 any lower is ignored)
- double emission_rate = 0;
+ const unsigned int max_particles = 256;
+ //! rate of particle emission per second
+ float emission_rate = 50;
//! min speed of the particles
- double min_speed = 0;
+ float min_speed = 100;
//! min speed of the particles
- double max_speed = 0;
+ float max_speed = 100;
//! min angle of particle emission
- double min_angle = 0;
+ float min_angle = 0;
//! max angle of particle emission
- double max_angle = 0;
- //! begin Lifespan of particle (only visual)
- double begin_lifespan = 0.0;
- //! end Lifespan of particle
- double end_lifespan = 0.0;
+ float max_angle = 0;
+ //! begin Lifespan of particle in seconds (only visual)
+ float begin_lifespan = 0.0;
+ //! end Lifespan of particle in seconds
+ float end_lifespan = 10.0;
//! force over time (physics)
vec2 force_over_time;
//! particle boundary
Boundary boundary;
- //! collection of particles
- std::vector<Particle> particles;
- //! sprite reference
- const Sprite & sprite;
};
public:
@@ -75,11 +78,27 @@ public:
* \param game_object_id Identifier for the game object using this emitter.
* \param data Configuration data defining particle properties.
*/
- ParticleEmitter(game_object_id_t game_object_id, const Data & data);
+ ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite, const Data & data);
public:
//! Configuration data for particle emission settings.
Data data;
+
+protected:
+ virtual std::unique_ptr<Component> save() const;
+ ParticleEmitter(const ParticleEmitter &) = default;
+ virtual void restore(const Component & snapshot);
+ virtual ParticleEmitter & operator=(const ParticleEmitter &);
+
+private:
+ //! Only ParticleSystem can move and read particles
+ friend ParticleSystem;
+ //! Only RenderSystem can read particles
+ friend RenderSystem;
+ //! Saves time left over from last update event.
+ float spawn_accumulator = 0;
+ //! collection of particles
+ std::vector<Particle> particles;
};
} // namespace crepe
diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h
index b08c8db..b2bfc0d 100644
--- a/src/crepe/api/Rigidbody.h
+++ b/src/crepe/api/Rigidbody.h
@@ -61,7 +61,7 @@ public:
* gravity force, allowing for fine-grained control over how the object responds to gravity.
*
*/
- float gravity_scale = 0;
+ float gravity_scale = 1.0;
//! Defines the type of the physics body, which determines how the physics system interacts with the object.
BodyType body_type = BodyType::DYNAMIC;
@@ -139,7 +139,7 @@ public:
* Each element represents a layer ID, and the GameObject will only detect
* collisions with other GameObjects that belong to these layers.
*/
- std::set<int> collision_layers;
+ std::set<int> collision_layers = {0};
};
public:
diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h
index dcca9d4..5c34b36 100644
--- a/src/crepe/api/Scene.h
+++ b/src/crepe/api/Scene.h
@@ -39,7 +39,7 @@ public:
* \brief Get the scene's name
* \return The scene's name
*/
- virtual std::string get_name() const = 0;
+ virtual std::string get_name() const { return ""; };
// TODO: Late references should ALWAYS be private! This is currently kept as-is so unit tests
// keep passing, but this reference should not be directly accessible by the user!!!
diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp
index 753a9e3..d5f3f06 100644
--- a/src/crepe/api/Script.cpp
+++ b/src/crepe/api/Script.cpp
@@ -1,6 +1,7 @@
#include <string>
#include "../manager/SceneManager.h"
+#include "../facade/SDLContext.h"
#include "Script.h"
@@ -25,3 +26,39 @@ void Script::set_next_scene(const string & name) {
}
SaveManager & Script::get_save_manager() const { return this->mediator->save_manager; }
+
+void Script::replay::record_start() {
+ ReplayManager & mgr = this->mediator->replay_manager;
+ return mgr.record_start();
+}
+
+recording_t Script::replay::record_end() {
+ ReplayManager & mgr = this->mediator->replay_manager;
+ return mgr.record_end();
+}
+
+void Script::replay::play(recording_t recording) {
+ ReplayManager & mgr = this->mediator->replay_manager;
+ return mgr.play(recording);
+}
+
+void Script::replay::release(recording_t recording) {
+ ReplayManager & mgr = this->mediator->replay_manager;
+ return mgr.release(recording);
+}
+
+LoopTimerManager & Script::get_loop_timer() const { return this->mediator->loop_timer; }
+
+const keyboard_state_t & Script::get_keyboard_state() const {
+ SDLContext & sdl_context = this->mediator->sdl_context;
+ return sdl_context.get_keyboard_state();
+}
+
+bool Script::get_key_state(Keycode key) const noexcept {
+ try {
+ return this->get_keyboard_state().at(key);
+ } catch (...) {
+ return false;
+ }
+}
+
diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h
index 668e5d1..634a459 100644
--- a/src/crepe/api/Script.h
+++ b/src/crepe/api/Script.h
@@ -3,9 +3,12 @@
#include <vector>
#include "../manager/EventManager.h"
+#include "../manager/LoopTimerManager.h"
#include "../manager/Mediator.h"
+#include "../manager/ReplayManager.h"
#include "../system/CollisionSystem.h"
#include "../types.h"
+#include "../util/Log.h"
#include "../util/OptionalRef.h"
namespace crepe {
@@ -46,9 +49,17 @@ protected:
/**
* \brief Script update function (empty by default)
*
+ * \param delta_time Time since last fixed update
+ *
* This function is called during the ScriptSystem::update() routine if the \c BehaviorScript
* component holding this script instance is active.
*/
+ virtual void update(duration_t delta_time) { return this->update(); }
+ /**
+ * \brief Fallback script update function (empty by default)
+ *
+ * Allows the game programmer to ignore parameters passed to \c update()
+ */
virtual void update() {}
//! \}
@@ -57,84 +68,116 @@ protected:
protected:
/**
- * \name Utility functions
+ * \name Component query functions
+ * \see ComponentManager
* \{
*/
-
/**
* \brief Get single component of type \c T on this game object
- *
* \tparam T Type of component
- *
* \returns Reference to component
- *
* \throws std::runtime_error if this game object does not have a component with type \c T
*/
template <typename T>
T & get_component() const;
- // TODO: make get_component calls for component types that can have more than 1 instance
- // cause compile-time errors
-
/**
* \brief Get all components of type \c T on this game object
- *
* \tparam T Type of component
- *
* \returns List of component references
*/
template <typename T>
RefVector<T> get_components() const;
-
- /**
- * \copydoc ComponentManager::get_components_by_id
- * \see ComponentManager::get_components_by_id
- */
+ //! \copydoc ComponentManager::get_components_by_id
template <typename T>
RefVector<T> get_components_by_id(game_object_id_t id) const;
- /**
- * \copydoc ComponentManager::get_components_by_name
- * \see ComponentManager::get_components_by_name
- */
+ //! \copydoc ComponentManager::get_components_by_name
template <typename T>
RefVector<T> get_components_by_name(const std::string & name) const;
- /**
- * \copydoc ComponentManager::get_components_by_tag
- * \see ComponentManager::get_components_by_tag
- */
+ //! \copydoc ComponentManager::get_components_by_tag
template <typename T>
RefVector<T> get_components_by_tag(const std::string & tag) const;
+ //! \}
/**
- * \brief Log a message using Log::logf
- *
- * \tparam Args Log::logf parameters
- * \param args Log::logf parameters
+ * \name Logging functions
+ * \see Log
+ * \{
*/
- template <typename... Args>
- void logf(Args &&... args);
+ //! \copydoc Log::logf
+ template <class... Args>
+ void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args);
+ //! \copydoc Log::logf
+ template <class... Args>
+ void logf(std::format_string<Args...> fmt, Args &&... args);
+ // \}
/**
- * \brief Subscribe to an event with an explicit channel
- * \see EventManager::subscribe
+ * \name Event manager functions
+ * \see EventManager
+ * \{
*/
+ //! \copydoc EventManager::subscribe
template <typename EventType>
void subscribe(const EventHandler<EventType> & callback, event_channel_t channel);
- /**
- * \brief Subscribe to an event on EventManager::CHANNEL_ALL
- * \see EventManager::subscribe
- */
+ //! \copydoc EventManager::subscribe
template <typename EventType>
void subscribe(const EventHandler<EventType> & callback);
+ //! \copydoc EventManager::trigger_event
+ template <typename EventType>
+ void trigger_event(const EventType & event = {},
+ event_channel_t channel = EventManager::CHANNEL_ALL);
+ //! \copydoc EventManager::queue_event
+ template <typename EventType>
+ void queue_event(const EventType & event = {},
+ event_channel_t channel = EventManager::CHANNEL_ALL);
+ //! \}
/**
- * \brief Set the next scene using SceneManager
- * \see SceneManager::set_next_scene
+ * \name Scene-related functions
+ * \see SceneManager
+ * \{
*/
+ //! \copydoc SceneManager::set_next_scene
void set_next_scene(const std::string & name);
+ //! \}
+ /**
+ * \name Save data management functions
+ * \see SaveManager
+ * \{
+ */
//! Retrieve SaveManager reference
SaveManager & get_save_manager() const;
+ //! \}
+
+ //! Replay management functions
+ struct replay { // NOLINT
+ //! \copydoc ReplayManager::record_start
+ void record_start();
+ //! \copydoc ReplayManager::record_end
+ recording_t record_end();
+ //! \copydoc ReplayManager::play
+ void play(recording_t);
+ //! \copydoc ReplayManager::release
+ void release(recording_t);
+
+ private:
+ OptionalRef<Mediator> & mediator;
+ replay(OptionalRef<Mediator> & mediator) : mediator(mediator) {}
+ friend class Script;
+ } replay{mediator};
+ //! Retrieve LoopTimerManager reference
+ LoopTimerManager & get_loop_timer() const;
+
+ /**
+ * \name Input
+ * \{
+ */
+ //! Get keyboard state
+ const keyboard_state_t & get_keyboard_state() const;
+ //! Get state of specific key
+ bool get_key_state(Keycode key) const noexcept;
//! \}
private:
diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp
index 225a51c..4462a41 100644
--- a/src/crepe/api/Script.hpp
+++ b/src/crepe/api/Script.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "../manager/ComponentManager.h"
+#include "../manager/ReplayManager.h"
#include "BehaviorScript.h"
#include "Script.h"
@@ -23,9 +24,14 @@ RefVector<T> Script::get_components() const {
return this->get_components_by_id<T>(this->game_object_id);
}
-template <typename... Args>
-void Script::logf(Args &&... args) {
- Log::logf(std::forward<Args>(args)...);
+template <class... Args>
+void Script::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) {
+ Log::logf(level, fmt, std::forward<Args>(args)...);
+}
+
+template <class... Args>
+void Script::logf(std::format_string<Args...> fmt, Args &&... args) {
+ Log::logf(fmt, std::forward<Args>(args)...);
}
template <typename EventType>
@@ -34,8 +40,18 @@ void Script::subscribe_internal(const EventHandler<EventType> & callback,
EventManager & mgr = this->mediator->event_manager;
subscription_t listener = mgr.subscribe<EventType>(
[this, callback](const EventType & data) -> bool {
+ // check if (parent) BehaviorScript component is active
bool & active = this->active;
if (!active) return false;
+
+ // check if replay manager is playing (if initialized)
+ try {
+ ReplayManager & replay = this->mediator->replay_manager;
+ if (replay.get_state() == ReplayManager::PLAYING) return false;
+ } catch (const std::runtime_error &) {
+ }
+
+ // call user-provided callback
return callback(data);
},
channel);
@@ -52,6 +68,18 @@ void Script::subscribe(const EventHandler<EventType> & callback) {
this->subscribe_internal(callback, EventManager::CHANNEL_ALL);
}
+template <typename EventType>
+void Script::trigger_event(const EventType & event, event_channel_t channel) {
+ EventManager & mgr = this->mediator->event_manager;
+ mgr.trigger_event(event, channel);
+}
+
+template <typename EventType>
+void Script::queue_event(const EventType & event, event_channel_t channel) {
+ EventManager & mgr = this->mediator->event_manager;
+ mgr.queue_event(event, channel);
+}
+
template <typename T>
RefVector<T> Script::get_components_by_id(game_object_id_t id) const {
Mediator & mediator = this->mediator;
diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp
index ba684ba..0107c7b 100644
--- a/src/crepe/api/Sprite.cpp
+++ b/src/crepe/api/Sprite.cpp
@@ -1,6 +1,6 @@
#include <cmath>
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "api/Asset.h"
#include "Component.h"
diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp
index a85b792..fcfce14 100644
--- a/src/crepe/api/Transform.cpp
+++ b/src/crepe/api/Transform.cpp
@@ -1,8 +1,9 @@
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "Transform.h"
using namespace crepe;
+using namespace std;
Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale)
: Component(id),
@@ -11,3 +12,11 @@ Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, d
scale(scale) {
dbg_trace();
}
+
+unique_ptr<Component> Transform::save() const {
+ return unique_ptr<Component>{new Transform(*this)};
+}
+
+void Transform::restore(const Component & snapshot) {
+ *this = static_cast<const Transform &>(snapshot);
+}
diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h
index 7ee6d65..a6f3486 100644
--- a/src/crepe/api/Transform.h
+++ b/src/crepe/api/Transform.h
@@ -35,6 +35,12 @@ protected:
virtual int get_instances_max() const { return 1; }
//! ComponentManager instantiates all components
friend class ComponentManager;
+
+protected:
+ virtual std::unique_ptr<Component> save() const;
+ Transform(const Transform &) = default;
+ virtual void restore(const Component & snapshot);
+ virtual Transform & operator=(const Transform &) = default;
};
} // namespace crepe
diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h
index bf9d124..f4ba2b2 100644
--- a/src/crepe/api/Vector2.h
+++ b/src/crepe/api/Vector2.h
@@ -1,5 +1,7 @@
#pragma once
+#include <format>
+
namespace crepe {
//! 2D vector
@@ -94,4 +96,9 @@ struct Vector2 {
} // namespace crepe
+template <typename T>
+struct std::formatter<crepe::Vector2<T>> : std::formatter<std::string> {
+ format_context::iterator format(crepe::Vector2<T> vec, format_context & ctx) const;
+};
+
#include "Vector2.hpp"
diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp
index ff53cb0..75d875a 100644
--- a/src/crepe/api/Vector2.hpp
+++ b/src/crepe/api/Vector2.hpp
@@ -164,3 +164,9 @@ Vector2<T> Vector2<T>::perpendicular() const {
}
} // namespace crepe
+
+template <typename T>
+std::format_context::iterator std::formatter<crepe::Vector2<T>>::format(crepe::Vector2<T> vec, format_context & ctx) const {
+ return formatter<string>::format(std::format("{{{}, {}}}", vec.x, vec.y), ctx);
+}
+
diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp
index ae2d4bc..7a3e473 100644
--- a/src/crepe/facade/DB.cpp
+++ b/src/crepe/facade/DB.cpp
@@ -1,6 +1,6 @@
#include <cstring>
-#include "util/Log.h"
+#include "util/dbg.h"
#include "DB.h"
diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp
index 20bb030..d6e3159 100644
--- a/src/crepe/facade/SDLContext.cpp
+++ b/src/crepe/facade/SDLContext.cpp
@@ -19,7 +19,7 @@
#include "../api/Color.h"
#include "../api/Config.h"
#include "../api/Sprite.h"
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "manager/Mediator.h"
#include "SDLContext.h"
@@ -35,10 +35,10 @@ SDLContext::SDLContext(Mediator & mediator) {
throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError()));
}
- auto & cfg = Config::get_instance().window_settings;
+ auto & cfg = Config::get_instance().window;
SDL_Window * tmp_window
- = SDL_CreateWindow(cfg.window_title.c_str(), SDL_WINDOWPOS_CENTERED,
- SDL_WINDOWPOS_CENTERED, cfg.default_size.x, cfg.default_size.y, 0);
+ = SDL_CreateWindow(cfg.title.c_str(), SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED, cfg.size.x, cfg.size.y, 0);
if (!tmp_window) {
throw runtime_error(format("SDLContext: SDL_Window error: {}", SDL_GetError()));
}
@@ -75,118 +75,22 @@ SDLContext::~SDLContext() {
SDL_Quit();
}
-Keycode SDLContext::sdl_to_keycode(SDL_Keycode sdl_key) {
- static const std::array<Keycode, SDL_NUM_SCANCODES> LOOKUP_TABLE = [] {
- std::array<Keycode, SDL_NUM_SCANCODES> table{};
- table.fill(Keycode::NONE);
-
- table[SDL_SCANCODE_SPACE] = Keycode::SPACE;
- table[SDL_SCANCODE_APOSTROPHE] = Keycode::APOSTROPHE;
- table[SDL_SCANCODE_COMMA] = Keycode::COMMA;
- table[SDL_SCANCODE_MINUS] = Keycode::MINUS;
- table[SDL_SCANCODE_PERIOD] = Keycode::PERIOD;
- table[SDL_SCANCODE_SLASH] = Keycode::SLASH;
- table[SDL_SCANCODE_0] = Keycode::D0;
- table[SDL_SCANCODE_1] = Keycode::D1;
- table[SDL_SCANCODE_2] = Keycode::D2;
- table[SDL_SCANCODE_3] = Keycode::D3;
- table[SDL_SCANCODE_4] = Keycode::D4;
- table[SDL_SCANCODE_5] = Keycode::D5;
- table[SDL_SCANCODE_6] = Keycode::D6;
- table[SDL_SCANCODE_7] = Keycode::D7;
- table[SDL_SCANCODE_8] = Keycode::D8;
- table[SDL_SCANCODE_9] = Keycode::D9;
- table[SDL_SCANCODE_SEMICOLON] = Keycode::SEMICOLON;
- table[SDL_SCANCODE_EQUALS] = Keycode::EQUAL;
- table[SDL_SCANCODE_A] = Keycode::A;
- table[SDL_SCANCODE_B] = Keycode::B;
- table[SDL_SCANCODE_C] = Keycode::C;
- table[SDL_SCANCODE_D] = Keycode::D;
- table[SDL_SCANCODE_E] = Keycode::E;
- table[SDL_SCANCODE_F] = Keycode::F;
- table[SDL_SCANCODE_G] = Keycode::G;
- table[SDL_SCANCODE_H] = Keycode::H;
- table[SDL_SCANCODE_I] = Keycode::I;
- table[SDL_SCANCODE_J] = Keycode::J;
- table[SDL_SCANCODE_K] = Keycode::K;
- table[SDL_SCANCODE_L] = Keycode::L;
- table[SDL_SCANCODE_M] = Keycode::M;
- table[SDL_SCANCODE_N] = Keycode::N;
- table[SDL_SCANCODE_O] = Keycode::O;
- table[SDL_SCANCODE_P] = Keycode::P;
- table[SDL_SCANCODE_Q] = Keycode::Q;
- table[SDL_SCANCODE_R] = Keycode::R;
- table[SDL_SCANCODE_S] = Keycode::S;
- table[SDL_SCANCODE_T] = Keycode::T;
- table[SDL_SCANCODE_U] = Keycode::U;
- table[SDL_SCANCODE_V] = Keycode::V;
- table[SDL_SCANCODE_W] = Keycode::W;
- table[SDL_SCANCODE_X] = Keycode::X;
- table[SDL_SCANCODE_Y] = Keycode::Y;
- table[SDL_SCANCODE_Z] = Keycode::Z;
- table[SDL_SCANCODE_LEFTBRACKET] = Keycode::LEFT_BRACKET;
- table[SDL_SCANCODE_BACKSLASH] = Keycode::BACKSLASH;
- table[SDL_SCANCODE_RIGHTBRACKET] = Keycode::RIGHT_BRACKET;
- table[SDL_SCANCODE_GRAVE] = Keycode::GRAVE_ACCENT;
- table[SDL_SCANCODE_ESCAPE] = Keycode::ESCAPE;
- table[SDL_SCANCODE_RETURN] = Keycode::ENTER;
- table[SDL_SCANCODE_TAB] = Keycode::TAB;
- table[SDL_SCANCODE_BACKSPACE] = Keycode::BACKSPACE;
- table[SDL_SCANCODE_INSERT] = Keycode::INSERT;
- table[SDL_SCANCODE_DELETE] = Keycode::DELETE;
- table[SDL_SCANCODE_RIGHT] = Keycode::RIGHT;
- table[SDL_SCANCODE_LEFT] = Keycode::LEFT;
- table[SDL_SCANCODE_DOWN] = Keycode::DOWN;
- table[SDL_SCANCODE_UP] = Keycode::UP;
- table[SDL_SCANCODE_PAGEUP] = Keycode::PAGE_UP;
- table[SDL_SCANCODE_PAGEDOWN] = Keycode::PAGE_DOWN;
- table[SDL_SCANCODE_HOME] = Keycode::HOME;
- table[SDL_SCANCODE_END] = Keycode::END;
- table[SDL_SCANCODE_CAPSLOCK] = Keycode::CAPS_LOCK;
- table[SDL_SCANCODE_SCROLLLOCK] = Keycode::SCROLL_LOCK;
- table[SDL_SCANCODE_NUMLOCKCLEAR] = Keycode::NUM_LOCK;
- table[SDL_SCANCODE_PRINTSCREEN] = Keycode::PRINT_SCREEN;
- table[SDL_SCANCODE_PAUSE] = Keycode::PAUSE;
- table[SDL_SCANCODE_F1] = Keycode::F1;
- table[SDL_SCANCODE_F2] = Keycode::F2;
- table[SDL_SCANCODE_F3] = Keycode::F3;
- table[SDL_SCANCODE_F4] = Keycode::F4;
- table[SDL_SCANCODE_F5] = Keycode::F5;
- table[SDL_SCANCODE_F6] = Keycode::F6;
- table[SDL_SCANCODE_F7] = Keycode::F7;
- table[SDL_SCANCODE_F8] = Keycode::F8;
- table[SDL_SCANCODE_F9] = Keycode::F9;
- table[SDL_SCANCODE_F10] = Keycode::F10;
- table[SDL_SCANCODE_F11] = Keycode::F11;
- table[SDL_SCANCODE_F12] = Keycode::F12;
- table[SDL_SCANCODE_KP_0] = Keycode::KP0;
- table[SDL_SCANCODE_KP_1] = Keycode::KP1;
- table[SDL_SCANCODE_KP_2] = Keycode::KP2;
- table[SDL_SCANCODE_KP_3] = Keycode::KP3;
- table[SDL_SCANCODE_KP_4] = Keycode::KP4;
- table[SDL_SCANCODE_KP_5] = Keycode::KP5;
- table[SDL_SCANCODE_KP_6] = Keycode::KP6;
- table[SDL_SCANCODE_KP_7] = Keycode::KP7;
- table[SDL_SCANCODE_KP_8] = Keycode::KP8;
- table[SDL_SCANCODE_KP_9] = Keycode::KP9;
- table[SDL_SCANCODE_LSHIFT] = Keycode::LEFT_SHIFT;
- table[SDL_SCANCODE_LCTRL] = Keycode::LEFT_CONTROL;
- table[SDL_SCANCODE_LALT] = Keycode::LEFT_ALT;
- table[SDL_SCANCODE_LGUI] = Keycode::LEFT_SUPER;
- table[SDL_SCANCODE_RSHIFT] = Keycode::RIGHT_SHIFT;
- table[SDL_SCANCODE_RCTRL] = Keycode::RIGHT_CONTROL;
- table[SDL_SCANCODE_RALT] = Keycode::RIGHT_ALT;
- table[SDL_SCANCODE_RGUI] = Keycode::RIGHT_SUPER;
- table[SDL_SCANCODE_MENU] = Keycode::MENU;
-
- return table;
- }();
-
- if (sdl_key < 0 || sdl_key >= SDL_NUM_SCANCODES) {
+Keycode SDLContext::sdl_to_keycode(SDL_Scancode sdl_key) {
+ if (!LOOKUP_TABLE.contains(sdl_key))
return Keycode::NONE;
- }
- return LOOKUP_TABLE[sdl_key];
+ return LOOKUP_TABLE.at(sdl_key);
+}
+void SDLContext::update_keyboard_state() {
+ // Array to hold the key states (true if pressed, false if not)
+ SDL_PumpEvents();
+ const Uint8 * current_state = SDL_GetKeyboardState(nullptr);
+
+ for (int i = 0; i < SDL_NUM_SCANCODES; ++i) {
+ Keycode key = sdl_to_keycode(static_cast<SDL_Scancode>(i));
+ if (key == Keycode::NONE) continue;
+ this->keyboard_state[key] = current_state[i] != 0;
+ }
}
MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) {
@@ -279,13 +183,13 @@ void SDLContext::draw(const RenderContext & ctx) {
}
void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) {
-
const Camera::Data & cam_data = cam.data;
+ const Config & config = Config::get_instance();
// resize window
int w, h;
SDL_GetWindowSize(this->game_window.get(), &w, &h);
- if (w != cam.screen.x || h != cam.screen.y) {
- SDL_SetWindowSize(this->game_window.get(), cam.screen.x, cam.screen.y);
+ if (w != config.window.size.x || h != config.window.size.y) {
+ SDL_SetWindowSize(this->game_window.get(), config.window.size.x, config.window.size.y);
}
vec2 & zoomed_viewport = this->cam_aux_data.zoomed_viewport;
@@ -294,28 +198,28 @@ void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) {
this->cam_aux_data.cam_pos = new_pos;
zoomed_viewport = cam.viewport_size * cam_data.zoom;
- float screen_aspect = static_cast<float>(cam.screen.x) / cam.screen.y;
+ float screen_aspect = static_cast<float>(config.window.size.x) / config.window.size.y;
float viewport_aspect = zoomed_viewport.x / zoomed_viewport.y;
// calculate black bars
if (screen_aspect > viewport_aspect) {
// pillarboxing
- float scale = cam.screen.y / zoomed_viewport.y;
+ float scale = config.window.size.y / zoomed_viewport.y;
float adj_width = zoomed_viewport.x * scale;
- float bar_width = (cam.screen.x - adj_width) / 2;
- this->black_bars[0] = {0, 0, bar_width, (float) cam.screen.y};
- this->black_bars[1] = {(cam.screen.x - bar_width), 0, bar_width, (float) cam.screen.y};
+ float bar_width = (config.window.size.x - adj_width) / 2;
+ this->black_bars[0] = {0, 0, bar_width, (float) config.window.size.y};
+ this->black_bars[1] = {(config.window.size.x - bar_width), 0, bar_width, (float) config.window.size.y};
bar_size = {bar_width, 0};
render_scale.x = render_scale.y = scale;
} else {
// letterboxing
- float scale = cam.screen.x / (cam.viewport_size.x * cam_data.zoom);
+ float scale = config.window.size.x / (cam.viewport_size.x * cam_data.zoom);
float adj_height = cam.viewport_size.y * scale;
- float bar_height = (cam.screen.y - adj_height) / 2;
- this->black_bars[0] = {0, 0, (float) cam.screen.x, bar_height};
+ float bar_height = (config.window.size.y - adj_height) / 2;
+ this->black_bars[0] = {0, 0, (float) config.window.size.x, bar_height};
this->black_bars[1]
- = {0, (cam.screen.y - bar_height), (float) cam.screen.x, bar_height};
+ = {0, (config.window.size.y - bar_height), (float) config.window.size.x, bar_height};
bar_size = {0, bar_height};
render_scale.x = render_scale.y = scale;
@@ -327,8 +231,8 @@ void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) {
SDL_Rect bg = {
.x = 0,
.y = 0,
- .w = cam.screen.x,
- .h = cam.screen.y,
+ .w = config.window.size.x,
+ .h = config.window.size.y,
};
// fill bg color
@@ -375,61 +279,127 @@ std::vector<SDLContext::EventData> SDLContext::get_events() {
mouse_pos.y = (event.button.y - cam.bar_size.y) / cam.render_scale.y;
switch (event.type) {
case SDL_QUIT:
- event_list.push_back(EventData{
- .event_type = SDLContext::EventType::SHUTDOWN,
- });
+ event_list.push_back({.event_type = SDLContext::EventType::SHUTDOWN});
break;
- case SDL_KEYDOWN:
- event_list.push_back(EventData{
- .event_type = SDLContext::EventType::KEYDOWN,
+ case SDL_KEYDOWN: {
+ this->update_keyboard_state();
+ EventData transfer_event;
+ transfer_event.event_type = SDLContext::EventType::KEYDOWN;
+ transfer_event.data.key_data = KeyData{
.key = sdl_to_keycode(event.key.keysym.scancode),
- .key_repeat = (event.key.repeat != 0),
- });
+ .key_repeat = event.key.repeat != 0,
+ };
+ event_list.push_back(transfer_event);
break;
- case SDL_KEYUP:
- event_list.push_back(EventData{
- .event_type = SDLContext::EventType::KEYUP,
+ }
+
+ case SDL_KEYUP: {
+ this->update_keyboard_state();
+ EventData transfer_event;
+ transfer_event.event_type = SDLContext::EventType::KEYUP;
+ transfer_event.data.key_data = KeyData{
.key = sdl_to_keycode(event.key.keysym.scancode),
- });
+ .key_repeat = false,
+ };
+ event_list.push_back(transfer_event);
break;
- case SDL_MOUSEBUTTONDOWN:
- event_list.push_back(EventData{
- .event_type = SDLContext::EventType::MOUSEDOWN,
+ }
+
+ case SDL_MOUSEBUTTONDOWN: {
+ EventData transfer_event;
+ transfer_event.event_type = SDLContext::EventType::MOUSEDOWN;
+ transfer_event.data.mouse_data = MouseData{
.mouse_button = sdl_to_mousebutton(event.button.button),
.mouse_position = mouse_pos,
- });
+ };
+ event_list.push_back(transfer_event);
break;
+ }
case SDL_MOUSEBUTTONUP: {
- int x, y;
- SDL_GetMouseState(&x, &y);
- event_list.push_back(EventData{
- .event_type = SDLContext::EventType::MOUSEUP,
+ EventData transfer_event;
+ transfer_event.event_type = SDLContext::EventType::MOUSEUP;
+ transfer_event.data.mouse_data = MouseData{
.mouse_button = sdl_to_mousebutton(event.button.button),
.mouse_position = mouse_pos,
- });
- } break;
+ };
+ event_list.push_back(transfer_event);
+ break;
+ }
case SDL_MOUSEMOTION: {
- event_list.push_back(
- EventData{.event_type = SDLContext::EventType::MOUSEMOVE,
- .mouse_position = mouse_pos,
- .rel_mouse_move = {event.motion.xrel, event.motion.yrel}});
- } break;
+ EventData transfer_event;
+ transfer_event.event_type = SDLContext::EventType::MOUSEMOVE;
+ transfer_event.data.mouse_data = MouseData{
+ .mouse_position = mouse_pos,
+ .rel_mouse_move = {event.motion.xrel, event.motion.yrel},
+ };
+ event_list.push_back(transfer_event);
+ break;
+ }
case SDL_MOUSEWHEEL: {
- event_list.push_back(EventData{
- .event_type = SDLContext::EventType::MOUSEWHEEL,
+ EventData transfer_event;
+ transfer_event.event_type = SDLContext::EventType::MOUSEWHEEL;
+ transfer_event.data.mouse_data = MouseData{
.mouse_position = mouse_pos,
- // TODO: why is this needed?
.scroll_direction = event.wheel.y < 0 ? -1 : 1,
.scroll_delta = event.wheel.preciseY,
- });
- } break;
+ };
+ event_list.push_back(transfer_event);
+ break;
+ }
+ case SDL_WINDOWEVENT:
+ handle_window_event(event.window, event_list);
+ break;
}
}
+
return event_list;
}
+
+void SDLContext::handle_window_event(const SDL_WindowEvent & window_event,
+ std::vector<SDLContext::EventData> & event_list) {
+ switch (window_event.event) {
+ case SDL_WINDOWEVENT_EXPOSED:
+ event_list.push_back(EventData{SDLContext::EventType::WINDOW_EXPOSE});
+ break;
+ case SDL_WINDOWEVENT_RESIZED: {
+ EventData transfer_event;
+ transfer_event.event_type = SDLContext::EventType::WINDOW_RESIZE;
+ transfer_event.data.window_data
+ = WindowData{.resize_dimension = {window_event.data1, window_event.data2}};
+ event_list.push_back(transfer_event);
+ break;
+ }
+ case SDL_WINDOWEVENT_MOVED: {
+ EventData transfer_event;
+ transfer_event.event_type = SDLContext::EventType::WINDOW_MOVE;
+ transfer_event.data.window_data
+ = WindowData{.move_delta = {window_event.data1, window_event.data2}};
+ event_list.push_back(transfer_event);
+ break;
+ }
+ case SDL_WINDOWEVENT_MINIMIZED:
+ event_list.push_back(EventData{SDLContext::EventType::WINDOW_MINIMIZE});
+ break;
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ event_list.push_back(EventData{SDLContext::EventType::WINDOW_MAXIMIZE});
+ break;
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ event_list.push_back(EventData{SDLContext::EventType::WINDOW_FOCUS_GAIN});
+ break;
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ event_list.push_back(EventData{SDLContext::EventType::WINDOW_FOCUS_LOST});
+ break;
+ }
+}
+
void SDLContext::set_color_texture(const Texture & texture, const Color & color) {
SDL_SetTextureColorMod(texture.get_img(), color.r, color.g, color.b);
SDL_SetTextureAlphaMod(texture.get_img(), color.a);
}
+
+const keyboard_state_t & SDLContext::get_keyboard_state() const {
+ return this->keyboard_state;
+}
+
diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h
index bcadf87..176982f 100644
--- a/src/crepe/facade/SDLContext.h
+++ b/src/crepe/facade/SDLContext.h
@@ -5,10 +5,12 @@
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_video.h>
+#include <array>
#include <cmath>
#include <functional>
#include <memory>
#include <string>
+#include <unordered_map>
#include "api/Camera.h"
#include "api/Color.h"
@@ -79,19 +81,56 @@ public:
KEYUP,
KEYDOWN,
SHUTDOWN,
-
+ WINDOW_MINIMIZE,
+ WINDOW_MAXIMIZE,
+ WINDOW_FOCUS_GAIN,
+ WINDOW_FOCUS_LOST,
+ WINDOW_MOVE,
+ WINDOW_RESIZE,
+ WINDOW_EXPOSE,
};
- //! EventData struct for passing event data from facade
- struct EventData {
- SDLContext::EventType event_type = SDLContext::EventType::NONE;
+ struct KeyData {
Keycode key = Keycode::NONE;
bool key_repeat = false;
+ };
+ struct MouseData {
MouseButton mouse_button = MouseButton::NONE;
ivec2 mouse_position = {-1, -1};
int scroll_direction = -1;
float scroll_delta = INFINITY;
ivec2 rel_mouse_move = {-1, -1};
};
+ struct WindowData {
+ ivec2 move_delta;
+ ivec2 resize_dimension;
+ };
+ //! EventData struct for passing event data from facade
+ struct EventData {
+ SDLContext::EventType event_type = SDLContext::EventType::NONE;
+
+ union EventDataUnion {
+ KeyData key_data;
+ MouseData mouse_data;
+ WindowData window_data;
+
+ EventDataUnion() {}
+ ~EventDataUnion() {}
+ } data;
+ };
+ /**
+ * \brief Retrieves the current state of the keyboard.
+ *
+ * This method updates the state of all keys on the keyboard. Each element of the unordered map corresponds to a
+ * specific key defined in the `Keycode` enum, and the value indicates whether
+ * the key is currently pressed (true) or not pressed (false).
+ *
+ */
+ void update_keyboard_state();
+ /**
+ * \brief Gets the singleton instance of SDLContext.
+ * \return Reference to the SDLContext instance.
+ */
+ static SDLContext & get_instance();
public:
SDLContext(const SDLContext &) = delete;
@@ -123,17 +162,24 @@ public:
* \return Events that occurred since last call to `get_events()`
*/
std::vector<SDLContext::EventData> get_events();
-
/**
- * \brief Converts an SDL key code to the custom Keycode type.
+ * \brief Fills event_list with triggered window events
+ *
+ * This method checks if any window events are triggered and adds them to the event_list.
+ *
+ */
+ void handle_window_event(const SDL_WindowEvent & window_event,
+ std::vector<SDLContext::EventData> & event_list);
+ /**
+ * \brief Converts an SDL scan code to the custom Keycode type.
*
- * This method maps an SDL key code to the corresponding `Keycode` enum value,
+ * This method maps an SDL scan code to the corresponding `Keycode` enum value,
* which is used internally by the system to identify the keys.
*
- * \param sdl_key The SDL key code to convert.
+ * \param sdl_key The SDL scan code to convert.
* \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized.
*/
- Keycode sdl_to_keycode(SDL_Keycode sdl_key);
+ Keycode sdl_to_keycode(SDL_Scancode sdl_key);
/**
* \brief Converts an SDL mouse button code to the custom MouseButton type.
@@ -224,6 +270,8 @@ public:
*/
void set_color_texture(const Texture & texture, const Color & color);
+ const keyboard_state_t & get_keyboard_state() const;
+
private:
//! sdl Window
std::unique_ptr<SDL_Window, std::function<void(SDL_Window *)>> game_window;
@@ -240,6 +288,108 @@ private:
* - this is defined in this class because get_events() needs this information aswell
*/
CameraAuxiliaryData cam_aux_data;
+
+private:
+ keyboard_state_t keyboard_state;
+ const std::unordered_map<SDL_Scancode, Keycode> LOOKUP_TABLE
+ = {{SDL_SCANCODE_SPACE, Keycode::SPACE},
+ {SDL_SCANCODE_APOSTROPHE, Keycode::APOSTROPHE},
+ {SDL_SCANCODE_COMMA, Keycode::COMMA},
+ {SDL_SCANCODE_MINUS, Keycode::MINUS},
+ {SDL_SCANCODE_PERIOD, Keycode::PERIOD},
+ {SDL_SCANCODE_SLASH, Keycode::SLASH},
+ {SDL_SCANCODE_0, Keycode::D0},
+ {SDL_SCANCODE_1, Keycode::D1},
+ {SDL_SCANCODE_2, Keycode::D2},
+ {SDL_SCANCODE_3, Keycode::D3},
+ {SDL_SCANCODE_4, Keycode::D4},
+ {SDL_SCANCODE_5, Keycode::D5},
+ {SDL_SCANCODE_6, Keycode::D6},
+ {SDL_SCANCODE_7, Keycode::D7},
+ {SDL_SCANCODE_8, Keycode::D8},
+ {SDL_SCANCODE_9, Keycode::D9},
+ {SDL_SCANCODE_SEMICOLON, Keycode::SEMICOLON},
+ {SDL_SCANCODE_EQUALS, Keycode::EQUAL},
+ {SDL_SCANCODE_A, Keycode::A},
+ {SDL_SCANCODE_B, Keycode::B},
+ {SDL_SCANCODE_C, Keycode::C},
+ {SDL_SCANCODE_D, Keycode::D},
+ {SDL_SCANCODE_E, Keycode::E},
+ {SDL_SCANCODE_F, Keycode::F},
+ {SDL_SCANCODE_G, Keycode::G},
+ {SDL_SCANCODE_H, Keycode::H},
+ {SDL_SCANCODE_I, Keycode::I},
+ {SDL_SCANCODE_J, Keycode::J},
+ {SDL_SCANCODE_K, Keycode::K},
+ {SDL_SCANCODE_L, Keycode::L},
+ {SDL_SCANCODE_M, Keycode::M},
+ {SDL_SCANCODE_N, Keycode::N},
+ {SDL_SCANCODE_O, Keycode::O},
+ {SDL_SCANCODE_P, Keycode::P},
+ {SDL_SCANCODE_Q, Keycode::Q},
+ {SDL_SCANCODE_R, Keycode::R},
+ {SDL_SCANCODE_S, Keycode::S},
+ {SDL_SCANCODE_T, Keycode::T},
+ {SDL_SCANCODE_U, Keycode::U},
+ {SDL_SCANCODE_V, Keycode::V},
+ {SDL_SCANCODE_W, Keycode::W},
+ {SDL_SCANCODE_X, Keycode::X},
+ {SDL_SCANCODE_Y, Keycode::Y},
+ {SDL_SCANCODE_Z, Keycode::Z},
+ {SDL_SCANCODE_LEFTBRACKET, Keycode::LEFT_BRACKET},
+ {SDL_SCANCODE_BACKSLASH, Keycode::BACKSLASH},
+ {SDL_SCANCODE_RIGHTBRACKET, Keycode::RIGHT_BRACKET},
+ {SDL_SCANCODE_GRAVE, Keycode::GRAVE_ACCENT},
+ {SDL_SCANCODE_ESCAPE, Keycode::ESCAPE},
+ {SDL_SCANCODE_RETURN, Keycode::ENTER},
+ {SDL_SCANCODE_TAB, Keycode::TAB},
+ {SDL_SCANCODE_BACKSPACE, Keycode::BACKSPACE},
+ {SDL_SCANCODE_INSERT, Keycode::INSERT},
+ {SDL_SCANCODE_DELETE, Keycode::DELETE},
+ {SDL_SCANCODE_RIGHT, Keycode::RIGHT},
+ {SDL_SCANCODE_LEFT, Keycode::LEFT},
+ {SDL_SCANCODE_DOWN, Keycode::DOWN},
+ {SDL_SCANCODE_UP, Keycode::UP},
+ {SDL_SCANCODE_PAGEUP, Keycode::PAGE_UP},
+ {SDL_SCANCODE_PAGEDOWN, Keycode::PAGE_DOWN},
+ {SDL_SCANCODE_HOME, Keycode::HOME},
+ {SDL_SCANCODE_END, Keycode::END},
+ {SDL_SCANCODE_CAPSLOCK, Keycode::CAPS_LOCK},
+ {SDL_SCANCODE_SCROLLLOCK, Keycode::SCROLL_LOCK},
+ {SDL_SCANCODE_NUMLOCKCLEAR, Keycode::NUM_LOCK},
+ {SDL_SCANCODE_PRINTSCREEN, Keycode::PRINT_SCREEN},
+ {SDL_SCANCODE_PAUSE, Keycode::PAUSE},
+ {SDL_SCANCODE_F1, Keycode::F1},
+ {SDL_SCANCODE_F2, Keycode::F2},
+ {SDL_SCANCODE_F3, Keycode::F3},
+ {SDL_SCANCODE_F4, Keycode::F4},
+ {SDL_SCANCODE_F5, Keycode::F5},
+ {SDL_SCANCODE_F6, Keycode::F6},
+ {SDL_SCANCODE_F7, Keycode::F7},
+ {SDL_SCANCODE_F8, Keycode::F8},
+ {SDL_SCANCODE_F9, Keycode::F9},
+ {SDL_SCANCODE_F10, Keycode::F10},
+ {SDL_SCANCODE_F11, Keycode::F11},
+ {SDL_SCANCODE_F12, Keycode::F12},
+ {SDL_SCANCODE_KP_0, Keycode::KP0},
+ {SDL_SCANCODE_KP_1, Keycode::KP1},
+ {SDL_SCANCODE_KP_2, Keycode::KP2},
+ {SDL_SCANCODE_KP_3, Keycode::KP3},
+ {SDL_SCANCODE_KP_4, Keycode::KP4},
+ {SDL_SCANCODE_KP_5, Keycode::KP5},
+ {SDL_SCANCODE_KP_6, Keycode::KP6},
+ {SDL_SCANCODE_KP_7, Keycode::KP7},
+ {SDL_SCANCODE_KP_8, Keycode::KP8},
+ {SDL_SCANCODE_KP_9, Keycode::KP9},
+ {SDL_SCANCODE_LSHIFT, Keycode::LEFT_SHIFT},
+ {SDL_SCANCODE_LCTRL, Keycode::LEFT_CONTROL},
+ {SDL_SCANCODE_LALT, Keycode::LEFT_ALT},
+ {SDL_SCANCODE_LGUI, Keycode::LEFT_SUPER},
+ {SDL_SCANCODE_RSHIFT, Keycode::RIGHT_SHIFT},
+ {SDL_SCANCODE_RCTRL, Keycode::RIGHT_CONTROL},
+ {SDL_SCANCODE_RALT, Keycode::RIGHT_ALT},
+ {SDL_SCANCODE_RGUI, Keycode::RIGHT_SUPER},
+ {SDL_SCANCODE_MENU, Keycode::MENU}};
};
} // namespace crepe
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 97e455e..b1e6463 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -1,5 +1,5 @@
#include "../api/Asset.h"
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "Sound.h"
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index b1f8cb3..5091e07 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -1,4 +1,4 @@
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "SoundContext.h"
diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp
index b63403d..23a9c8e 100644
--- a/src/crepe/facade/Texture.cpp
+++ b/src/crepe/facade/Texture.cpp
@@ -1,4 +1,4 @@
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "facade/SDLContext.h"
#include "manager/Mediator.h"
diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt
index f73e165..48e444f 100644
--- a/src/crepe/manager/CMakeLists.txt
+++ b/src/crepe/manager/CMakeLists.txt
@@ -6,6 +6,8 @@ target_sources(crepe PUBLIC
SceneManager.cpp
LoopTimerManager.cpp
ResourceManager.cpp
+ ReplayManager.cpp
+ SystemManager.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -21,5 +23,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
LoopTimerManager.h
ResourceManager.h
ResourceManager.hpp
+ ReplayManager.h
+ SystemManager.h
+ SystemManager.hpp
)
diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp
index df30d27..745ddae 100644
--- a/src/crepe/manager/ComponentManager.cpp
+++ b/src/crepe/manager/ComponentManager.cpp
@@ -1,7 +1,7 @@
#include "../api/GameObject.h"
#include "../api/Metadata.h"
#include "../types.h"
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "ComponentManager.h"
@@ -53,7 +53,7 @@ GameObject ComponentManager::new_object(const string & name, const string & tag,
this->next_id++;
}
- GameObject object{*this, this->next_id, name, tag, position, rotation, scale};
+ GameObject object{this->mediator, this->next_id, name, tag, position, rotation, scale};
this->next_id++;
return object;
@@ -72,3 +72,28 @@ set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) c
return this->get_objects_by_predicate<Metadata>(
[tag](const Metadata & data) { return data.tag == tag; });
}
+
+ComponentManager::Snapshot ComponentManager::save() {
+ Snapshot snapshot{};
+ for (const auto & [type, by_id_index] : this->components) {
+ for (game_object_id_t id = 0; id < by_id_index.size(); id++) {
+ const auto & components = by_id_index[id];
+ for (size_t index = 0; index < components.size(); index++) {
+ const Component & component = *components[index];
+ snapshot.components.push_back(SnapshotComponent{
+ .type = type,
+ .id = id,
+ .index = index,
+ .component = component.save(),
+ });
+ }
+ }
+ }
+ return snapshot;
+}
+
+void ComponentManager::restore(const Snapshot & snapshot) {
+ for (const SnapshotComponent & info : snapshot.components) {
+ this->components[info.type][info.id][info.index]->restore(*info.component);
+ }
+}
diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h
index 19a8e81..c3a5b4a 100644
--- a/src/crepe/manager/ComponentManager.h
+++ b/src/crepe/manager/ComponentManager.h
@@ -21,13 +21,6 @@ class GameObject;
* This class manages all components. It provides methods to add, delete and get components.
*/
class ComponentManager : public Manager {
- // TODO: This relation should be removed! I (loek) believe that the scene manager should
- // create/destroy components because the GameObject's are stored in concrete Scene classes,
- // which will in turn call GameObject's destructor, which will in turn call
- // ComponentManager::delete_components_by_id or something. This is a pretty major change, so
- // here is a comment and temporary fix instead :tada:
- friend class SceneManager;
-
public:
ComponentManager(Mediator & mediator);
~ComponentManager(); // dbg_trace
@@ -49,12 +42,7 @@ public:
const vec2 & position = {0, 0}, double rotation = 0,
double scale = 1);
-protected:
- /**
- * GameObject is used as an interface to add/remove components, and the game programmer is
- * supposed to use it instead of interfacing with the component manager directly.
- */
- friend class GameObject;
+public:
/**
* \brief Add a component to the ComponentManager
*
@@ -154,6 +142,40 @@ public:
template <typename T>
RefVector<T> get_components_by_tag(const std::string & tag) const;
+ //! Snapshot of single component (including path in \c components)
+ struct SnapshotComponent {
+ //! \c components path
+ std::type_index type;
+ //! \c components path
+ game_object_id_t id;
+ //! \c components path
+ size_t index;
+ //! Actual component snapshot
+ std::unique_ptr<Component> component;
+ };
+ //! Snapshot of the entire component manager state
+ struct Snapshot {
+ //! All components
+ std::vector<SnapshotComponent> components;
+ // TODO: some kind of hash code that ensures components exist in all the same places as
+ // this snapshot
+ };
+ /**
+ * \name ReplayManager (Memento) functions
+ * \{
+ */
+ /**
+ * \brief Save a snapshot of the component manager state
+ * \returns Deep copy of the component manager's internal state
+ */
+ Snapshot save();
+ /**
+ * \brief Restore component manager from a snapshot
+ * \param snapshot Snapshot to restore from (as returned by \c save())
+ */
+ void restore(const Snapshot & snapshot);
+ //! \}
+
private:
/**
* \brief Get object IDs by predicate function
diff --git a/src/crepe/manager/LoopTimerManager.cpp b/src/crepe/manager/LoopTimerManager.cpp
index a6e4788..e78f92f 100644
--- a/src/crepe/manager/LoopTimerManager.cpp
+++ b/src/crepe/manager/LoopTimerManager.cpp
@@ -1,7 +1,7 @@
#include <chrono>
#include <thread>
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "LoopTimerManager.h"
diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h
index 76b02d3..2f1e6b6 100644
--- a/src/crepe/manager/LoopTimerManager.h
+++ b/src/crepe/manager/LoopTimerManager.h
@@ -6,6 +6,8 @@
namespace crepe {
+class Engine;
+
typedef std::chrono::duration<float> duration_t;
typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t;
@@ -107,7 +109,7 @@ public:
private:
//! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update.
- friend class LoopManager;
+ friend class Engine;
/**
* \brief Start the loop timer.
*
diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h
index a336410..842f1de 100644
--- a/src/crepe/manager/Mediator.h
+++ b/src/crepe/manager/Mediator.h
@@ -11,6 +11,8 @@ class LoopTimerManager;
class SaveManager;
class ResourceManager;
class SDLContext;
+class ReplayManager;
+class SystemManager;
/**
* Struct to pass references to classes that would otherwise need to be singletons down to
@@ -32,6 +34,8 @@ struct Mediator {
OptionalRef<LoopTimerManager> loop_timer;
OptionalRef<SaveManager> save_manager;
OptionalRef<ResourceManager> resource_manager;
+ OptionalRef<ReplayManager> replay_manager;
+ OptionalRef<SystemManager> system_manager;
};
} // namespace crepe
diff --git a/src/crepe/manager/ReplayManager.cpp b/src/crepe/manager/ReplayManager.cpp
new file mode 100644
index 0000000..090a94e
--- /dev/null
+++ b/src/crepe/manager/ReplayManager.cpp
@@ -0,0 +1,70 @@
+#include <format>
+
+#include "Manager.h"
+#include "ReplayManager.h"
+
+using namespace crepe;
+using namespace std;
+
+ReplayManager::ReplayManager(Mediator & mediator) : Manager(mediator) {
+ mediator.replay_manager = *this;
+}
+
+void ReplayManager::record_start() {
+ if (this->state == RECORDING) this->release(this->id);
+ this->id++;
+ this->memory[this->id] = make_unique<Recording>();
+ this->recording = *this->memory.at(this->id);
+ this->state = RECORDING;
+}
+
+recording_t ReplayManager::record_end() {
+ this->state = IDLE;
+ return this->id;
+}
+
+void ReplayManager::play(recording_t handle) {
+ if (!this->memory.contains(handle))
+ throw out_of_range(format("ReplayManager: no recording for handle {}", handle));
+ this->recording = *this->memory.at(handle);
+ this->recording->frame = 0;
+ this->state = PLAYING;
+}
+
+void ReplayManager::release(recording_t handle) {
+ if (!this->memory.contains(handle)) return;
+ this->memory.erase(handle);
+}
+
+void ReplayManager::frame_record() {
+ if (this->state != RECORDING)
+ throw runtime_error("ReplayManager: frame_step called while not playing");
+
+ ComponentManager & components = this->mediator.component_manager;
+ Recording & recording = this->recording;
+
+ recording.frames.push_back(components.save());
+ recording.frame++;
+}
+
+bool ReplayManager::frame_step() {
+ if (this->state != PLAYING)
+ throw runtime_error("ReplayManager: frame_step called while not playing");
+
+ ComponentManager & components = this->mediator.component_manager;
+ Recording & recording = this->recording;
+
+ ComponentManager::Snapshot & frame = recording.frames.at(recording.frame);
+
+ components.restore(frame);
+ recording.frame++;
+
+ if (recording.frame < recording.frames.size()) return false;
+ // end of recording
+ recording.frame = 0;
+ this->state = IDLE;
+ this->recording.clear();
+ return true;
+}
+
+ReplayManager::State ReplayManager::get_state() const { return this->state; }
diff --git a/src/crepe/manager/ReplayManager.h b/src/crepe/manager/ReplayManager.h
new file mode 100644
index 0000000..ab15b27
--- /dev/null
+++ b/src/crepe/manager/ReplayManager.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include <unordered_map>
+
+#include "ComponentManager.h"
+#include "Manager.h"
+#include "util/OptionalRef.h"
+
+namespace crepe {
+
+typedef size_t recording_t;
+
+/**
+ * \brief Replay manager
+ *
+ * The replay manager is responsible for creating, storing and restoring ComponentManager
+ * snapshots. Sequential snapshots can be recorded and replayed in combination with
+ * ReplaySystem.
+ */
+class ReplayManager : public Manager {
+ // TODO: Delete recordings at end of scene
+
+public:
+ ReplayManager(Mediator & mediator);
+
+public:
+ //! Start a new recording
+ void record_start();
+ /**
+ * \brief End the latest recording started by \c record_start()
+ * \returns Handle to recording
+ */
+ recording_t record_end();
+ /**
+ * \brief Play a recording
+ * \param handle Handle to recording (as returned by \c record_end())
+ */
+ void play(recording_t handle);
+ /**
+ * \brief Delete a recording from memory
+ * \param handle Handle to recording (as returned by \c record_end())
+ */
+ void release(recording_t handle);
+
+public:
+ //! Internal state
+ enum State {
+ IDLE, //!< Not doing anything
+ RECORDING, //!< Currently recording
+ PLAYING, //!< Currently playing back a recording
+ };
+ //! Get current internal state
+ State get_state() const;
+
+public:
+ /**
+ * \brief Record a single frame to the current recording
+ *
+ * This function is called by ReplaySystem after the game programmer has called \c
+ * record_start()
+ */
+ void frame_record();
+ /**
+ * \brief Play the next frame of the current recording
+ *
+ * \returns `true` if the recording is finished playing
+ * \returns `false` if there are more frames
+ *
+ * This function also automatically resets the internal state from PLAYING to IDLE at the end
+ * of a recording.
+ */
+ bool frame_step();
+
+private:
+ /**
+ * \brief Recording data
+ */
+ struct Recording {
+ //! Current frame being shown
+ size_t frame = 0;
+ //! All frames in recording
+ std::vector<ComponentManager::Snapshot> frames;
+ };
+ //! Internal state
+ State state = IDLE;
+ //! Current recording handle
+ recording_t id = -1;
+ //! Current recording data
+ OptionalRef<Recording> recording;
+ //! Recording storage
+ std::unordered_map<recording_t, std::unique_ptr<Recording>> memory;
+};
+
+} // namespace crepe
diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp
index a141a46..5713183 100644
--- a/src/crepe/manager/ResourceManager.cpp
+++ b/src/crepe/manager/ResourceManager.cpp
@@ -1,4 +1,4 @@
-#include "util/Log.h"
+#include "util/dbg.h"
#include "ResourceManager.h"
diff --git a/src/crepe/manager/SystemManager.cpp b/src/crepe/manager/SystemManager.cpp
new file mode 100644
index 0000000..f029aa5
--- /dev/null
+++ b/src/crepe/manager/SystemManager.cpp
@@ -0,0 +1,67 @@
+#include "../system/AISystem.h"
+#include "../system/AnimatorSystem.h"
+#include "../system/AudioSystem.h"
+#include "../system/CollisionSystem.h"
+#include "../system/EventSystem.h"
+#include "../system/InputSystem.h"
+#include "../system/ParticleSystem.h"
+#include "../system/PhysicsSystem.h"
+#include "../system/RenderSystem.h"
+#include "../system/ReplaySystem.h"
+#include "../system/ScriptSystem.h"
+
+#include "SystemManager.h"
+
+using namespace crepe;
+using namespace std;
+
+SystemManager::SystemManager(Mediator & mediator) : Manager(mediator) {
+ this->load_system<InputSystem>();
+ this->load_system<EventSystem>();
+ this->load_system<ScriptSystem>();
+ this->load_system<ParticleSystem>();
+ this->load_system<AISystem>();
+ this->load_system<PhysicsSystem>();
+ this->load_system<CollisionSystem>();
+ this->load_system<AudioSystem>();
+ this->load_system<AnimatorSystem>();
+ this->load_system<RenderSystem>();
+ this->load_system<ReplaySystem>();
+
+ this->mediator.system_manager = *this;
+}
+
+void SystemManager::fixed_update() {
+ for (System & system : this->system_order) {
+ if (!system.active) continue;
+ system.fixed_update();
+ }
+}
+
+void SystemManager::frame_update() {
+ for (System & system : this->system_order) {
+ if (!system.active) continue;
+ system.frame_update();
+ }
+}
+
+SystemManager::Snapshot SystemManager::save() {
+ Snapshot snapshot;
+ for (auto & [type, system] : this->systems) {
+ snapshot[type] = system->active;
+ }
+ return snapshot;
+}
+
+void SystemManager::restore(const Snapshot & snapshot) {
+ for (auto & [type, active] : snapshot) {
+ this->systems[type]->active = active;
+ }
+}
+
+void SystemManager::disable_all() {
+ for (auto & [type, system] : this->systems) {
+ system->active = false;
+ }
+}
+
diff --git a/src/crepe/manager/SystemManager.h b/src/crepe/manager/SystemManager.h
new file mode 100644
index 0000000..b4a001c
--- /dev/null
+++ b/src/crepe/manager/SystemManager.h
@@ -0,0 +1,93 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <typeindex>
+#include <unordered_map>
+
+#include "../system/System.h"
+
+#include "Manager.h"
+
+namespace crepe {
+
+/**
+ * \brief Collection of all systems
+ *
+ * This manager aggregates all systems and provides utility functions to retrieve references to
+ * and update systems.
+ */
+class SystemManager : public Manager {
+public:
+ SystemManager(Mediator &);
+
+ /**
+ * \brief Per-frame update.
+ *
+ * Updates the game state based on the elapsed time since the last frame.
+ */
+ void frame_update();
+
+ /**
+ * \brief Fixed update executed at a fixed rate.
+ *
+ * This function updates physics and game logic based on LoopTimer's fixed_delta_time.
+ */
+ void fixed_update();
+
+private:
+ /**
+ * \brief Collection of System instances
+ *
+ * This map holds System instances indexed by the system's class typeid. It is filled in the
+ * constructor of \c SystemManager using SystemManager::load_system.
+ */
+ std::unordered_map<std::type_index, std::unique_ptr<System>> systems;
+ /**
+ * \brief Collection of System instances
+ *
+ * This map holds System instances indexed by the system's class typeid. It is filled in the
+ * constructor of \c SystemManager using SystemManager::load_system.
+ */
+ std::vector<std::reference_wrapper<System>> system_order;
+ /**
+ * \brief Initialize a system
+ * \tparam T System type (must be derivative of \c System)
+ */
+ template <class T>
+ void load_system();
+
+public:
+ /**
+ * \brief Retrieve a reference to ECS system
+ * \tparam T System type
+ * \returns Reference to system instance
+ * \throws std::runtime_error if the System is not initialized
+ */
+ template <class T>
+ T & get_system();
+
+public:
+ /**
+ * \brief SystemManager snapshot
+ *
+ * The SystemManager snapshot only stores which systems are active
+ */
+ typedef std::unordered_map<std::type_index, bool> Snapshot;
+ /**
+ * \brief Save a snapshot of the systems' state
+ * \returns Copy of each system's active property
+ */
+ Snapshot save();
+ /**
+ * \brief Restore system active state from a snapshot
+ * \param snapshot Snapshot to restore from (as returned by \c save())
+ */
+ void restore(const Snapshot & snapshot);
+ //! Disable all systems
+ void disable_all();
+};
+
+} // namespace crepe
+
+#include "SystemManager.hpp"
diff --git a/src/crepe/api/LoopManager.hpp b/src/crepe/manager/SystemManager.hpp
index 266758a..8d06eb1 100644
--- a/src/crepe/api/LoopManager.hpp
+++ b/src/crepe/manager/SystemManager.hpp
@@ -4,26 +4,19 @@
#include <format>
#include <memory>
-#include "../system/System.h"
-
-#include "LoopManager.h"
+#include "SystemManager.h"
namespace crepe {
template <class T>
-void LoopManager::add_scene() {
- this->scene_manager.add_scene<T>();
-}
-
-template <class T>
-T & LoopManager::get_system() {
+T & SystemManager::get_system() {
using namespace std;
static_assert(is_base_of<System, T>::value,
"get_system must recieve a derivative class of System");
const type_info & type = typeid(T);
if (!this->systems.contains(type))
- throw runtime_error(format("LoopManager: {} is not initialized", type.name()));
+ throw runtime_error(format("SystemManager: {} is not initialized", type.name()));
System * system = this->systems.at(type).get();
T * concrete_system = dynamic_cast<T *>(system);
@@ -33,16 +26,17 @@ T & LoopManager::get_system() {
}
template <class T>
-void LoopManager::load_system() {
+void SystemManager::load_system() {
using namespace std;
static_assert(is_base_of<System, T>::value,
"load_system must recieve a derivative class of System");
const type_info & type = typeid(T);
if (this->systems.contains(type))
- throw runtime_error(format("LoopManager: {} is already initialized", type.name()));
+ throw runtime_error(format("SystemManager: {} is already initialized", type.name()));
System * system = new T(this->mediator);
this->systems[type] = unique_ptr<System>(system);
+ this->system_order.push_back(*this->systems[type]);
}
} // namespace crepe
diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp
index 680dbb8..0f35010 100644
--- a/src/crepe/system/AISystem.cpp
+++ b/src/crepe/system/AISystem.cpp
@@ -10,7 +10,7 @@
using namespace crepe;
using namespace std::chrono;
-void AISystem::update() {
+void AISystem::fixed_update() {
const Mediator & mediator = this->mediator;
ComponentManager & mgr = mediator.component_manager;
LoopTimerManager & loop_timer = mediator.loop_timer;
diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h
index d5f8a8e..04807cf 100644
--- a/src/crepe/system/AISystem.h
+++ b/src/crepe/system/AISystem.h
@@ -20,7 +20,7 @@ public:
using System::System;
//! Update the AI system
- void update() override;
+ void fixed_update() override;
private:
/**
diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp
index 107b25d..e5ab2fa 100644
--- a/src/crepe/system/AnimatorSystem.cpp
+++ b/src/crepe/system/AnimatorSystem.cpp
@@ -1,5 +1,3 @@
-
-
#include "../api/Animator.h"
#include "../manager/ComponentManager.h"
#include "../manager/LoopTimerManager.h"
@@ -10,7 +8,7 @@
using namespace crepe;
using namespace std::chrono;
-void AnimatorSystem::update() {
+void AnimatorSystem::frame_update() {
ComponentManager & mgr = this->mediator.component_manager;
LoopTimerManager & timer = this->mediator.loop_timer;
RefVector<Animator> animations = mgr.get_components_by_type<Animator>();
diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h
index 7d3f565..092e131 100644
--- a/src/crepe/system/AnimatorSystem.h
+++ b/src/crepe/system/AnimatorSystem.h
@@ -22,7 +22,7 @@ public:
* Animator components, moving the animations forward and managing their behavior (e.g.,
* looping).
*/
- void update() override;
+ void frame_update() override;
};
} // namespace crepe
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
index b1aa0f8..d4e8b9f 100644
--- a/src/crepe/system/AudioSystem.cpp
+++ b/src/crepe/system/AudioSystem.cpp
@@ -7,7 +7,7 @@
using namespace crepe;
using namespace std;
-void AudioSystem::update() {
+void AudioSystem::fixed_update() {
ComponentManager & component_manager = this->mediator.component_manager;
ResourceManager & resource_manager = this->mediator.resource_manager;
RefVector<AudioSource> components
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
index 2ddc443..56fc98c 100644
--- a/src/crepe/system/AudioSystem.h
+++ b/src/crepe/system/AudioSystem.h
@@ -11,7 +11,7 @@ namespace crepe {
class AudioSystem : public System {
public:
using System::System;
- void update() override;
+ void fixed_update() override;
private:
/**
diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt
index 0e2db76..52369d0 100644
--- a/src/crepe/system/CMakeLists.txt
+++ b/src/crepe/system/CMakeLists.txt
@@ -8,6 +8,8 @@ target_sources(crepe PUBLIC
AudioSystem.cpp
AnimatorSystem.cpp
InputSystem.cpp
+ EventSystem.cpp
+ ReplaySystem.cpp
AISystem.cpp
)
@@ -20,5 +22,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
AudioSystem.h
AnimatorSystem.h
InputSystem.h
+ EventSystem.h
+ ReplaySystem.h
AISystem.h
)
diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp
index af8adce..9d88d9f 100644
--- a/src/crepe/system/CollisionSystem.cpp
+++ b/src/crepe/system/CollisionSystem.cpp
@@ -23,7 +23,7 @@
using namespace crepe;
-void CollisionSystem::update() {
+void CollisionSystem::fixed_update() {
std::vector<CollisionInternal> all_colliders;
game_object_id_t id = 0;
ComponentManager & mgr = this->mediator.component_manager;
diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h
index 5b136c6..48a8f86 100644
--- a/src/crepe/system/CollisionSystem.h
+++ b/src/crepe/system/CollisionSystem.h
@@ -85,7 +85,7 @@ public:
public:
//! Updates the collision system by checking for collisions between colliders and handling them.
- void update() override;
+ void fixed_update() override;
private:
/**
diff --git a/src/crepe/system/EventSystem.cpp b/src/crepe/system/EventSystem.cpp
new file mode 100644
index 0000000..7e168ab
--- /dev/null
+++ b/src/crepe/system/EventSystem.cpp
@@ -0,0 +1,9 @@
+#include "EventSystem.h"
+#include "../manager/EventManager.h"
+
+using namespace crepe;
+
+void EventSystem::fixed_update() {
+ EventManager & ev = this->mediator.event_manager;
+ ev.dispatch_events();
+}
diff --git a/src/crepe/system/EventSystem.h b/src/crepe/system/EventSystem.h
new file mode 100644
index 0000000..0ae48d2
--- /dev/null
+++ b/src/crepe/system/EventSystem.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "System.h"
+
+namespace crepe {
+
+/**
+ * \brief EventManager dispatch helper system
+ */
+class EventSystem : public System {
+public:
+ using System::System;
+
+ /**
+ * \brief Dispatch queued events
+ * \see EventManager::dispatch_events
+ */
+ void fixed_update() override;
+};
+
+} // namespace crepe
diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp
index a710ae2..38d12c5 100644
--- a/src/crepe/system/InputSystem.cpp
+++ b/src/crepe/system/InputSystem.cpp
@@ -8,14 +8,16 @@
using namespace crepe;
-void InputSystem::update() {
+void InputSystem::fixed_update() {
ComponentManager & mgr = this->mediator.component_manager;
- EventManager & event_mgr = this->mediator.event_manager;
+
SDLContext & context = this->mediator.sdl_context;
+ context.update_keyboard_state();
std::vector<SDLContext::EventData> event_list = context.get_events();
RefVector<Button> buttons = mgr.get_components_by_type<Button>();
RefVector<Camera> cameras = mgr.get_components_by_type<Camera>();
OptionalRef<Camera> curr_cam_ref;
+
// Find the active camera
for (Camera & cam : cameras) {
if (!cam.active) continue;
@@ -23,150 +25,190 @@ void InputSystem::update() {
break;
}
if (!curr_cam_ref) return;
+
Camera & current_cam = curr_cam_ref;
RefVector<Transform> transform_vec
= mgr.get_components_by_id<Transform>(current_cam.game_object_id);
Transform & cam_transform = transform_vec.front().get();
- int camera_origin_x = cam_transform.position.x + current_cam.data.postion_offset.x
- - (current_cam.viewport_size.x / 2);
- int camera_origin_y = cam_transform.position.y + current_cam.data.postion_offset.y
- - (current_cam.viewport_size.y / 2);
+
+ vec2 camera_origin = cam_transform.position + current_cam.data.postion_offset
+ - (current_cam.viewport_size / 2);
for (const SDLContext::EventData & event : event_list) {
- int world_mouse_x = event.mouse_position.x + camera_origin_x;
- int world_mouse_y = event.mouse_position.y + camera_origin_y;
- // check if the mouse is within the viewport
- bool mouse_in_viewport
- = !(world_mouse_x < camera_origin_x
- || world_mouse_x > camera_origin_x + current_cam.viewport_size.x
- || world_mouse_y < camera_origin_y
- || world_mouse_y > camera_origin_y + current_cam.viewport_size.y);
-
- switch (event.event_type) {
- case SDLContext::EventType::KEYDOWN:
- event_mgr.queue_event<KeyPressEvent>(KeyPressEvent{
- .repeat = event.key_repeat,
- .key = event.key,
- });
- break;
- case SDLContext::EventType::KEYUP:
- event_mgr.queue_event<KeyReleaseEvent>(KeyReleaseEvent{
- .key = event.key,
- });
- break;
- case SDLContext::EventType::MOUSEDOWN:
- if (!mouse_in_viewport) {
- break;
- }
- event_mgr.queue_event<MousePressEvent>(MousePressEvent{
- .mouse_x = world_mouse_x,
- .mouse_y = world_mouse_y,
- .button = event.mouse_button,
- });
- this->last_mouse_down_position = {world_mouse_x, world_mouse_y};
- this->last_mouse_button = event.mouse_button;
- break;
- case SDLContext::EventType::MOUSEUP: {
- if (!mouse_in_viewport) {
- break;
- }
- event_mgr.queue_event<MouseReleaseEvent>(MouseReleaseEvent{
- .mouse_x = world_mouse_x,
- .mouse_y = world_mouse_y,
- .button = event.mouse_button,
- });
- //check if its a click by checking the last button down
- int delta_x = world_mouse_x - this->last_mouse_down_position.x;
- int delta_y = world_mouse_y - this->last_mouse_down_position.y;
-
- if (this->last_mouse_button == event.mouse_button
- && std::abs(delta_x) <= click_tolerance
- && std::abs(delta_y) <= click_tolerance) {
- event_mgr.queue_event<MouseClickEvent>(MouseClickEvent{
- .mouse_x = world_mouse_x,
- .mouse_y = world_mouse_y,
- .button = event.mouse_button,
- });
-
- this->handle_click(event.mouse_button, world_mouse_x, world_mouse_y);
- }
- } break;
- case SDLContext::EventType::MOUSEMOVE:
- if (!mouse_in_viewport) {
- break;
- }
- event_mgr.queue_event<MouseMoveEvent>(MouseMoveEvent{
- .mouse_x = world_mouse_x,
- .mouse_y = world_mouse_y,
- .delta_x = event.rel_mouse_move.x,
- .delta_y = event.rel_mouse_move.y,
- });
- this->handle_move(event, world_mouse_x, world_mouse_y);
- break;
- case SDLContext::EventType::MOUSEWHEEL:
- event_mgr.queue_event<MouseScrollEvent>(MouseScrollEvent{
- .mouse_x = world_mouse_x,
- .mouse_y = world_mouse_y,
- .scroll_direction = event.scroll_direction,
- .scroll_delta = event.scroll_delta,
+ // Only calculate mouse coordinates for relevant events
+ if (this->is_mouse_event(event.event_type)) {
+ this->handle_mouse_event(event, camera_origin, current_cam);
+
+ } else {
+ this->handle_non_mouse_event(event);
+ }
+ }
+}
+
+void InputSystem::handle_mouse_event(const SDLContext::EventData & event,
+ const vec2 & camera_origin, const Camera & current_cam) {
+ EventManager & event_mgr = this->mediator.event_manager;
+ vec2 adjusted_mouse;
+ adjusted_mouse.x = event.data.mouse_data.mouse_position.x + camera_origin.x;
+ adjusted_mouse.x = event.data.mouse_data.mouse_position.y + camera_origin.y;
+ // Check if the mouse is within the viewport
+ if ((adjusted_mouse.x < camera_origin.x
+ || adjusted_mouse.x > camera_origin.x + current_cam.viewport_size.x
+ || adjusted_mouse.y < camera_origin.y
+ || adjusted_mouse.y > camera_origin.y + current_cam.viewport_size.y))
+ return;
+
+ // Handle mouse-specific events
+ switch (event.event_type) {
+ case SDLContext::EventType::MOUSEDOWN:
+ event_mgr.queue_event<MousePressEvent>({
+ .mouse_pos = adjusted_mouse,
+ .button = event.data.mouse_data.mouse_button,
+ });
+ this->last_mouse_down_position = adjusted_mouse;
+ this->last_mouse_button = event.data.mouse_data.mouse_button;
+ break;
+
+ case SDLContext::EventType::MOUSEUP: {
+ event_mgr.queue_event<MouseReleaseEvent>({
+ .mouse_pos = adjusted_mouse,
+ .button = event.data.mouse_data.mouse_button,
+ });
+ vec2 delta_move = adjusted_mouse - this->last_mouse_down_position;
+ int click_tolerance = Config::get_instance().input.click_tolerance;
+ if (this->last_mouse_button == event.data.mouse_data.mouse_button
+ && std::abs(delta_move.x) <= click_tolerance
+ && std::abs(delta_move.y) <= click_tolerance) {
+ event_mgr.queue_event<MouseClickEvent>({
+ .mouse_pos = adjusted_mouse,
+ .button = event.data.mouse_data.mouse_button,
});
- break;
- case SDLContext::EventType::SHUTDOWN:
- event_mgr.queue_event<ShutDownEvent>(ShutDownEvent{});
- break;
- default:
- break;
+ this->handle_click(event.data.mouse_data.mouse_button, adjusted_mouse);
+ }
+ break;
}
+
+ case SDLContext::EventType::MOUSEMOVE:
+ event_mgr.queue_event<MouseMoveEvent>({
+ .mouse_pos = adjusted_mouse,
+ .mouse_delta = event.data.mouse_data.rel_mouse_move,
+ });
+ this->handle_move(event, adjusted_mouse);
+ break;
+
+ case SDLContext::EventType::MOUSEWHEEL:
+ event_mgr.queue_event<MouseScrollEvent>({
+ .mouse_pos = adjusted_mouse,
+ .scroll_direction = event.data.mouse_data.scroll_direction,
+ .scroll_delta = event.data.mouse_data.scroll_delta,
+ });
+ break;
+
+ default:
+ break;
+ }
+}
+
+void InputSystem::handle_non_mouse_event(const SDLContext::EventData & event) {
+ EventManager & event_mgr = this->mediator.event_manager;
+ switch (event.event_type) {
+ case SDLContext::EventType::KEYDOWN:
+
+ event_mgr.queue_event<KeyPressEvent>(
+ {.repeat = event.data.key_data.key_repeat, .key = event.data.key_data.key});
+ break;
+ case SDLContext::EventType::KEYUP:
+ event_mgr.queue_event<KeyReleaseEvent>({.key = event.data.key_data.key});
+ break;
+ case SDLContext::EventType::SHUTDOWN:
+ event_mgr.queue_event<ShutDownEvent>({});
+ break;
+ case SDLContext::EventType::WINDOW_EXPOSE:
+ event_mgr.queue_event<WindowExposeEvent>({});
+ break;
+ case SDLContext::EventType::WINDOW_RESIZE:
+ event_mgr.queue_event<WindowResizeEvent>(
+ WindowResizeEvent{.dimensions = event.data.window_data.resize_dimension});
+ break;
+ case SDLContext::EventType::WINDOW_MOVE:
+ event_mgr.queue_event<WindowMoveEvent>(
+ {.delta_move = event.data.window_data.move_delta});
+ break;
+ case SDLContext::EventType::WINDOW_MINIMIZE:
+ event_mgr.queue_event<WindowMinimizeEvent>({});
+ break;
+ case SDLContext::EventType::WINDOW_MAXIMIZE:
+ event_mgr.queue_event<WindowMaximizeEvent>({});
+ break;
+ case SDLContext::EventType::WINDOW_FOCUS_GAIN:
+ event_mgr.queue_event<WindowFocusGainEvent>({});
+ break;
+ case SDLContext::EventType::WINDOW_FOCUS_LOST:
+ event_mgr.queue_event<WindowFocusLostEvent>({});
+ break;
+ default:
+ break;
}
}
+
+bool InputSystem::is_mouse_event(SDLContext::EventType event_type) {
+ return (event_type == SDLContext::EventType::MOUSEDOWN
+ || event_type == SDLContext::EventType::MOUSEUP
+ || event_type == SDLContext::EventType::MOUSEMOVE
+ || event_type == SDLContext::EventType::MOUSEWHEEL);
+}
+
void InputSystem::handle_move(const SDLContext::EventData & event_data,
- const int world_mouse_x, const int world_mouse_y) {
+ const vec2 & mouse_pos) {
ComponentManager & mgr = this->mediator.component_manager;
RefVector<Button> buttons = mgr.get_components_by_type<Button>();
for (Button & button : buttons) {
+ if (!button.active) continue;
RefVector<Transform> transform_vec
= mgr.get_components_by_id<Transform>(button.game_object_id);
Transform & transform(transform_vec.front().get());
bool was_hovering = button.hover;
- if (button.active
- && this->is_mouse_inside_button(world_mouse_x, world_mouse_y, button, transform)) {
+ if (this->is_mouse_inside_button(mouse_pos, button, transform)) {
button.hover = true;
- if (!was_hovering && button.on_mouse_enter) {
+ if (!button.on_mouse_enter) continue;
+ if (!was_hovering) {
button.on_mouse_enter();
}
} else {
button.hover = false;
// Trigger the on_exit callback if the hover state just changed to false
- if (was_hovering && button.on_mouse_exit) {
+ if (!button.on_mouse_exit) continue;
+ if (was_hovering) {
button.on_mouse_exit();
}
}
}
}
-void InputSystem::handle_click(const MouseButton & mouse_button, const int world_mouse_x,
- const int world_mouse_y) {
+void InputSystem::handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos) {
ComponentManager & mgr = this->mediator.component_manager;
RefVector<Button> buttons = mgr.get_components_by_type<Button>();
for (Button & button : buttons) {
+ if (!button.active) continue;
+ if (!button.on_click) continue;
RefVector<Transform> transform_vec
= mgr.get_components_by_id<Transform>(button.game_object_id);
Transform & transform = transform_vec.front().get();
- if (button.active
- && this->is_mouse_inside_button(world_mouse_x, world_mouse_y, button, transform)) {
- this->handle_button_press(button);
+ if (this->is_mouse_inside_button(mouse_pos, button, transform)) {
+
+ button.on_click();
}
}
}
-bool InputSystem::is_mouse_inside_button(const int mouse_x, const int mouse_y,
- const Button & button, const Transform & transform) {
+bool InputSystem::is_mouse_inside_button(const vec2 & mouse_pos, const Button & button,
+ const Transform & transform) {
int actual_x = transform.position.x + button.offset.x;
int actual_y = transform.position.y + button.offset.y;
@@ -174,17 +216,6 @@ bool InputSystem::is_mouse_inside_button(const int mouse_x, const int mouse_y,
int half_height = button.dimensions.y / 2;
// Check if the mouse is within the button's boundaries
- return mouse_x >= actual_x - half_width && mouse_x <= actual_x + half_width
- && mouse_y >= actual_y - half_height && mouse_y <= actual_y + half_height;
-}
-
-void InputSystem::handle_button_press(Button & button) {
- if (button.is_toggle) {
- if (!button.is_pressed && button.on_click) {
- button.on_click();
- }
- button.is_pressed = !button.is_pressed;
- } else if (button.on_click) {
- button.on_click();
- }
+ return mouse_pos.x >= actual_x - half_width && mouse_pos.x <= actual_x + half_width
+ && mouse_pos.y >= actual_y - half_height && mouse_pos.y <= actual_y + half_height;
}
diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h
index 87e86f8..b03805a 100644
--- a/src/crepe/system/InputSystem.h
+++ b/src/crepe/system/InputSystem.h
@@ -1,5 +1,6 @@
#pragma once
+#include "../api/Config.h"
#include "../facade/SDLContext.h"
#include "../types.h"
#include "../util/OptionalRef.h"
@@ -27,19 +28,40 @@ public:
* \brief Updates the system, processing all input events.
* This method processes all events and triggers corresponding actions.
*/
- void update() override;
+ void fixed_update() override;
private:
//! Stores the last position of the mouse when the button was pressed.
- ivec2 last_mouse_down_position;
+ vec2 last_mouse_down_position;
// TODO: specify world/hud space and make regular `vec2`
//! Stores the last mouse button pressed.
MouseButton last_mouse_button = MouseButton::NONE;
-
- //! The maximum allowable distance between mouse down and mouse up to register as a click.
- const int click_tolerance = 5;
-
+ /**
+ * \brief Determines whether the given event type is a mouse event.
+ * \param event_type The event type to check.
+ * \return True if the event type corresponds to a mouse event, false otherwise.
+ */
+ bool is_mouse_event(SDLContext::EventType event_type);
+ /**
+ * \brief Handles mouse-related events.
+ * \param event The event data for the mouse event.
+ * \param camera_origin The origin position of the camera in world space.
+ * \param current_cam The currently active camera.
+ *
+ * This method processes mouse events, adjusts the mouse position to world coordinates,
+ * and triggers the appropriate mouse-specific event handling logic.
+ */
+ void handle_mouse_event(const SDLContext::EventData & event, const vec2 & camera_origin,
+ const Camera & current_cam);
+ /**
+ * \brief Handles non-mouse-related events.
+ * \param event The event data for the non-mouse event.
+ *
+ * This method processes events that do not involve the mouse, such as keyboard events,
+ * window events, and shutdown events, and triggers the corresponding event actions.
+ */
+ void handle_non_mouse_event(const SDLContext::EventData & event);
/**
* \brief Handles the mouse click event.
* \param mouse_button The mouse button involved in the click.
@@ -48,8 +70,7 @@ private:
*
* This method processes the mouse click event and triggers the corresponding button action.
*/
- void handle_click(const MouseButton & mouse_button, const int world_mouse_x,
- const int world_mouse_y);
+ void handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos);
/**
* \brief Handles the mouse movement event.
@@ -59,8 +80,7 @@ private:
*
* This method processes the mouse movement event and updates the button hover state.
*/
- void handle_move(const SDLContext::EventData & event_data, const int world_mouse_x,
- const int world_mouse_y);
+ void handle_move(const SDLContext::EventData & event_data, const vec2 & mouse_pos);
/**
* \brief Checks if the mouse position is inside the bounds of the button.
@@ -70,8 +90,8 @@ private:
* \param transform The transform component of the button.
* \return True if the mouse is inside the button, false otherwise.
*/
- bool is_mouse_inside_button(const int world_mouse_x, const int world_mouse_y,
- const Button & button, const Transform & transform);
+ bool is_mouse_inside_button(const vec2 & mouse_pos, const Button & button,
+ const Transform & transform);
/**
* \brief Handles the button press event, calling the on_click callback if necessary.
diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp
index b14c52f..4684ada 100644
--- a/src/crepe/system/ParticleSystem.cpp
+++ b/src/crepe/system/ParticleSystem.cpp
@@ -1,3 +1,4 @@
+#include <chrono>
#include <cmath>
#include <cstdlib>
#include <ctime>
@@ -5,14 +6,19 @@
#include "../api/ParticleEmitter.h"
#include "../api/Transform.h"
#include "../manager/ComponentManager.h"
+#include "../manager/LoopTimerManager.h"
#include "ParticleSystem.h"
using namespace crepe;
-void ParticleSystem::update() {
+void ParticleSystem::frame_update() {
// Get all emitters
- ComponentManager & mgr = this->mediator.component_manager;
+ const Mediator & mediator = this->mediator;
+ LoopTimerManager & loop_timer = mediator.loop_timer;
+ ComponentManager & mgr = mediator.component_manager;
+ float dt = loop_timer.get_scaled_fixed_delta_time().count();
+
RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>();
for (ParticleEmitter & emitter : emitters) {
@@ -21,38 +27,39 @@ void ParticleSystem::update() {
= mgr.get_components_by_id<Transform>(emitter.game_object_id).front().get();
// Emit particles based on emission_rate
- int updates = calculate_update(this->update_count, emitter.data.emission_rate);
- for (size_t i = 0; i < updates; i++) {
- emit_particle(emitter, transform);
+ emitter.spawn_accumulator = emitter.data.emission_rate * dt;
+ while (emitter.spawn_accumulator >= 1.0) {
+ this->emit_particle(emitter, transform);
+ emitter.spawn_accumulator -= 1.0;
}
// Update all particles
- for (Particle & particle : emitter.data.particles) {
+ for (Particle & particle : emitter.particles) {
if (particle.active) {
- particle.update();
+ particle.update(dt);
}
}
// Check if within boundary
- check_bounds(emitter, transform);
+ this->check_bounds(emitter, transform);
}
-
- this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT;
}
void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) {
constexpr float DEG_TO_RAD = M_PI / 180.0;
vec2 initial_position = emitter.data.position + transform.position;
- float random_angle = generate_random_angle(emitter.data.min_angle, emitter.data.max_angle);
+ float random_angle
+ = this->generate_random_angle(emitter.data.min_angle, emitter.data.max_angle);
- float random_speed = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed);
+ float random_speed
+ = this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed);
float angle_radians = random_angle * DEG_TO_RAD;
vec2 velocity
= {random_speed * std::cos(angle_radians), random_speed * std::sin(angle_radians)};
- for (Particle & particle : emitter.data.particles) {
+ for (Particle & particle : emitter.particles) {
if (!particle.active) {
particle.reset(emitter.data.end_lifespan, initial_position, velocity,
random_angle);
@@ -61,66 +68,54 @@ void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform &
}
}
-int ParticleSystem::calculate_update(int count, double emission) const {
- double integer_part = std::floor(emission);
- double fractional_part = emission - integer_part;
-
- if (fractional_part > 0) {
- int denominator = static_cast<int>(1.0 / fractional_part);
- return (count % denominator == 0) ? 1 : 0;
- }
-
- return static_cast<int>(emission);
-}
-
void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & transform) {
vec2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position;
- double half_width = emitter.data.boundary.width / 2.0;
- double half_height = emitter.data.boundary.height / 2.0;
+ float half_width = emitter.data.boundary.width / 2.0;
+ float half_height = emitter.data.boundary.height / 2.0;
- const double LEFT = offset.x - half_width;
- const double RIGHT = offset.x + half_width;
- const double TOP = offset.y - half_height;
- const double BOTTOM = offset.y + half_height;
+ float left = offset.x - half_width;
+ float right = offset.x + half_width;
+ float top = offset.y - half_height;
+ float bottom = offset.y + half_height;
- for (Particle & particle : emitter.data.particles) {
+ for (Particle & particle : emitter.particles) {
const vec2 & position = particle.position;
- bool within_bounds = (position.x >= LEFT && position.x <= RIGHT && position.y >= TOP
- && position.y <= BOTTOM);
-
+ bool within_bounds = (position.x >= left && position.x <= right && position.y >= top
+ && position.y <= bottom);
+ //if not within bounds do a reset or stop velocity
if (!within_bounds) {
if (emitter.data.boundary.reset_on_exit) {
particle.active = false;
} else {
particle.velocity = {0, 0};
- if (position.x < LEFT) particle.position.x = LEFT;
- else if (position.x > RIGHT) particle.position.x = RIGHT;
- if (position.y < TOP) particle.position.y = TOP;
- else if (position.y > BOTTOM) particle.position.y = BOTTOM;
+ if (position.x < left) particle.position.x = left;
+ else if (position.x > right) particle.position.x = right;
+ if (position.y < top) particle.position.y = top;
+ else if (position.y > bottom) particle.position.y = bottom;
}
}
}
}
-double ParticleSystem::generate_random_angle(double min_angle, double max_angle) const {
+float ParticleSystem::generate_random_angle(float min_angle, float max_angle) const {
if (min_angle == max_angle) {
return min_angle;
} else if (min_angle < max_angle) {
return min_angle
- + static_cast<double>(std::rand() % static_cast<int>(max_angle - min_angle));
+ + static_cast<float>(std::rand() % static_cast<int>(max_angle - min_angle));
} else {
- double angle_offset = (360 - min_angle) + max_angle;
- double random_angle
- = min_angle + static_cast<double>(std::rand() % static_cast<int>(angle_offset));
+ float angle_offset = (360 - min_angle) + max_angle;
+ float random_angle
+ = min_angle + static_cast<float>(std::rand() % static_cast<int>(angle_offset));
return (random_angle >= 360) ? random_angle - 360 : random_angle;
}
}
-double ParticleSystem::generate_random_speed(double min_speed, double max_speed) const {
+float ParticleSystem::generate_random_speed(float min_speed, float max_speed) const {
if (min_speed == max_speed) {
return min_speed;
} else {
return min_speed
- + static_cast<double>(std::rand() % static_cast<int>(max_speed - min_speed));
+ + static_cast<float>(std::rand() % static_cast<int>(max_speed - min_speed));
}
}
diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h
index 068f01c..9eacc70 100644
--- a/src/crepe/system/ParticleSystem.h
+++ b/src/crepe/system/ParticleSystem.h
@@ -20,7 +20,7 @@ public:
* \brief Updates all particle emitters by emitting particles, updating particle states, and
* checking bounds.
*/
- void update() override;
+ void frame_update() override;
private:
/**
@@ -32,16 +32,6 @@ private:
void emit_particle(ParticleEmitter & emitter, const Transform & transform);
/**
- * \brief Calculates the number of times particles should be emitted based on emission rate
- * and update count.
- *
- * \param count Current update count.
- * \param emission Emission rate.
- * \return The number of particles to emit.
- */
- int calculate_update(int count, double emission) const;
-
- /**
* \brief Checks whether particles are within the emitter’s boundary, resets or stops
* particles if they exit.
*
@@ -57,7 +47,7 @@ private:
* \param max_angle Maximum emission angle in degrees.
* \return Random angle in degrees.
*/
- double generate_random_angle(double min_angle, double max_angle) const;
+ float generate_random_angle(float min_angle, float max_angle) const;
/**
* \brief Generates a random speed for particle emission within the specified range.
@@ -66,15 +56,7 @@ private:
* \param max_speed Maximum emission speed.
* \return Random speed.
*/
- double generate_random_speed(double min_speed, double max_speed) const;
-
-private:
- //! Counter to count updates to determine how many times emit_particle is
- // called.
- unsigned int update_count = 0;
- //! Determines the lowest amount of emission rate (1000 = 0.001 = 1 particle per 1000
- // updates).
- static constexpr unsigned int MAX_UPDATE_COUNT = 100;
+ float generate_random_speed(float min_speed, float max_speed) const;
};
} // namespace crepe
diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp
index 3b3b8ab..62f8132 100644
--- a/src/crepe/system/PhysicsSystem.cpp
+++ b/src/crepe/system/PhysicsSystem.cpp
@@ -12,8 +12,7 @@
using namespace crepe;
-void PhysicsSystem::update() {
-
+void PhysicsSystem::fixed_update() {
const Mediator & mediator = this->mediator;
ComponentManager & mgr = mediator.component_manager;
LoopTimerManager & loop_timer = mediator.loop_timer;
diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h
index 26152a5..5ed624f 100644
--- a/src/crepe/system/PhysicsSystem.h
+++ b/src/crepe/system/PhysicsSystem.h
@@ -18,7 +18,7 @@ public:
*
* It calculates new velocties and changes the postion in the transform.
*/
- void update() override;
+ void fixed_update() override;
};
} // namespace crepe
diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp
index afd9548..c403f6c 100644
--- a/src/crepe/system/RenderSystem.cpp
+++ b/src/crepe/system/RenderSystem.cpp
@@ -64,7 +64,7 @@ RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const {
return sorted_objs;
}
-void RenderSystem::update() {
+void RenderSystem::frame_update() {
this->clear_screen();
this->render();
this->present_screen();
@@ -83,11 +83,11 @@ bool RenderSystem::render_particle(const Sprite & sprite, const double & scale)
bool rendering_particles = false;
for (const ParticleEmitter & em : emitters) {
- if (&em.data.sprite != &sprite) continue;
+ if (&em.sprite != &sprite) continue;
rendering_particles = true;
if (!em.active) continue;
- for (const Particle & p : em.data.particles) {
+ for (const Particle & p : em.particles) {
if (!p.active) continue;
ctx.draw(SDLContext::RenderContext{
diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h
index fc7b46e..1a61f99 100644
--- a/src/crepe/system/RenderSystem.h
+++ b/src/crepe/system/RenderSystem.h
@@ -24,7 +24,7 @@ public:
* \brief Updates the RenderSystem for the current frame.
* This method is called to perform all rendering operations for the current game frame.
*/
- void update() override;
+ void frame_update() override;
private:
//! Clears the screen in preparation for rendering.
diff --git a/src/crepe/system/ReplaySystem.cpp b/src/crepe/system/ReplaySystem.cpp
new file mode 100644
index 0000000..efc3be4
--- /dev/null
+++ b/src/crepe/system/ReplaySystem.cpp
@@ -0,0 +1,54 @@
+#include "../manager/ReplayManager.h"
+#include "../manager/SystemManager.h"
+
+#include "EventSystem.h"
+#include "RenderSystem.h"
+#include "ReplaySystem.h"
+
+using namespace crepe;
+using namespace std;
+
+void ReplaySystem::fixed_update() {
+ ReplayManager & replay = this->mediator.replay_manager;
+ ReplayManager::State state = replay.get_state();
+ ReplayManager::State last_state = this->last_state;
+ this->last_state = state;
+
+ switch (state) {
+ case ReplayManager::IDLE:
+ break;
+ case ReplayManager::RECORDING: {
+ replay.frame_record();
+ break;
+ }
+ case ReplayManager::PLAYING: {
+ if (last_state != ReplayManager::PLAYING) this->playback_begin();
+ bool last = replay.frame_step();
+ if (last) this->playback_end();
+ break;
+ }
+ }
+}
+
+void ReplaySystem::playback_begin() {
+ SystemManager & systems = this->mediator.system_manager;
+ ComponentManager & components = this->mediator.component_manager;
+
+ this->playback = {
+ .components = components.save(),
+ .systems = systems.save(),
+ };
+
+ systems.disable_all();
+ systems.get_system<RenderSystem>().active = true;
+ systems.get_system<ReplaySystem>().active = true;
+ systems.get_system<EventSystem>().active = true;
+}
+
+void ReplaySystem::playback_end() {
+ SystemManager & systems = this->mediator.system_manager;
+ ComponentManager & components = this->mediator.component_manager;
+
+ components.restore(this->playback.components);
+ systems.restore(this->playback.systems);
+}
diff --git a/src/crepe/system/ReplaySystem.h b/src/crepe/system/ReplaySystem.h
new file mode 100644
index 0000000..bbc8d76
--- /dev/null
+++ b/src/crepe/system/ReplaySystem.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "../manager/ReplayManager.h"
+#include "../manager/SystemManager.h"
+
+#include "System.h"
+
+namespace crepe {
+
+/**
+ * \brief ReplayManager helper system
+ *
+ * This system records and replays recordings using ReplayManager.
+ */
+class ReplaySystem : public System {
+public:
+ using System::System;
+
+ void fixed_update() override;
+
+private:
+ //! Last ReplayManager state
+ ReplayManager::State last_state = ReplayManager::IDLE;
+
+ /**
+ * \brief Playback snapshot
+ *
+ * When starting playback, the component state is saved and most systems are disabled. This
+ * struct stores the engine state before ReplayManager::play is called.
+ */
+ struct Snapshot {
+ ComponentManager::Snapshot components;
+ SystemManager::Snapshot systems;
+ };
+ //! Before playback snapshot
+ Snapshot playback;
+
+ //! Snapshot state and disable systems during playback
+ void playback_begin();
+ //! Restore state from before \c playback_begin()
+ void playback_end();
+};
+
+} // namespace crepe
diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp
index d6b2ca1..58055d6 100644
--- a/src/crepe/system/ScriptSystem.cpp
+++ b/src/crepe/system/ScriptSystem.cpp
@@ -1,16 +1,18 @@
#include "../api/BehaviorScript.h"
#include "../api/Script.h"
#include "../manager/ComponentManager.h"
+#include "../util/dbg.h"
#include "ScriptSystem.h"
using namespace std;
using namespace crepe;
-void ScriptSystem::update() {
+void ScriptSystem::fixed_update() {
dbg_trace();
ComponentManager & mgr = this->mediator.component_manager;
+ LoopTimerManager & timer = this->mediator.loop_timer;
RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>();
for (BehaviorScript & behavior_script : behavior_scripts) {
@@ -23,6 +25,8 @@ void ScriptSystem::update() {
script->init();
script->initialized = true;
}
- script->update();
+
+ duration_t delta_time = timer.get_delta_time();
+ script->update(delta_time);
}
}
diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h
index 3db1b1e..612c2ae 100644
--- a/src/crepe/system/ScriptSystem.h
+++ b/src/crepe/system/ScriptSystem.h
@@ -22,7 +22,7 @@ public:
* method. It also calls Script::init() if this has not been done before on
* the \c BehaviorScript instance.
*/
- void update() override;
+ void fixed_update() override;
};
} // namespace crepe
diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h
index 063dfbf..e2ce7eb 100644
--- a/src/crepe/system/System.h
+++ b/src/crepe/system/System.h
@@ -14,10 +14,12 @@ class ComponentManager;
*/
class System {
public:
- /**
- * \brief Process all components this system is responsible for.
- */
- virtual void update() = 0;
+ //! Code that runs in the fixed loop
+ virtual void fixed_update() {};
+ //! Code that runs in the frame loop
+ virtual void frame_update() {};
+ //! Indicates that the update functions of this system should be run
+ bool active = true;
public:
System(const Mediator & m);
diff --git a/src/crepe/util/Log.cpp b/src/crepe/util/Log.cpp
index 84d80a8..ce25a1d 100644
--- a/src/crepe/util/Log.cpp
+++ b/src/crepe/util/Log.cpp
@@ -4,6 +4,7 @@
#include "../api/Config.h"
#include "Log.h"
+#include "LogColor.h"
using namespace crepe;
using namespace std;
diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h
index fc0bb3a..b43fe30 100644
--- a/src/crepe/util/Log.h
+++ b/src/crepe/util/Log.h
@@ -2,27 +2,6 @@
#include <format>
-// allow user to disable debug macros
-#ifndef CREPE_DISABLE_MACROS
-
-#include "LogColor.h"
-
-// utility macros
-#define _crepe_logf_here(level, fmt, ...) \
- crepe::Log::logf(level, "{}" fmt, \
- crepe::LogColor().fg_white(false).str(std::format( \
- "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \
- __VA_ARGS__)
-
-// very illegal global function-style macros
-// NOLINTBEGIN
-#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__)
-#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str)
-#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "")
-// NOLINTEND
-
-#endif
-
namespace crepe {
/**
diff --git a/src/crepe/util/dbg.h b/src/crepe/util/dbg.h
new file mode 100644
index 0000000..c7283ee
--- /dev/null
+++ b/src/crepe/util/dbg.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "Log.h"
+#include "LogColor.h"
+
+// utility macros
+#define _crepe_logf_here(level, fmt, ...) \
+ crepe::Log::logf(level, "{}" fmt, \
+ crepe::LogColor().fg_white(false).str(std::format( \
+ "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \
+ __VA_ARGS__)
+
+// very illegal global function-style macros
+// NOLINTBEGIN
+#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__)
+#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str)
+#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "")
+// NOLINTEND