From 8c1d521e18044cff8b59f81a8cdccc78cb91ebb2 Mon Sep 17 00:00:00 2001
From: Joseph Walton-Rivers <joseph@walton-rivers.uk>
Date: Sat, 23 Apr 2022 10:52:22 +0100
Subject: [PATCH] start work on dem scene, add a base class to make development
 a little cleaner

---
 demo/CMakeLists.txt            |  18 +++--
 demo/{ => demo}/GameScene.cpp  | 120 +++++++++++++++------------------
 demo/{ => demo}/main.cpp       |  37 ++++++++--
 demo/demo/rollball.cpp         |  43 ++++++++++++
 demo/{ => include}/GameScene.h |  37 +++++-----
 demo/include/rollball.hpp      |  45 +++++++++++++
 fggl/CMakeLists.txt            |   1 +
 fggl/app.cpp                   |   8 ++-
 fggl/gui/widget.cpp            |   8 +--
 fggl/gui/widgets.cpp           |   1 +
 fggl/scenes/game.cpp           |  62 +++++++++++++++++
 include/fggl/app.hpp           |   6 +-
 include/fggl/scenes/game.hpp   |  60 +++++++++++++++++
 include/fggl/util/states.hpp   |   4 ++
 14 files changed, 345 insertions(+), 105 deletions(-)
 rename demo/{ => demo}/GameScene.cpp (62%)
 rename demo/{ => demo}/main.cpp (66%)
 create mode 100644 demo/demo/rollball.cpp
 rename demo/{ => include}/GameScene.h (61%)
 create mode 100644 demo/include/rollball.hpp
 create mode 100644 fggl/scenes/game.cpp
 create mode 100644 include/fggl/scenes/game.hpp

diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
index e2efa8d..4318eb6 100644
--- a/demo/CMakeLists.txt
+++ b/demo/CMakeLists.txt
@@ -1,10 +1,20 @@
+cmake_minimum_required(VERSION 3.16)
+project(demo)
+
 # Executable
