Fix editor scene persistence and XC scene workflow
This commit is contained in:
@@ -89,7 +89,6 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
std::ofstream((assetsPath / L"Textures" / L"Stone.png").wstring());
|
||||
std::ofstream((assetsPath / L"Models" / L"Character.fbx").wstring());
|
||||
std::ofstream((assetsPath / L"Scripts" / L"PlayerController.cs").wstring());
|
||||
std::ofstream((assetsPath / L"Scenes" / L"Main.unity").wstring());
|
||||
}
|
||||
|
||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||
@@ -227,7 +226,7 @@ AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std
|
||||
item->type = "Script";
|
||||
} else if (ext == L".mat") {
|
||||
item->type = "Material";
|
||||
} else if (ext == L".unity" || ext == L".scene") {
|
||||
} else if (ext == L".xc" || ext == L".unity" || ext == L".scene") {
|
||||
item->type = "Scene";
|
||||
} else if (ext == L".prefab") {
|
||||
item->type = "Prefab";
|
||||
@@ -240,4 +239,4 @@ AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,106 @@
|
||||
#include "SceneManager.h"
|
||||
#include "Core/EventBus.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
#include <XCEngine/Components/AudioSourceComponent.h>
|
||||
#include <XCEngine/Components/AudioListenerComponent.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
SceneManager::SceneManager() = default;
|
||||
namespace {
|
||||
|
||||
std::pair<std::string, std::string> SerializeComponent(const ::XCEngine::Components::Component* component) {
|
||||
std::ostringstream payload;
|
||||
component->Serialize(payload);
|
||||
return { component->GetName(), payload.str() };
|
||||
}
|
||||
|
||||
::XCEngine::Components::Component* CreateComponentByName(
|
||||
::XCEngine::Components::GameObject* gameObject,
|
||||
const std::string& componentName) {
|
||||
if (!gameObject) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (componentName == "Camera") {
|
||||
return gameObject->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||
}
|
||||
if (componentName == "Light") {
|
||||
return gameObject->AddComponent<::XCEngine::Components::LightComponent>();
|
||||
}
|
||||
if (componentName == "AudioSource") {
|
||||
return gameObject->AddComponent<::XCEngine::Components::AudioSourceComponent>();
|
||||
}
|
||||
if (componentName == "AudioListener") {
|
||||
return gameObject->AddComponent<::XCEngine::Components::AudioListenerComponent>();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SceneManager::SceneManager(EventBus* eventBus)
|
||||
: m_eventBus(eventBus) {}
|
||||
|
||||
void SceneManager::SetSceneDirty(bool dirty) {
|
||||
m_isSceneDirty = dirty;
|
||||
}
|
||||
|
||||
void SceneManager::MarkSceneDirty() {
|
||||
SetSceneDirty(true);
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject* SceneManager::CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent) {
|
||||
if (!m_scene) {
|
||||
m_scene = new ::XCEngine::Components::Scene("EditorScene");
|
||||
m_scene = std::make_unique<::XCEngine::Components::Scene>("EditorScene");
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject* entity = m_scene->CreateGameObject(name, parent);
|
||||
|
||||
if (parent == nullptr) {
|
||||
m_rootEntities.push_back(entity);
|
||||
const auto entityId = entity->GetID();
|
||||
SyncRootEntities();
|
||||
SetSceneDirty(true);
|
||||
|
||||
OnEntityCreated.Invoke(entityId);
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityCreatedEvent{ entityId });
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
|
||||
OnEntityCreated.Invoke(entity->GetID());
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
void SceneManager::DeleteEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
if (!m_scene) return;
|
||||
|
||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||
if (!entity) return;
|
||||
|
||||
std::vector<::XCEngine::Components::GameObject*> children = entity->GetChildren();
|
||||
for (auto* child : children) {
|
||||
DeleteEntity(child->GetID());
|
||||
}
|
||||
|
||||
if (entity->GetParent() == nullptr) {
|
||||
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), entity), m_rootEntities.end());
|
||||
}
|
||||
|
||||
|
||||
const auto entityId = entity->GetID();
|
||||
m_scene->DestroyGameObject(entity);
|
||||
OnEntityDeleted.Invoke(entity->GetID());
|
||||
SyncRootEntities();
|
||||
SetSceneDirty(true);
|
||||
|
||||
OnEntityDeleted.Invoke(entityId);
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityDeletedEvent{ entityId });
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
}
|
||||
|
||||
SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::Components::GameObject* entity) {
|
||||
@@ -50,6 +113,14 @@ SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::
|
||||
data.localScale = transform->GetLocalScale();
|
||||
}
|
||||
|
||||
auto components = entity->GetComponents<::XCEngine::Components::Component>();
|
||||
for (auto* component : components) {
|
||||
if (!component || component == entity->GetTransform()) {
|
||||
continue;
|
||||
}
|
||||
data.components.push_back(SerializeComponent(component));
|
||||
}
|
||||
|
||||
for (auto* child : entity->GetChildren()) {
|
||||
data.children.push_back(CopyEntityRecursive(child));
|
||||
}
|
||||
@@ -60,7 +131,7 @@ SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::
|
||||
void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
if (!m_scene) return;
|
||||
|
||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||
if (!entity) return;
|
||||
|
||||
m_clipboard = CopyEntityRecursive(entity);
|
||||
@@ -69,7 +140,7 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
::XCEngine::Components::GameObject::ID SceneManager::PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent) {
|
||||
::XCEngine::Components::GameObject* parentObj = nullptr;
|
||||
if (parent != 0) {
|
||||
parentObj = m_scene->Find(std::to_string(parent));
|
||||
parentObj = m_scene->FindByID(parent);
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject* newEntity = m_scene->CreateGameObject(data.name, parentObj);
|
||||
@@ -79,9 +150,14 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
transform->SetLocalRotation(data.localRotation);
|
||||
transform->SetLocalScale(data.localScale);
|
||||
}
|
||||
|
||||
if (parentObj == nullptr) {
|
||||
m_rootEntities.push_back(newEntity);
|
||||
|
||||
for (const auto& componentData : data.components) {
|
||||
if (auto* component = CreateComponentByName(newEntity, componentData.first)) {
|
||||
if (!componentData.second.empty()) {
|
||||
std::istringstream payloadStream(componentData.second);
|
||||
component->Deserialize(payloadStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& childData : data.children) {
|
||||
@@ -93,13 +169,25 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
|
||||
::XCEngine::Components::GameObject::ID SceneManager::PasteEntity(::XCEngine::Components::GameObject::ID parent) {
|
||||
if (!m_clipboard || !m_scene) return 0;
|
||||
return PasteEntityRecursive(*m_clipboard, parent);
|
||||
|
||||
const auto newEntityId = PasteEntityRecursive(*m_clipboard, parent);
|
||||
SyncRootEntities();
|
||||
|
||||
OnEntityCreated.Invoke(newEntityId);
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityCreatedEvent{ newEntityId });
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
|
||||
return newEntityId;
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject::ID SceneManager::DuplicateEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
if (!m_scene) return 0;
|
||||
|
||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||
if (!entity) return 0;
|
||||
|
||||
CopyEntity(id);
|
||||
@@ -110,54 +198,210 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
return PasteEntity(parentId);
|
||||
}
|
||||
|
||||
void SceneManager::NewScene(const std::string& name) {
|
||||
m_scene = std::make_unique<::XCEngine::Components::Scene>(name);
|
||||
m_rootEntities.clear();
|
||||
m_clipboard.reset();
|
||||
m_currentScenePath.clear();
|
||||
m_currentSceneName = name;
|
||||
SetSceneDirty(true);
|
||||
|
||||
OnSceneChanged.Invoke();
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneManager::LoadScene(const std::string& filePath) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
if (!fs::exists(filePath) || !fs::is_regular_file(filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto scene = std::make_unique<::XCEngine::Components::Scene>();
|
||||
scene->Load(filePath);
|
||||
|
||||
m_scene = std::move(scene);
|
||||
m_clipboard.reset();
|
||||
SyncRootEntities();
|
||||
m_currentScenePath = filePath;
|
||||
m_currentSceneName = m_scene ? m_scene->GetName() : "Untitled Scene";
|
||||
SetSceneDirty(false);
|
||||
|
||||
OnSceneChanged.Invoke();
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneManager::SaveScene() {
|
||||
if (!m_scene) {
|
||||
return false;
|
||||
}
|
||||
if (m_currentScenePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
fs::path savePath = fs::path(m_currentScenePath).replace_extension(".xc");
|
||||
fs::create_directories(savePath.parent_path());
|
||||
|
||||
m_scene->Save(savePath.string());
|
||||
m_currentScenePath = savePath.string();
|
||||
SetSceneDirty(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneManager::SaveSceneAs(const std::string& filePath) {
|
||||
if (!m_scene || filePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
fs::path savePath = fs::path(filePath).replace_extension(".xc");
|
||||
fs::create_directories(savePath.parent_path());
|
||||
|
||||
m_scene->Save(savePath.string());
|
||||
m_currentScenePath = savePath.string();
|
||||
m_currentSceneName = m_scene->GetName();
|
||||
SetSceneDirty(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneManager::LoadStartupScene(const std::string& projectPath) {
|
||||
const std::string defaultScenePath = ResolveDefaultScenePath(projectPath);
|
||||
if (IsSceneFileUsable(defaultScenePath) && LoadScene(defaultScenePath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CreateDemoScene();
|
||||
return SaveSceneAs(defaultScenePath);
|
||||
}
|
||||
|
||||
void SceneManager::RenameEntity(::XCEngine::Components::GameObject::ID id, const std::string& newName) {
|
||||
if (!m_scene) return;
|
||||
|
||||
::XCEngine::Components::GameObject* obj = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* obj = m_scene->FindByID(id);
|
||||
if (!obj) return;
|
||||
|
||||
obj->SetName(newName);
|
||||
SetSceneDirty(true);
|
||||
OnEntityChanged.Invoke(id);
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityChangedEvent{ id });
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParentId) {
|
||||
if (!m_scene) return;
|
||||
|
||||
::XCEngine::Components::GameObject* obj = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* obj = m_scene->FindByID(id);
|
||||
if (!obj) return;
|
||||
const auto oldParentId = obj->GetParent() ? obj->GetParent()->GetID() : 0;
|
||||
|
||||
::XCEngine::Components::GameObject* newParent = nullptr;
|
||||
if (newParentId != 0) {
|
||||
newParent = m_scene->Find(std::to_string(newParentId));
|
||||
newParent = m_scene->FindByID(newParentId);
|
||||
}
|
||||
|
||||
obj->SetParent(newParent);
|
||||
SyncRootEntities();
|
||||
SetSceneDirty(true);
|
||||
|
||||
OnEntityChanged.Invoke(id);
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityParentChangedEvent{ id, oldParentId, newParentId });
|
||||
m_eventBus->Publish(EntityChangedEvent{ id });
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::CreateDemoScene() {
|
||||
if (m_scene) {
|
||||
delete m_scene;
|
||||
}
|
||||
m_scene = new ::XCEngine::Components::Scene("DemoScene");
|
||||
m_scene = std::make_unique<::XCEngine::Components::Scene>("Main Scene");
|
||||
m_rootEntities.clear();
|
||||
m_clipboard.reset();
|
||||
m_currentScenePath.clear();
|
||||
m_currentSceneName = m_scene->GetName();
|
||||
SetSceneDirty(true);
|
||||
|
||||
::XCEngine::Components::GameObject* camera = CreateEntity("Main Camera", nullptr);
|
||||
camera->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
camera->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||
|
||||
::XCEngine::Components::GameObject* light = CreateEntity("Directional Light", nullptr);
|
||||
light->AddComponent<::XCEngine::Components::LightComponent>();
|
||||
|
||||
::XCEngine::Components::GameObject* cube = CreateEntity("Cube", nullptr);
|
||||
cube->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
|
||||
::XCEngine::Components::GameObject* sphere = CreateEntity("Sphere", nullptr);
|
||||
sphere->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
|
||||
::XCEngine::Components::GameObject* player = CreateEntity("Player", nullptr);
|
||||
::XCEngine::Components::GameObject* weapon = CreateEntity("Weapon", player);
|
||||
|
||||
SyncRootEntities();
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::SyncRootEntities() {
|
||||
if (!m_scene) {
|
||||
m_rootEntities.clear();
|
||||
m_currentSceneName = "Untitled Scene";
|
||||
SetSceneDirty(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_rootEntities = m_scene->GetRootGameObjects();
|
||||
m_currentSceneName = m_scene->GetName();
|
||||
}
|
||||
|
||||
std::string SceneManager::GetScenesDirectory(const std::string& projectPath) {
|
||||
return (std::filesystem::path(projectPath) / "Assets" / "Scenes").string();
|
||||
}
|
||||
|
||||
std::string SceneManager::ResolveDefaultScenePath(const std::string& projectPath) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path scenesDir = GetScenesDirectory(projectPath);
|
||||
const fs::path mainXC = scenesDir / "Main.xc";
|
||||
if (fs::exists(mainXC)) {
|
||||
return mainXC.string();
|
||||
}
|
||||
|
||||
const fs::path mainScene = scenesDir / "Main.scene";
|
||||
if (fs::exists(mainScene)) {
|
||||
return mainScene.string();
|
||||
}
|
||||
|
||||
const fs::path mainUnity = scenesDir / "Main.unity";
|
||||
if (fs::exists(mainUnity)) {
|
||||
return mainUnity.string();
|
||||
}
|
||||
|
||||
if (fs::exists(scenesDir)) {
|
||||
for (const auto& entry : fs::directory_iterator(scenesDir)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fs::path ext = entry.path().extension();
|
||||
if (ext == ".xc" || ext == ".scene" || ext == ".unity") {
|
||||
return entry.path().string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mainXC.string();
|
||||
}
|
||||
|
||||
bool SceneManager::IsSceneFileUsable(const std::string& filePath) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
return !filePath.empty() &&
|
||||
fs::exists(filePath) &&
|
||||
fs::is_regular_file(filePath) &&
|
||||
fs::file_size(filePath) > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include <XCEngine/Core/Event.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
@@ -18,9 +19,11 @@
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class EventBus;
|
||||
|
||||
class SceneManager : public ISceneManager {
|
||||
public:
|
||||
SceneManager();
|
||||
explicit SceneManager(EventBus* eventBus = nullptr);
|
||||
|
||||
::XCEngine::Components::GameObject* CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent = nullptr);
|
||||
|
||||
@@ -45,7 +48,17 @@ public:
|
||||
::XCEngine::Components::GameObject::ID DuplicateEntity(::XCEngine::Components::GameObject::ID id);
|
||||
void MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParent);
|
||||
|
||||
void CreateDemoScene();
|
||||
void NewScene(const std::string& name = "Untitled Scene") override;
|
||||
bool LoadScene(const std::string& filePath) override;
|
||||
bool SaveScene() override;
|
||||
bool SaveSceneAs(const std::string& filePath) override;
|
||||
bool LoadStartupScene(const std::string& projectPath) override;
|
||||
bool HasActiveScene() const override { return m_scene != nullptr; }
|
||||
bool IsSceneDirty() const override { return m_isSceneDirty; }
|
||||
void MarkSceneDirty() override;
|
||||
const std::string& GetCurrentScenePath() const override { return m_currentScenePath; }
|
||||
const std::string& GetCurrentSceneName() const override { return m_currentSceneName; }
|
||||
void CreateDemoScene() override;
|
||||
|
||||
bool HasClipboardData() const { return m_clipboard.has_value(); }
|
||||
|
||||
@@ -60,15 +73,25 @@ private:
|
||||
Math::Vector3 localPosition = Math::Vector3::Zero();
|
||||
Math::Quaternion localRotation = Math::Quaternion::Identity();
|
||||
Math::Vector3 localScale = Math::Vector3::One();
|
||||
std::vector<std::pair<std::string, std::string>> components;
|
||||
std::vector<ClipboardData> children;
|
||||
};
|
||||
|
||||
ClipboardData CopyEntityRecursive(const ::XCEngine::Components::GameObject* entity);
|
||||
::XCEngine::Components::GameObject::ID PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent);
|
||||
void SetSceneDirty(bool dirty);
|
||||
void SyncRootEntities();
|
||||
static std::string GetScenesDirectory(const std::string& projectPath);
|
||||
static std::string ResolveDefaultScenePath(const std::string& projectPath);
|
||||
static bool IsSceneFileUsable(const std::string& filePath);
|
||||
|
||||
::XCEngine::Components::Scene* m_scene = nullptr;
|
||||
std::unique_ptr<::XCEngine::Components::Scene> m_scene;
|
||||
std::vector<::XCEngine::Components::GameObject*> m_rootEntities;
|
||||
std::optional<ClipboardData> m_clipboard;
|
||||
EventBus* m_eventBus = nullptr;
|
||||
std::string m_currentScenePath;
|
||||
std::string m_currentSceneName = "Untitled Scene";
|
||||
bool m_isSceneDirty = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user