Fix editor scene persistence and XC scene workflow
This commit is contained in:
@@ -243,9 +243,17 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/Component.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/TransformComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/GameObject.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/CameraComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/LightComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/AudioSourceComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/AudioListenerComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/Component.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/TransformComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/GameObject.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/CameraComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/LightComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/AudioSourceComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Components/AudioListenerComponent.cpp
|
||||
|
||||
# Scene
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/Scene.h
|
||||
|
||||
57
engine/include/XCEngine/Components/CameraComponent.h
Normal file
57
engine/include/XCEngine/Components/CameraComponent.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
enum class CameraProjectionType {
|
||||
Perspective = 0,
|
||||
Orthographic
|
||||
};
|
||||
|
||||
class CameraComponent : public Component {
|
||||
public:
|
||||
std::string GetName() const override { return "Camera"; }
|
||||
|
||||
CameraProjectionType GetProjectionType() const { return m_projectionType; }
|
||||
void SetProjectionType(CameraProjectionType type) { m_projectionType = type; }
|
||||
|
||||
float GetFieldOfView() const { return m_fieldOfView; }
|
||||
void SetFieldOfView(float value);
|
||||
|
||||
float GetOrthographicSize() const { return m_orthographicSize; }
|
||||
void SetOrthographicSize(float value);
|
||||
|
||||
float GetNearClipPlane() const { return m_nearClipPlane; }
|
||||
void SetNearClipPlane(float value);
|
||||
|
||||
float GetFarClipPlane() const { return m_farClipPlane; }
|
||||
void SetFarClipPlane(float value);
|
||||
|
||||
float GetDepth() const { return m_depth; }
|
||||
void SetDepth(float value) { m_depth = value; }
|
||||
|
||||
bool IsPrimary() const { return m_primary; }
|
||||
void SetPrimary(bool value) { m_primary = value; }
|
||||
|
||||
const Math::Color& GetClearColor() const { return m_clearColor; }
|
||||
void SetClearColor(const Math::Color& value) { m_clearColor = value; }
|
||||
|
||||
void Serialize(std::ostream& os) const override;
|
||||
void Deserialize(std::istream& is) override;
|
||||
|
||||
private:
|
||||
CameraProjectionType m_projectionType = CameraProjectionType::Perspective;
|
||||
float m_fieldOfView = 60.0f;
|
||||
float m_orthographicSize = 5.0f;
|
||||
float m_nearClipPlane = 0.1f;
|
||||
float m_farClipPlane = 1000.0f;
|
||||
float m_depth = 0.0f;
|
||||
bool m_primary = true;
|
||||
Math::Color m_clearColor = Math::Color(0.192f, 0.302f, 0.475f, 1.0f);
|
||||
};
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
@@ -5,10 +5,12 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
#include <random>
|
||||
#include <type_traits>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
@@ -38,6 +40,10 @@ public:
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T* AddComponent(Args&&... args) {
|
||||
if constexpr (std::is_base_of_v<TransformComponent, T>) {
|
||||
return dynamic_cast<T*>(m_transform);
|
||||
}
|
||||
|
||||
auto component = std::make_unique<T>(std::forward<Args>(args)...);
|
||||
component->m_gameObject = this;
|
||||
T* ptr = component.get();
|
||||
@@ -47,6 +53,10 @@ public:
|
||||
|
||||
template<typename T>
|
||||
T* GetComponent() {
|
||||
if (T* casted = dynamic_cast<T*>(m_transform)) {
|
||||
return casted;
|
||||
}
|
||||
|
||||
for (auto& comp : m_components) {
|
||||
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
||||
return casted;
|
||||
@@ -57,6 +67,10 @@ public:
|
||||
|
||||
template<typename T>
|
||||
const T* GetComponent() const {
|
||||
if (const T* casted = dynamic_cast<const T*>(m_transform)) {
|
||||
return casted;
|
||||
}
|
||||
|
||||
for (auto& comp : m_components) {
|
||||
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
||||
return casted;
|
||||
@@ -68,6 +82,9 @@ public:
|
||||
template<typename T>
|
||||
std::vector<T*> GetComponents() {
|
||||
std::vector<T*> result;
|
||||
if (T* casted = dynamic_cast<T*>(m_transform)) {
|
||||
result.push_back(casted);
|
||||
}
|
||||
for (auto& comp : m_components) {
|
||||
if (T* casted = dynamic_cast<T*>(comp.get())) {
|
||||
result.push_back(casted);
|
||||
@@ -79,6 +96,9 @@ public:
|
||||
template<typename T>
|
||||
std::vector<const T*> GetComponents() const {
|
||||
std::vector<const T*> result;
|
||||
if (const T* casted = dynamic_cast<const T*>(m_transform)) {
|
||||
result.push_back(casted);
|
||||
}
|
||||
for (auto& comp : m_components) {
|
||||
if (const T* casted = dynamic_cast<const T*>(comp.get())) {
|
||||
result.push_back(casted);
|
||||
@@ -89,6 +109,10 @@ public:
|
||||
|
||||
template<typename T>
|
||||
void RemoveComponent() {
|
||||
if (dynamic_cast<T*>(m_transform)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = m_components.begin(); it != m_components.end(); ++it) {
|
||||
if (T* casted = dynamic_cast<T*>(it->get())) {
|
||||
m_components.erase(it);
|
||||
@@ -97,6 +121,23 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool RemoveComponent(Component* component) {
|
||||
if (!component || component == m_transform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = std::find_if(m_components.begin(), m_components.end(),
|
||||
[component](const std::unique_ptr<Component>& entry) {
|
||||
return entry.get() == component;
|
||||
});
|
||||
if (it == m_components.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_components.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* GetComponentInChildren() {
|
||||
T* comp = GetComponent<T>();
|
||||
@@ -187,4 +228,4 @@ private:
|
||||
};
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
} // namespace XCEngine
|
||||
|
||||
50
engine/include/XCEngine/Components/LightComponent.h
Normal file
50
engine/include/XCEngine/Components/LightComponent.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
enum class LightType {
|
||||
Directional = 0,
|
||||
Point,
|
||||
Spot
|
||||
};
|
||||
|
||||
class LightComponent : public Component {
|
||||
public:
|
||||
std::string GetName() const override { return "Light"; }
|
||||
|
||||
LightType GetLightType() const { return m_lightType; }
|
||||
void SetLightType(LightType value) { m_lightType = value; }
|
||||
|
||||
const Math::Color& GetColor() const { return m_color; }
|
||||
void SetColor(const Math::Color& value) { m_color = value; }
|
||||
|
||||
float GetIntensity() const { return m_intensity; }
|
||||
void SetIntensity(float value);
|
||||
|
||||
float GetRange() const { return m_range; }
|
||||
void SetRange(float value);
|
||||
|
||||
float GetSpotAngle() const { return m_spotAngle; }
|
||||
void SetSpotAngle(float value);
|
||||
|
||||
bool GetCastsShadows() const { return m_castsShadows; }
|
||||
void SetCastsShadows(bool value) { m_castsShadows = value; }
|
||||
|
||||
void Serialize(std::ostream& os) const override;
|
||||
void Deserialize(std::istream& is) override;
|
||||
|
||||
private:
|
||||
LightType m_lightType = LightType::Directional;
|
||||
Math::Color m_color = Math::Color::White();
|
||||
float m_intensity = 1.0f;
|
||||
float m_range = 10.0f;
|
||||
float m_spotAngle = 30.0f;
|
||||
bool m_castsShadows = false;
|
||||
};
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
77
engine/src/Components/CameraComponent.cpp
Normal file
77
engine/src/Components/CameraComponent.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "Components/CameraComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
void CameraComponent::SetFieldOfView(float value) {
|
||||
m_fieldOfView = std::clamp(value, 1.0f, 179.0f);
|
||||
}
|
||||
|
||||
void CameraComponent::SetOrthographicSize(float value) {
|
||||
m_orthographicSize = std::max(0.001f, value);
|
||||
}
|
||||
|
||||
void CameraComponent::SetNearClipPlane(float value) {
|
||||
m_nearClipPlane = std::max(0.001f, value);
|
||||
if (m_farClipPlane <= m_nearClipPlane) {
|
||||
m_farClipPlane = m_nearClipPlane + 0.001f;
|
||||
}
|
||||
}
|
||||
|
||||
void CameraComponent::SetFarClipPlane(float value) {
|
||||
m_farClipPlane = std::max(m_nearClipPlane + 0.001f, value);
|
||||
}
|
||||
|
||||
void CameraComponent::Serialize(std::ostream& os) const {
|
||||
os << "projection=" << static_cast<int>(m_projectionType) << ";";
|
||||
os << "fov=" << m_fieldOfView << ";";
|
||||
os << "orthoSize=" << m_orthographicSize << ";";
|
||||
os << "near=" << m_nearClipPlane << ";";
|
||||
os << "far=" << m_farClipPlane << ";";
|
||||
os << "depth=" << m_depth << ";";
|
||||
os << "primary=" << (m_primary ? 1 : 0) << ";";
|
||||
os << "clearColor=" << m_clearColor.r << "," << m_clearColor.g << "," << m_clearColor.b << "," << m_clearColor.a << ";";
|
||||
}
|
||||
|
||||
void CameraComponent::Deserialize(std::istream& is) {
|
||||
std::string token;
|
||||
while (std::getline(is, token, ';')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eqPos = token.find('=');
|
||||
if (eqPos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = token.substr(0, eqPos);
|
||||
std::string value = token.substr(eqPos + 1);
|
||||
|
||||
if (key == "projection") {
|
||||
m_projectionType = static_cast<CameraProjectionType>(std::stoi(value));
|
||||
} else if (key == "fov") {
|
||||
SetFieldOfView(std::stof(value));
|
||||
} else if (key == "orthoSize") {
|
||||
SetOrthographicSize(std::stof(value));
|
||||
} else if (key == "near") {
|
||||
SetNearClipPlane(std::stof(value));
|
||||
} else if (key == "far") {
|
||||
SetFarClipPlane(std::stof(value));
|
||||
} else if (key == "depth") {
|
||||
m_depth = std::stof(value);
|
||||
} else if (key == "primary") {
|
||||
m_primary = (std::stoi(value) != 0);
|
||||
} else if (key == "clearColor") {
|
||||
std::replace(value.begin(), value.end(), ',', ' ');
|
||||
std::istringstream ss(value);
|
||||
ss >> m_clearColor.r >> m_clearColor.g >> m_clearColor.b >> m_clearColor.a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
@@ -48,32 +48,30 @@ void GameObject::SetParent(GameObject* parent) {
|
||||
}
|
||||
|
||||
void GameObject::SetParent(GameObject* parent, bool worldPositionStays) {
|
||||
if (m_parent == parent) {
|
||||
if (m_parent == parent || parent == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
Math::Vector3 worldPos = worldPositionStays ? GetTransform()->GetPosition() : GetTransform()->GetLocalPosition();
|
||||
Math::Quaternion worldRot = worldPositionStays ? GetTransform()->GetRotation() : GetTransform()->GetLocalRotation();
|
||||
Math::Vector3 worldScale = worldPositionStays ? GetTransform()->GetScale() : GetTransform()->GetLocalScale();
|
||||
|
||||
if (m_parent) {
|
||||
auto& siblings = m_parent->m_children;
|
||||
siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end());
|
||||
} else if (m_scene) {
|
||||
auto& roots = m_scene->m_rootGameObjects;
|
||||
roots.erase(std::remove(roots.begin(), roots.end(), m_id), roots.end());
|
||||
}
|
||||
|
||||
m_parent = parent;
|
||||
|
||||
if (m_parent) {
|
||||
m_parent->m_children.push_back(this);
|
||||
} else if (m_scene) {
|
||||
auto& roots = m_scene->m_rootGameObjects;
|
||||
if (std::find(roots.begin(), roots.end(), m_id) == roots.end()) {
|
||||
roots.push_back(m_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (worldPositionStays) {
|
||||
GetTransform()->SetPosition(worldPos);
|
||||
GetTransform()->SetRotation(worldRot);
|
||||
GetTransform()->SetScale(worldScale);
|
||||
}
|
||||
|
||||
GetTransform()->SetDirty();
|
||||
GetTransform()->SetParent(parent ? parent->GetTransform() : nullptr, worldPositionStays);
|
||||
}
|
||||
|
||||
GameObject* GameObject::GetChild(size_t index) const {
|
||||
@@ -88,32 +86,17 @@ std::vector<GameObject*> GameObject::GetChildren() const {
|
||||
}
|
||||
|
||||
void GameObject::DetachChildren() {
|
||||
for (auto* child : m_children) {
|
||||
auto children = m_children;
|
||||
for (auto* child : children) {
|
||||
if (child) {
|
||||
child->m_parent = nullptr;
|
||||
child->SetParent(nullptr, true);
|
||||
}
|
||||
}
|
||||
m_children.clear();
|
||||
}
|
||||
|
||||
void GameObject::DetachFromParent() {
|
||||
if (m_parent) {
|
||||
Math::Vector3 worldPos = GetTransform()->GetPosition();
|
||||
Math::Quaternion worldRot = GetTransform()->GetRotation();
|
||||
Math::Vector3 worldScale = GetTransform()->GetScale();
|
||||
|
||||
auto& siblings = m_parent->m_children;
|
||||
siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end());
|
||||
m_parent = nullptr;
|
||||
|
||||
if (m_scene) {
|
||||
m_scene->m_rootGameObjects.push_back(m_id);
|
||||
}
|
||||
|
||||
GetTransform()->SetPosition(worldPos);
|
||||
GetTransform()->SetRotation(worldRot);
|
||||
GetTransform()->SetScale(worldScale);
|
||||
GetTransform()->SetDirty();
|
||||
SetParent(nullptr, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,4 +243,4 @@ void GameObject::Deserialize(std::istream& is) {
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
} // namespace XCEngine
|
||||
|
||||
64
engine/src/Components/LightComponent.cpp
Normal file
64
engine/src/Components/LightComponent.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "Components/LightComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
void LightComponent::SetIntensity(float value) {
|
||||
m_intensity = std::max(0.0f, value);
|
||||
}
|
||||
|
||||
void LightComponent::SetRange(float value) {
|
||||
m_range = std::max(0.001f, value);
|
||||
}
|
||||
|
||||
void LightComponent::SetSpotAngle(float value) {
|
||||
m_spotAngle = std::clamp(value, 1.0f, 179.0f);
|
||||
}
|
||||
|
||||
void LightComponent::Serialize(std::ostream& os) const {
|
||||
os << "type=" << static_cast<int>(m_lightType) << ";";
|
||||
os << "color=" << m_color.r << "," << m_color.g << "," << m_color.b << "," << m_color.a << ";";
|
||||
os << "intensity=" << m_intensity << ";";
|
||||
os << "range=" << m_range << ";";
|
||||
os << "spotAngle=" << m_spotAngle << ";";
|
||||
os << "shadows=" << (m_castsShadows ? 1 : 0) << ";";
|
||||
}
|
||||
|
||||
void LightComponent::Deserialize(std::istream& is) {
|
||||
std::string token;
|
||||
while (std::getline(is, token, ';')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eqPos = token.find('=');
|
||||
if (eqPos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = token.substr(0, eqPos);
|
||||
std::string value = token.substr(eqPos + 1);
|
||||
|
||||
if (key == "type") {
|
||||
m_lightType = static_cast<LightType>(std::stoi(value));
|
||||
} else if (key == "color") {
|
||||
std::replace(value.begin(), value.end(), ',', ' ');
|
||||
std::istringstream ss(value);
|
||||
ss >> m_color.r >> m_color.g >> m_color.b >> m_color.a;
|
||||
} else if (key == "intensity") {
|
||||
SetIntensity(std::stof(value));
|
||||
} else if (key == "range") {
|
||||
SetRange(std::stof(value));
|
||||
} else if (key == "spotAngle") {
|
||||
SetSpotAngle(std::stof(value));
|
||||
} else if (key == "shadows") {
|
||||
m_castsShadows = (std::stoi(value) != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
@@ -1,10 +1,126 @@
|
||||
#include "Scene/Scene.h"
|
||||
#include "Components/GameObject.h"
|
||||
#include "Components/TransformComponent.h"
|
||||
#include "Components/CameraComponent.h"
|
||||
#include "Components/LightComponent.h"
|
||||
#include "Components/AudioSourceComponent.h"
|
||||
#include "Components/AudioListenerComponent.h"
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
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<PendingComponentData> 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;
|
||||
}
|
||||
|
||||
Component* CreateComponentByType(GameObject* gameObject, const std::string& type) {
|
||||
if (!gameObject) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (type == "Camera") {
|
||||
return gameObject->AddComponent<CameraComponent>();
|
||||
}
|
||||
if (type == "Light") {
|
||||
return gameObject->AddComponent<LightComponent>();
|
||||
}
|
||||
if (type == "AudioSource") {
|
||||
return gameObject->AddComponent<AudioSourceComponent>();
|
||||
}
|
||||
if (type == "AudioListener") {
|
||||
return gameObject->AddComponent<AudioListenerComponent>();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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<Component>();
|
||||
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") {
|
||||
}
|
||||
@@ -24,14 +140,13 @@ GameObject* Scene::CreateGameObject(const std::string& name, GameObject* parent)
|
||||
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->m_scene = this;
|
||||
ptr->Awake();
|
||||
|
||||
m_onGameObjectCreated.Invoke(ptr);
|
||||
@@ -159,26 +274,118 @@ void Scene::Load(const std::string& filePath) {
|
||||
m_rootGameObjects.clear();
|
||||
m_gameObjectIDs.clear();
|
||||
|
||||
std::vector<PendingGameObjectData> pendingObjects;
|
||||
std::string line;
|
||||
PendingGameObjectData* currentObject = nullptr;
|
||||
GameObject::ID maxId = 0;
|
||||
|
||||
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));
|
||||
|
||||
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<GameObject::ID>(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<GameObject::ID>(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<GameObject::ID, GameObject*> createdObjects;
|
||||
createdObjects.reserve(pendingObjects.size());
|
||||
|
||||
for (const PendingGameObjectData& pending : pendingObjects) {
|
||||
auto go = std::make_unique<GameObject>(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 = CreateComponentByType(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,15 +396,14 @@ void Scene::Save(const std::string& filePath) {
|
||||
}
|
||||
|
||||
file << "# XCEngine Scene File\n";
|
||||
file << "scene=" << m_name << ";\n";
|
||||
file << "active=" << (m_active ? "1" : "0") << ";\n\n";
|
||||
file << "scene=" << EscapeString(m_name) << "\n";
|
||||
file << "active=" << (m_active ? "1" : "0") << "\n\n";
|
||||
|
||||
for (auto* go : GetRootGameObjects()) {
|
||||
file << "gameobject=";
|
||||
go->Serialize(file);
|
||||
SerializeGameObjectRecursive(file, go);
|
||||
file << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
} // namespace XCEngine
|
||||
|
||||
Reference in New Issue
Block a user