Implement scene system; move render loop from Game to Engine

This commit is contained in:
Lexi / Zoe 2026-07-18 21:53:36 +02:00
parent b4635cdb99
commit 48b70b0bc8
Signed by: binaryDiv
GPG key ID: F8D4956E224DA232
6 changed files with 196 additions and 89 deletions

View file

@ -47,12 +47,10 @@ export namespace rutile
return sdl::AppResult::Continue; return sdl::AppResult::Continue;
} }
sdl::AppResult handle_event(const sdl::Event& event) sdl::AppResult handle_event(const sdl::Event& event) const
{ {
try { try {
if (!engine_->handle_event(event)) { engine_->handle_event(event);
game_->handle_event(event);
}
} }
catch (const std::runtime_error& e) { catch (const std::runtime_error& e) {
std::cerr << "Unhandled exception during event handling: " << e.what() << '\n'; std::cerr << "Unhandled exception during event handling: " << e.what() << '\n';
@ -62,12 +60,11 @@ export namespace rutile
return engine_->keep_running() ? sdl::AppResult::Continue : sdl::AppResult::Success; return engine_->keep_running() ? sdl::AppResult::Continue : sdl::AppResult::Success;
} }
sdl::AppResult iterate() sdl::AppResult iterate() const
{ {
try { try {
engine_->update(); engine_->update();
game_->update(); engine_->render();
game_->render();
} }
catch (const std::runtime_error& e) { catch (const std::runtime_error& e) {
std::cerr << "Unhandled exception during updating: " << e.what() << '\n'; std::cerr << "Unhandled exception during updating: " << e.what() << '\n';

View file

@ -8,6 +8,7 @@ import std;
import rutile.config; import rutile.config;
import rutile.core.render_server; import rutile.core.render_server;
import rutile.core.resource_server; import rutile.core.resource_server;
import rutile.core.scenes.scene_manager;
import rutile.core.ui.ui_server; import rutile.core.ui.ui_server;
import wrappers.sdl; import wrappers.sdl;
@ -16,7 +17,7 @@ export namespace rutile
class Engine class Engine
{ {
// Whether this class is currently instantiated (to prevent multiple instances) // Whether this class is currently instantiated (to prevent multiple instances)
static bool instantiated_; static Engine* instance_;
// If this is set to false, the application will exit // If this is set to false, the application will exit
bool keep_running_ = true; bool keep_running_ = true;
@ -24,6 +25,7 @@ export namespace rutile
sdl::Window window_; sdl::Window window_;
RenderServer render_server_; RenderServer render_server_;
ResourceServer resource_server_; ResourceServer resource_server_;
SceneManager scene_manager_;
UIServer ui_server_; UIServer ui_server_;
// Private constructor // Private constructor
@ -33,7 +35,7 @@ export namespace rutile
resource_server_{render_server_.get_renderer()}, resource_server_{render_server_.get_renderer()},
ui_server_{render_server_.get_renderer(), window_} ui_server_{render_server_.get_renderer(), window_}
{ {
instantiated_ = true; instance_ = this;
} }
public: public:
@ -47,13 +49,13 @@ export namespace rutile
~Engine() ~Engine()
{ {
instantiated_ = false; instance_ = nullptr;
} }
static std::unique_ptr<Engine> create() static std::unique_ptr<Engine> create()
{ {
// Prevent the class from being instantiated multiple times // Prevent the class from being instantiated multiple times
assert(!instantiated_); assert(!instance_);
auto [sdl_window, sdl_renderer] = sdl::create_window_and_renderer( auto [sdl_window, sdl_renderer] = sdl::create_window_and_renderer(
config::get_window_title(), config::get_window_title(),
@ -72,6 +74,13 @@ export namespace rutile
// NOLINTEND // NOLINTEND
} }
// Get singleton instance of engine
static Engine& get()
{
assert(instance_);
return *instance_;
}
bool keep_running() const bool keep_running() const
{ {
return keep_running_; return keep_running_;
@ -92,6 +101,11 @@ export namespace rutile
return resource_server_; return resource_server_;
} }
SceneManager& get_scene_manager()
{
return scene_manager_;
}
UIServer& get_ui_server() UIServer& get_ui_server()
{ {
return ui_server_; return ui_server_;
@ -110,11 +124,38 @@ export namespace rutile
return true; return true;
} }
const auto scene = scene_manager_.get_current_scene();
if (scene && scene->handle_event(event)) {
return true;
}
return false; return false;
} }
void update() void update() const
{ {
// Update current scene
if (const auto scene = scene_manager_.get_current_scene()) {
scene->update();
}
// Update UI
ui_server_.update();
}
void render() const
{
render_server_.start_frame();
// Render current scene
if (const auto scene = scene_manager_.get_current_scene()) {
scene->render(render_server_);
}
// Render UI
ui_server_.render();
render_server_.finish_frame();
} }
void shutdown() void shutdown()
@ -122,5 +163,5 @@ export namespace rutile
} }
}; };
bool Engine::instantiated_ = false; Engine* Engine::instance_ = nullptr;
} }

View file

@ -0,0 +1,27 @@
module;
export module rutile.core.scenes.base_scene;
import std;
import rutile.core.render_server;
import rutile.core.resource_server;
import wrappers.sdl;
export namespace rutile
{
class BaseScene
{
public:
// Always define virtual destructor in abstract classes
virtual ~BaseScene() = default;
// Handle an SDL event. Return true if the event has been handled.
virtual bool handle_event(const sdl::Event& event) = 0;
// Update the scene
virtual void update() = 0;
// Render the scene
virtual void render(const RenderServer& render_server) = 0;
};
}

View file

@ -0,0 +1,26 @@
module;
export module rutile.core.scenes.scene_manager;
import rutile.core.scenes.base_scene;
export namespace rutile
{
class SceneManager
{
// Pointer to current scene
// TODO: Ownership? Should this be a regular pointer or maybe a shared_ptr shared with Game?
BaseScene* current_scene_ = nullptr;
public:
BaseScene* get_current_scene() const
{
return current_scene_;
}
void set_current_scene(BaseScene& scene)
{
current_scene_ = &scene;
}
};
}

View file

@ -5,13 +5,12 @@ module;
export module rutile.game.game; export module rutile.game.game;
import std; import std;
import rutile.core.drawing.sprite;
import rutile.core.drawing.tile_map;
import rutile.core.engine; import rutile.core.engine;
import rutile.core.render_server; import rutile.core.render_server;
import rutile.core.scenes.base_scene;
import rutile.game.scenes.game_scene;
import wrappers.rmlui.core; import wrappers.rmlui.core;
import wrappers.sdl; import wrappers.sdl;
import wrappers.sdl_image;
export namespace rutile export namespace rutile
{ {
@ -23,26 +22,12 @@ export namespace rutile
// Reference to the engine // Reference to the engine
Engine& engine_; Engine& engine_;
// Sprites and stuff for testing // Main game scene
Sprite player_sprite_; GameScene game_scene_;
std::vector<Sprite> sprites_;
TileMap tile_map_;
// Private constructor // Private constructor
explicit Game(Engine& engine) explicit Game(Engine& engine)
: engine_(engine), : engine_{engine}
player_sprite_{
Sprite::create_from_texture(
engine_.get_resource_server().load_texture("assets/sprites/neocat_64.png"),
std::nullopt,
sdl::FPoint{0, 0},
sdl::FPoint{-32, -32}
)
},
tile_map_{
engine_.get_resource_server().load_tile_set("assets/tilesets/terrain.json"),
32, 16, 12, {50, 50}
}
{ {
// Initialize UI // Initialize UI
const auto& ui_server = engine_.get_ui_server(); const auto& ui_server = engine_.get_ui_server();
@ -50,13 +35,8 @@ export namespace rutile
Rml::ElementDocument* document = ui_server.load_document("assets/ui/main_ui.rml"); Rml::ElementDocument* document = ui_server.load_document("assets/ui/main_ui.rml");
document->Show(); document->Show();
// Initialize tile map with example data // Set current scene
for (auto x = 0; x < 10; x++) { engine_.get_scene_manager().set_current_scene(game_scene_);
for (auto y = 0; y < 10; y++) {
const auto tile_index = 2 + (x + y) % 2;
tile_map_.set_tile(x, y, tile_index);
}
}
instantiated_ = true; instantiated_ = true;
} }
@ -84,55 +64,6 @@ export namespace rutile
}; };
} }
// Handles an SDL event. Returns true if the event has been handled.
bool handle_event(const sdl::Event& event)
{
if (event.type == sdl::EventTypes::MouseMotion) {
player_sprite_.set_position({event.motion.x, event.motion.y});
return true;
}
if (event.type == sdl::EventTypes::MouseButtonUp) {
sprites_.push_back(
Sprite::create_from_texture(
engine_.get_resource_server().load_texture("assets/sprites/neofox_64.png"),
std::nullopt,
sdl::FPoint{event.motion.x, event.motion.y},
sdl::FPoint{-32, -32}
)
);
return true;
}
return false;
}
void update()
{
// TODO: Move this to Engine
engine_.get_ui_server().update();
}
void render() const
{
const auto& render_server = engine_.get_render_server();
render_server.start_frame();
// Render tilemap
tile_map_.draw(render_server);
// Render sprites
for (const Sprite& sprite : sprites_) {
sprite.draw(render_server);
}
player_sprite_.draw(render_server);
// TODO: Move this to Engine
engine_.get_ui_server().render();
render_server.finish_frame();
}
void shutdown() void shutdown()
{ {
} }

