#include "Scene/Scene.h" #include "Components/ComponentFactoryRegistry.h" #include "Components/GameObject.h" #include "Components/TransformComponent.h" #include #include #include namespace XCEngine { namespace Components { namespace { struct PendingComponentData { std::string type; std::string payload; }; struct PendingGameObjectData { GameObject::ID id = GameObject::INVALID_ID; std::string name = "GameObject"; bool active = true; GameObject::ID parentId = GameObject::INVALID_ID; std::string transformPayload; std::vector components; }; std::string EscapeString(const std::string& value) { std::string escaped; escaped.reserve(value.size()); for (char ch : value) { if (ch == '\\' || ch == '\n' || ch == '\r') { escaped.push_back('\\'); if (ch == '\n') { escaped.push_back('n'); } else if (ch == '\r') { escaped.push_back('r'); } else { escaped.push_back(ch); } } else { escaped.push_back(ch); } } return escaped; } std::string UnescapeString(const std::string& value) { std::string unescaped; unescaped.reserve(value.size()); for (size_t i = 0; i < value.size(); ++i) { if (value[i] == '\\' && i + 1 < value.size()) { ++i; switch (value[i]) { case 'n': unescaped.push_back('\n'); break; case 'r': unescaped.push_back('\r'); break; default: unescaped.push_back(value[i]); break; } } else { unescaped.push_back(value[i]); } } return unescaped; } void SerializeGameObjectRecursive(std::ostream& os, GameObject* gameObject) { if (!gameObject) { return; } os << "gameobject_begin\n"; os << "id=" << gameObject->GetID() << "\n"; os << "name=" << EscapeString(gameObject->GetName()) << "\n"; os << "active=" << (gameObject->IsActive() ? 1 : 0) << "\n"; os << "parent=" << (gameObject->GetParent() ? gameObject->GetParent()->GetID() : GameObject::INVALID_ID) << "\n"; os << "transform="; gameObject->GetTransform()->Serialize(os); os << "\n"; auto components = gameObject->GetComponents(); for (Component* component : components) { if (!component || component == gameObject->GetTransform()) { continue; } os << "component=" << component->GetName() << ";"; component->Serialize(os); os << "\n"; } os << "gameobject_end\n"; for (GameObject* child : gameObject->GetChildren()) { SerializeGameObjectRecursive(os, child); } } } // namespace Scene::Scene() : m_name("Untitled") { } Scene::Scene(const std::string& name) : m_name(name) { } Scene::~Scene() { m_gameObjects.clear(); } GameObject* Scene::CreateGameObject(const std::string& name, GameObject* parent) { auto gameObject = std::make_unique(name); GameObject* ptr = gameObject.get(); GameObject::GetGlobalRegistry()[ptr->m_id] = ptr; m_gameObjectIDs.insert(ptr->m_id); m_gameObjects.emplace(ptr->m_id, std::move(gameObject)); ptr->m_scene = this; if (parent) { ptr->SetParent(parent); } else { m_rootGameObjects.push_back(ptr->m_id); } ptr->Awake(); m_onGameObjectCreated.Invoke(ptr); return ptr; } void Scene::DestroyGameObject(GameObject* gameObject) { if (!gameObject || gameObject->m_scene != this) { return; } for (auto* child : gameObject->GetChildren()) { DestroyGameObject(child); } if (gameObject->m_parent) { auto& siblings = gameObject->m_parent->m_children; siblings.erase(std::remove(siblings.begin(), siblings.end(), gameObject), siblings.end()); } else { m_rootGameObjects.erase( std::remove(m_rootGameObjects.begin(), m_rootGameObjects.end(), gameObject->m_id), m_rootGameObjects.end() ); } m_onGameObjectDestroyed.Invoke(gameObject); gameObject->OnDestroy(); m_gameObjectIDs.erase(gameObject->m_id); m_gameObjects.erase(gameObject->m_id); } GameObject* Scene::Find(const std::string& name) const { for (auto* go : GetRootGameObjects()) { if (go->GetName() == name) { return go; } GameObject* found = FindInChildren(go, name); if (found) { return found; } } return nullptr; } GameObject* Scene::FindByID(GameObjectID id) const { auto it = m_gameObjects.find(id); if (it != m_gameObjects.end()) { return it->second.get(); } return nullptr; } GameObject* Scene::FindInChildren(GameObject* parent, const std::string& name) const { for (size_t i = 0; i < parent->GetChildCount(); ++i) { GameObject* child = parent->GetChild(i); if (child->GetName() == name) { return child; } GameObject* found = FindInChildren(child, name); if (found) { return found; } } return nullptr; } GameObject* Scene::FindGameObjectWithTag(const std::string& tag) const { for (auto* go : GetRootGameObjects()) { if (go->GetName() == tag) { return go; } GameObject* found = FindInChildren(go, tag); if (found) { return found; } } return nullptr; } std::vector Scene::GetRootGameObjects() const { std::vector result; for (auto id : m_rootGameObjects) { auto it = m_gameObjects.find(id); if (it != m_gameObjects.end()) { result.push_back(it->second.get()); } } return result; } void Scene::Update(float deltaTime) { for (auto* go : GetRootGameObjects()) { if (go->IsActiveInHierarchy()) { go->Update(deltaTime); } } } void Scene::FixedUpdate(float fixedDeltaTime) { for (auto* go : GetRootGameObjects()) { if (go->IsActiveInHierarchy()) { go->FixedUpdate(); } } } void Scene::LateUpdate(float deltaTime) { for (auto* go : GetRootGameObjects()) { if (go->IsActiveInHierarchy()) { go->LateUpdate(deltaTime); } } } void Scene::DeserializeFromString(const std::string& data) { m_gameObjects.clear(); m_rootGameObjects.clear(); m_gameObjectIDs.clear(); std::vector pendingObjects; std::istringstream input(data); std::string line; PendingGameObjectData* currentObject = nullptr; GameObject::ID maxId = 0; while (std::getline(input, line)) { if (line.empty() || line[0] == '#') continue; if (line == "gameobject_begin") { pendingObjects.emplace_back(); currentObject = &pendingObjects.back(); continue; } if (line == "gameobject_end") { currentObject = nullptr; continue; } const size_t eqPos = line.find('='); if (eqPos == std::string::npos) { continue; } const std::string key = line.substr(0, eqPos); const std::string value = line.substr(eqPos + 1); if (!currentObject) { if (key == "scene") { m_name = UnescapeString(value); } else if (key == "active") { m_active = (value == "1"); } continue; } if (key == "id") { currentObject->id = static_cast(std::stoull(value)); maxId = std::max(maxId, currentObject->id); } else if (key == "name") { currentObject->name = UnescapeString(value); } else if (key == "active") { currentObject->active = (value == "1"); } else if (key == "parent") { currentObject->parentId = static_cast(std::stoull(value)); } else if (key == "transform") { currentObject->transformPayload = value; } else if (key == "component") { const size_t typeEnd = value.find(';'); PendingComponentData componentData; if (typeEnd == std::string::npos) { componentData.type = value; } else { componentData.type = value.substr(0, typeEnd); componentData.payload = value.substr(typeEnd + 1); } currentObject->components.push_back(std::move(componentData)); } } std::unordered_map createdObjects; createdObjects.reserve(pendingObjects.size()); for (const PendingGameObjectData& pending : pendingObjects) { auto go = std::make_unique(pending.name); go->m_id = pending.id; go->m_activeSelf = pending.active; go->m_scene = this; if (!pending.transformPayload.empty()) { std::istringstream transformStream(pending.transformPayload); go->m_transform->Deserialize(transformStream); } for (const PendingComponentData& componentData : pending.components) { if (Component* component = ComponentFactoryRegistry::Get().CreateComponent(go.get(), componentData.type)) { if (!componentData.payload.empty()) { std::istringstream componentStream(componentData.payload); component->Deserialize(componentStream); } } } GameObject* ptr = go.get(); GameObject::GetGlobalRegistry()[ptr->m_id] = ptr; m_gameObjectIDs.insert(ptr->m_id); createdObjects[ptr->m_id] = ptr; m_gameObjects.emplace(ptr->m_id, std::move(go)); } m_rootGameObjects.clear(); for (const PendingGameObjectData& pending : pendingObjects) { auto it = createdObjects.find(pending.id); if (it == createdObjects.end()) { continue; } GameObject* gameObject = it->second; if (pending.parentId == GameObject::INVALID_ID) { m_rootGameObjects.push_back(gameObject->GetID()); } else { auto parentIt = createdObjects.find(pending.parentId); if (parentIt != createdObjects.end()) { gameObject->SetParent(parentIt->second, false); } else { m_rootGameObjects.push_back(gameObject->GetID()); } } } if (maxId != GameObject::INVALID_ID && GameObject::s_nextID <= maxId) { GameObject::s_nextID = maxId + 1; } } std::string Scene::SerializeToString() const { std::ostringstream output; output << "# XCEngine Scene File\n"; output << "scene=" << EscapeString(m_name) << "\n"; output << "active=" << (m_active ? "1" : "0") << "\n\n"; for (auto* go : GetRootGameObjects()) { SerializeGameObjectRecursive(output, go); output << "\n"; } return output.str(); } void Scene::Load(const std::string& filePath) { std::ifstream file(filePath); if (!file.is_open()) { return; } std::stringstream buffer; buffer << file.rdbuf(); DeserializeFromString(buffer.str()); } void Scene::Save(const std::string& filePath) { std::ofstream file(filePath); if (!file.is_open()) { return; } file << SerializeToString(); } } // namespace Components } // namespace XCEngine