Not so well working game mode
This commit is contained in:
@@ -25,6 +25,8 @@ set(EDITSCENE_SOURCES
|
||||
systems/CellGridSystem.cpp
|
||||
systems/RoomLayoutSystem.cpp
|
||||
systems/FurnitureLibrary.cpp
|
||||
systems/StartupMenuSystem.cpp
|
||||
systems/PlayerControllerSystem.cpp
|
||||
systems/CharacterSlotSystem.cpp
|
||||
systems/AnimationTreeSystem.cpp
|
||||
systems/CharacterSystem.cpp
|
||||
@@ -55,6 +57,8 @@ set(EDITSCENE_SOURCES
|
||||
|
||||
ui/ClearAreaEditor.cpp
|
||||
ui/FurnitureTemplateEditor.cpp
|
||||
ui/StartupMenuEditor.cpp
|
||||
ui/PlayerControllerEditor.cpp
|
||||
ui/ComponentRegistration.cpp
|
||||
components/LightModule.cpp
|
||||
components/CameraModule.cpp
|
||||
@@ -71,6 +75,8 @@ set(EDITSCENE_SOURCES
|
||||
components/CellGridModule.cpp
|
||||
components/CellGridEditorsModule.cpp
|
||||
components/CellGrid.cpp
|
||||
components/StartupMenuModule.cpp
|
||||
components/PlayerControllerModule.cpp
|
||||
camera/EditorCamera.cpp
|
||||
gizmo/Gizmo.cpp
|
||||
physics/physics.cpp
|
||||
@@ -98,6 +104,10 @@ set(EDITSCENE_HEADERS
|
||||
components/AnimationTree.hpp
|
||||
components/Character.hpp
|
||||
components/CellGrid.hpp
|
||||
components/StartupMenu.hpp
|
||||
components/PlayerController.hpp
|
||||
systems/StartupMenuSystem.hpp
|
||||
systems/PlayerControllerSystem.hpp
|
||||
systems/EditorUISystem.hpp
|
||||
systems/CellGridSystem.hpp
|
||||
systems/RoomLayoutSystem.hpp
|
||||
@@ -144,6 +154,8 @@ set(EDITSCENE_HEADERS
|
||||
|
||||
ui/ClearAreaEditor.hpp
|
||||
ui/FurnitureTemplateEditor.hpp
|
||||
ui/StartupMenuEditor.hpp
|
||||
ui/PlayerControllerEditor.hpp
|
||||
camera/EditorCamera.hpp
|
||||
gizmo/Gizmo.hpp
|
||||
physics/physics.h
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "systems/CharacterSystem.hpp"
|
||||
#include "systems/CellGridSystem.hpp"
|
||||
#include "systems/RoomLayoutSystem.hpp"
|
||||
#include "systems/StartupMenuSystem.hpp"
|
||||
#include "systems/PlayerControllerSystem.hpp"
|
||||
#include "systems/SceneSerializer.hpp"
|
||||
#include "camera/EditorCamera.hpp"
|
||||
#include "components/EntityName.hpp"
|
||||
#include "components/Transform.hpp"
|
||||
@@ -35,6 +38,8 @@
|
||||
#include "components/CharacterSlots.hpp"
|
||||
#include "components/AnimationTree.hpp"
|
||||
#include "components/Character.hpp"
|
||||
#include "components/StartupMenu.hpp"
|
||||
#include "components/PlayerController.hpp"
|
||||
#include "components/CellGrid.hpp"
|
||||
#include "components/CellGridModule.hpp"
|
||||
#include <OgreRTShaderSystem.h>
|
||||
@@ -46,10 +51,12 @@
|
||||
|
||||
ImGuiRenderListener::ImGuiRenderListener(Ogre::ImGuiOverlay *imguiOverlay,
|
||||
EditorUISystem *uiSystem,
|
||||
Ogre::RenderWindow *renderWindow)
|
||||
Ogre::RenderWindow *renderWindow,
|
||||
EditorApp *editorApp)
|
||||
: m_imguiOverlay(imguiOverlay)
|
||||
, m_uiSystem(uiSystem)
|
||||
, m_renderWindow(renderWindow)
|
||||
, m_editorApp(editorApp)
|
||||
{
|
||||
m_lastTime = m_timer.getMilliseconds();
|
||||
}
|
||||
@@ -71,6 +78,14 @@ void ImGuiRenderListener::preViewportUpdate(
|
||||
if (m_uiSystem) {
|
||||
m_uiSystem->update(m_deltaTime);
|
||||
}
|
||||
|
||||
// Render startup menu in game mode (inside ImGui frame scope)
|
||||
if (m_editorApp && m_editorApp->getGameMode() == EditorApp::GameMode::Game &&
|
||||
m_editorApp->getGamePlayState() == EditorApp::GamePlayState::Menu) {
|
||||
StartupMenuSystem *sms = m_editorApp->getStartupMenuSystem();
|
||||
if (sms)
|
||||
sms->update(m_deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiRenderListener::postViewportUpdate(
|
||||
@@ -100,6 +115,8 @@ EditorApp::EditorApp()
|
||||
, m_overlaySystem(nullptr)
|
||||
, m_imguiOverlay(nullptr)
|
||||
, m_currentModifiers(0)
|
||||
, m_gameMode(GameMode::Editor)
|
||||
, m_gamePlayState(GamePlayState::Menu)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -125,6 +142,8 @@ EditorApp::~EditorApp()
|
||||
}
|
||||
|
||||
// Release all systems
|
||||
m_playerControllerSystem.reset();
|
||||
m_startupMenuSystem.reset();
|
||||
m_characterSlotSystem.reset();
|
||||
m_animationTreeSystem.reset();
|
||||
m_characterSystem.reset();
|
||||
@@ -172,7 +191,6 @@ void EditorApp::setup()
|
||||
m_imguiOverlay = initialiseImGui();
|
||||
if (m_imguiOverlay) {
|
||||
m_imguiOverlay->setZOrder(300);
|
||||
m_imguiOverlay->show();
|
||||
ImGui::StyleColorsDark();
|
||||
}
|
||||
|
||||
@@ -218,13 +236,13 @@ void EditorApp::setup()
|
||||
// Setup ProceduralTexture system
|
||||
m_proceduralTextureSystem =
|
||||
std::make_unique<ProceduralTextureSystem>(m_world,
|
||||
m_sceneMgr);
|
||||
m_sceneMgr);
|
||||
m_proceduralTextureSystem->initialize();
|
||||
|
||||
// Setup ProceduralMaterial system
|
||||
m_proceduralMaterialSystem =
|
||||
std::make_unique<ProceduralMaterialSystem>(m_world,
|
||||
m_sceneMgr);
|
||||
m_sceneMgr);
|
||||
m_proceduralMaterialSystem->initialize();
|
||||
|
||||
// Setup ProceduralMesh system
|
||||
@@ -257,6 +275,34 @@ void EditorApp::setup()
|
||||
std::make_unique<RoomLayoutSystem>(m_world, m_sceneMgr);
|
||||
m_roomLayoutSystem->initialize();
|
||||
|
||||
// Setup game systems
|
||||
m_startupMenuSystem =
|
||||
std::make_unique<StartupMenuSystem>(m_world, m_sceneMgr,
|
||||
this);
|
||||
m_playerControllerSystem =
|
||||
std::make_unique<PlayerControllerSystem>(
|
||||
m_world, m_sceneMgr, this);
|
||||
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
// Load startup menu scene configured in editor
|
||||
SceneSerializer serializer(m_world, m_sceneMgr);
|
||||
if (!serializer.loadFromFile("startup_menu.json",
|
||||
m_uiSystem.get())) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game mode: Failed to load startup_menu.json: " +
|
||||
serializer.getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-load menu font before showing overlay
|
||||
// (OGRE builds the atlas in createFontTexture() during show())
|
||||
if (m_startupMenuSystem)
|
||||
m_startupMenuSystem->prepareFont();
|
||||
|
||||
// Now show the overlay — font atlas will be built with our font
|
||||
if (m_imguiOverlay)
|
||||
m_imguiOverlay->show();
|
||||
|
||||
// Add default entities to UI cache
|
||||
for (auto &e : m_defaultEntities) {
|
||||
m_uiSystem->addEntity(e);
|
||||
@@ -264,13 +310,16 @@ void EditorApp::setup()
|
||||
|
||||
// Create and register ImGui render listener
|
||||
m_imguiListener = std::make_unique<ImGuiRenderListener>(
|
||||
m_imguiOverlay, m_uiSystem.get(), getRenderWindow());
|
||||
m_imguiOverlay, m_uiSystem.get(), getRenderWindow(), this);
|
||||
getRenderWindow()->addListener(m_imguiListener.get());
|
||||
|
||||
// Register input listeners
|
||||
addInputListener(this);
|
||||
addInputListener(getImGuiInputListener());
|
||||
|
||||
// Game mode can be set externally before setup() is called
|
||||
m_setupComplete = true;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Setup failed: " + Ogre::String(e.what()));
|
||||
@@ -278,6 +327,71 @@ void EditorApp::setup()
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::setGameMode(GameMode mode)
|
||||
{
|
||||
if (m_setupComplete) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"setGameMode ignored: cannot change mode after setup");
|
||||
return;
|
||||
}
|
||||
m_gameMode = mode;
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
m_gamePlayState = GamePlayState::Menu;
|
||||
if (m_uiSystem)
|
||||
m_uiSystem->setEditorUIEnabled(false);
|
||||
} else {
|
||||
m_gamePlayState = GamePlayState::Menu;
|
||||
if (m_uiSystem)
|
||||
m_uiSystem->setEditorUIEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::setGamePlayState(GamePlayState state)
|
||||
{
|
||||
m_gamePlayState = state;
|
||||
|
||||
// Grab/ungrab mouse based on gameplay state
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
if (state == GamePlayState::Playing) {
|
||||
setWindowGrab(true);
|
||||
} else if (state == GamePlayState::Menu) {
|
||||
setWindowGrab(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::clearScene()
|
||||
{
|
||||
// Destroy all entities with EditorMarkerComponent
|
||||
std::vector<flecs::entity> entitiesToDelete;
|
||||
m_world.query<EditorMarkerComponent>().each(
|
||||
[&](flecs::entity e, EditorMarkerComponent) {
|
||||
entitiesToDelete.push_back(e);
|
||||
});
|
||||
for (auto &e : entitiesToDelete) {
|
||||
if (e.is_alive()) {
|
||||
e.destruct();
|
||||
}
|
||||
}
|
||||
if (m_uiSystem) {
|
||||
m_uiSystem->clearEntityCache();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::startNewGame(const Ogre::String &scenePath)
|
||||
{
|
||||
clearScene();
|
||||
SceneSerializer serializer(m_world, m_sceneMgr);
|
||||
if (serializer.loadFromFile(scenePath, m_uiSystem.get())) {
|
||||
m_gamePlayState = GamePlayState::Playing;
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Game started: loaded scene " + scenePath);
|
||||
} else {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Failed to load scene: " + serializer.getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::setupECS()
|
||||
{
|
||||
// Register components
|
||||
@@ -322,6 +436,10 @@ void EditorApp::setupECS()
|
||||
// Register Character component
|
||||
m_world.component<CharacterComponent>();
|
||||
|
||||
// Register game components
|
||||
m_world.component<StartupMenuComponent>();
|
||||
m_world.component<PlayerControllerComponent>();
|
||||
|
||||
// Register CellGrid/Town components
|
||||
CellGridModule::registerComponents(m_world);
|
||||
}
|
||||
@@ -442,9 +560,18 @@ void EditorApp::createAxes()
|
||||
|
||||
bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
{
|
||||
// Update camera
|
||||
if (m_camera) {
|
||||
m_camera->update(evt.timeSinceLastFrame);
|
||||
if (m_gameMode == GameMode::Editor) {
|
||||
// Update camera
|
||||
if (m_camera) {
|
||||
m_camera->update(evt.timeSinceLastFrame);
|
||||
}
|
||||
} else if (m_gameMode == GameMode::Game) {
|
||||
if (m_gamePlayState == GamePlayState::Playing) {
|
||||
if (m_playerControllerSystem) {
|
||||
m_playerControllerSystem->update(
|
||||
evt.timeSinceLastFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Visual mesh setup (must run before animation) --- */
|
||||
@@ -498,12 +625,22 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
m_proceduralMaterialSystem->update();
|
||||
}
|
||||
|
||||
// Reset per-frame input state
|
||||
m_gameInput.resetPerFrame();
|
||||
|
||||
// Don't call base class - it crashes when iterating input listeners
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorApp::mouseMoved(const OgreBites::MouseMotionEvent &evt)
|
||||
{
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
m_gameInput.mouseMoved = true;
|
||||
m_gameInput.mouseDeltaX += evt.xrel;
|
||||
m_gameInput.mouseDeltaY += evt.yrel;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip if ImGui wants to capture mouse (for gizmo, we still want to process even if over UI)
|
||||
// But we need to update hover state
|
||||
if (m_camera && m_uiSystem) {
|
||||
@@ -526,6 +663,10 @@ bool EditorApp::mouseMoved(const OgreBites::MouseMotionEvent &evt)
|
||||
|
||||
bool EditorApp::mousePressed(const OgreBites::MouseButtonEvent &evt)
|
||||
{
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get mouse ray for gizmo interaction FIRST (before ImGui check)
|
||||
// This allows clicking on 3D gizmo even when mouse is over empty UI areas
|
||||
if (m_camera && m_uiSystem) {
|
||||
@@ -552,6 +693,10 @@ bool EditorApp::mousePressed(const OgreBites::MouseButtonEvent &evt)
|
||||
|
||||
bool EditorApp::mouseReleased(const OgreBites::MouseButtonEvent &evt)
|
||||
{
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle gizmo mouse release (always process to end dragging)
|
||||
if (m_uiSystem) {
|
||||
if (m_uiSystem->onMouseReleased()) {
|
||||
@@ -571,6 +716,42 @@ bool EditorApp::keyPressed(const OgreBites::KeyboardEvent &evt)
|
||||
{
|
||||
m_currentModifiers = evt.keysym.mod;
|
||||
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
bool pressed = true;
|
||||
switch (evt.keysym.sym) {
|
||||
case 'w':
|
||||
case 'W':
|
||||
m_gameInput.w = pressed;
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
m_gameInput.s = pressed;
|
||||
break;
|
||||
case 'a':
|
||||
case 'A':
|
||||
m_gameInput.a = pressed;
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
m_gameInput.d = pressed;
|
||||
break;
|
||||
case OgreBites::SDLK_LSHIFT:
|
||||
m_gameInput.shift = pressed;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
m_gameInput.e = pressed;
|
||||
m_gameInput.ePressed = true;
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
m_gameInput.f = pressed;
|
||||
m_gameInput.fPressed = true;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forward to camera for FPS movement
|
||||
if (m_camera) {
|
||||
m_camera->handleKeyboard(evt);
|
||||
@@ -601,6 +782,40 @@ bool EditorApp::keyReleased(const OgreBites::KeyboardEvent &evt)
|
||||
{
|
||||
m_currentModifiers = evt.keysym.mod;
|
||||
|
||||
if (m_gameMode == GameMode::Game) {
|
||||
bool pressed = false;
|
||||
switch (evt.keysym.sym) {
|
||||
case 'w':
|
||||
case 'W':
|
||||
m_gameInput.w = pressed;
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
m_gameInput.s = pressed;
|
||||
break;
|
||||
case 'a':
|
||||
case 'A':
|
||||
m_gameInput.a = pressed;
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
m_gameInput.d = pressed;
|
||||
break;
|
||||
case OgreBites::SDLK_LSHIFT:
|
||||
m_gameInput.shift = pressed;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
m_gameInput.e = pressed;
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
m_gameInput.f = pressed;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forward to camera for FPS movement
|
||||
if (m_camera) {
|
||||
m_camera->handleKeyboard(evt);
|
||||
@@ -632,4 +847,4 @@ void EditorApp::locateResources()
|
||||
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
|
||||
"./characters/female", "FileSystem", "Characters", false, true);
|
||||
OgreBites::ApplicationContext::locateResources();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,36 @@ class AnimationTreeSystem;
|
||||
class CharacterSystem;
|
||||
class CellGridSystem;
|
||||
class RoomLayoutSystem;
|
||||
class StartupMenuSystem;
|
||||
class PlayerControllerSystem;
|
||||
class EditorApp;
|
||||
|
||||
/**
|
||||
* Shared input state for game mode
|
||||
*/
|
||||
struct GameInputState {
|
||||
bool w = false;
|
||||
bool a = false;
|
||||
bool s = false;
|
||||
bool d = false;
|
||||
bool shift = false;
|
||||
bool e = false;
|
||||
bool f = false;
|
||||
bool ePressed = false;
|
||||
bool fPressed = false;
|
||||
float mouseDeltaX = 0.0f;
|
||||
float mouseDeltaY = 0.0f;
|
||||
bool mouseMoved = false;
|
||||
|
||||
void resetPerFrame()
|
||||
{
|
||||
mouseMoved = false;
|
||||
mouseDeltaX = 0.0f;
|
||||
mouseDeltaY = 0.0f;
|
||||
ePressed = false;
|
||||
fPressed = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* RenderTargetListener for ImGui frame management
|
||||
@@ -35,7 +65,8 @@ class ImGuiRenderListener : public Ogre::RenderTargetListener {
|
||||
public:
|
||||
ImGuiRenderListener(Ogre::ImGuiOverlay *imguiOverlay,
|
||||
EditorUISystem *uiSystem,
|
||||
Ogre::RenderWindow *renderWindow);
|
||||
Ogre::RenderWindow *renderWindow,
|
||||
EditorApp *editorApp);
|
||||
|
||||
void
|
||||
preViewportUpdate(const Ogre::RenderTargetViewportEvent &evt) override;
|
||||
@@ -46,6 +77,7 @@ private:
|
||||
Ogre::ImGuiOverlay *m_imguiOverlay;
|
||||
EditorUISystem *m_uiSystem;
|
||||
Ogre::RenderWindow *m_renderWindow;
|
||||
EditorApp *m_editorApp;
|
||||
|
||||
// Timer for delta time calculation
|
||||
Ogre::Timer m_timer;
|
||||
@@ -57,11 +89,14 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
* Main application class for the scene editor
|
||||
* Main application class for the scene editor / game
|
||||
*/
|
||||
class EditorApp : public OgreBites::ApplicationContext,
|
||||
public OgreBites::InputListener {
|
||||
public:
|
||||
enum class GameMode { Editor, Game };
|
||||
enum class GamePlayState { Menu, Playing, Paused };
|
||||
|
||||
EditorApp();
|
||||
virtual ~EditorApp();
|
||||
|
||||
@@ -86,6 +121,17 @@ public:
|
||||
void setupECS();
|
||||
void createDefaultEntities();
|
||||
|
||||
// Game mode management
|
||||
void setGameMode(GameMode mode);
|
||||
GameMode getGameMode() const { return m_gameMode; }
|
||||
GamePlayState getGamePlayState() const { return m_gamePlayState; }
|
||||
void setGamePlayState(GamePlayState state);
|
||||
void startNewGame(const Ogre::String &scenePath);
|
||||
void clearScene();
|
||||
|
||||
// Input access
|
||||
GameInputState &getGameInputState() { return m_gameInput; }
|
||||
|
||||
// Getters
|
||||
flecs::entity getSelectedEntity() const;
|
||||
Ogre::SceneManager *getSceneManager() const
|
||||
@@ -96,6 +142,20 @@ public:
|
||||
{
|
||||
return &m_world;
|
||||
}
|
||||
EditorCamera *getEditorCamera() const { return m_camera.get(); }
|
||||
AnimationTreeSystem *getAnimationTreeSystem() const
|
||||
{
|
||||
return m_animationTreeSystem.get();
|
||||
}
|
||||
CharacterSlotSystem *getCharacterSlotSystem() const
|
||||
{
|
||||
return m_characterSlotSystem.get();
|
||||
}
|
||||
StartupMenuSystem *getStartupMenuSystem() const
|
||||
{
|
||||
return m_startupMenuSystem.get();
|
||||
}
|
||||
Ogre::ImGuiOverlay *getImGuiOverlay() const { return m_imguiOverlay; }
|
||||
|
||||
private:
|
||||
// Ogre objects
|
||||
@@ -125,8 +185,16 @@ private:
|
||||
std::unique_ptr<CellGridSystem> m_cellGridSystem;
|
||||
std::unique_ptr<RoomLayoutSystem> m_roomLayoutSystem;
|
||||
|
||||
// Game systems
|
||||
std::unique_ptr<StartupMenuSystem> m_startupMenuSystem;
|
||||
std::unique_ptr<PlayerControllerSystem> m_playerControllerSystem;
|
||||
|
||||
// State
|
||||
uint16_t m_currentModifiers;
|
||||
GameMode m_gameMode = GameMode::Editor;
|
||||
GamePlayState m_gamePlayState = GamePlayState::Menu;
|
||||
GameInputState m_gameInput;
|
||||
bool m_setupComplete = false;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_EDITORAPP_HPP
|
||||
|
||||
26
src/features/editScene/components/PlayerController.hpp
Normal file
26
src/features/editScene/components/PlayerController.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef EDITSCENE_PLAYERCONTROLLER_HPP
|
||||
#define EDITSCENE_PLAYERCONTROLLER_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* Player controller component.
|
||||
* Only active in game mode. Editable in editor mode.
|
||||
*/
|
||||
struct PlayerControllerComponent {
|
||||
enum CameraMode { TPS = 0, FPS = 1 };
|
||||
int cameraMode = TPS;
|
||||
Ogre::String targetCharacterName = "";
|
||||
Ogre::String fpsBoneName = "Head";
|
||||
float tpsDistance = 3.0f;
|
||||
float tpsHeight = 2.0f;
|
||||
float mouseSensitivity = 0.2f;
|
||||
/* Animation state machine configuration */
|
||||
Ogre::String locomotionStateMachine = "locomotion";
|
||||
Ogre::String idleState = "idle";
|
||||
Ogre::String walkState = "walking";
|
||||
Ogre::String runState = "running";
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_PLAYERCONTROLLER_HPP
|
||||
24
src/features/editScene/components/PlayerControllerModule.cpp
Normal file
24
src/features/editScene/components/PlayerControllerModule.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/PlayerControllerEditor.hpp"
|
||||
#include "PlayerController.hpp"
|
||||
|
||||
REGISTER_COMPONENT_GROUP("Player Controller", "Game",
|
||||
PlayerControllerComponent, PlayerControllerEditor)
|
||||
{
|
||||
registry.registerComponent<PlayerControllerComponent>(
|
||||
PlayerControllerComponent_name,
|
||||
PlayerControllerComponent_group,
|
||||
std::make_unique<PlayerControllerEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<PlayerControllerComponent>()) {
|
||||
e.set<PlayerControllerComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<PlayerControllerComponent>()) {
|
||||
e.remove<PlayerControllerComponent>();
|
||||
}
|
||||
});
|
||||
}
|
||||
20
src/features/editScene/components/StartupMenu.hpp
Normal file
20
src/features/editScene/components/StartupMenu.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef EDITSCENE_STARTUPMENU_HPP
|
||||
#define EDITSCENE_STARTUPMENU_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* Configurable startup menu component.
|
||||
* Only active in game mode. Editable in editor mode.
|
||||
*/
|
||||
struct StartupMenuComponent {
|
||||
Ogre::String fontName = "Kenney Bold.ttf";
|
||||
float fontSize = 36.0f;
|
||||
Ogre::String newGameScene = "scene.json";
|
||||
bool showLoadGame = true;
|
||||
bool showOptions = true;
|
||||
bool showQuit = true;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_STARTUPMENU_HPP
|
||||
23
src/features/editScene/components/StartupMenuModule.cpp
Normal file
23
src/features/editScene/components/StartupMenuModule.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/StartupMenuEditor.hpp"
|
||||
#include "StartupMenu.hpp"
|
||||
|
||||
REGISTER_COMPONENT_GROUP("Startup Menu", "Game", StartupMenuComponent,
|
||||
StartupMenuEditor)
|
||||
{
|
||||
registry.registerComponent<StartupMenuComponent>(
|
||||
StartupMenuComponent_name, StartupMenuComponent_group,
|
||||
std::make_unique<StartupMenuEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<StartupMenuComponent>()) {
|
||||
e.set<StartupMenuComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<StartupMenuComponent>()) {
|
||||
e.remove<StartupMenuComponent>();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -6,17 +6,34 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
try {
|
||||
EditorApp app;
|
||||
|
||||
// Parse command line arguments
|
||||
bool gameMode = false;
|
||||
Ogre::String sceneFile;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
Ogre::String arg = argv[i];
|
||||
if (arg == "--game") {
|
||||
gameMode = true;
|
||||
} else if (arg.length() > 0 && arg[0] != '-') {
|
||||
sceneFile = arg;
|
||||
}
|
||||
}
|
||||
|
||||
if (gameMode) {
|
||||
app.setGameMode(EditorApp::GameMode::Game);
|
||||
}
|
||||
|
||||
app.initApp();
|
||||
|
||||
// Auto-load scene if provided as argument
|
||||
if (argc > 1) {
|
||||
std::cout << "Auto-loading scene: " << argv[1] << std::endl;
|
||||
|
||||
// Auto-load scene if provided as argument (editor mode only)
|
||||
if (!sceneFile.empty() && app.getGameMode() == EditorApp::GameMode::Editor) {
|
||||
std::cout << "Auto-loading scene: " << sceneFile << std::endl;
|
||||
SceneSerializer serializer(*app.getWorld(), app.getSceneManager());
|
||||
if (!serializer.loadFromFile(argv[1])) {
|
||||
if (!serializer.loadFromFile(sceneFile, nullptr)) {
|
||||
std::cerr << "Failed to load scene: " << serializer.getLastError() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
app.getRoot()->startRendering();
|
||||
app.closeApp();
|
||||
} catch (const std::exception &e) {
|
||||
|
||||
@@ -10,6 +10,7 @@ FileSystem=resources/buildings
|
||||
FileSystem=resources/buildings/parts/pier
|
||||
FileSystem=resources/buildings/parts/furniture
|
||||
FileSystem=resources/vehicles
|
||||
FileSystem=resources/fonts
|
||||
|
||||
[Popular]
|
||||
FileSystem=resources/materials/programs
|
||||
|
||||
@@ -281,3 +281,25 @@ void CharacterSlotSystem::destroyCharacterParts(flecs::entity e)
|
||||
it->second.parts.clear();
|
||||
m_entities.erase(it);
|
||||
}
|
||||
|
||||
Ogre::Entity *CharacterSlotSystem::getSlotEntity(flecs::entity e,
|
||||
const Ogre::String &slot)
|
||||
{
|
||||
auto it = m_entities.find(e.id());
|
||||
if (it == m_entities.end())
|
||||
return nullptr;
|
||||
auto pit = it->second.parts.find(slot);
|
||||
if (pit == it->second.parts.end())
|
||||
return nullptr;
|
||||
return pit->second;
|
||||
}
|
||||
|
||||
void CharacterSlotSystem::setSlotVisible(flecs::entity e,
|
||||
const Ogre::String &slot,
|
||||
bool visible)
|
||||
{
|
||||
Ogre::Entity *ent = getSlotEntity(e, slot);
|
||||
if (ent) {
|
||||
ent->setVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ public:
|
||||
const Ogre::String &sex,
|
||||
const Ogre::String &slot);
|
||||
|
||||
/* Slot visibility helpers */
|
||||
Ogre::Entity *getSlotEntity(flecs::entity e,
|
||||
const Ogre::String &slot);
|
||||
void setSlotVisible(flecs::entity e, const Ogre::String &slot,
|
||||
bool visible);
|
||||
|
||||
private:
|
||||
static bool s_catalogLoaded;
|
||||
static nlohmann::json s_bodyParts;
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "../components/CharacterSlots.hpp"
|
||||
#include "../components/Character.hpp"
|
||||
#include "../components/AnimationTree.hpp"
|
||||
#include "../components/StartupMenu.hpp"
|
||||
#include "../components/PlayerController.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include "../ui/TransformEditor.hpp"
|
||||
#include "../ui/RenderableEditor.hpp"
|
||||
@@ -182,6 +184,12 @@ void EditorUISystem::registerModularComponents()
|
||||
|
||||
void EditorUISystem::update(float deltaTime)
|
||||
{
|
||||
if (!m_editorUIEnabled) {
|
||||
// Only render FPS overlay when editor UI is disabled
|
||||
renderFPSOverlay(deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
// Render UI windows
|
||||
// Note: NewFrame() is called by ImGuiRenderListener::preViewportUpdate
|
||||
renderHierarchyWindow();
|
||||
@@ -651,6 +659,20 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render StartupMenu if present
|
||||
if (entity.has<StartupMenuComponent>()) {
|
||||
auto &sm = entity.get_mut<StartupMenuComponent>();
|
||||
m_componentRegistry.render<StartupMenuComponent>(entity, sm);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render PlayerController if present
|
||||
if (entity.has<PlayerControllerComponent>()) {
|
||||
auto &pc = entity.get_mut<PlayerControllerComponent>();
|
||||
m_componentRegistry.render<PlayerControllerComponent>(entity, pc);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render CellGrid if present
|
||||
if (entity.has<CellGridComponent>()) {
|
||||
auto &grid = entity.get_mut<CellGridComponent>();
|
||||
|
||||
@@ -52,6 +52,14 @@ public:
|
||||
*/
|
||||
void addEntity(flecs::entity entity) { m_allEntities.push_back(entity); }
|
||||
|
||||
/**
|
||||
* Clear entity cache and deselect
|
||||
*/
|
||||
void clearEntityCache() {
|
||||
m_allEntities.clear();
|
||||
setSelectedEntity(flecs::entity::null());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected entity
|
||||
*/
|
||||
@@ -88,6 +96,11 @@ public:
|
||||
* Set physics system for debug toggle
|
||||
*/
|
||||
void setPhysicsSystem(EditorPhysicsSystem* physics) { m_physicsSystem = physics; }
|
||||
|
||||
/**
|
||||
* Enable/disable editor UI rendering
|
||||
*/
|
||||
void setEditorUIEnabled(bool enabled) { m_editorUIEnabled = enabled; }
|
||||
|
||||
/**
|
||||
* Set last frame batch count (called from render listener)
|
||||
@@ -167,6 +180,9 @@ private:
|
||||
// Physics system reference (for debug toggle)
|
||||
EditorPhysicsSystem* m_physicsSystem = nullptr;
|
||||
|
||||
// Editor UI enabled flag
|
||||
bool m_editorUIEnabled = true;
|
||||
|
||||
// Render window reference (for viewport access)
|
||||
Ogre::RenderWindow* m_renderWindow = nullptr;
|
||||
|
||||
|
||||
382
src/features/editScene/systems/PlayerControllerSystem.cpp
Normal file
382
src/features/editScene/systems/PlayerControllerSystem.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
#include "PlayerControllerSystem.hpp"
|
||||
#include "../EditorApp.hpp"
|
||||
#include "../components/PlayerController.hpp"
|
||||
#include "../components/Character.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "../components/EntityName.hpp"
|
||||
#include "AnimationTreeSystem.hpp"
|
||||
#include "CharacterSlotSystem.hpp"
|
||||
#include "../camera/EditorCamera.hpp"
|
||||
#include <OgreEntity.h>
|
||||
#include <OgreCamera.h>
|
||||
#include <OgreLogManager.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreSkeletonInstance.h>
|
||||
#include <cmath>
|
||||
|
||||
PlayerControllerSystem::PlayerControllerSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr,
|
||||
EditorApp *editorApp)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_editorApp(editorApp)
|
||||
{
|
||||
}
|
||||
|
||||
PlayerControllerSystem::~PlayerControllerSystem()
|
||||
{
|
||||
for (auto &pair : m_states) {
|
||||
shutdownController(pair.second);
|
||||
}
|
||||
m_states.clear();
|
||||
}
|
||||
|
||||
void PlayerControllerSystem::shutdownController(ControllerState &state)
|
||||
{
|
||||
if (state.pivotNode) {
|
||||
m_sceneMgr->destroySceneNode(state.pivotNode);
|
||||
state.pivotNode = nullptr;
|
||||
}
|
||||
if (state.goalNode) {
|
||||
m_sceneMgr->destroySceneNode(state.goalNode);
|
||||
state.goalNode = nullptr;
|
||||
}
|
||||
state.initialized = false;
|
||||
}
|
||||
|
||||
flecs::entity PlayerControllerSystem::findTargetEntity(const Ogre::String &name)
|
||||
{
|
||||
if (name.empty())
|
||||
return flecs::entity::null();
|
||||
|
||||
flecs::entity result = flecs::entity::null();
|
||||
m_world.query<EntityNameComponent>().each(
|
||||
[&](flecs::entity e, EntityNameComponent &en) {
|
||||
if (result.is_alive())
|
||||
return;
|
||||
if (en.name == name)
|
||||
result = e;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void PlayerControllerSystem::initController(flecs::entity controllerEntity,
|
||||
PlayerControllerComponent &pc,
|
||||
ControllerState &state)
|
||||
{
|
||||
(void)controllerEntity;
|
||||
state.targetEntity = findTargetEntity(pc.targetCharacterName);
|
||||
if (!state.targetEntity.is_alive())
|
||||
return;
|
||||
|
||||
if (!state.pivotNode) {
|
||||
state.pivotNode =
|
||||
m_sceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
}
|
||||
if (!state.goalNode) {
|
||||
state.goalNode =
|
||||
m_sceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
}
|
||||
|
||||
state.initialized = true;
|
||||
state.faceHidden = false;
|
||||
}
|
||||
|
||||
void PlayerControllerSystem::update(float deltaTime)
|
||||
{
|
||||
if (!m_editorApp ||
|
||||
m_editorApp->getGameMode() != EditorApp::GameMode::Game ||
|
||||
m_editorApp->getGamePlayState() != EditorApp::GamePlayState::Playing)
|
||||
return;
|
||||
|
||||
m_world.query<PlayerControllerComponent>().each(
|
||||
[&](flecs::entity e, PlayerControllerComponent &pc) {
|
||||
auto it = m_states.find(e.id());
|
||||
if (it == m_states.end()) {
|
||||
ControllerState newState;
|
||||
initController(e, pc, newState);
|
||||
it = m_states.insert({ e.id(), newState }).first;
|
||||
}
|
||||
|
||||
ControllerState &state = it->second;
|
||||
|
||||
// Re-resolve target if name changed or entity invalid
|
||||
if (!state.targetEntity.is_alive()) {
|
||||
initController(e, pc, state);
|
||||
}
|
||||
if (!state.targetEntity.is_alive())
|
||||
return;
|
||||
|
||||
if (!state.targetEntity.has<TransformComponent>())
|
||||
return;
|
||||
|
||||
auto &transform =
|
||||
state.targetEntity.get<TransformComponent>();
|
||||
if (!transform.node)
|
||||
return;
|
||||
|
||||
Ogre::Vector3 charPos =
|
||||
transform.node->_getDerivedPosition();
|
||||
|
||||
// Update camera
|
||||
if (pc.cameraMode == PlayerControllerComponent::TPS) {
|
||||
updateTPSCamera(pc, state, charPos, deltaTime);
|
||||
} else {
|
||||
updateFPSCamera(pc, state, charPos);
|
||||
}
|
||||
|
||||
// Update locomotion
|
||||
updateLocomotion(pc, state, deltaTime);
|
||||
});
|
||||
}
|
||||
|
||||
void PlayerControllerSystem::updateTPSCamera(PlayerControllerComponent &pc,
|
||||
ControllerState &state,
|
||||
const Ogre::Vector3 &charPos,
|
||||
float deltaTime)
|
||||
{
|
||||
if (!state.pivotNode || !state.goalNode)
|
||||
return;
|
||||
|
||||
EditorCamera *editorCam = m_editorApp->getEditorCamera();
|
||||
if (!editorCam)
|
||||
return;
|
||||
Ogre::Camera *cam = editorCam->getCamera();
|
||||
if (!cam)
|
||||
return;
|
||||
Ogre::SceneNode *camNode = cam->getParentSceneNode();
|
||||
if (!camNode)
|
||||
return;
|
||||
|
||||
// Restore face visibility when switching from FPS
|
||||
if (state.faceHidden) {
|
||||
CharacterSlotSystem *css =
|
||||
m_editorApp->getCharacterSlotSystem();
|
||||
if (css) {
|
||||
css->setSlotVisible(state.targetEntity, "face",
|
||||
true);
|
||||
}
|
||||
state.faceHidden = false;
|
||||
}
|
||||
|
||||
// Read mouse input
|
||||
GameInputState &input = m_editorApp->getGameInputState();
|
||||
if (input.mouseMoved) {
|
||||
state.yaw -= input.mouseDeltaX * pc.mouseSensitivity;
|
||||
state.pitch -= input.mouseDeltaY * pc.mouseSensitivity;
|
||||
// Clamp pitch
|
||||
if (state.pitch > 25.0f)
|
||||
state.pitch = 25.0f;
|
||||
if (state.pitch < -60.0f)
|
||||
state.pitch = -60.0f;
|
||||
}
|
||||
|
||||
// Position pivot at character shoulder height
|
||||
Ogre::Vector3 pivotPos = charPos + Ogre::Vector3(0, pc.tpsHeight, 0);
|
||||
state.pivotNode->setPosition(pivotPos);
|
||||
|
||||
// Compute goal position based on yaw/pitch/distance
|
||||
Ogre::Quaternion yawRot(Ogre::Degree(state.yaw),
|
||||
Ogre::Vector3::UNIT_Y);
|
||||
Ogre::Quaternion pitchRot(Ogre::Degree(state.pitch),
|
||||
Ogre::Vector3::UNIT_X);
|
||||
Ogre::Vector3 offset = yawRot * pitchRot *
|
||||
Ogre::Vector3(0, 0, pc.tpsDistance);
|
||||
Ogre::Vector3 goalPos = pivotPos + offset;
|
||||
|
||||
state.goalNode->setPosition(goalPos);
|
||||
|
||||
// Smoothly interpolate camera to goal
|
||||
Ogre::Vector3 currentPos = camNode->getPosition();
|
||||
Ogre::Vector3 newPos = Ogre::Math::lerp(currentPos, goalPos,
|
||||
deltaTime * 9.0f);
|
||||
camNode->setPosition(newPos);
|
||||
camNode->lookAt(pivotPos, Ogre::Node::TS_WORLD);
|
||||
}
|
||||
|
||||
void PlayerControllerSystem::updateFPSCamera(PlayerControllerComponent &pc,
|
||||
ControllerState &state,
|
||||
const Ogre::Vector3 &charPos)
|
||||
{
|
||||
(void)charPos;
|
||||
|
||||
EditorCamera *editorCam = m_editorApp->getEditorCamera();
|
||||
if (!editorCam)
|
||||
return;
|
||||
Ogre::Camera *cam = editorCam->getCamera();
|
||||
if (!cam)
|
||||
return;
|
||||
Ogre::SceneNode *camNode = cam->getParentSceneNode();
|
||||
if (!camNode)
|
||||
return;
|
||||
|
||||
// Find animated entity for bone access
|
||||
AnimationTreeSystem *ats =
|
||||
m_editorApp->getAnimationTreeSystem();
|
||||
if (!ats)
|
||||
return;
|
||||
|
||||
Ogre::Entity *ent = ats->findAnimatedEntity(state.targetEntity);
|
||||
if (!ent || !ent->hasSkeleton()) {
|
||||
// Fallback to TPS if no skeleton
|
||||
pc.cameraMode = PlayerControllerComponent::TPS;
|
||||
return;
|
||||
}
|
||||
|
||||
Ogre::SkeletonInstance *skel = ent->getSkeleton();
|
||||
Ogre::Bone *bone = nullptr;
|
||||
try {
|
||||
bone = skel->getBone(pc.fpsBoneName);
|
||||
} catch (...) {
|
||||
// Try common alternatives
|
||||
const char *alternatives[] = { "Head", "head", "Neck", "neck",
|
||||
"Camera", "camera" };
|
||||
for (const char *name : alternatives) {
|
||||
try {
|
||||
bone = skel->getBone(name);
|
||||
break;
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bone) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"PlayerControllerSystem: Bone " + pc.fpsBoneName +
|
||||
" not found, falling back to TPS");
|
||||
pc.cameraMode = PlayerControllerComponent::TPS;
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide face slot
|
||||
if (!state.faceHidden) {
|
||||
CharacterSlotSystem *css =
|
||||
m_editorApp->getCharacterSlotSystem();
|
||||
if (css) {
|
||||
css->setSlotVisible(state.targetEntity, "face",
|
||||
false);
|
||||
}
|
||||
state.faceHidden = true;
|
||||
}
|
||||
|
||||
// Get character scene node
|
||||
auto &transform =
|
||||
state.targetEntity.get<TransformComponent>();
|
||||
Ogre::SceneNode *charNode = transform.node;
|
||||
|
||||
// Compute bone world transform
|
||||
Ogre::Vector3 boneWorldPos =
|
||||
charNode->_getDerivedOrientation() *
|
||||
bone->_getDerivedPosition() +
|
||||
charNode->_getDerivedPosition();
|
||||
Ogre::Quaternion boneWorldRot =
|
||||
charNode->_getDerivedOrientation() *
|
||||
bone->_getDerivedOrientation();
|
||||
|
||||
// Offset slightly forward
|
||||
Ogre::Vector3 offset = boneWorldRot * Ogre::Vector3(0, 0, 0.15f);
|
||||
|
||||
camNode->setPosition(boneWorldPos + offset);
|
||||
|
||||
// Apply mouse look
|
||||
GameInputState &input = m_editorApp->getGameInputState();
|
||||
if (input.mouseMoved) {
|
||||
state.yaw -= input.mouseDeltaX * pc.mouseSensitivity;
|
||||
state.pitch -= input.mouseDeltaY * pc.mouseSensitivity;
|
||||
if (state.pitch > 89.0f)
|
||||
state.pitch = 89.0f;
|
||||
if (state.pitch < -89.0f)
|
||||
state.pitch = -89.0f;
|
||||
}
|
||||
|
||||
Ogre::Quaternion yawRot(Ogre::Degree(state.yaw),
|
||||
Ogre::Vector3::UNIT_Y);
|
||||
Ogre::Quaternion pitchRot(Ogre::Degree(state.pitch),
|
||||
Ogre::Vector3::UNIT_X);
|
||||
camNode->setOrientation(yawRot * pitchRot);
|
||||
}
|
||||
|
||||
void PlayerControllerSystem::updateLocomotion(PlayerControllerComponent &pc,
|
||||
ControllerState &state,
|
||||
float deltaTime)
|
||||
{
|
||||
(void)deltaTime;
|
||||
if (!state.targetEntity.has<CharacterComponent>())
|
||||
return;
|
||||
|
||||
GameInputState &input = m_editorApp->getGameInputState();
|
||||
auto &cc = state.targetEntity.get_mut<CharacterComponent>();
|
||||
|
||||
// Get camera yaw for relative movement
|
||||
Ogre::Quaternion yawRot(Ogre::Degree(state.yaw),
|
||||
Ogre::Vector3::UNIT_Y);
|
||||
Ogre::Vector3 forward = yawRot * Ogre::Vector3::NEGATIVE_UNIT_Z;
|
||||
Ogre::Vector3 right = yawRot * Ogre::Vector3::UNIT_X;
|
||||
|
||||
// Flatten to horizontal plane
|
||||
forward.y = 0;
|
||||
right.y = 0;
|
||||
if (forward.squaredLength() > 0.0001f)
|
||||
forward.normalise();
|
||||
if (right.squaredLength() > 0.0001f)
|
||||
right.normalise();
|
||||
|
||||
Ogre::Vector3 desiredVel = Ogre::Vector3::ZERO;
|
||||
if (input.w)
|
||||
desiredVel += forward;
|
||||
if (input.s)
|
||||
desiredVel -= forward;
|
||||
if (input.a)
|
||||
desiredVel -= right;
|
||||
if (input.d)
|
||||
desiredVel += right;
|
||||
|
||||
bool isMoving = desiredVel.squaredLength() > 0.0001f;
|
||||
float speed = input.shift ? 5.0f : 2.5f;
|
||||
|
||||
if (isMoving) {
|
||||
desiredVel.normalise();
|
||||
cc.linearVelocity = desiredVel * speed;
|
||||
|
||||
// Rotate character to face movement direction
|
||||
auto &transform =
|
||||
state.targetEntity.get_mut<TransformComponent>();
|
||||
if (transform.node) {
|
||||
Ogre::Vector3 flatForward = desiredVel;
|
||||
flatForward.y = 0;
|
||||
if (flatForward.squaredLength() > 0.0001f) {
|
||||
flatForward.normalise();
|
||||
Ogre::Quaternion targetRot = Ogre::Vector3::NEGATIVE_UNIT_Z.getRotationTo(
|
||||
flatForward);
|
||||
Ogre::Quaternion currentRot = transform.node->getOrientation();
|
||||
transform.node->setOrientation(
|
||||
Ogre::Quaternion::Slerp(deltaTime * 10.0f,
|
||||
currentRot,
|
||||
targetRot,
|
||||
true));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
}
|
||||
|
||||
// Update animation state
|
||||
AnimationTreeSystem *ats =
|
||||
m_editorApp->getAnimationTreeSystem();
|
||||
if (!ats)
|
||||
return;
|
||||
|
||||
Ogre::String animState;
|
||||
if (!isMoving) {
|
||||
animState = pc.idleState;
|
||||
} else if (input.shift) {
|
||||
animState = pc.runState;
|
||||
} else {
|
||||
animState = pc.walkState;
|
||||
}
|
||||
|
||||
if (!animState.empty()) {
|
||||
ats->setState(state.targetEntity,
|
||||
pc.locomotionStateMachine, animState);
|
||||
}
|
||||
}
|
||||
61
src/features/editScene/systems/PlayerControllerSystem.hpp
Normal file
61
src/features/editScene/systems/PlayerControllerSystem.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef EDITSCENE_PLAYERCONTROLLERSYSTEM_HPP
|
||||
#define EDITSCENE_PLAYERCONTROLLERSYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include <memory>
|
||||
|
||||
#include "../components/PlayerController.hpp"
|
||||
|
||||
class EditorApp;
|
||||
class AnimationTreeSystem;
|
||||
class CharacterSlotSystem;
|
||||
|
||||
/**
|
||||
* System that handles player input, camera control (FPS/TPS),
|
||||
* character locomotion, and animation state setting in game mode.
|
||||
* Only active when EditorApp is in GameMode::Game and GamePlayState::Playing.
|
||||
*/
|
||||
class PlayerControllerSystem {
|
||||
public:
|
||||
PlayerControllerSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr,
|
||||
EditorApp *editorApp);
|
||||
~PlayerControllerSystem();
|
||||
|
||||
void update(float deltaTime);
|
||||
|
||||
private:
|
||||
struct ControllerState {
|
||||
flecs::entity targetEntity = flecs::entity::null();
|
||||
float yaw = 0.0f;
|
||||
float pitch = 0.0f;
|
||||
Ogre::SceneNode *pivotNode = nullptr;
|
||||
Ogre::SceneNode *goalNode = nullptr;
|
||||
bool initialized = false;
|
||||
bool faceHidden = false;
|
||||
};
|
||||
|
||||
void initController(flecs::entity controllerEntity,
|
||||
PlayerControllerComponent &pc,
|
||||
ControllerState &state);
|
||||
void shutdownController(ControllerState &state);
|
||||
flecs::entity findTargetEntity(const Ogre::String &name);
|
||||
void updateTPSCamera(PlayerControllerComponent &pc,
|
||||
ControllerState &state,
|
||||
const Ogre::Vector3 &charPos, float deltaTime);
|
||||
void updateFPSCamera(PlayerControllerComponent &pc,
|
||||
ControllerState &state,
|
||||
const Ogre::Vector3 &charPos);
|
||||
void updateLocomotion(PlayerControllerComponent &pc,
|
||||
ControllerState &state, float deltaTime);
|
||||
|
||||
flecs::world &m_world;
|
||||
Ogre::SceneManager *m_sceneMgr;
|
||||
EditorApp *m_editorApp;
|
||||
|
||||
std::unordered_map<flecs::entity_t, ControllerState> m_states;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_PLAYERCONTROLLERSYSTEM_HPP
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "../components/Character.hpp"
|
||||
#include "../components/CharacterSlots.hpp"
|
||||
#include "../components/AnimationTree.hpp"
|
||||
#include "../components/StartupMenu.hpp"
|
||||
#include "../components/PlayerController.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include "../components/GeneratedPhysicsTag.hpp"
|
||||
#include "EditorUISystem.hpp"
|
||||
@@ -192,6 +194,14 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
if (entity.has<AnimationTreeComponent>()) {
|
||||
json["animationTree"] = serializeAnimationTree(entity);
|
||||
}
|
||||
|
||||
if (entity.has<StartupMenuComponent>()) {
|
||||
json["startupMenu"] = serializeStartupMenu(entity);
|
||||
}
|
||||
|
||||
if (entity.has<PlayerControllerComponent>()) {
|
||||
json["playerController"] = serializePlayerController(entity);
|
||||
}
|
||||
|
||||
// CellGrid/Town components
|
||||
if (entity.has<CellGridComponent>()) {
|
||||
@@ -321,6 +331,14 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
|
||||
deserializeAnimationTree(entity, json["animationTree"]);
|
||||
}
|
||||
|
||||
if (json.contains("startupMenu")) {
|
||||
deserializeStartupMenu(entity, json["startupMenu"]);
|
||||
}
|
||||
|
||||
if (json.contains("playerController")) {
|
||||
deserializePlayerController(entity, json["playerController"]);
|
||||
}
|
||||
|
||||
if (json.contains("triangleBuffer")) {
|
||||
deserializeTriangleBuffer(entity, json["triangleBuffer"]);
|
||||
}
|
||||
@@ -1960,3 +1978,67 @@ void SceneSerializer::deserializeClearArea(flecs::entity entity, const nlohmann:
|
||||
clearArea.markDirty();
|
||||
entity.set<ClearAreaComponent>(clearArea);
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json SceneSerializer::serializeStartupMenu(flecs::entity entity)
|
||||
{
|
||||
auto &sm = entity.get<StartupMenuComponent>();
|
||||
nlohmann::json json;
|
||||
json["fontName"] = sm.fontName;
|
||||
json["fontSize"] = sm.fontSize;
|
||||
json["newGameScene"] = sm.newGameScene;
|
||||
json["showLoadGame"] = sm.showLoadGame;
|
||||
json["showOptions"] = sm.showOptions;
|
||||
json["showQuit"] = sm.showQuit;
|
||||
return json;
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializePlayerController(flecs::entity entity)
|
||||
{
|
||||
auto &pc = entity.get<PlayerControllerComponent>();
|
||||
nlohmann::json json;
|
||||
json["cameraMode"] = pc.cameraMode;
|
||||
json["targetCharacterName"] = pc.targetCharacterName;
|
||||
json["fpsBoneName"] = pc.fpsBoneName;
|
||||
json["tpsDistance"] = pc.tpsDistance;
|
||||
json["tpsHeight"] = pc.tpsHeight;
|
||||
json["mouseSensitivity"] = pc.mouseSensitivity;
|
||||
json["locomotionStateMachine"] = pc.locomotionStateMachine;
|
||||
json["idleState"] = pc.idleState;
|
||||
json["walkState"] = pc.walkState;
|
||||
json["runState"] = pc.runState;
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeStartupMenu(flecs::entity entity,
|
||||
const nlohmann::json &json)
|
||||
{
|
||||
StartupMenuComponent sm;
|
||||
sm.fontName = json.value("fontName", sm.fontName);
|
||||
sm.fontSize = json.value("fontSize", sm.fontSize);
|
||||
sm.newGameScene = json.value("newGameScene", sm.newGameScene);
|
||||
sm.showLoadGame = json.value("showLoadGame", sm.showLoadGame);
|
||||
sm.showOptions = json.value("showOptions", sm.showOptions);
|
||||
sm.showQuit = json.value("showQuit", sm.showQuit);
|
||||
entity.set<StartupMenuComponent>(sm);
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializePlayerController(flecs::entity entity,
|
||||
const nlohmann::json &json)
|
||||
{
|
||||
PlayerControllerComponent pc;
|
||||
pc.cameraMode = json.value("cameraMode", pc.cameraMode);
|
||||
pc.targetCharacterName = json.value("targetCharacterName",
|
||||
pc.targetCharacterName);
|
||||
pc.fpsBoneName = json.value("fpsBoneName", pc.fpsBoneName);
|
||||
pc.tpsDistance = json.value("tpsDistance", pc.tpsDistance);
|
||||
pc.tpsHeight = json.value("tpsHeight", pc.tpsHeight);
|
||||
pc.mouseSensitivity = json.value("mouseSensitivity",
|
||||
pc.mouseSensitivity);
|
||||
pc.locomotionStateMachine = json.value("locomotionStateMachine",
|
||||
pc.locomotionStateMachine);
|
||||
pc.idleState = json.value("idleState", pc.idleState);
|
||||
pc.walkState = json.value("walkState", pc.walkState);
|
||||
pc.runState = json.value("runState", pc.runState);
|
||||
entity.set<PlayerControllerComponent>(pc);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ private:
|
||||
nlohmann::json serializeCharacter(flecs::entity entity);
|
||||
nlohmann::json serializeCharacterSlots(flecs::entity entity);
|
||||
nlohmann::json serializeAnimationTree(flecs::entity entity);
|
||||
nlohmann::json serializeStartupMenu(flecs::entity entity);
|
||||
nlohmann::json serializePlayerController(flecs::entity entity);
|
||||
|
||||
// CellGrid/Town component serialization
|
||||
nlohmann::json serializeCellGrid(flecs::entity entity);
|
||||
@@ -87,6 +89,8 @@ private:
|
||||
void deserializeCharacter(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeCharacterSlots(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeAnimationTree(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeStartupMenu(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializePlayerController(flecs::entity entity, const nlohmann::json& json);
|
||||
|
||||
// CellGrid/Town component deserialization
|
||||
void deserializeCellGrid(flecs::entity entity, const nlohmann::json& json);
|
||||
|
||||
217
src/features/editScene/systems/StartupMenuSystem.cpp
Normal file
217
src/features/editScene/systems/StartupMenuSystem.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "StartupMenuSystem.hpp"
|
||||
#include "../EditorApp.hpp"
|
||||
#include "../components/StartupMenu.hpp"
|
||||
#include <imgui.h>
|
||||
#include <OgreFontManager.h>
|
||||
#include <OgreImGuiOverlay.h>
|
||||
#include <OgreLogManager.h>
|
||||
#include <OgreOverlayManager.h>
|
||||
|
||||
StartupMenuSystem::StartupMenuSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr,
|
||||
EditorApp *editorApp)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_editorApp(editorApp)
|
||||
{
|
||||
}
|
||||
|
||||
StartupMenuSystem::~StartupMenuSystem() = default;
|
||||
|
||||
void StartupMenuSystem::ensureFontLoaded(const Ogre::String &fontName,
|
||||
float fontSize)
|
||||
{
|
||||
if (m_fontLoaded && m_currentFontName == fontName &&
|
||||
m_currentFontSize == fontSize)
|
||||
return;
|
||||
|
||||
Ogre::ImGuiOverlay *overlay = m_editorApp->getImGuiOverlay();
|
||||
if (!overlay)
|
||||
return;
|
||||
|
||||
// Try to load via Ogre font manager
|
||||
Ogre::FontPtr font;
|
||||
try {
|
||||
if (Ogre::FontManager::getSingleton().resourceExists(
|
||||
"StartupMenuFont", "General")) {
|
||||
Ogre::FontManager::getSingleton().remove(
|
||||
"StartupMenuFont", "General");
|
||||
}
|
||||
font = Ogre::FontManager::getSingleton().create(
|
||||
"StartupMenuFont", "General");
|
||||
font->setType(Ogre::FontType::FT_TRUETYPE);
|
||||
font->setSource(fontName);
|
||||
font->setTrueTypeSize(fontSize);
|
||||
font->setTrueTypeResolution(75);
|
||||
font->addCodePointRange(Ogre::Font::CodePointRange(32, 255));
|
||||
font->load();
|
||||
} catch (...) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"StartupMenuSystem: Failed to load font " + fontName);
|
||||
m_menuFont = nullptr;
|
||||
m_fontLoaded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_menuFont = overlay->addFont("StartupMenuFont", "General");
|
||||
m_currentFontName = fontName;
|
||||
m_currentFontSize = fontSize;
|
||||
m_fontLoaded = true;
|
||||
}
|
||||
|
||||
void StartupMenuSystem::prepareFont()
|
||||
{
|
||||
// Must be called BEFORE Ogre::ImGuiOverlay::show() so that the font
|
||||
// is added to the atlas before OGRE builds it in createFontTexture().
|
||||
if (!m_editorApp)
|
||||
return;
|
||||
|
||||
// Find an entity with StartupMenuComponent
|
||||
flecs::entity menuEntity = flecs::entity::null();
|
||||
m_world.query<StartupMenuComponent>().each(
|
||||
[&](flecs::entity e, StartupMenuComponent &) {
|
||||
if (!menuEntity.is_alive())
|
||||
menuEntity = e;
|
||||
});
|
||||
|
||||
if (menuEntity.is_alive()) {
|
||||
auto &sm = menuEntity.get_mut<StartupMenuComponent>();
|
||||
ensureFontLoaded(sm.fontName, sm.fontSize);
|
||||
}
|
||||
}
|
||||
|
||||
void StartupMenuSystem::update(float deltaTime)
|
||||
{
|
||||
(void)deltaTime;
|
||||
|
||||
if (!m_editorApp ||
|
||||
m_editorApp->getGamePlayState() != EditorApp::GamePlayState::Menu)
|
||||
return;
|
||||
|
||||
// Find an entity with StartupMenuComponent
|
||||
flecs::entity menuEntity = flecs::entity::null();
|
||||
m_world.query<StartupMenuComponent>().each(
|
||||
[&](flecs::entity e, StartupMenuComponent &) {
|
||||
if (!menuEntity.is_alive())
|
||||
menuEntity = e;
|
||||
});
|
||||
|
||||
if (!menuEntity.is_alive()) {
|
||||
// No startup menu entity configured
|
||||
renderMissingMenuError();
|
||||
return;
|
||||
}
|
||||
|
||||
auto &sm = menuEntity.get_mut<StartupMenuComponent>();
|
||||
renderMenu(sm);
|
||||
}
|
||||
|
||||
void StartupMenuSystem::renderMenu(StartupMenuComponent &sm)
|
||||
{
|
||||
|
||||
ImVec2 size = ImGui::GetMainViewport()->Size;
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(size.x, size.y), ImGuiCond_Always);
|
||||
|
||||
ImVec4 solidColor = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, solidColor);
|
||||
|
||||
ImGui::Begin("StartupMenu", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoInputs);
|
||||
|
||||
if (m_menuFont)
|
||||
ImGui::PushFont(m_menuFont);
|
||||
|
||||
struct ButtonData {
|
||||
const char *label;
|
||||
std::function<void()> action;
|
||||
};
|
||||
|
||||
std::vector<ButtonData> buttons;
|
||||
buttons.push_back({ "NEW GAME", [&]() {
|
||||
m_editorApp->startNewGame(sm.newGameScene);
|
||||
} });
|
||||
|
||||
if (sm.showLoadGame)
|
||||
buttons.push_back({ "LOAD GAME", [&]() {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Load game not implemented");
|
||||
} });
|
||||
|
||||
if (sm.showOptions)
|
||||
buttons.push_back({ "OPTIONS", [&]() {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"Options not implemented");
|
||||
} });
|
||||
|
||||
if (sm.showQuit)
|
||||
buttons.push_back({ "QUIT", [&]() {
|
||||
Ogre::Root::getSingleton().queueEndRendering();
|
||||
} });
|
||||
|
||||
// Calculate button dimensions
|
||||
float buttonWidth = 0.0f;
|
||||
float buttonsHeight = 0.0f;
|
||||
float extraPixels = 20.0f;
|
||||
for (const auto &b : buttons) {
|
||||
ImVec2 textSize = ImGui::CalcTextSize(b.label);
|
||||
float bwidth = textSize.x +
|
||||
(ImGui::GetStyle().FramePadding.x * 2.0f) +
|
||||
extraPixels;
|
||||
float bheight = textSize.y +
|
||||
(ImGui::GetStyle().FramePadding.y * 2.0f);
|
||||
if (buttonWidth < bwidth)
|
||||
buttonWidth = bwidth;
|
||||
buttonsHeight += bheight +
|
||||
ImGui::GetStyle().ItemSpacing.y;
|
||||
}
|
||||
if (!buttons.empty())
|
||||
buttonsHeight -= ImGui::GetStyle().ItemSpacing.y;
|
||||
|
||||
ImGui::SetCursorPosY((size.y - buttonsHeight) * 0.5f);
|
||||
for (const auto &b : buttons) {
|
||||
ImGui::SetCursorPosX((size.x - buttonWidth) * 0.5f);
|
||||
if (ImGui::Button(b.label, ImVec2(buttonWidth, 0)))
|
||||
b.action();
|
||||
}
|
||||
|
||||
if (m_menuFont)
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void StartupMenuSystem::renderMissingMenuError()
|
||||
{
|
||||
ImVec2 size = ImGui::GetMainViewport()->Size;
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(size.x, size.y), ImGuiCond_Always);
|
||||
|
||||
ImVec4 solidColor = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, solidColor);
|
||||
|
||||
ImGui::Begin("StartupMenuError", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoInputs);
|
||||
|
||||
const char *msg = "Error: No StartupMenuComponent entity found.\n"
|
||||
"Make sure startup_menu.json is loaded and contains\n"
|
||||
"an entity with a StartupMenuComponent.";
|
||||
ImVec2 textSize = ImGui::CalcTextSize(msg);
|
||||
ImGui::SetCursorPosX((size.x - textSize.x) * 0.5f);
|
||||
ImGui::SetCursorPosY((size.y - textSize.y) * 0.5f);
|
||||
ImGui::TextUnformatted(msg);
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
47
src/features/editScene/systems/StartupMenuSystem.hpp
Normal file
47
src/features/editScene/systems/StartupMenuSystem.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef EDITSCENE_STARTUPMENUSYSTEM_HPP
|
||||
#define EDITSCENE_STARTUPMENUSYSTEM_HPP
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include <imgui.h>
|
||||
#include <memory>
|
||||
|
||||
#include "../components/StartupMenu.hpp"
|
||||
|
||||
class EditorApp;
|
||||
|
||||
/**
|
||||
* System that renders the full-screen startup menu in game mode.
|
||||
* Only active when EditorApp is in GameMode::Game and GamePlayState::Menu.
|
||||
*/
|
||||
class StartupMenuSystem {
|
||||
public:
|
||||
StartupMenuSystem(flecs::world &world, Ogre::SceneManager *sceneMgr,
|
||||
EditorApp *editorApp);
|
||||
~StartupMenuSystem();
|
||||
|
||||
void update(float deltaTime);
|
||||
|
||||
/**
|
||||
* Pre-load the menu font before ImGui NewFrame().
|
||||
* Must be called outside an active ImGui frame (before NewFrame).
|
||||
*/
|
||||
void prepareFont();
|
||||
|
||||
private:
|
||||
void renderMenu(StartupMenuComponent &sm);
|
||||
void renderMissingMenuError();
|
||||
void ensureFontLoaded(const Ogre::String &fontName, float fontSize);
|
||||
|
||||
flecs::world &m_world;
|
||||
Ogre::SceneManager *m_sceneMgr;
|
||||
EditorApp *m_editorApp;
|
||||
|
||||
bool m_fontLoaded = false;
|
||||
Ogre::String m_currentFontName;
|
||||
float m_currentFontSize = 0.0f;
|
||||
ImFont *m_menuFont = nullptr;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_STARTUPMENUSYSTEM_HPP
|
||||
85
src/features/editScene/ui/PlayerControllerEditor.cpp
Normal file
85
src/features/editScene/ui/PlayerControllerEditor.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "PlayerControllerEditor.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
bool PlayerControllerEditor::renderComponent(flecs::entity entity,
|
||||
PlayerControllerComponent &pc)
|
||||
{
|
||||
(void)entity;
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Player Controller",
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
const char *modes[] = { "Third Person", "First Person" };
|
||||
int mode = pc.cameraMode;
|
||||
if (ImGui::Combo("Camera Mode", &mode, modes, 2)) {
|
||||
pc.cameraMode = mode;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
char nameBuf[256];
|
||||
snprintf(nameBuf, sizeof(nameBuf), "%s",
|
||||
pc.targetCharacterName.c_str());
|
||||
if (ImGui::InputText("Target Character Name", nameBuf,
|
||||
sizeof(nameBuf))) {
|
||||
pc.targetCharacterName = nameBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
char boneBuf[256];
|
||||
snprintf(boneBuf, sizeof(boneBuf), "%s",
|
||||
pc.fpsBoneName.c_str());
|
||||
if (ImGui::InputText("FPS Bone Name", boneBuf,
|
||||
sizeof(boneBuf))) {
|
||||
pc.fpsBoneName = boneBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::DragFloat("TPS Distance", &pc.tpsDistance, 0.1f,
|
||||
0.5f, 20.0f))
|
||||
modified = true;
|
||||
if (ImGui::DragFloat("TPS Height", &pc.tpsHeight, 0.1f,
|
||||
0.0f, 10.0f))
|
||||
modified = true;
|
||||
if (ImGui::DragFloat("Mouse Sensitivity", &pc.mouseSensitivity,
|
||||
0.01f, 0.01f, 2.0f))
|
||||
modified = true;
|
||||
|
||||
char smBuf[256];
|
||||
snprintf(smBuf, sizeof(smBuf), "%s",
|
||||
pc.locomotionStateMachine.c_str());
|
||||
if (ImGui::InputText("Locomotion SM", smBuf, sizeof(smBuf))) {
|
||||
pc.locomotionStateMachine = smBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
char idleBuf[256];
|
||||
snprintf(idleBuf, sizeof(idleBuf), "%s",
|
||||
pc.idleState.c_str());
|
||||
if (ImGui::InputText("Idle State", idleBuf, sizeof(idleBuf))) {
|
||||
pc.idleState = idleBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
char walkBuf[256];
|
||||
snprintf(walkBuf, sizeof(walkBuf), "%s",
|
||||
pc.walkState.c_str());
|
||||
if (ImGui::InputText("Walk State", walkBuf, sizeof(walkBuf))) {
|
||||
pc.walkState = walkBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
char runBuf[256];
|
||||
snprintf(runBuf, sizeof(runBuf), "%s",
|
||||
pc.runState.c_str());
|
||||
if (ImGui::InputText("Run State", runBuf, sizeof(runBuf))) {
|
||||
pc.runState = runBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
18
src/features/editScene/ui/PlayerControllerEditor.hpp
Normal file
18
src/features/editScene/ui/PlayerControllerEditor.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef EDITSCENE_PLAYERCONTROLLEREDITOR_HPP
|
||||
#define EDITSCENE_PLAYERCONTROLLEREDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/PlayerController.hpp"
|
||||
|
||||
/**
|
||||
* Editor for PlayerControllerComponent
|
||||
*/
|
||||
class PlayerControllerEditor : public ComponentEditor<PlayerControllerComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity,
|
||||
PlayerControllerComponent &pc) override;
|
||||
const char *getName() const override { return "Player Controller"; }
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_PLAYERCONTROLLEREDITOR_HPP
|
||||
48
src/features/editScene/ui/StartupMenuEditor.cpp
Normal file
48
src/features/editScene/ui/StartupMenuEditor.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "StartupMenuEditor.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
bool StartupMenuEditor::renderComponent(flecs::entity entity,
|
||||
StartupMenuComponent &sm)
|
||||
{
|
||||
(void)entity;
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Startup Menu",
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
char fontNameBuf[256];
|
||||
snprintf(fontNameBuf, sizeof(fontNameBuf), "%s",
|
||||
sm.fontName.c_str());
|
||||
if (ImGui::InputText("Font Name", fontNameBuf,
|
||||
sizeof(fontNameBuf))) {
|
||||
sm.fontName = fontNameBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::DragFloat("Font Size", &sm.fontSize, 0.5f, 8.0f,
|
||||
128.0f)) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
char sceneBuf[256];
|
||||
snprintf(sceneBuf, sizeof(sceneBuf), "%s",
|
||||
sm.newGameScene.c_str());
|
||||
if (ImGui::InputText("New Game Scene", sceneBuf,
|
||||
sizeof(sceneBuf))) {
|
||||
sm.newGameScene = sceneBuf;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Show Load Game", &sm.showLoadGame))
|
||||
modified = true;
|
||||
if (ImGui::Checkbox("Show Options", &sm.showOptions))
|
||||
modified = true;
|
||||
if (ImGui::Checkbox("Show Quit", &sm.showQuit))
|
||||
modified = true;
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
18
src/features/editScene/ui/StartupMenuEditor.hpp
Normal file
18
src/features/editScene/ui/StartupMenuEditor.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef EDITSCENE_STARTUPMENUEDITOR_HPP
|
||||
#define EDITSCENE_STARTUPMENUEDITOR_HPP
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include "../components/StartupMenu.hpp"
|
||||
|
||||
/**
|
||||
* Editor for StartupMenuComponent
|
||||
*/
|
||||
class StartupMenuEditor : public ComponentEditor<StartupMenuComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity,
|
||||
StartupMenuComponent &sm) override;
|
||||
const char *getName() const override { return "Startup Menu"; }
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_STARTUPMENUEDITOR_HPP
|
||||
Reference in New Issue
Block a user