添加 Components 和 Scene 序列化支持
- Component: 添加 Serialize/Deserialize 虚函数 - TransformComponent: 实现 Transform 数据的序列化/反序列化 - GameObject: 实现对象序列化/反序列化 - Scene: 实现 Save/Load 方法,支持场景文件保存和加载 - 测试: 添加 Save_And_Load 和 Save_ContainsGameObjectData 测试
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
@@ -25,6 +26,9 @@ public:
|
|||||||
|
|
||||||
virtual std::string GetName() const = 0;
|
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; }
|
GameObject* GetGameObject() const { return m_gameObject; }
|
||||||
TransformComponent& transform() const;
|
TransformComponent& transform() const;
|
||||||
Scene* GetScene() const;
|
Scene* GetScene() const;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <istream>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
@@ -121,7 +123,6 @@ public:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hierarchy
|
|
||||||
GameObject* GetParent() const { return m_parent; }
|
GameObject* GetParent() const { return m_parent; }
|
||||||
void SetParent(GameObject* parent);
|
void SetParent(GameObject* parent);
|
||||||
void SetParent(GameObject* parent, bool worldPositionStays);
|
void SetParent(GameObject* parent, bool worldPositionStays);
|
||||||
@@ -131,17 +132,14 @@ public:
|
|||||||
std::vector<GameObject*> GetChildren() const;
|
std::vector<GameObject*> GetChildren() const;
|
||||||
void DetachChildren();
|
void DetachChildren();
|
||||||
|
|
||||||
// Active state
|
|
||||||
bool IsActive() const { return m_activeSelf; }
|
bool IsActive() const { return m_activeSelf; }
|
||||||
void SetActive(bool active);
|
void SetActive(bool active);
|
||||||
bool IsActiveInHierarchy() const;
|
bool IsActiveInHierarchy() const;
|
||||||
|
|
||||||
// Static find
|
|
||||||
static GameObject* Find(const std::string& name);
|
static GameObject* Find(const std::string& name);
|
||||||
static std::vector<GameObject*> FindObjectsOfType();
|
static std::vector<GameObject*> FindObjectsOfType();
|
||||||
static std::vector<GameObject*> FindGameObjectsWithTag(const std::string& tag);
|
static std::vector<GameObject*> FindGameObjectsWithTag(const std::string& tag);
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
void Awake();
|
void Awake();
|
||||||
void Start();
|
void Start();
|
||||||
void Update(float deltaTime);
|
void Update(float deltaTime);
|
||||||
@@ -151,6 +149,9 @@ public:
|
|||||||
|
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
|
||||||
|
void Serialize(std::ostream& os) const;
|
||||||
|
void Deserialize(std::istream& is);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ID m_id = INVALID_ID;
|
ID m_id = INVALID_ID;
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <XCEngine/Math/Matrix4.h>
|
#include <XCEngine/Math/Matrix4.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <istream>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
@@ -25,7 +26,9 @@ public:
|
|||||||
|
|
||||||
std::string GetName() const override { return "Transform"; }
|
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; }
|
const Math::Vector3& GetLocalPosition() const { return m_localPosition; }
|
||||||
void SetLocalPosition(const Math::Vector3& position) { m_localPosition = position; SetDirty(); }
|
void SetLocalPosition(const Math::Vector3& position) { m_localPosition = position; SetDirty(); }
|
||||||
|
|
||||||
@@ -38,7 +41,6 @@ public:
|
|||||||
Math::Vector3 GetLocalEulerAngles() const;
|
Math::Vector3 GetLocalEulerAngles() const;
|
||||||
void SetLocalEulerAngles(const Math::Vector3& eulers);
|
void SetLocalEulerAngles(const Math::Vector3& eulers);
|
||||||
|
|
||||||
// World space getters/setters
|
|
||||||
Math::Vector3 GetPosition() const;
|
Math::Vector3 GetPosition() const;
|
||||||
void SetPosition(const Math::Vector3& position);
|
void SetPosition(const Math::Vector3& position);
|
||||||
|
|
||||||
@@ -48,45 +50,37 @@ public:
|
|||||||
Math::Vector3 GetScale() const;
|
Math::Vector3 GetScale() const;
|
||||||
void SetScale(const Math::Vector3& scale);
|
void SetScale(const Math::Vector3& scale);
|
||||||
|
|
||||||
// Direction vectors
|
|
||||||
Math::Vector3 GetForward() const;
|
Math::Vector3 GetForward() const;
|
||||||
Math::Vector3 GetRight() const;
|
Math::Vector3 GetRight() const;
|
||||||
Math::Vector3 GetUp() const;
|
Math::Vector3 GetUp() const;
|
||||||
|
|
||||||
// Matrix operations
|
|
||||||
const Math::Matrix4x4& GetLocalToWorldMatrix() const;
|
const Math::Matrix4x4& GetLocalToWorldMatrix() const;
|
||||||
Math::Matrix4x4 GetWorldToLocalMatrix() const;
|
Math::Matrix4x4 GetWorldToLocalMatrix() const;
|
||||||
|
|
||||||
// Hierarchy - parent
|
|
||||||
TransformComponent* GetParent() const { return m_parent; }
|
TransformComponent* GetParent() const { return m_parent; }
|
||||||
void SetParent(TransformComponent* parent, bool worldPositionStays = true);
|
void SetParent(TransformComponent* parent, bool worldPositionStays = true);
|
||||||
|
|
||||||
// Hierarchy - children
|
|
||||||
size_t GetChildCount() const { return m_children.size(); }
|
size_t GetChildCount() const { return m_children.size(); }
|
||||||
TransformComponent* GetChild(size_t index) const;
|
TransformComponent* GetChild(size_t index) const;
|
||||||
TransformComponent* Find(const std::string& name) const;
|
TransformComponent* Find(const std::string& name) const;
|
||||||
void DetachChildren();
|
void DetachChildren();
|
||||||
|
|
||||||
// Hierarchy - sibling
|
|
||||||
int GetSiblingIndex() const { return m_siblingIndex; }
|
int GetSiblingIndex() const { return m_siblingIndex; }
|
||||||
void SetSiblingIndex(int index);
|
void SetSiblingIndex(int index);
|
||||||
void SetAsFirstSibling();
|
void SetAsFirstSibling();
|
||||||
void SetAsLastSibling();
|
void SetAsLastSibling();
|
||||||
|
|
||||||
// Transform operations
|
|
||||||
void LookAt(const Math::Vector3& target);
|
void LookAt(const Math::Vector3& target);
|
||||||
void LookAt(const Math::Vector3& target, const Math::Vector3& up);
|
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& eulers, Space relativeTo = Space::Self);
|
||||||
void Rotate(const Math::Vector3& axis, float angle, 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);
|
void Translate(const Math::Vector3& translation, Space relativeTo = Space::Self);
|
||||||
|
|
||||||
// Point/direction transforms
|
|
||||||
Math::Vector3 TransformPoint(const Math::Vector3& point) const;
|
Math::Vector3 TransformPoint(const Math::Vector3& point) const;
|
||||||
Math::Vector3 InverseTransformPoint(const Math::Vector3& point) const;
|
Math::Vector3 InverseTransformPoint(const Math::Vector3& point) const;
|
||||||
Math::Vector3 TransformDirection(const Math::Vector3& direction) const;
|
Math::Vector3 TransformDirection(const Math::Vector3& direction) const;
|
||||||
Math::Vector3 InverseTransformDirection(const Math::Vector3& direction) const;
|
Math::Vector3 InverseTransformDirection(const Math::Vector3& direction) const;
|
||||||
|
|
||||||
// Internal
|
|
||||||
void SetDirty();
|
void SetDirty();
|
||||||
void UpdateWorldTransform() const;
|
void UpdateWorldTransform() const;
|
||||||
void NotifyHierarchyChanged();
|
void NotifyHierarchyChanged();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <fstream>
|
||||||
#include <XCEngine/Core/Event.h>
|
#include <XCEngine/Core/Event.h>
|
||||||
#include <XCEngine/Components/GameObject.h>
|
#include <XCEngine/Components/GameObject.h>
|
||||||
|
|
||||||
@@ -62,8 +63,8 @@ public:
|
|||||||
void FixedUpdate(float fixedDeltaTime);
|
void FixedUpdate(float fixedDeltaTime);
|
||||||
void LateUpdate(float deltaTime);
|
void LateUpdate(float deltaTime);
|
||||||
|
|
||||||
void Load(const std::string& filePath);
|
|
||||||
void Save(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*>& OnGameObjectCreated() { return m_onGameObjectCreated; }
|
||||||
Core::Event<GameObject*>& OnGameObjectDestroyed() { return m_onGameObjectDestroyed; }
|
Core::Event<GameObject*>& OnGameObjectDestroyed() { return m_onGameObjectDestroyed; }
|
||||||
|
|||||||
@@ -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 Components
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "Components/GameObject.h"
|
#include "Components/GameObject.h"
|
||||||
#include "Scene/Scene.h"
|
#include "Scene/Scene.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
@@ -273,5 +274,47 @@ void TransformComponent::NotifyHierarchyChanged() {
|
|||||||
SetDirty();
|
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 Components
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "Scene/Scene.h"
|
#include "Scene/Scene.h"
|
||||||
#include "Components/GameObject.h"
|
#include "Components/GameObject.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
@@ -141,11 +142,53 @@ void Scene::LateUpdate(float deltaTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Scene::Load(const std::string& filePath) {
|
void Scene::Load(const std::string& filePath) {
|
||||||
|
std::ifstream file(filePath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_gameObjects.clear();
|
m_gameObjects.clear();
|
||||||
m_rootGameObjects.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) {
|
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
|
} // namespace Components
|
||||||
|
|||||||
5
test_scene.scene
Normal file
5
test_scene.scene
Normal file
@@ -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;;
|
||||||
6
test_scene_multi.scene
Normal file
6
test_scene_multi.scene
Normal file
@@ -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;;
|
||||||
@@ -2,8 +2,12 @@
|
|||||||
#include <XCEngine/Scene/Scene.h>
|
#include <XCEngine/Scene/Scene.h>
|
||||||
#include <XCEngine/Components/GameObject.h>
|
#include <XCEngine/Components/GameObject.h>
|
||||||
#include <XCEngine/Components/TransformComponent.h>
|
#include <XCEngine/Components/TransformComponent.h>
|
||||||
|
#include <XCEngine/Math/Vector3.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
using namespace XCEngine::Components;
|
using namespace XCEngine::Components;
|
||||||
|
using namespace XCEngine::Math;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -214,4 +218,34 @@ TEST_F(SceneTest, OnGameObjectDestroyed_Event) {
|
|||||||
EXPECT_TRUE(eventFired);
|
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
|
} // namespace
|
||||||
Reference in New Issue
Block a user