-add_executable(FgglDemo
-        main.cpp
-        GameScene.cpp
+add_executable(demo
+        demo/main.cpp
+        demo/GameScene.cpp
+        demo/rollball.cpp
         )
 
-target_link_libraries(FgglDemo fggl)
+
+target_include_directories(demo
+        PRIVATE
+            ${CMAKE_CURRENT_SOURCE_DIR}/include
+)
+
+target_link_libraries(demo fggl)
 #target_include_directories(FgglDemo PUBLIC ${PROJECT_BINARY_DIR})
 
 # rssources
diff --git a/demo/GameScene.cpp b/demo/demo/GameScene.cpp
similarity index 62%
rename from demo/GameScene.cpp
rename to demo/demo/GameScene.cpp
index e2b176e..045b784 100644
--- a/demo/GameScene.cpp
+++ b/demo/demo/GameScene.cpp
@@ -40,7 +40,7 @@ static void placeObject(fggl::ecs3::World& world, fggl::ecs::entity_t parent, fg
 	result->origin( targetPos );
 }
 
-static void process_camera(fggl::ecs3::World& ecs, const std::shared_ptr<fggl::input::Input>& input) {
+static void process_camera(fggl::ecs3::World& ecs, const fggl::input::Input& input) {
 	auto cameras = ecs.findMatching<fggl::gfx::Camera>();
 	fggl::ecs3::entity_t cam = cameras[0];
 
@@ -52,53 +52,48 @@ static void process_camera(fggl::ecs3::World& ecs, const std::shared_ptr<fggl::i
 
 	// scroll wheel
 	glm::vec3 motion(0.0f);
-	float delta = input->mouse.axis( fggl::input::MouseAxis::SCROLL_Y );
+	float delta = input.mouse.axis( fggl::input::MouseAxis::SCROLL_Y );
 	if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
 		motion -= (forward * delta);
 	camTransform->origin( camTransform->origin() + motion );
 
-	if ( cam_mode == cam_arcball || input->mouse.down( fggl::input::MouseButton::MIDDLE ) ) {
-		fggl::input::process_arcball(ecs, *input, cam);
+	if ( cam_mode == cam_arcball || input.mouse.down( fggl::input::MouseButton::MIDDLE ) ) {
+		fggl::input::process_arcball(ecs, input, cam);
 	} else if ( cam_mode == cam_free ) {
-		fggl::input::process_freecam(ecs, *input, cam);
+		fggl::input::process_freecam(ecs, input, cam);
 	}
-	fggl::input::process_edgescroll( ecs, *input, cam );
+	fggl::input::process_edgescroll( ecs, input, cam );
 }
 
-void GameScene::setup() {
-	m_canvas.size( fggl::math::vec2(0,0), fggl::math::vec2(100, 100));
+static void setupCamera(fggl::ecs3::World& world, fggl::ecs3::TypeRegistry& types) {
+	auto prototype = world.create(false);
+	world.add(prototype, types.find(fggl::math::Transform::name));
+	world.add(prototype, types.find(fggl::gfx::Camera::name));
+	world.add(prototype, types.find(fggl::input::FreeCamKeys::name));
 
-	auto types = m_world->types();
-
-	// create camera using strings
-	{
-		auto prototype = m_world->create(false);
-		m_world->add(prototype, types.find(fggl::math::Transform::name));
-		m_world->add(prototype, types.find(fggl::gfx::Camera::name));
-		m_world->add(prototype, types.find(fggl::input::FreeCamKeys::name));
-
-		auto camTf = m_world->get<fggl::math::Transform>(prototype);
-		if ( camTf != nullptr) {
-			camTf->origin(glm::vec3(10.0f, 3.0f, 10.0f));
-		}
+	auto camTf = world.get<fggl::math::Transform>(prototype);
+	if ( camTf != nullptr) {
+		camTf->origin(glm::vec3(10.0f, 3.0f, 10.0f));
+	}
 
-		auto cameraKeys = m_world->get<fggl::input::FreeCamKeys>(prototype);
-		if ( cameraKeys != nullptr ) {
-			cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W);
-			cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S);
-			cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A);
-			cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D);
-			cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
-			cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
-		}
+	auto cameraKeys = world.get<fggl::input::FreeCamKeys>(prototype);
+	if ( cameraKeys != nullptr ) {
+		cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W);
+		cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S);
+		cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A);
+		cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D);
+		cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
+		cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
 	}
+}
 
+static fggl::ecs3::entity_t setupTerrain(fggl::ecs3::World& world, fggl::ecs3::TypeRegistry& types) {
 	fggl::ecs3::entity_t terrain;
 	{
-		terrain = m_world->create(false);
-		m_world->add(terrain, types.find(fggl::math::Transform::name));
+		terrain = world.create(false);
+		world.add(terrain, types.find(fggl::math::Transform::name));
 
-		auto camTf = m_world->get<fggl::math::Transform>(terrain);
+		auto camTf = world.get<fggl::math::Transform>(terrain);
 		camTf->origin( glm::vec3(0.0f, 0.0f, 0.0f) );
 
 		//auto terrainData = m_world.get<fggl::data::HeightMap>(terrain);
@@ -114,31 +109,23 @@ void GameScene::setup() {
 				terrainData.heightValues[x * 255 +y] = (float)noise;
 			}
 		}
-		m_world->set<fggl::data::HeightMap>(terrain, &terrainData);
+		world.set<fggl::data::HeightMap>(terrain, &terrainData);
 	}
+	return terrain;
+}
 
-	// create foundation object
-	fggl::ecs3::entity_t foundation;
-	{
-		foundation = m_world->create(true);
-		m_world->add(foundation, types.find(fggl::math::Transform::name));
-
-		// plot rendering
-		fggl::data::Mesh mesh;
-		fggl::data::make_cube(mesh);
-		mesh.removeDups();
-
-		// add mesh as a component
-		constexpr char shader[] = "phong";
-		fggl::data::StaticMesh staticMesh{mesh, shader};
-		m_world->set<fggl::data::StaticMesh>(foundation, &staticMesh);
-	}
+static fggl::ecs3::entity_t setupEnvironment(fggl::ecs3::World& world) {
+	auto& types = world.types();
+	setupCamera(world, types);
+	return setupTerrain(world, types);
+}
 
