Character display

This commit is contained in:
2026-04-19 18:19:48 +03:00
parent e2960d67e4
commit a392eb0bf9
12 changed files with 602 additions and 0 deletions

View File

@@ -25,6 +25,7 @@ set(EDITSCENE_SOURCES
systems/CellGridSystem.cpp
systems/RoomLayoutSystem.cpp
systems/FurnitureLibrary.cpp
systems/CharacterSlotSystem.cpp
ui/TransformEditor.cpp
ui/RenderableEditor.cpp
ui/PhysicsColliderEditor.cpp
@@ -39,6 +40,7 @@ set(EDITSCENE_SOURCES
ui/ProceduralMaterialEditor.cpp
ui/PrimitiveEditor.cpp
ui/TriangleBufferEditor.cpp
ui/CharacterSlotsEditor.cpp
ui/CellGridEditor.cpp
ui/LotEditor.cpp
ui/DistrictEditor.cpp
@@ -58,6 +60,7 @@ set(EDITSCENE_SOURCES
components/ProceduralMaterialModule.cpp
components/PrimitiveModule.cpp
components/TriangleBufferModule.cpp
components/CharacterSlotsModule.cpp
components/CellGridModule.cpp
components/CellGridEditorsModule.cpp
components/CellGrid.cpp
@@ -84,6 +87,7 @@ set(EDITSCENE_HEADERS
components/ProceduralMaterial.hpp
components/Primitive.hpp
components/TriangleBuffer.hpp
components/CharacterSlots.hpp
components/CellGrid.hpp
systems/EditorUISystem.hpp
systems/CellGridSystem.hpp
@@ -91,6 +95,7 @@ set(EDITSCENE_HEADERS
systems/FurnitureLibrary.hpp
systems/ProceduralMaterialSystem.hpp
systems/ProceduralMeshSystem.hpp
systems/CharacterSlotSystem.hpp
systems/ProceduralTextureSystem.hpp
systems/StaticGeometrySystem.hpp
systems/SceneSerializer.hpp
@@ -115,6 +120,7 @@ set(EDITSCENE_HEADERS
ui/ProceduralMaterialEditor.hpp
ui/PrimitiveEditor.hpp
ui/TriangleBufferEditor.hpp
ui/CharacterSlotsEditor.hpp
ui/CellGridEditor.hpp
ui/LotEditor.hpp
ui/DistrictEditor.hpp

View File

@@ -9,6 +9,7 @@
#include "systems/ProceduralTextureSystem.hpp"
#include "systems/ProceduralMaterialSystem.hpp"
#include "systems/ProceduralMeshSystem.hpp"
#include "systems/CharacterSlotSystem.hpp"
#include "systems/CellGridSystem.hpp"
#include "systems/RoomLayoutSystem.hpp"
#include "camera/EditorCamera.hpp"
@@ -29,6 +30,7 @@
#include "components/ProceduralMaterial.hpp"
#include "components/Primitive.hpp"
#include "components/TriangleBuffer.hpp"
#include "components/CharacterSlots.hpp"
#include "components/CellGrid.hpp"
#include "components/CellGridModule.hpp"
#include <OgreRTShaderSystem.h>
@@ -119,6 +121,7 @@ EditorApp::~EditorApp()
}
// Release all systems
m_characterSlotSystem.reset();
m_proceduralMeshSystem.reset();
m_proceduralMaterialSystem.reset();
m_proceduralTextureSystem.reset();
@@ -223,6 +226,11 @@ void EditorApp::setup()
m_world, m_sceneMgr);
m_proceduralMeshSystem->initialize();
// Setup CharacterSlot system
m_characterSlotSystem = std::make_unique<CharacterSlotSystem>(
m_world, m_sceneMgr);
m_characterSlotSystem->initialize();
// Setup CellGrid system
m_cellGridSystem =
std::make_unique<CellGridSystem>(m_world, m_sceneMgr);
@@ -289,6 +297,9 @@ void EditorApp::setupECS()
m_world.component<PrimitiveComponent>();
m_world.component<TriangleBufferComponent>();
// Register CharacterSlots component
m_world.component<CharacterSlotsComponent>();
// Register CellGrid/Town components
CellGridModule::registerComponents(m_world);
}
@@ -449,6 +460,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
m_proceduralMaterialSystem->update();
}
// Update CharacterSlot system
if (m_characterSlotSystem) {
m_characterSlotSystem->update();
}
// Update ProceduralMesh system
if (m_proceduralMeshSystem) {
m_proceduralMeshSystem->update();

View File

@@ -21,6 +21,7 @@ class StaticGeometrySystem;
class ProceduralTextureSystem;
class ProceduralMaterialSystem;
class ProceduralMeshSystem;
class CharacterSlotSystem;
class CellGridSystem;
class RoomLayoutSystem;
@@ -116,6 +117,7 @@ private:
std::unique_ptr<ProceduralTextureSystem> m_proceduralTextureSystem;
std::unique_ptr<ProceduralMaterialSystem> m_proceduralMaterialSystem;
std::unique_ptr<ProceduralMeshSystem> m_proceduralMeshSystem;
std::unique_ptr<CharacterSlotSystem> m_characterSlotSystem;
std::unique_ptr<CellGridSystem> m_cellGridSystem;
std::unique_ptr<RoomLayoutSystem> m_roomLayoutSystem;

View File

@@ -0,0 +1,20 @@
#ifndef EDITSCENE_CHARACTERSLOTS_HPP
#define EDITSCENE_CHARACTERSLOTS_HPP
#pragma once
#include <Ogre.h>
#include <unordered_map>
/**
* Multi-slot mesh component for character parts sharing a skeleton.
* The "face" slot (or first available slot) serves as the master skeleton.
*/
struct CharacterSlotsComponent {
Ogre::String age = "adult";
Ogre::String sex = "male";
std::unordered_map<Ogre::String, Ogre::String> slots;
bool dirty = true;
CharacterSlotsComponent() = default;
};
#endif // EDITSCENE_CHARACTERSLOTS_HPP

View File

@@ -0,0 +1,35 @@
#include "CharacterSlots.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/CharacterSlotsEditor.hpp"
#include "../systems/CharacterSlotSystem.hpp"
#include "Transform.hpp"
REGISTER_COMPONENT_GROUP("Character Slots", "Rendering",
CharacterSlotsComponent, CharacterSlotsEditor)
{
CharacterSlotSystem::loadCatalog();
registry.registerComponent<CharacterSlotsComponent>(
"Character Slots",
"Rendering",
std::make_unique<CharacterSlotsEditor>(sceneMgr),
/* Adder */
[sceneMgr](flecs::entity e) {
if (!e.has<TransformComponent>()) {
TransformComponent transform;
transform.node =
sceneMgr->getRootSceneNode()
->createChildSceneNode();
e.set<TransformComponent>(transform);
}
CharacterSlotsComponent cs;
cs.dirty = true;
e.set<CharacterSlotsComponent>(cs);
},
/* Remover */
[sceneMgr](flecs::entity e) {
(void)sceneMgr;
e.remove<CharacterSlotsComponent>();
}
);
}

View File

@@ -0,0 +1,273 @@
#include "CharacterSlotSystem.hpp"
#include "../components/Transform.hpp"
#include <OgreDataStream.h>
#include <OgreEntity.h>
#include <OgreLogManager.h>
#include <OgreResourceGroupManager.h>
#include <OgreSceneNode.h>
#include <iostream>
bool CharacterSlotSystem::s_catalogLoaded = false;
nlohmann::json CharacterSlotSystem::s_bodyParts = nlohmann::json::object();
std::set<Ogre::String> CharacterSlotSystem::s_meshNames;
CharacterSlotSystem::CharacterSlotSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
{
m_world.observer<CharacterSlotsComponent>("CharacterSlotsCleanup")
.event(flecs::OnRemove)
.each([this](flecs::entity e, CharacterSlotsComponent &) {
destroyCharacterParts(e);
});
}
CharacterSlotSystem::~CharacterSlotSystem()
{
std::vector<flecs::entity_t> toRemove;
for (auto &pair : m_entities)
toRemove.push_back(pair.first);
for (auto id : toRemove)
destroyCharacterParts(m_world.entity(id));
}
void CharacterSlotSystem::initialize()
{
if (m_initialized)
return;
loadCatalog();
m_initialized = true;
}
void CharacterSlotSystem::loadCatalog()
{
if (s_catalogLoaded)
return;
s_bodyParts = nlohmann::json::object();
s_meshNames.clear();
Ogre::ResourceGroupManager &rgm =
Ogre::ResourceGroupManager::getSingleton();
Ogre::StringVector groups = rgm.getResourceGroups();
std::vector<Ogre::String> partNames;
for (const auto &group : groups) {
Ogre::StringVectorPtr names =
rgm.findResourceNames(group, "body_part_*.json");
if (names)
partNames.insert(partNames.end(), names->begin(),
names->end());
}
for (const auto &name : partNames) {
Ogre::String group = rgm.findGroupContainingResource(name);
Ogre::DataStreamPtr stream = rgm.openResource(name, group);
Ogre::String jsonStr = stream->getAsString();
try {
nlohmann::json jdata = nlohmann::json::parse(jsonStr);
if (!jdata.contains("age") || !jdata.contains("sex") ||
!jdata.contains("slot") || !jdata.contains("mesh"))
continue;
Ogre::String age = jdata["age"].get<Ogre::String>();
Ogre::String sex = jdata["sex"].get<Ogre::String>();
Ogre::String slot = jdata["slot"].get<Ogre::String>();
Ogre::String mesh = jdata["mesh"].get<Ogre::String>();
if (!s_bodyParts.contains(age))
s_bodyParts[age] = nlohmann::json::object();
if (!s_bodyParts[age].contains(sex))
s_bodyParts[age][sex] = nlohmann::json::object();
if (!s_bodyParts[age][sex].contains(slot))
s_bodyParts[age][sex][slot] =
nlohmann::json::array();
s_bodyParts[age][sex][slot].push_back(mesh);
s_meshNames.insert(mesh);
/* Preload mesh into Characters group */
try {
Ogre::MeshManager::getSingleton().load(mesh,
"Characters");
} catch (...) {
}
} catch (...) {
continue;
}
}
s_catalogLoaded = true;
}
bool CharacterSlotSystem::isCatalogLoaded()
{
return s_catalogLoaded;
}
std::vector<Ogre::String> CharacterSlotSystem::getAges()
{
std::vector<Ogre::String> ages;
if (!s_catalogLoaded)
return ages;
for (auto &el : s_bodyParts.items())
ages.push_back(el.key());
return ages;
}
std::vector<Ogre::String> CharacterSlotSystem::getSexes(
const Ogre::String &age)
{
std::vector<Ogre::String> sexes;
if (!s_catalogLoaded || !s_bodyParts.contains(age))
return sexes;
for (auto &el : s_bodyParts[age].items())
sexes.push_back(el.key());
return sexes;
}
std::vector<Ogre::String> CharacterSlotSystem::getSlots(
const Ogre::String &age, const Ogre::String &sex)
{
std::vector<Ogre::String> slots;
if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
!s_bodyParts[age].contains(sex))
return slots;
for (auto &el : s_bodyParts[age][sex].items())
slots.push_back(el.key());
return slots;
}
std::vector<Ogre::String> CharacterSlotSystem::getMeshes(
const Ogre::String &age, const Ogre::String &sex,
const Ogre::String &slot)
{
std::vector<Ogre::String> meshes;
if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
!s_bodyParts[age].contains(sex) ||
!s_bodyParts[age][sex].contains(slot))
return meshes;
for (auto &m : s_bodyParts[age][sex][slot])
meshes.push_back(m.get<Ogre::String>());
return meshes;
}
void CharacterSlotSystem::update()
{
if (!m_initialized)
return;
m_world.query<CharacterSlotsComponent>().each(
[this](flecs::entity e, CharacterSlotsComponent &cs) {
if (cs.dirty) {
std::cout << "CharacterSlotSystem: building entity "
<< e.id() << std::endl;
buildCharacter(e, cs);
cs.dirty = false;
}
});
}
void CharacterSlotSystem::buildCharacter(flecs::entity e,
CharacterSlotsComponent &cs)
{
std::cout << "CharacterSlotSystem::buildCharacter: entity=" << e.id()
<< " age=" << cs.age << " sex=" << cs.sex
<< " slots=" << cs.slots.size() << std::endl;
destroyCharacterParts(e);
if (!e.has<TransformComponent>()) {
std::cout << " no TransformComponent" << std::endl;
return;
}
auto &transform = e.get_mut<TransformComponent>();
if (!transform.node) {
std::cout << " transform.node is null" << std::endl;
return;
}
/* Determine master slot (face preferred, else first non-empty) */
Ogre::String masterSlot;
if (cs.slots.find("face") != cs.slots.end() &&
!cs.slots.at("face").empty()) {
masterSlot = "face";
} else {
for (const auto &pair : cs.slots) {
if (!pair.second.empty()) {
masterSlot = pair.first;
break;
}
}
}
if (masterSlot.empty()) {
std::cout << " masterSlot empty" << std::endl;
return;
}
std::cout << " masterSlot=" << masterSlot
<< " mesh=" << cs.slots.at(masterSlot) << std::endl;
Ogre::Entity *masterEnt = nullptr;
try {
masterEnt = m_sceneMgr->createEntity(cs.slots.at(masterSlot));
transform.node->attachObject(masterEnt);
m_entities[e.id()].parts[masterSlot] = masterEnt;
std::cout << " master loaded: " << masterEnt->getName()
<< std::endl;
} catch (const Ogre::Exception &ex) {
std::cout << " FAILED to load master mesh: "
<< ex.getDescription() << std::endl;
Ogre::LogManager::getSingleton().logMessage(
"CharacterSlotSystem: Failed to load master mesh '" +
cs.slots.at(masterSlot) + "': " + ex.getDescription());
return;
}
for (const auto &pair : cs.slots) {
const Ogre::String &slot = pair.first;
const Ogre::String &mesh = pair.second;
if (slot == masterSlot || mesh.empty())
continue;
try {
Ogre::Entity *partEnt = m_sceneMgr->createEntity(mesh);
partEnt->shareSkeletonInstanceWith(masterEnt);
transform.node->attachObject(partEnt);
m_entities[e.id()].parts[slot] = partEnt;
std::cout << " part loaded: " << slot << "="
<< partEnt->getName() << std::endl;
} catch (const Ogre::Exception &ex) {
std::cout << " FAILED to load part " << slot
<< ": " << ex.getDescription() << std::endl;
Ogre::LogManager::getSingleton().logMessage(
"CharacterSlotSystem: Failed to load part '" +
slot + "' mesh '" + mesh +
"': " + ex.getDescription());
}
}
}
void CharacterSlotSystem::destroyCharacterParts(flecs::entity e)
{
auto it = m_entities.find(e.id());
if (it == m_entities.end())
return;
for (auto &pair : it->second.parts) {
Ogre::Entity *ent = pair.second;
if (ent) {
try {
ent->detachFromParent();
m_sceneMgr->destroyEntity(ent);
} catch (...) {
}
}
}
it->second.parts.clear();
m_entities.erase(it);
}

View File

@@ -0,0 +1,56 @@
#ifndef EDITSCENE_CHARACTERSLOTSYSTEM_HPP
#define EDITSCENE_CHARACTERSLOTSYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
#include <nlohmann/json.hpp>
#include <set>
#include <unordered_map>
#include <vector>
#include "../components/CharacterSlots.hpp"
/**
* System that manages multi-slot character meshes with shared skeleton.
* Loads body part catalog from body_part_*.json files and creates/updates
* Ogre entities for each slot, sharing the skeleton from the master slot.
*/
class CharacterSlotSystem {
public:
CharacterSlotSystem(flecs::world &world, Ogre::SceneManager *sceneMgr);
~CharacterSlotSystem();
void initialize();
void update();
/* Static catalog access */
static void loadCatalog();
static bool isCatalogLoaded();
static std::vector<Ogre::String> getAges();
static std::vector<Ogre::String> getSexes(const Ogre::String &age);
static std::vector<Ogre::String> getSlots(const Ogre::String &age,
const Ogre::String &sex);
static std::vector<Ogre::String> getMeshes(const Ogre::String &age,
const Ogre::String &sex,
const Ogre::String &slot);
private:
static bool s_catalogLoaded;
static nlohmann::json s_bodyParts;
static std::set<Ogre::String> s_meshNames;
void buildCharacter(flecs::entity e, CharacterSlotsComponent &cs);
void destroyCharacterParts(flecs::entity e);
flecs::world &m_world;
Ogre::SceneManager *m_sceneMgr;
bool m_initialized = false;
struct PartEntities {
std::unordered_map<Ogre::String, Ogre::Entity *> parts;
};
std::unordered_map<flecs::entity_t, PartEntities> m_entities;
};
#endif // EDITSCENE_CHARACTERSLOTSYSTEM_HPP

View File

@@ -16,6 +16,7 @@
#include "../components/Primitive.hpp"
#include "../components/TriangleBuffer.hpp"
#include "../components/LodSettings.hpp"
#include "../components/CharacterSlots.hpp"
#include "../components/CellGrid.hpp"
#include "../ui/TransformEditor.hpp"
#include "../ui/RenderableEditor.hpp"
@@ -624,6 +625,13 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
m_componentRegistry.render<TriangleBufferComponent>(entity, tb);
componentCount++;
}
// Render CharacterSlots if present
if (entity.has<CharacterSlotsComponent>()) {
auto &cs = entity.get_mut<CharacterSlotsComponent>();
m_componentRegistry.render<CharacterSlotsComponent>(entity, cs);
componentCount++;
}
// Render CellGrid if present
if (entity.has<CellGridComponent>()) {

View File

@@ -15,6 +15,7 @@
#include "../components/ProceduralMaterial.hpp"
#include "../components/Primitive.hpp"
#include "../components/TriangleBuffer.hpp"
#include "../components/CharacterSlots.hpp"
#include "../components/CellGrid.hpp"
#include "../components/GeneratedPhysicsTag.hpp"
#include "EditorUISystem.hpp"
@@ -177,6 +178,10 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
if (entity.has<TriangleBufferComponent>()) {
json["triangleBuffer"] = serializeTriangleBuffer(entity);
}
if (entity.has<CharacterSlotsComponent>()) {
json["characterSlots"] = serializeCharacterSlots(entity);
}
// CellGrid/Town components
if (entity.has<CellGridComponent>()) {
@@ -294,6 +299,10 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
deserializePrimitive(entity, json["primitive"]);
}
if (json.contains("characterSlots")) {
deserializeCharacterSlots(entity, json["characterSlots"]);
}
if (json.contains("triangleBuffer")) {
deserializeTriangleBuffer(entity, json["triangleBuffer"]);
}
@@ -1263,6 +1272,34 @@ void SceneSerializer::deserializeTriangleBuffer(flecs::entity entity, const nloh
}
nlohmann::json SceneSerializer::serializeCharacterSlots(flecs::entity entity)
{
auto& cs = entity.get<CharacterSlotsComponent>();
nlohmann::json json;
json["age"] = cs.age;
json["sex"] = cs.sex;
json["slots"] = nlohmann::json::object();
for (const auto& pair : cs.slots)
json["slots"][pair.first] = pair.second;
return json;
}
void SceneSerializer::deserializeCharacterSlots(flecs::entity entity, const nlohmann::json& json)
{
CharacterSlotsComponent cs;
cs.age = json.value("age", "adult");
cs.sex = json.value("sex", "male");
if (json.contains("slots") && json["slots"].is_object()) {
for (auto& [slot, mesh] : json["slots"].items())
cs.slots[slot] = mesh.get<std::string>();
}
cs.dirty = true;
entity.set<CharacterSlotsComponent>(cs);
}
// ============================================================================
// CellGrid/Town Component Serialization
// ============================================================================

View File

@@ -54,6 +54,7 @@ private:
nlohmann::json serializeProceduralMaterial(flecs::entity entity);
nlohmann::json serializePrimitive(flecs::entity entity);
nlohmann::json serializeTriangleBuffer(flecs::entity entity);
nlohmann::json serializeCharacterSlots(flecs::entity entity);
// CellGrid/Town component serialization
nlohmann::json serializeCellGrid(flecs::entity entity);
@@ -81,6 +82,7 @@ private:
void deserializeProceduralMaterial(flecs::entity entity, const nlohmann::json& json);
void deserializePrimitive(flecs::entity entity, const nlohmann::json& json);
void deserializeTriangleBuffer(flecs::entity entity, const nlohmann::json& json);
void deserializeCharacterSlots(flecs::entity entity, const nlohmann::json& json);
// CellGrid/Town component deserialization
void deserializeCellGrid(flecs::entity entity, const nlohmann::json& json);

View File

@@ -0,0 +1,122 @@
#include "CharacterSlotsEditor.hpp"
#include "../systems/CharacterSlotSystem.hpp"
#include <imgui.h>
#include <algorithm>
CharacterSlotsEditor::CharacterSlotsEditor(Ogre::SceneManager *sceneMgr)
: m_sceneMgr(sceneMgr)
{
}
bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
CharacterSlotsComponent &cs)
{
bool modified = false;
(void)entity;
if (ImGui::CollapsingHeader("Character Slots",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
CharacterSlotSystem::loadCatalog();
/* Age selector */
std::vector<Ogre::String> ages =
CharacterSlotSystem::getAges();
Ogre::String currentAge = cs.age;
if (ImGui::BeginCombo("Age", currentAge.c_str())) {
for (const auto &age : ages) {
bool isSelected = (currentAge == age);
if (ImGui::Selectable(age.c_str(), isSelected)) {
cs.age = age;
modified = true;
cs.dirty = true;
}
if (isSelected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
/* Sex selector */
std::vector<Ogre::String> sexes =
CharacterSlotSystem::getSexes(cs.age);
Ogre::String currentSex = cs.sex;
if (ImGui::BeginCombo("Sex", currentSex.c_str())) {
for (const auto &sex : sexes) {
bool isSelected = (currentSex == sex);
if (ImGui::Selectable(sex.c_str(), isSelected)) {
cs.sex = sex;
modified = true;
cs.dirty = true;
}
if (isSelected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::Separator();
/* Collect available and configured slots */
std::vector<Ogre::String> availableSlots =
CharacterSlotSystem::getSlots(cs.age, cs.sex);
for (const auto &pair : cs.slots) {
if (std::find(availableSlots.begin(), availableSlots.end(),
pair.first) == availableSlots.end())
availableSlots.push_back(pair.first);
}
std::sort(availableSlots.begin(), availableSlots.end());
/* Render mesh selector for each slot */
for (const auto &slot : availableSlots) {
Ogre::String currentMesh = "";
auto it = cs.slots.find(slot);
if (it != cs.slots.end())
currentMesh = it->second;
std::vector<Ogre::String> meshes =
CharacterSlotSystem::getMeshes(cs.age, cs.sex,
slot);
Ogre::String label = slot;
Ogre::String preview = currentMesh.empty() ?
"(none)" :
currentMesh;
if (ImGui::BeginCombo(label.c_str(), preview.c_str())) {
bool noneSelected = currentMesh.empty();
if (ImGui::Selectable("(none)", noneSelected)) {
cs.slots[slot] = "";
modified = true;
cs.dirty = true;
}
if (noneSelected)
ImGui::SetItemDefaultFocus();
for (const auto &mesh : meshes) {
bool isSelected = (currentMesh == mesh);
if (ImGui::Selectable(mesh.c_str(),
isSelected)) {
cs.slots[slot] = mesh;
modified = true;
cs.dirty = true;
}
if (isSelected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
}
/* Rebuild button */
if (ImGui::Button("Rebuild")) {
cs.dirty = true;
modified = true;
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,25 @@
#ifndef EDITSCENE_CHARACTERSLOTSEDITOR_HPP
#define EDITSCENE_CHARACTERSLOTSEDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/CharacterSlots.hpp"
#include <OgreSceneManager.h>
/**
* Editor for CharacterSlotsComponent
*/
class CharacterSlotsEditor : public ComponentEditor<CharacterSlotsComponent> {
public:
explicit CharacterSlotsEditor(Ogre::SceneManager *sceneMgr);
const char *getName() const override { return "Character Slots"; }
protected:
bool renderComponent(flecs::entity entity,
CharacterSlotsComponent &cs) override;
private:
Ogre::SceneManager *m_sceneMgr;
};
#endif // EDITSCENE_CHARACTERSLOTSEDITOR_HPP