Added prefabs

This commit is contained in:
2026-04-26 16:43:37 +03:00
parent ce2f6c1306
commit 7557c710fb
11 changed files with 598 additions and 25 deletions

View File

@@ -46,6 +46,8 @@ set(EDITSCENE_SOURCES
systems/SmartObjectSystem.cpp
components/SmartObjectModule.cpp
ui/SmartObjectEditor.cpp
systems/PrefabSystem.cpp
ui/PrefabInstanceEditor.cpp
ui/TransformEditor.cpp
ui/RenderableEditor.cpp
@@ -170,6 +172,9 @@ set(EDITSCENE_HEADERS
systems/SmartObjectSystem.hpp
components/SmartObject.hpp
ui/SmartObjectEditor.hpp
systems/PrefabSystem.hpp
components/PrefabInstance.hpp
ui/PrefabInstanceEditor.hpp
systems/ProceduralTextureSystem.hpp
systems/StaticGeometrySystem.hpp

View File

@@ -62,6 +62,8 @@
#include "components/ActionDebug.hpp"
#include "components/BehaviorTree.hpp"
#include "components/GoapBlackboard.hpp"
#include "components/PrefabInstance.hpp"
#include "systems/PrefabSystem.hpp"
#include "components/NavMesh.hpp"
#include "components/SmartObject.hpp"
@@ -371,6 +373,8 @@ void EditorApp::setup()
"Game mode: Loading startup_menu.json...");
if (serializer.loadFromFile("startup_menu.json",
m_uiSystem.get())) {
PrefabSystem prefabSys(m_world, m_sceneMgr);
prefabSys.resolveInstances();
Ogre::LogManager::getSingleton().logMessage(
"Game mode: startup_menu.json loaded");
} else {
@@ -492,6 +496,8 @@ void EditorApp::startNewGame(const Ogre::String &scenePath)
clearScene();
SceneSerializer serializer(m_world, m_sceneMgr);
if (serializer.loadFromFile(scenePath, m_uiSystem.get())) {
PrefabSystem prefabSys(m_world, m_sceneMgr);
prefabSys.resolveInstances();
setGamePlayState(GamePlayState::Playing);
Ogre::LogManager::getSingleton().logMessage(
"Game started: loaded scene " + scenePath);
@@ -575,6 +581,9 @@ void EditorApp::setupECS()
// Register CellGrid/Town components
CellGridModule::registerComponents(m_world);
// Register PrefabInstance component
m_world.component<PrefabInstanceComponent>();
}
void EditorApp::createDefaultEntities()

View File

@@ -0,0 +1,24 @@
#ifndef EDITSCENE_PREFABINSTANCE_HPP
#define EDITSCENE_PREFABINSTANCE_HPP
#pragma once
#include <string>
/**
* @brief Marks an entity as an instance of a prefab asset.
*
* The entity's TransformComponent acts as the world-space override
* for the prefab root. All other components (and the child subtree)
* are loaded from the prefab file at runtime and are NOT serialized
* with the main scene.
*/
struct PrefabInstanceComponent {
/** Path to the prefab JSON file (relative to working dir) */
std::string prefabPath;
/** Set to true once the prefab has been instantiated.
* Prevents double-instantiation on repeated resolve calls.
*/
bool instantiated = false;
};
#endif // EDITSCENE_PREFABINSTANCE_HPP

View File

@@ -1,5 +1,6 @@
#include "../components/GeneratedPhysicsTag.hpp"
#include "EditorUISystem.hpp"
#include "PrefabSystem.hpp"
#include "../components/EntityName.hpp"
#include "../components/Transform.hpp"
#include "../components/Renderable.hpp"
@@ -34,12 +35,14 @@
#include "../components/GoapBlackboard.hpp"
#include "../components/NavMesh.hpp"
#include "../components/SmartObject.hpp"
#include "../components/PrefabInstance.hpp"
#include "../ui/TransformEditor.hpp"
#include "../ui/RenderableEditor.hpp"
#include "../ui/PhysicsColliderEditor.hpp"
#include "../ui/RigidBodyEditor.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/PrefabInstanceEditor.hpp"
#include "PhysicsSystem.hpp"
#include "BuoyancySystem.hpp"
#include "NavMeshSystem.hpp"
@@ -190,6 +193,23 @@ void EditorUISystem::registerComponentEditors()
}
});
// Register PrefabInstance component
auto prefabEditor = std::make_unique<PrefabInstanceEditor>();
m_componentRegistry.registerComponent<PrefabInstanceComponent>(
"Prefab Instance", "Scene", std::move(prefabEditor),
// Adder
[this](flecs::entity e) {
if (!e.has<PrefabInstanceComponent>()) {
e.set<PrefabInstanceComponent>({});
}
},
// Remover
[this](flecs::entity e) {
if (e.has<PrefabInstanceComponent>()) {
e.remove<PrefabInstanceComponent>();
}
});
// Register modular components (Light, Camera, etc.)
registerModularComponents();
}
@@ -224,6 +244,13 @@ void EditorUISystem::update(float deltaTime)
renderFileDialog();
}
// Render prefab dialogs
if (m_showCreatePrefabDialog) {
showCreatePrefabDialog(m_prefabSourceEntity);
}
renderPrefabBrowser();
// Render FPS overlay
renderFPSOverlay(deltaTime);
}
@@ -520,6 +547,11 @@ void EditorUISystem::renderEntityContextMenu(flecs::entity entity)
if (ImGui::MenuItem("Duplicate")) {
duplicateEntity(entity);
}
if (ImGui::MenuItem("Create Prefab")) {
m_prefabSourceEntity = entity;
m_showCreatePrefabDialog = true;
m_prefabNameBuffer[0] = '\0';
}
ImGui::Separator();
if (ImGui::MenuItem("Delete")) {
deleteEntity(entity);
@@ -1460,3 +1492,118 @@ void EditorUISystem::renderFPSOverlay(float deltaTime)
}
ImGui::End();
}
void EditorUISystem::showCreatePrefabDialog(flecs::entity entity)
{
ImGui::OpenPopup("Create Prefab");
if (ImGui::BeginPopupModal("Create Prefab", &m_showCreatePrefabDialog,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::InputText("Prefab Name", m_prefabNameBuffer,
sizeof(m_prefabNameBuffer));
if (ImGui::Button("Save", ImVec2(120, 0))) {
if (entity.is_alive() &&
m_prefabNameBuffer[0] != '\0') {
std::string prefabPath =
PrefabSystem::getPrefabsDirectory() +
"/" + m_prefabNameBuffer +
".json";
PrefabSystem prefabSys(m_world, m_sceneMgr);
if (prefabSys.savePrefab(entity, prefabPath)) {
// Convert source entity to prefab instance
entity.set<PrefabInstanceComponent>(
PrefabInstanceComponent{
prefabPath, true });
// Re-instantiate so children come
// from prefab
prefabSys.resolveInstances();
m_refreshPrefabList = true;
}
}
m_showCreatePrefabDialog = false;
m_prefabSourceEntity = flecs::entity::null();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
m_showCreatePrefabDialog = false;
m_prefabSourceEntity = flecs::entity::null();
}
ImGui::EndPopup();
}
}
void EditorUISystem::renderPrefabBrowser()
{
if (!m_showPrefabBrowser)
return;
ImGui::SetNextWindowPos(
ImVec2(LEFT_PANEL_WIDTH, 300), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(250, 400),
ImGuiCond_FirstUseEver);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse;
if (ImGui::Begin("Prefab Browser", &m_showPrefabBrowser, flags)) {
// Refresh prefab list
if (m_refreshPrefabList) {
m_prefabFiles.clear();
std::string prefabDir =
PrefabSystem::getPrefabsDirectory();
if (std::filesystem::exists(prefabDir)) {
for (const auto &entry :
std::filesystem::directory_iterator(
prefabDir)) {
if (entry.is_regular_file() &&
entry.path().extension() ==
".json") {
m_prefabFiles.push_back(
entry.path().filename()
.string());
}
}
}
m_refreshPrefabList = false;
}
// Refresh button
if (ImGui::Button("Refresh")) {
m_refreshPrefabList = true;
}
ImGui::SameLine();
if (ImGui::Button("Create Instance")) {
// Will show file picker or use selected prefab
}
ImGui::Separator();
// Prefab list
for (const auto &file : m_prefabFiles) {
std::string path = PrefabSystem::getPrefabsDirectory() +
"/" + file;
if (ImGui::Selectable(file.c_str())) {
Ogre::Vector3 pos(0, 0, 0);
if (m_selectedEntity.is_alive() &&
m_selectedEntity.has<TransformComponent>()) {
pos = m_selectedEntity
.get<TransformComponent>()
.position +
Ogre::Vector3(2, 0, 0);
}
PrefabSystem prefabSys(m_world,
m_sceneMgr);
flecs::entity parent =
m_selectedEntity.is_alive()
? m_selectedEntity
: flecs::entity::null();
auto instance = prefabSys.createInstance(
path, parent, pos,
file.substr(0, file.find_last_of('.')),
this);
if (instance.is_alive()) {
setSelectedEntity(instance);
}
}
}
}
ImGui::End();
}

View File

@@ -166,6 +166,12 @@ public:
void renderFileDialog();
void closeFileDialog();
/**
* Prefab operations
*/
void showCreatePrefabDialog(flecs::entity entity);
void renderPrefabBrowser();
private:
// File menu
void renderFileMenu();
@@ -242,6 +248,14 @@ private:
bool m_refreshDirectory = true;
char m_filenameBuffer[256] = "scene.json";
// Prefab dialog state
bool m_showCreatePrefabDialog = false;
flecs::entity m_prefabSourceEntity = flecs::entity::null();
char m_prefabNameBuffer[256] = { 0 };
bool m_showPrefabBrowser = true;
std::vector<std::string> m_prefabFiles;
bool m_refreshPrefabList = true;
// Queries
flecs::query<EntityNameComponent> m_nameQuery;

View File

@@ -0,0 +1,97 @@
#include "PrefabSystem.hpp"
#include "SceneSerializer.hpp"
#include "EditorUISystem.hpp"
#include "../components/PrefabInstance.hpp"
#include "../components/Transform.hpp"
#include "../components/EntityName.hpp"
#include "../components/EditorMarker.hpp"
#include <OgreLogManager.h>
#include <filesystem>
std::string PrefabSystem::getPrefabsDirectory()
{
return "prefabs";
}
PrefabSystem::PrefabSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
{
}
void PrefabSystem::resolveInstances()
{
m_world.query<PrefabInstanceComponent>().each(
[&](flecs::entity entity, PrefabInstanceComponent &prefab) {
if (prefab.instantiated || prefab.prefabPath.empty())
return;
SceneSerializer serializer(m_world, m_sceneMgr);
if (serializer.instantiatePrefab(entity,
prefab.prefabPath)) {
prefab.instantiated = true;
} else {
m_lastError = serializer.getLastError();
Ogre::LogManager::getSingleton().logMessage(
"PrefabSystem: Failed to instantiate '" +
prefab.prefabPath + "': " + m_lastError);
}
});
}
flecs::entity PrefabSystem::createInstance(const std::string &prefabPath,
flecs::entity parent,
const Ogre::Vector3 &position,
const std::string &name,
EditorUISystem *uiSystem)
{
flecs::entity instance = m_world.entity();
instance.add<EditorMarkerComponent>();
instance.set<EntityNameComponent>(EntityNameComponent(name));
instance.set<PrefabInstanceComponent>(
PrefabInstanceComponent{ prefabPath, false });
// Create transform
TransformComponent transform;
Ogre::SceneNode *parentNode = m_sceneMgr->getRootSceneNode();
if (parent.is_valid() && parent != 0 &&
parent.has<TransformComponent>()) {
parentNode = parent.get<TransformComponent>().node;
instance.child_of(parent);
}
transform.node = parentNode->createChildSceneNode();
transform.position = position;
transform.rotation = Ogre::Quaternion::IDENTITY;
transform.scale = Ogre::Vector3::UNIT_SCALE;
transform.applyToNode();
instance.set<TransformComponent>(transform);
if (uiSystem)
uiSystem->addEntity(instance);
// Instantiate prefab
SceneSerializer serializer(m_world, m_sceneMgr);
if (serializer.instantiatePrefab(instance, prefabPath, uiSystem)) {
auto &prefab = instance.get_mut<PrefabInstanceComponent>();
prefab.instantiated = true;
} else {
m_lastError = serializer.getLastError();
Ogre::LogManager::getSingleton().logMessage(
"PrefabSystem: Failed to create instance of '" +
prefabPath + "': " + m_lastError);
}
return instance;
}
bool PrefabSystem::savePrefab(flecs::entity rootEntity,
const std::string &prefabPath)
{
SceneSerializer serializer(m_world, m_sceneMgr);
if (!serializer.savePrefab(rootEntity, prefabPath)) {
m_lastError = serializer.getLastError();
return false;
}
return true;
}

View File

@@ -0,0 +1,74 @@
#ifndef EDITSCENE_PREFABSYSTEM_HPP
#define EDITSCENE_PREFABSYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
#include <string>
// Forward declarations
class EditorUISystem;
class SceneSerializer;
/**
* @brief System for managing prefab instances.
*
* Prefabs are reusable entity subtrees stored in external JSON files.
* Entities with PrefabInstanceComponent are lightweight proxies that
* reference a prefab file. Their children are created at runtime and
* are NOT serialized with the main scene.
*/
class PrefabSystem {
public:
PrefabSystem(flecs::world &world, Ogre::SceneManager *sceneMgr);
/**
* @brief Instantiate all unloaded prefab instances.
*
* Should be called once after scene load.
*/
void resolveInstances();
/**
* @brief Create a new prefab instance entity and instantiate it.
* @param prefabPath Path to the prefab JSON file.
* @param parent flecs parent entity (null for root).
* @param position World-space position for the instance.
* @param name Display name for the instance entity.
* @param uiSystem Optional UI system for entity registration.
* @return The instance entity.
*/
flecs::entity createInstance(const std::string &prefabPath,
flecs::entity parent,
const Ogre::Vector3 &position,
const std::string &name,
EditorUISystem *uiSystem = nullptr);
/**
* @brief Save an entity subtree as a prefab file.
* @param rootEntity Root of the subtree to save.
* @param prefabPath Destination file path.
* @return true on success.
*/
bool savePrefab(flecs::entity rootEntity,
const std::string &prefabPath);
/**
* @brief Get the directory where prefabs are stored.
*/
static std::string getPrefabsDirectory();
/**
* @brief Get last error message.
*/
const std::string &getLastError() const
{
return m_lastError;
}
private:
flecs::world &m_world;
Ogre::SceneManager *m_sceneMgr;
std::string m_lastError;
};
#endif // EDITSCENE_PREFABSYSTEM_HPP

View File

@@ -34,10 +34,12 @@
#include "../components/BehaviorTree.hpp"
#include "../components/GoapBlackboard.hpp"
#include "../components/NavMesh.hpp"
#include "../components/PrefabInstance.hpp"
#include "EditorUISystem.hpp"
#include <random>
#include <fstream>
#include <iostream>
#include <filesystem>
SceneSerializer::SceneSerializer(flecs::world &world,
Ogre::SceneManager *sceneMgr)
@@ -300,6 +302,10 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
json["behaviorTree"] = serializeBehaviorTree(entity);
}
if (entity.has<PrefabInstanceComponent>()) {
json["prefabInstance"] = serializePrefabInstance(entity);
}
if (entity.has<SunComponent>()) {
json["sun"] = serializeSun(entity);
}
@@ -536,14 +542,26 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
entity.child_of(parent);
}
// Deserialize components that don't depend on material references
if (json.contains("name")) {
deserializeEntityName(entity, json["name"]);
} else {
entity.set<EntityNameComponent>(EntityNameComponent("Entity"));
deserializeEntityComponents(entity, json, parent, uiSystem,
true, true, true);
}
void SceneSerializer::deserializeEntityComponents(
flecs::entity entity, const nlohmann::json &json,
flecs::entity parent, EditorUISystem *uiSystem,
bool processTransform, bool processName,
bool addEditorMarker)
{
if (processName) {
if (json.contains("name")) {
deserializeEntityName(entity, json["name"]);
} else {
entity.set<EntityNameComponent>(
EntityNameComponent("Entity"));
}
}
if (json.contains("transform")) {
if (processTransform && json.contains("transform")) {
deserializeTransform(entity, json["transform"], parent);
}
@@ -585,7 +603,8 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
}
if (json.contains("proceduralTexture")) {
deserializeProceduralTexture(entity, json["proceduralTexture"]);
deserializeProceduralTexture(entity,
json["proceduralTexture"]);
}
if (json.contains("proceduralMaterial")) {
@@ -619,23 +638,25 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
}
if (json.contains("playerController")) {
deserializePlayerController(entity, json["playerController"]);
deserializePlayerController(entity,
json["playerController"]);
}
if (json.contains("triangleBuffer")) {
deserializeTriangleBuffer(entity, json["triangleBuffer"]);
}
// CellGrid/Town components - deserialize WITHOUT resolving material references
// Material references will be resolved in the second pass
if (json.contains("prefabInstance")) {
deserializePrefabInstance(entity, json["prefabInstance"]);
}
// CellGrid/Town components - deserialize WITHOUT resolving
// material references (deferred to second pass)
if (json.contains("cellGrid")) {
deserializeCellGrid(entity, json["cellGrid"]);
}
if (json.contains("town")) {
// Store the raw JSON for second-pass resolution
m_pendingTownResolutions.push_back({ entity, json["town"] });
// Still deserialize basic town data (name, colorRects, etc.)
// but skip material entity resolution
TownComponent town;
town.townName = json["town"].value("townName", "New Town");
town.materialName = json["town"].value("materialName", "");
@@ -643,7 +664,6 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
json["town"].value("proceduralMaterialEntityId", "");
town.textureRectName =
json["town"].value("textureRectName", "");
// Deserialize color rects
if (json["town"].contains("colorRects") &&
json["town"]["colorRects"].is_object()) {
for (auto &[name, rectJson] :
@@ -668,10 +688,8 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
entity.set<TownComponent>(town);
}
if (json.contains("district")) {
// Store the raw JSON for second-pass resolution
m_pendingDistrictResolutions.push_back(
{ entity, json["district"] });
// Still deserialize basic district data
DistrictComponent district;
district.radius = json["district"].value("radius", 50.0f);
district.elevation = json["district"].value("elevation", 0.0f);
@@ -696,9 +714,7 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
entity.set<DistrictComponent>(district);
}
if (json.contains("lot")) {
// Store the raw JSON for second-pass resolution
m_pendingLotResolutions.push_back({ entity, json["lot"] });
// Still deserialize basic lot data
LotComponent lot;
lot.width = json["lot"].value("width", 10);
lot.depth = json["lot"].value("depth", 10);
@@ -721,7 +737,8 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
deserializeRoof(entity, json["roof"]);
}
if (json.contains("furnitureTemplate")) {
deserializeFurnitureTemplate(entity, json["furnitureTemplate"]);
deserializeFurnitureTemplate(entity,
json["furnitureTemplate"]);
}
if (json.contains("clearArea")) {
deserializeClearArea(entity, json["clearArea"]);
@@ -732,11 +749,10 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
}
if (json.contains("navMeshGeometrySource")) {
deserializeNavMeshGeometrySource(entity,
json["navMeshGeometrySource"]);
deserializeNavMeshGeometrySource(
entity, json["navMeshGeometrySource"]);
}
// Buoyancy components
if (json.contains("buoyancyInfo")) {
deserializeBuoyancyInfo(entity, json["buoyancyInfo"]);
}
@@ -770,19 +786,116 @@ void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
deserializeSkybox(entity, json["skybox"]);
}
// Add to UI system if provided
if (uiSystem) {
uiSystem->addEntity(entity);
}
// Deserialize children recursively (first pass)
if (json.contains("children") && json["children"].is_array()) {
for (const auto &childJson : json["children"]) {
deserializeEntityFirstPass(childJson, entity, uiSystem);
flecs::entity child = m_world.entity();
if (addEditorMarker)
child.add<EditorMarkerComponent>();
if (json.contains("id")) {
uint64_t cid = childJson.value("id", 0ULL);
if (cid)
m_entityMap[cid] = child;
}
child.child_of(entity);
deserializeEntityComponents(child, childJson, entity,
uiSystem, true, true,
addEditorMarker);
}
}
}
// ------------------------------------------------------------------
// Prefab support
// ------------------------------------------------------------------
nlohmann::json SceneSerializer::serializePrefabInstance(flecs::entity entity)
{
nlohmann::json json;
auto &prefab = entity.get<PrefabInstanceComponent>();
json["prefabPath"] = prefab.prefabPath;
return json;
}
void SceneSerializer::deserializePrefabInstance(
flecs::entity entity, const nlohmann::json &json)
{
PrefabInstanceComponent prefab;
prefab.prefabPath = json.value("prefabPath", "");
prefab.instantiated = false;
entity.set<PrefabInstanceComponent>(prefab);
}
bool SceneSerializer::savePrefab(flecs::entity rootEntity,
const std::string &filepath)
{
try {
std::filesystem::path p(filepath);
std::filesystem::create_directories(p.parent_path());
nlohmann::json prefab = serializeEntity(rootEntity);
std::ofstream file(filepath);
if (!file.is_open()) {
m_lastError = "Failed to open prefab for writing: " +
filepath;
return false;
}
file << prefab.dump(4);
file.close();
return true;
} catch (const std::exception &e) {
m_lastError = std::string("Prefab save error: ") + e.what();
return false;
}
}
bool SceneSerializer::instantiatePrefab(flecs::entity instanceEntity,
const std::string &filepath,
EditorUISystem *uiSystem)
{
try {
std::ifstream file(filepath);
if (!file.is_open()) {
m_lastError = "Failed to open prefab: " + filepath;
return false;
}
nlohmann::json prefabJson;
file >> prefabJson;
file.close();
// Save entity map state — prefabs use their own local IDs
auto savedMap = m_entityMap;
m_entityMap.clear();
// Store prefab root ID mapping if present
if (prefabJson.contains("id")) {
m_entityMap[prefabJson["id"]] = instanceEntity;
}
// Apply prefab root components to instance entity.
// Skip transform — the instance entity's TransformComponent
// is the world-space override.
// Skip name — the instance entity keeps its own name.
flecs::entity parent = instanceEntity.parent();
deserializeEntityComponents(instanceEntity, prefabJson,
parent, uiSystem, false,
false, false);
// Restore main scene entity map
m_entityMap = savedMap;
return true;
} catch (const std::exception &e) {
m_lastError = std::string("Prefab instantiate error: ") +
e.what();
return false;
}
}
void SceneSerializer::resolveMaterialReferences()
{
// Resolve Town material references

View File

@@ -37,6 +37,29 @@ public:
return m_lastError;
}
/**
* Save an entity subtree as a prefab JSON file.
*/
bool savePrefab(flecs::entity rootEntity,
const std::string &filepath);
/**
* Instantiate a prefab onto an existing entity.
*
* The prefab root components are copied onto instanceEntity,
* except transform (the instance entity's transform is the
* world-space override). Prefab children are created as
* children of instanceEntity without EditorMarkerComponent.
*
* @param instanceEntity Existing entity with PrefabInstanceComponent.
* @param filepath Path to prefab JSON file.
* @param uiSystem Optional UI system for registering new entities.
* @return true on success.
*/
bool instantiatePrefab(flecs::entity instanceEntity,
const std::string &filepath,
EditorUISystem *uiSystem = nullptr);
private:
// Serialization helpers
nlohmann::json serializeEntity(flecs::entity entity);
@@ -182,6 +205,33 @@ private:
void deserializeBehaviorTree(flecs::entity entity,
const nlohmann::json &json);
// PrefabInstance serialization
nlohmann::json serializePrefabInstance(flecs::entity entity);
void deserializePrefabInstance(flecs::entity entity,
const nlohmann::json &json);
/**
* Deserialize all components from JSON onto an existing entity.
* Used by both scene load and prefab instantiation.
*
* @param entity Target entity.
* @param json Entity JSON.
* @param parent flecs parent entity.
* @param uiSystem Optional UI system.
* @param processTransform If false, skip the "transform" key
* (used when the instance entity already has an override
* TransformComponent).
* @param processName If false, skip the "name" key.
* @param addEditorMarker If true, children get EditorMarkerComponent.
*/
void deserializeEntityComponents(flecs::entity entity,
const nlohmann::json &json,
flecs::entity parent,
EditorUISystem *uiSystem,
bool processTransform,
bool processName = true,
bool addEditorMarker = true);
flecs::world &m_world;
Ogre::SceneManager *m_sceneMgr;
std::string m_lastError;

View File

@@ -0,0 +1,20 @@
#include "PrefabInstanceEditor.hpp"
#include <imgui.h>
bool PrefabInstanceEditor::renderComponent(flecs::entity entity,
PrefabInstanceComponent &component)
{
bool modified = false;
char pathBuf[256] = { 0 };
strncpy(pathBuf, component.prefabPath.c_str(), sizeof(pathBuf) - 1);
if (ImGui::InputText("Prefab Path", pathBuf, sizeof(pathBuf))) {
component.prefabPath = pathBuf;
modified = true;
}
ImGui::TextDisabled("Instantiated: %s",
component.instantiated ? "yes" : "no");
return modified;
}

View File

@@ -0,0 +1,20 @@
#ifndef EDITSCENE_PREFABINSTANCEEDITOR_HPP
#define EDITSCENE_PREFABINSTANCEEDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/PrefabInstance.hpp"
/**
* @brief Editor UI for PrefabInstanceComponent
*/
class PrefabInstanceEditor : public ComponentEditor<PrefabInstanceComponent> {
public:
bool renderComponent(flecs::entity entity,
PrefabInstanceComponent &component) override;
const char *getName() const override
{
return "Prefab Instance";
}
};
#endif // EDITSCENE_PREFABINSTANCEEDITOR_HPP