From 70571d11df5f2951c72e008644bedc6cfcc43181 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 22 Mar 2026 03:42:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Components=20=E5=92=8C=20S?= =?UTF-8?q?cene=20=E5=BA=8F=E5=88=97=E5=8C=96=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Component: 添加 Serialize/Deserialize 虚函数 - TransformComponent: 实现 Transform 数据的序列化/反序列化 - GameObject: 实现对象序列化/反序列化 - Scene: 实现 Save/Load 方法,支持场景文件保存和加载 - 测试: 添加 Save_And_Load 和 Save_ContainsGameObjectData 测试 --- .../include/XCEngine/Components/Component.h | 4 ++ .../include/XCEngine/Components/GameObject.h | 9 ++-- .../XCEngine/Components/TransformComponent.h | 14 ++---- engine/include/XCEngine/Scene/Scene.h | 3 +- engine/src/Components/GameObject.cpp | 43 +++++++++++++++++++ engine/src/Components/TransformComponent.cpp | 43 +++++++++++++++++++ engine/src/Scene/Scene.cpp | 43 +++++++++++++++++++ test_scene.scene | 5 +++ test_scene_multi.scene | 6 +++ tests/Scene/test_scene.cpp | 34 +++++++++++++++ 10 files changed, 189 insertions(+), 15 deletions(-) create mode 100644 test_scene.scene create mode 100644 test_scene_multi.scene diff --git a/engine/include/XCEngine/Components/Component.h b/engine/include/XCEngine/Components/Component.h index f7c5bef3..4cc62aad 100644 --- a/engine/include/XCEngine/Components/Component.h +++ b/engine/include/XCEngine/Components/Component.h @@ -2,6 +2,7 @@ #include #include +#include namespace XCEngine { namespace Components { @@ -25,6 +26,9 @@ public: virtual std::string GetName() const = 0; + virtual void Serialize(std::ostream& os) const {} + virtual void Deserialize(std::istream& is) {} + GameObject* GetGameObject() const { return m_gameObject; } TransformComponent& transform() const; Scene* GetScene() const; diff --git a/engine/include/XCEngine/Components/GameObject.h b/engine/include/XCEngine/Components/GameObject.h index 114839ca..efe85c3a 100644 --- a/engine/include/XCEngine/Components/GameObject.h +++ b/engine/include/XCEngine/Components/GameObject.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace XCEngine { namespace Components { @@ -121,7 +123,6 @@ public: return result; } - // Hierarchy GameObject* GetParent() const { return m_parent; } void SetParent(GameObject* parent); void SetParent(GameObject* parent, bool worldPositionStays); @@ -131,17 +132,14 @@ public: std::vector GetChildren() const; void DetachChildren(); - // Active state bool IsActive() const { return m_activeSelf; } void SetActive(bool active); bool IsActiveInHierarchy() const; - // Static find static GameObject* Find(const std::string& name); static std::vector FindObjectsOfType(); static std::vector FindGameObjectsWithTag(const std::string& tag); - // Lifecycle void Awake(); void Start(); void Update(float deltaTime); @@ -151,6 +149,9 @@ public: void Destroy(); + void Serialize(std::ostream& os) const; + void Deserialize(std::istream& is); + private: ID m_id = INVALID_ID; std::string m_name; diff --git a/engine/include/XCEngine/Components/TransformComponent.h b/engine/include/XCEngine/Components/TransformComponent.h index 7e4a2dec..dbafa508 100644 --- a/engine/include/XCEngine/Components/TransformComponent.h +++ b/engine/include/XCEngine/Components/TransformComponent.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace XCEngine { namespace Components { @@ -25,7 +26,9 @@ public: std::string GetName() const override { return "Transform"; } - // Local space getters/setters + void Serialize(std::ostream& os) const override; + void Deserialize(std::istream& is) override; + const Math::Vector3& GetLocalPosition() const { return m_localPosition; } void SetLocalPosition(const Math::Vector3& position) { m_localPosition = position; SetDirty(); } @@ -38,7 +41,6 @@ public: Math::Vector3 GetLocalEulerAngles() const; void SetLocalEulerAngles(const Math::Vector3& eulers); - // World space getters/setters Math::Vector3 GetPosition() const; void SetPosition(const Math::Vector3& position); @@ -48,45 +50,37 @@ public: Math::Vector3 GetScale() const; void SetScale(const Math::Vector3& scale); - // Direction vectors Math::Vector3 GetForward() const; Math::Vector3 GetRight() const; Math::Vector3 GetUp() const; - // Matrix operations const Math::Matrix4x4& GetLocalToWorldMatrix() const; Math::Matrix4x4 GetWorldToLocalMatrix() const; - // Hierarchy - parent TransformComponent* GetParent() const { return m_parent; } void SetParent(TransformComponent* parent, bool worldPositionStays = true); - // Hierarchy - children size_t GetChildCount() const { return m_children.size(); } TransformComponent* GetChild(size_t index) const; TransformComponent* Find(const std::string& name) const; void DetachChildren(); - // Hierarchy - sibling int GetSiblingIndex() const { return m_siblingIndex; } void SetSiblingIndex(int index); void SetAsFirstSibling(); void SetAsLastSibling(); - // Transform operations void LookAt(const Math::Vector3& target); void LookAt(const Math::Vector3& target, const Math::Vector3& up); void Rotate(const Math::Vector3& eulers, Space relativeTo = Space::Self); void Rotate(const Math::Vector3& axis, float angle, Space relativeTo = Space::Self); void Translate(const Math::Vector3& translation, Space relativeTo = Space::Self); - // Point/direction transforms Math::Vector3 TransformPoint(const Math::Vector3& point) const; Math::Vector3 InverseTransformPoint(const Math::Vector3& point) const; Math::Vector3 TransformDirection(const Math::Vector3& direction) const; Math::Vector3 InverseTransformDirection(const Math::Vector3& direction) const; - // Internal void SetDirty(); void UpdateWorldTransform() const; void NotifyHierarchyChanged(); diff --git a/engine/include/XCEngine/Scene/Scene.h b/engine/include/XCEngine/Scene/Scene.h index f4ccb769..60e3f2b2 100644 --- a/engine/include/XCEngine/Scene/Scene.h +++ b/engine/include/XCEngine/Scene/Scene.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -62,8 +63,8 @@ public: void FixedUpdate(float fixedDeltaTime); void LateUpdate(float deltaTime); - void Load(const std::string& filePath); void Save(const std::string& filePath); + void Load(const std::string& filePath); Core::Event& OnGameObjectCreated() { return m_onGameObjectCreated; } Core::Event& OnGameObjectDestroyed() { return m_onGameObjectDestroyed; } diff --git a/engine/src/Components/GameObject.cpp b/engine/src/Components/GameObject.cpp index 06b6acc8..157699ba 100644 --- a/engine/src/Components/GameObject.cpp +++ b/engine/src/Components/GameObject.cpp @@ -187,5 +187,48 @@ void GameObject::Destroy() { } } +void GameObject::Serialize(std::ostream& os) const { + os << "name=" << m_name << ";"; + os << "active=" << (m_activeSelf ? "1" : "0") << ";"; + os << "id=" << m_id << ";"; + os << "transform="; + m_transform->Serialize(os); + os << ";"; +} + +void GameObject::Deserialize(std::istream& is) { + std::string token; + + while (is.peek() != -1 && is.peek() != '\n' && is.peek() != '\r') { + if (is.peek() == ';') { + is.get(); + if (is.peek() == ';') break; + continue; + } + + char key[64]; + is.get(key, 64, '='); + if (key[0] == '\0') break; + is.get(); + + if (strcmp(key, "name") == 0) { + std::getline(is, m_name, ';'); + } else if (strcmp(key, "active") == 0) { + char val; + is.get(val); + m_activeSelf = (val == '1'); + if (is.peek() == ';') is.get(); + } else if (strcmp(key, "id") == 0) { + is >> m_id; + if (is.peek() == ';') is.get(); + } else if (strcmp(key, "transform") == 0) { + m_transform->Deserialize(is); + if (is.peek() == ';') is.get(); + } else { + std::getline(is, token, ';'); + } + } +} + } // namespace Components } // namespace XCEngine \ No newline at end of file diff --git a/engine/src/Components/TransformComponent.cpp b/engine/src/Components/TransformComponent.cpp index 643db23d..24a0c6df 100644 --- a/engine/src/Components/TransformComponent.cpp +++ b/engine/src/Components/TransformComponent.cpp @@ -2,6 +2,7 @@ #include "Components/GameObject.h" #include "Scene/Scene.h" #include +#include namespace XCEngine { namespace Components { @@ -273,5 +274,47 @@ void TransformComponent::NotifyHierarchyChanged() { SetDirty(); } +void TransformComponent::Serialize(std::ostream& os) const { + os << "position=" << m_localPosition.x << "," << m_localPosition.y << "," << m_localPosition.z << ";"; + os << "rotation=" << m_localRotation.x << "," << m_localRotation.y << "," << m_localRotation.z << "," << m_localRotation.w << ";"; + os << "scale=" << m_localScale.x << "," << m_localScale.y << "," << m_localScale.z << ";"; +} + +void TransformComponent::Deserialize(std::istream& is) { + std::string token; + + while (std::getline(is, token, ';')) { + if (token.empty()) continue; + + size_t eqPos = token.find('='); + if (eqPos == std::string::npos) continue; + + std::string key = token.substr(0, eqPos); + std::string value = token.substr(eqPos + 1); + + if (key == "position") { + std::replace(value.begin(), value.end(), ',', ' '); + std::istringstream vs(value); + float x, y, z; + vs >> x >> y >> z; + m_localPosition = Math::Vector3(x, y, z); + } else if (key == "rotation") { + std::replace(value.begin(), value.end(), ',', ' '); + std::istringstream vs(value); + float x, y, z, w; + vs >> x >> y >> z >> w; + m_localRotation = Math::Quaternion(x, y, z, w); + } else if (key == "scale") { + std::replace(value.begin(), value.end(), ',', ' '); + std::istringstream vs(value); + float x, y, z; + vs >> x >> y >> z; + m_localScale = Math::Vector3(x, y, z); + } + } + + SetDirty(); +} + } // namespace Components } // namespace XCEngine \ No newline at end of file diff --git a/engine/src/Scene/Scene.cpp b/engine/src/Scene/Scene.cpp index a86e8df8..41f2cad9 100644 --- a/engine/src/Scene/Scene.cpp +++ b/engine/src/Scene/Scene.cpp @@ -1,5 +1,6 @@ #include "Scene/Scene.h" #include "Components/GameObject.h" +#include namespace XCEngine { namespace Components { @@ -141,11 +142,53 @@ void Scene::LateUpdate(float deltaTime) { } void Scene::Load(const std::string& filePath) { + std::ifstream file(filePath); + if (!file.is_open()) { + return; + } + m_gameObjects.clear(); m_rootGameObjects.clear(); + m_gameObjectIDs.clear(); + + std::string line; + while (std::getline(file, line)) { + if (line.empty() || line[0] == '#') continue; + + std::istringstream iss(line); + std::string token; + std::getline(iss, token, '='); + + if (token == "scene") { + std::getline(iss, m_name, ';'); + } else if (token == "gameobject") { + auto go = std::make_unique(); + go->Deserialize(iss); + go->m_scene = this; + GameObject* ptr = go.get(); + GameObject::GetGlobalRegistry()[ptr->m_id] = ptr; + m_gameObjectIDs.insert(ptr->m_id); + m_rootGameObjects.push_back(ptr->m_id); + m_gameObjects.emplace(ptr->m_id, std::move(go)); + } + } } void Scene::Save(const std::string& filePath) { + std::ofstream file(filePath); + if (!file.is_open()) { + return; + } + + file << "# XCEngine Scene File\n"; + file << "scene=" << m_name << ";\n"; + file << "active=" << (m_active ? "1" : "0") << ";\n\n"; + + for (auto* go : GetRootGameObjects()) { + file << "gameobject="; + go->Serialize(file); + file << "\n"; + } } } // namespace Components diff --git a/test_scene.scene b/test_scene.scene new file mode 100644 index 00000000..6f028315 --- /dev/null +++ b/test_scene.scene @@ -0,0 +1,5 @@ +# XCEngine Scene File +scene=TestScene; +active=1; + +gameobject=name=SavedObject;active=1;id=21;transform=position=1,2,3;rotation=0,0,0,1;scale=1,1,1;; diff --git a/test_scene_multi.scene b/test_scene_multi.scene new file mode 100644 index 00000000..c1aed40a --- /dev/null +++ b/test_scene_multi.scene @@ -0,0 +1,6 @@ +# XCEngine Scene File +scene=TestScene; +active=1; + +gameobject=name=Player;active=1;id=23;transform=position=0,0,0;rotation=0,0,0,1;scale=1,1,1;; +gameobject=name=Enemy;active=1;id=24;transform=position=0,0,0;rotation=0,0,0,1;scale=1,1,1;; diff --git a/tests/Scene/test_scene.cpp b/tests/Scene/test_scene.cpp index 29cb0732..9deaca53 100644 --- a/tests/Scene/test_scene.cpp +++ b/tests/Scene/test_scene.cpp @@ -2,8 +2,12 @@ #include #include #include +#include +#include +#include using namespace XCEngine::Components; +using namespace XCEngine::Math; namespace { @@ -214,4 +218,34 @@ TEST_F(SceneTest, OnGameObjectDestroyed_Event) { EXPECT_TRUE(eventFired); } +TEST_F(SceneTest, Save_And_Load) { + GameObject* go = testScene->CreateGameObject("SavedObject"); + go->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f)); + go->SetActive(true); + + testScene->Save("test_scene.scene"); + + Scene loadedScene; + loadedScene.Load("test_scene.scene"); + + GameObject* loadedGo = loadedScene.Find("SavedObject"); + EXPECT_NE(loadedGo, nullptr); + EXPECT_EQ(loadedGo->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f)); +} + +TEST_F(SceneTest, Save_ContainsGameObjectData) { + testScene->CreateGameObject("Player"); + testScene->CreateGameObject("Enemy"); + + testScene->Save("test_scene_multi.scene"); + + std::ifstream file("test_scene_multi.scene"); + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + + EXPECT_TRUE(content.find("Player") != std::string::npos); + EXPECT_TRUE(content.find("Enemy") != std::string::npos); +} + } // namespace \ No newline at end of file