Added prefabs
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
24
src/features/editScene/components/PrefabInstance.hpp
Normal file
24
src/features/editScene/components/PrefabInstance.hpp
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
97
src/features/editScene/systems/PrefabSystem.cpp
Normal file
97
src/features/editScene/systems/PrefabSystem.cpp
Normal 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;
|
||||
}
|
||||
74
src/features/editScene/systems/PrefabSystem.hpp
Normal file
74
src/features/editScene/systems/PrefabSystem.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
20
src/features/editScene/ui/PrefabInstanceEditor.cpp
Normal file
20
src/features/editScene/ui/PrefabInstanceEditor.cpp
Normal 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;
|
||||
}
|
||||
20
src/features/editScene/ui/PrefabInstanceEditor.hpp
Normal file
20
src/features/editScene/ui/PrefabInstanceEditor.hpp
Normal 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
|
||||
Reference in New Issue
Block a user