2026-03-20 19:59:06 +08:00
|
|
|
#include "Components/GameObject.h"
|
|
|
|
|
#include "Components/TransformComponent.h"
|
|
|
|
|
#include "Scene/Scene.h"
|
|
|
|
|
|
|
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Components {
|
|
|
|
|
|
|
|
|
|
GameObject::ID GameObject::s_nextID = 1;
|
|
|
|
|
|
|
|
|
|
GameObject::GameObject()
|
|
|
|
|
: m_name("GameObject") {
|
|
|
|
|
m_id = s_nextID++;
|
2026-03-25 01:23:08 +08:00
|
|
|
static std::random_device rd;
|
|
|
|
|
static std::mt19937_64 gen(rd());
|
|
|
|
|
static std::uniform_int_distribution<uint64_t> dis(1, UINT64_MAX);
|
|
|
|
|
m_uuid = dis(gen);
|
2026-03-20 19:59:06 +08:00
|
|
|
m_transform = new TransformComponent();
|
|
|
|
|
m_transform->m_gameObject = this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameObject::GameObject(const std::string& name)
|
|
|
|
|
: m_name(name) {
|
|
|
|
|
m_id = s_nextID++;
|
2026-03-25 01:23:08 +08:00
|
|
|
static std::random_device rd;
|
|
|
|
|
static std::mt19937_64 gen(rd());
|
|
|
|
|
static std::uniform_int_distribution<uint64_t> dis(1, UINT64_MAX);
|
|
|
|
|
m_uuid = dis(gen);
|
2026-03-20 19:59:06 +08:00
|
|
|
m_transform = new TransformComponent();
|
|
|
|
|
m_transform->m_gameObject = this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameObject::~GameObject() {
|
2026-03-22 02:10:32 +08:00
|
|
|
GetGlobalRegistry().erase(m_id);
|
2026-03-20 19:59:06 +08:00
|
|
|
if (m_transform) {
|
|
|
|
|
delete m_transform;
|
|
|
|
|
m_transform = nullptr;
|
|
|
|
|
}
|
|
|
|
|
m_components.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unordered_map<GameObject::ID, GameObject*>& GameObject::GetGlobalRegistry() {
|
|
|
|
|
static std::unordered_map<ID, GameObject*> registry;
|
|
|
|
|
return registry;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 20:14:58 +08:00
|
|
|
void GameObject::NotifyComponentsBecameActive() {
|
|
|
|
|
for (auto& comp : m_components) {
|
|
|
|
|
if (comp->IsEnabled()) {
|
|
|
|
|
comp->OnEnable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::NotifyComponentsBecameInactive() {
|
|
|
|
|
for (auto& comp : m_components) {
|
|
|
|
|
if (comp->IsEnabled()) {
|
|
|
|
|
comp->OnDisable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::PropagateActiveInHierarchyChange(bool oldParentActiveInHierarchy, bool newParentActiveInHierarchy) {
|
|
|
|
|
const bool wasActiveInHierarchy = oldParentActiveInHierarchy && m_activeSelf;
|
|
|
|
|
const bool isActiveInHierarchy = newParentActiveInHierarchy && m_activeSelf;
|
|
|
|
|
|
|
|
|
|
if (wasActiveInHierarchy != isActiveInHierarchy) {
|
|
|
|
|
if (isActiveInHierarchy) {
|
|
|
|
|
NotifyComponentsBecameActive();
|
|
|
|
|
} else {
|
|
|
|
|
NotifyComponentsBecameInactive();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto* child : m_children) {
|
|
|
|
|
if (child) {
|
|
|
|
|
child->PropagateActiveInHierarchyChange(wasActiveInHierarchy, isActiveInHierarchy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 19:59:06 +08:00
|
|
|
void GameObject::SetParent(GameObject* parent) {
|
|
|
|
|
SetParent(parent, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::SetParent(GameObject* parent, bool worldPositionStays) {
|
2026-03-26 01:26:26 +08:00
|
|
|
if (m_parent == parent || parent == this) {
|
2026-03-20 19:59:06 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 20:14:58 +08:00
|
|
|
const bool oldParentActiveInHierarchy = m_parent ? m_parent->IsActiveInHierarchy() : true;
|
|
|
|
|
const bool wasActiveInHierarchy = oldParentActiveInHierarchy && m_activeSelf;
|
|
|
|
|
|
2026-03-20 19:59:06 +08:00
|
|
|
if (m_parent) {
|
|
|
|
|
auto& siblings = m_parent->m_children;
|
|
|
|
|
siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end());
|
2026-03-26 01:26:26 +08:00
|
|
|
} else if (m_scene) {
|
|
|
|
|
auto& roots = m_scene->m_rootGameObjects;
|
|
|
|
|
roots.erase(std::remove(roots.begin(), roots.end(), m_id), roots.end());
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_parent = parent;
|
|
|
|
|
|
|
|
|
|
if (m_parent) {
|
|
|
|
|
m_parent->m_children.push_back(this);
|
2026-03-26 01:26:26 +08:00
|
|
|
} 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);
|
|
|
|
|
}
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 01:26:26 +08:00
|
|
|
GetTransform()->SetParent(parent ? parent->GetTransform() : nullptr, worldPositionStays);
|
2026-03-26 20:14:58 +08:00
|
|
|
|
|
|
|
|
const bool newParentActiveInHierarchy = m_parent ? m_parent->IsActiveInHierarchy() : true;
|
|
|
|
|
const bool isActiveInHierarchy = newParentActiveInHierarchy && m_activeSelf;
|
|
|
|
|
if (wasActiveInHierarchy != isActiveInHierarchy) {
|
|
|
|
|
if (isActiveInHierarchy) {
|
|
|
|
|
NotifyComponentsBecameActive();
|
|
|
|
|
} else {
|
|
|
|
|
NotifyComponentsBecameInactive();
|
|
|
|
|
}
|
|
|
|
|
for (auto* child : m_children) {
|
|
|
|
|
if (child) {
|
|
|
|
|
child->PropagateActiveInHierarchyChange(wasActiveInHierarchy, isActiveInHierarchy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameObject* GameObject::GetChild(size_t index) const {
|
|
|
|
|
if (index < m_children.size()) {
|
|
|
|
|
return m_children[index];
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<GameObject*> GameObject::GetChildren() const {
|
|
|
|
|
return m_children;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::DetachChildren() {
|
2026-03-26 01:26:26 +08:00
|
|
|
auto children = m_children;
|
|
|
|
|
for (auto* child : children) {
|
2026-03-20 19:59:06 +08:00
|
|
|
if (child) {
|
2026-03-26 01:26:26 +08:00
|
|
|
child->SetParent(nullptr, true);
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 01:23:08 +08:00
|
|
|
void GameObject::DetachFromParent() {
|
|
|
|
|
if (m_parent) {
|
2026-03-26 01:26:26 +08:00
|
|
|
SetParent(nullptr, true);
|
2026-03-25 01:23:08 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 19:59:06 +08:00
|
|
|
void GameObject::SetActive(bool active) {
|
2026-03-26 20:14:58 +08:00
|
|
|
if (m_activeSelf == active) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool parentActiveInHierarchy = m_parent ? m_parent->IsActiveInHierarchy() : true;
|
|
|
|
|
const bool wasActiveInHierarchy = parentActiveInHierarchy && m_activeSelf;
|
|
|
|
|
|
|
|
|
|
m_activeSelf = active;
|
|
|
|
|
|
|
|
|
|
const bool isActiveInHierarchy = parentActiveInHierarchy && m_activeSelf;
|
|
|
|
|
if (wasActiveInHierarchy != isActiveInHierarchy) {
|
|
|
|
|
if (isActiveInHierarchy) {
|
|
|
|
|
NotifyComponentsBecameActive();
|
|
|
|
|
} else {
|
|
|
|
|
NotifyComponentsBecameInactive();
|
|
|
|
|
}
|
|
|
|
|
for (auto* child : m_children) {
|
|
|
|
|
if (child) {
|
|
|
|
|
child->PropagateActiveInHierarchyChange(wasActiveInHierarchy, isActiveInHierarchy);
|
|
|
|
|
}
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GameObject::IsActiveInHierarchy() const {
|
|
|
|
|
if (!m_activeSelf) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (m_parent) {
|
|
|
|
|
return m_parent->IsActiveInHierarchy();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameObject* GameObject::Find(const std::string& name) {
|
|
|
|
|
auto& registry = GetGlobalRegistry();
|
|
|
|
|
for (auto& pair : registry) {
|
|
|
|
|
if (pair.second->GetName() == name) {
|
|
|
|
|
return pair.second;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<GameObject*> GameObject::FindObjectsOfType() {
|
|
|
|
|
auto& registry = GetGlobalRegistry();
|
|
|
|
|
std::vector<GameObject*> result;
|
|
|
|
|
for (auto& pair : registry) {
|
|
|
|
|
result.push_back(pair.second);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<GameObject*> GameObject::FindGameObjectsWithTag(const std::string& tag) {
|
|
|
|
|
auto& registry = GetGlobalRegistry();
|
|
|
|
|
std::vector<GameObject*> result;
|
|
|
|
|
for (auto& pair : registry) {
|
|
|
|
|
if (pair.second->GetName() == tag) {
|
|
|
|
|
result.push_back(pair.second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::Awake() {
|
|
|
|
|
for (auto& comp : m_components) {
|
|
|
|
|
comp->Awake();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::Start() {
|
2026-03-26 20:14:58 +08:00
|
|
|
if (!IsActiveInHierarchy()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_started) {
|
|
|
|
|
for (auto& comp : m_components) {
|
|
|
|
|
if (comp->IsEnabled()) {
|
|
|
|
|
comp->Start();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_started = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto* child : m_children) {
|
|
|
|
|
if (child) {
|
|
|
|
|
child->Start();
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::Update(float deltaTime) {
|
2026-03-26 20:14:58 +08:00
|
|
|
if (!IsActiveInHierarchy()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 19:59:06 +08:00
|
|
|
for (auto& comp : m_components) {
|
|
|
|
|
if (comp->IsEnabled()) {
|
|
|
|
|
comp->Update(deltaTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-26 20:14:58 +08:00
|
|
|
|
|
|
|
|
for (auto* child : m_children) {
|
|
|
|
|
if (child) {
|
|
|
|
|
child->Update(deltaTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::FixedUpdate() {
|
2026-03-26 20:14:58 +08:00
|
|
|
if (!IsActiveInHierarchy()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 19:59:06 +08:00
|
|
|
for (auto& comp : m_components) {
|
|
|
|
|
if (comp->IsEnabled()) {
|
|
|
|
|
comp->FixedUpdate();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-26 20:14:58 +08:00
|
|
|
|
|
|
|
|
for (auto* child : m_children) {
|
|
|
|
|
if (child) {
|
|
|
|
|
child->FixedUpdate();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::LateUpdate(float deltaTime) {
|
2026-03-26 20:14:58 +08:00
|
|
|
if (!IsActiveInHierarchy()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 19:59:06 +08:00
|
|
|
for (auto& comp : m_components) {
|
|
|
|
|
if (comp->IsEnabled()) {
|
|
|
|
|
comp->LateUpdate(deltaTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-26 20:14:58 +08:00
|
|
|
|
|
|
|
|
for (auto* child : m_children) {
|
|
|
|
|
if (child) {
|
|
|
|
|
child->LateUpdate(deltaTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::OnDestroy() {
|
|
|
|
|
for (auto& comp : m_components) {
|
|
|
|
|
comp->OnDestroy();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameObject::Destroy() {
|
|
|
|
|
if (m_scene) {
|
|
|
|
|
m_scene->DestroyGameObject(this);
|
2026-03-26 20:14:58 +08:00
|
|
|
} else {
|
|
|
|
|
OnDestroy();
|
2026-03-20 19:59:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-22 03:42:40 +08:00
|
|
|
void GameObject::Serialize(std::ostream& os) const {
|
|
|
|
|
os << "name=" << m_name << ";";
|
|
|
|
|
os << "active=" << (m_activeSelf ? "1" : "0") << ";";
|
|
|
|
|
os << "id=" << m_id << ";";
|
2026-03-26 20:14:58 +08:00
|
|
|
os << "uuid=" << m_uuid << ";";
|
2026-03-22 03:42:40 +08:00
|
|
|
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();
|
2026-03-26 20:14:58 +08:00
|
|
|
} else if (strcmp(key, "uuid") == 0) {
|
|
|
|
|
is >> m_uuid;
|
|
|
|
|
if (is.peek() == ';') is.get();
|
2026-03-22 03:42:40 +08:00
|
|
|
} else if (strcmp(key, "transform") == 0) {
|
|
|
|
|
m_transform->Deserialize(is);
|
|
|
|
|
if (is.peek() == ';') is.get();
|
|
|
|
|
} else {
|
|
|
|
|
std::getline(is, token, ';');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 19:59:06 +08:00
|
|
|
} // namespace Components
|
2026-03-26 01:26:26 +08:00
|
|
|
} // namespace XCEngine
|