添加 Components 和 Scene 序列化支持

- Component: 添加 Serialize/Deserialize 虚函数
- TransformComponent: 实现 Transform 数据的序列化/反序列化
- GameObject: 实现对象序列化/反序列化
- Scene: 实现 Save/Load 方法,支持场景文件保存和加载
- 测试: 添加 Save_And_Load 和 Save_ContainsGameObjectData 测试
This commit is contained in:
2026-03-22 03:42:40 +08:00
parent a9d5a68dd6
commit 70571d11df
10 changed files with 189 additions and 15 deletions

View File

@@ -2,6 +2,7 @@
#include <string>
#include <cstdint>
#include <ostream>
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;

View File

@@ -6,6 +6,8 @@
#include <vector>
#include <memory>
#include <unordered_map>
#include <istream>
#include <ostream>
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<GameObject*> 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<GameObject*> FindObjectsOfType();
static std::vector<GameObject*> 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;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Math/Matrix4.h>
#include <vector>
#include <string>
#include <istream>
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();

View File

@@ -4,6 +4,7 @@
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <fstream>
#include <XCEngine/Core/Event.h>
#include <XCEngine/Components/GameObject.h>
@@ -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<GameObject*>& OnGameObjectCreated() { return m_onGameObjectCreated; }
Core::Event<GameObject*>& OnGameObjectDestroyed() { return m_onGameObjectDestroyed; }

View File

@@ -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

View File

@@ -2,6 +2,7 @@
#include "Components/GameObject.h"
#include "Scene/Scene.h"
#include <algorithm>
#include <sstream>
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

View File

@@ -1,5 +1,6 @@
#include "Scene/Scene.h"
#include "Components/GameObject.h"
#include <sstream>
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<GameObject>();
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