-	// create building prototype
+static fggl::ecs3::entity_t setupBunkerPrototype(fggl::ecs3::World& world) {
+	auto& types = world.types();
 	fggl::ecs3::entity_t bunker;
 	{
-		bunker = m_world->create(true);
-		m_world->add(bunker, types.find(fggl::math::Transform::name));
+		bunker = world.create(true);
+		world.add(bunker, types.find(fggl::math::Transform::name));
 
 		// mesh
 		int nSections = 2;
@@ -164,33 +151,36 @@ void GameScene::setup() {
 		mesh.removeDups();
 
 		fggl::data::StaticMesh staticMesh{mesh, shader};
-		m_world->set<fggl::data::StaticMesh>(bunker, &staticMesh);
+		world.set<fggl::data::StaticMesh>(bunker, &staticMesh);
 	}
+	return bunker;
+}
 
-	for (int i=0; i<3; ++i) {
-		glm::vec3 location(i * 6.5f + 1.0f, 0.0f, -7.0f);
-		placeObject(*m_world, terrain, foundation, location);
-	}
+void GameScene::setup() {
+	m_canvas.size( fggl::math::vec2(0,0), fggl::math::vec2(100, 100));
 
+	auto terrain = setupEnvironment(world());
+	auto bunkerPrototype = setupBunkerPrototype(world());
+
+	// create building prototype
 	int nCubes = 3;
 	for ( int i=0; i<nCubes; i++ ) {
 		glm::vec3 location;
 		location.x = i * 6.f + 1.0f;
 		location.z = -5.0f + 1.0f;
-		placeObject(*m_world, terrain, bunker, location);
+		placeObject(world(), terrain, bunkerPrototype, location);
 	}
 }
 
 void GameScene::update() {
-process_camera(*m_world, m_inputs);
+	Game::update();
+
+	auto& inputSystem = input();
+	process_camera(world(), input());
 }
 
 void GameScene::render(fggl::gfx::Graphics &gfx) {
-
-		// render the 3D scene
-		if ( m_world != nullptr ) {
-			gfx.drawScene( *m_world );
-		}
+	Game::render(gfx);
 
 		const fggl::math::vec2 panelSize { 250.0F, 250.0F };
 		const auto canvasY = gfx.canvasBounds().bottom - panelSize.y;
diff --git a/demo/main.cpp b/demo/demo/main.cpp
similarity index 66%
rename from demo/main.cpp
rename to demo/demo/main.cpp
index 589f45e..8a03ad9 100644
--- a/demo/main.cpp
+++ b/demo/demo/main.cpp
@@ -1,21 +1,43 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
 #include <filesystem>
 #include <iostream>
 #include <memory>
 
-#include <fggl/app.hpp>
+#include "fggl/app.hpp"
 
-#include <fggl/gfx/atlas.hpp>
-#include <fggl/gfx/window.hpp>
+#include "fggl/gfx/atlas.hpp"
+#include "fggl/gfx/window.hpp"
 #include "fggl/gfx/compat.hpp"
 #include "fggl/gfx/ogl/compat.hpp"
 
-#include <fggl/data/storage.hpp>
-#include <fggl/util/service.h>
+#include "fggl/data/storage.hpp"
+#include "fggl/util/service.h"
 
-#include <fggl/ecs3/types.hpp>
+#include "fggl/ecs3/types.hpp"
 
 #include "fggl/scenes/menu.hpp"
+
 #include "GameScene.h"
+#include "rollball.hpp"
 
 // prototype of resource discovery
 void discover(const std::filesystem::path& base) {
@@ -76,11 +98,12 @@ int main(int argc, const char* argv[]) {
 	// Add a basic main menu
     auto *menu = app.add_state<fggl::scenes::BasicMenu>("menu");
     menu->add("start", [&app]() { app.change_state("game"); });
-	menu->add("options", [&app]() { app.change_state("game"); });
+	menu->add("rollball", [&app]() { app.change_state("rollball"); });
 	menu->add("quit", [&app]() { app.running(false); });
 
 	// the game state itself
     app.add_state<GameScene>("game");
+	app.add_state<demo::RollBall>("rollball");
 
 	return app.run(argc, argv);
 }
diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp
new file mode 100644
index 0000000..95be675
--- /dev/null
+++ b/demo/demo/rollball.cpp
@@ -0,0 +1,43 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "rollball.hpp"
+
+#include "fggl/util/service.h"
+
+namespace demo {
+
+	RollBall::RollBall(fggl::App &app) : Game(app) {
+	}
+
+	void RollBall::activate() {
+		Game::activate();
+	}
+
+	void RollBall::update() {
+		Game::update();
+	}
+
+	void RollBall::render(fggl::gfx::Graphics &gfx) {
+		Game::render(gfx);
+	}
+
+} // namespace demo
+
diff --git a/demo/GameScene.h b/demo/include/GameScene.h
similarity index 61%
rename from demo/GameScene.h
rename to demo/include/GameScene.h
index 90bf636..521584e 100644
--- a/demo/GameScene.h
+++ b/demo/include/GameScene.h
@@ -25,35 +25,31 @@
 #ifndef FGGL_DEMO_GAMESCENE_H
 #define FGGL_DEMO_GAMESCENE_H
 
-#include <fggl/app.hpp>
-#include <fggl/gui/gui.hpp>
+#include "fggl/app.hpp"
+#include "fggl/gui/gui.hpp"
+#include "fggl/scenes/game.hpp"
 
-#include <fggl/data/heightmap.h>
-#include <fggl/data/procedural.hpp>
+#include "fggl/data/heightmap.h"
+#include "fggl/data/procedural.hpp"
 
-#include <fggl/gfx/camera.hpp>
-#include <fggl/input/input.hpp>
-#include <fggl/input/camera_input.h>
+#include "fggl/gfx/camera.hpp"
+#include "fggl/input/input.hpp"
+#include "fggl/input/camera_input.h"
 
-#include <fggl/util/service.h>
-#include <fggl/util/chrono.hpp>
+#include "fggl/util/service.h"
+#include "fggl/util/chrono.hpp"
 
-#include <PerlinNoise.hpp>
+#include "PerlinNoise.hpp"
 
 enum camera_type { cam_free, cam_arcball };
 
-class GameScene : public fggl::AppState {
+	class GameScene : public fggl::scenes::Game {
 	public:
-		explicit GameScene(fggl::App& app) : fggl::AppState(app), m_world(nullptr), m_sceneTime(nullptr), m_inputs(nullptr) { };
+		explicit GameScene(fggl::App& app) : Game(app), m_sceneTime(nullptr) { };
 		//~GameScene() override = default;
 
 		void activate() override {
-			auto& locator = fggl::util::ServiceLocator::instance();
-			m_inputs = locator.providePtr<fggl::input::Input>();
-
-			// setup the world using the global type registry
-			// FIXME: type registry probably doesn't need to be application-global - will make savegame/mod support complicated
-			m_world = std::make_unique<fggl::ecs3::World>( *m_owner.registry() );
+			Game::activate();
 
 			m_sceneTime = std::make_unique<fggl::util::Timer>();
 			m_sceneTime->frequency( glfwGetTimerFrequency() );
@@ -62,15 +58,14 @@ class GameScene : public fggl::AppState {
 			setup();
 		}
 
-		void setup();
 		void update() override;
 		void render(fggl::gfx::Graphics& gfx) override;
 
 	private:
-		std::unique_ptr<fggl::ecs3::World> m_world;
 		std::unique_ptr<fggl::util::Timer> m_sceneTime;
-		std::shared_ptr<fggl::input::Input> m_inputs;
 		fggl::gui::Panel m_canvas;
+
+		void setup();
 };
 
 
diff --git a/demo/include/rollball.hpp b/demo/include/rollball.hpp
new file mode 100644
index 0000000..755c9fb
--- /dev/null
+++ b/demo/include/rollball.hpp
@@ -0,0 +1,45 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+//
+// Created by webpigeon on 23/04/22.
+//
+
+#ifndef DEMO_ROLLBALL_HPP
+#define DEMO_ROLLBALL_HPP
+
+#include "fggl/scenes/game.hpp"
+
+namespace demo {
+
+	class RollBall : public fggl::scenes::Game {
+
+		public:
+			explicit RollBall(fggl::App& app);
+
+			void activate() override;
+
+			void update() override;
+			void render(fggl::gfx::Graphics& gfx) override;
+	};
+
+}
+
+#endif //DEMO_ROLLBALL_HPP
diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt
index e5c5f08..486ce21 100644
--- a/fggl/CMakeLists.txt
+++ b/fggl/CMakeLists.txt
@@ -45,6 +45,7 @@ target_sources(${PROJECT_NAME}
         ecs3/prototype/world.cpp
 
         scenes/menu.cpp
+        scenes/game.cpp
 
         input/input.cpp
         input/mouse.cpp
diff --git a/fggl/app.cpp b/fggl/app.cpp
index 1349993..27e5ead 100644
--- a/fggl/app.cpp
+++ b/fggl/app.cpp
@@ -26,12 +26,17 @@ namespace fggl {
         {
             // activate the first state
             auto& state = m_states.active();
+			m_expectedScene = m_states.activeID();
             state.activate();
         }
 
         while ( m_running ) {
-            auto& state = m_states.active();
+			// trigger a state change if expected
+			if ( m_expectedScene != m_states.activeID() ) {
+				m_states.change(m_expectedScene);
+			}
 
+            auto& state = m_states.active();
             m_modules->onUpdate();
             state.update();
 
@@ -44,7 +49,6 @@ namespace fggl {
 				auto& graphics = m_window->graphics();
                 state.render(graphics);
 
-
                 m_window->frameEnd();
                 m_modules->onFrameEnd();
 
diff --git a/fggl/gui/widget.cpp b/fggl/gui/widget.cpp
index 9aa8fe8..0162082 100644
--- a/fggl/gui/widget.cpp
+++ b/fggl/gui/widget.cpp
@@ -107,7 +107,7 @@ namespace fggl::gui {
 		}
 
 		// bottom side
-		path.colour( darkColour );
+		path.colour( lightColour );
 		path.moveTo( outerTop );
 		path.pathTo( innerTop );
 		path.pathTo( { innerBottom.x, innerTop.y } );
@@ -115,7 +115,7 @@ namespace fggl::gui {
 		path.pathTo( outerTop );
 
 		// left side
-		path.colour( darkColour );
+		path.colour( lightColour );
 		path.moveTo( outerTop );
 		path.pathTo( innerTop );
 		path.pathTo( { innerTop.x, innerBottom.y } );
@@ -123,7 +123,7 @@ namespace fggl::gui {
 		path.pathTo( outerTop );
 
 		// top side
-		path.colour( lightColour );
+		path.colour( darkColour );
 		path.moveTo( { outerTop.x, outerBottom.y} );
 		path.pathTo( { innerTop.x, innerBottom.y} );
 		path.pathTo( innerBottom );
@@ -131,7 +131,7 @@ namespace fggl::gui {
 		path.pathTo( { outerTop.x, outerBottom.y}  );
 
 		// right side
-		path.colour( lightColour );
+		path.colour( darkColour );
 		path.moveTo( outerBottom );
 		path.pathTo( innerBottom );
 		path.pathTo( { innerBottom.x, innerTop.y } );
diff --git a/fggl/gui/widgets.cpp b/fggl/gui/widgets.cpp
index 467d2a1..a7c6f6e 100644
--- a/fggl/gui/widgets.cpp
+++ b/fggl/gui/widgets.cpp
@@ -24,6 +24,7 @@ namespace fggl::gui {
 		if ( m_active ) {
 			for( auto& callback : m_callbacks ) {
 				callback();
+				m_active = false;
 			}
 		}
 	}
diff --git a/fggl/scenes/game.cpp b/fggl/scenes/game.cpp
new file mode 100644
index 0000000..623247d
--- /dev/null
+++ b/fggl/scenes/game.cpp
@@ -0,0 +1,62 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+//
+// Created by webpigeon on 23/04/22.
+//
+
+#include "fggl/scenes/game.hpp"
+#include "fggl/util/service.h"
+
+namespace fggl::scenes {
+
+	Game::Game(fggl::App &app) : AppState(app) {
+		auto& locator = fggl::util::ServiceLocator::instance();
+		m_input = locator.get<fggl::input::Input>();
+	}
+
+	void Game::activate() {
+		fggl::AppState::activate();
+
+		// setup the scene
+		m_world = std::make_unique<fggl::ecs3::World>(*m_owner.registry());
+	}
+
+	void Game::deactivate() {
+		m_world.reset();
+	}
+
+	void Game::update() {
+		if ( m_input != nullptr ) {
+			bool escapePressed = m_input->keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_ESCAPE));
+			if (escapePressed) {
+				m_owner.change_state(m_previous);
+			}
+		}
+	}
+
+	void Game::render(fggl::gfx::Graphics &gfx) {
+		if ( m_world != nullptr ) {
+			gfx.drawScene( *m_world );
+		}
+	}
+
+} // namespace demo
+
diff --git a/include/fggl/app.hpp b/include/fggl/app.hpp
index a83dece..4ba1ca9 100644
--- a/include/fggl/app.hpp
+++ b/include/fggl/app.hpp
@@ -120,9 +120,10 @@ namespace fggl {
 			}
 
 			inline void change_state(const Identifer &name) {
-				m_states.active().deactivate();
+				m_expectedScene = name;
+				/*m_states.active().deactivate();
 				m_states.change(name);
-				m_states.active().activate();
+				m_states.active().activate();*/
 			}
 
 			inline AppState &active_state() const {
@@ -147,6 +148,7 @@ namespace fggl {
 			std::unique_ptr<ecs3::ModuleManager> m_modules;
 			std::unique_ptr<gfx::Window> m_window;
 			AppMachine m_states;
+			Identifer m_expectedScene;
 	};
 
 }
diff --git a/include/fggl/scenes/game.hpp b/include/fggl/scenes/game.hpp
new file mode 100644
index 0000000..fa9cafc
--- /dev/null
+++ b/include/fggl/scenes/game.hpp
@@ -0,0 +1,60 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+//
+// Created by webpigeon on 23/04/22.
+//
+
+#ifndef FGGL_SCENES_GAME_HPP
+#define FGGL_SCENES_GAME_HPP
+
+#include "fggl/app.hpp"
+
+namespace fggl::scenes {
+
+	class Game : public fggl::AppState {
+
+		public:
+			explicit Game(fggl::App& app);
+
+			void activate() override;
+			void deactivate() override;
+
+			void update() override;
+			void render(fggl::gfx::Graphics& gfx) override;
+
+		protected:
+			inline auto world() -> ecs3::World& {
+				return *m_world;
+			}
+
+			inline auto input() -> input::Input& {
+				return *m_input;
+			}
+
+		private:
+			std::shared_ptr<input::Input> m_input;
+			std::unique_ptr<ecs3::World> m_world;
+			std::string m_previous = "menu";
+	};
+
+} // namespace fggl::scenes
+
+#endif //FGGL_SCENES_GAME_HPP
diff --git a/include/fggl/util/states.hpp b/include/fggl/util/states.hpp
index 1eb2970..cdb2a3d 100644
--- a/include/fggl/util/states.hpp
+++ b/include/fggl/util/states.hpp
@@ -67,6 +67,10 @@ namespace fggl::util {
 				return *(m_states.at(m_active).get());
 			}
 
+			Identifer activeID() {
+				return m_active;
+			}
+
 		private:
 			Identifer m_active;
 			std::unordered_map<Identifer, std::unique_ptr<StateType>> m_states;
-- 
GitLab