View file

@ -0,0 +1,85 @@
module;
export module rutile.game.scenes.game_scene;
import std;
import rutile.core.drawing.sprite;
import rutile.core.drawing.tile_map;
import rutile.core.engine;
import rutile.core.render_server;
import rutile.core.scenes.base_scene;
import wrappers.sdl;
export namespace rutile
{
class GameScene : public BaseScene
{
// Sprites and stuff for testing
Sprite player_sprite_;
std::vector<Sprite> sprites_;
TileMap tile_map_;
public:
GameScene()
: player_sprite_{
Sprite::create_from_texture(
Engine::get().get_resource_server().load_texture("assets/sprites/neocat_64.png"),
std::nullopt,
sdl::FPoint{0, 0},
sdl::FPoint{-32, -32}
)
},
tile_map_{
Engine::get().get_resource_server().load_tile_set("assets/tilesets/terrain.json"),
32, 16, 12, {50, 50}
}
{
// Initialize tile map with example data
for (auto x = 0; x < 10; x++) {
for (auto y = 0; y < 10; y++) {
const auto tile_index = 2 + (x + y) % 2;
tile_map_.set_tile(x, y, tile_index);
}
}
}
// Handles an SDL event. Returns true if the event has been handled.
bool handle_event(const sdl::Event& event) override
{
if (event.type == sdl::EventTypes::MouseMotion) {
player_sprite_.set_position({event.motion.x, event.motion.y});
return true;
}
if (event.type == sdl::EventTypes::MouseButtonUp) {
sprites_.push_back(
Sprite::create_from_texture(
Engine::get().get_resource_server().load_texture("assets/sprites/neofox_64.png"),
std::nullopt,
sdl::FPoint{event.motion.x, event.motion.y},
sdl::FPoint{-32, -32}
)
);
return true;
}
return false;
}
void update() override
{
}
void render(const RenderServer& render_server) override
{
// Render tilemap
tile_map_.draw(render_server);
// Render sprites
for (const Sprite& sprite : sprites_) {
sprite.draw(render_server);
}
player_sprite_.draw(render_server);
}
};
}