UI Editor: Enhance GameObject system and panel functionality

This commit is contained in:
2026-03-20 19:43:17 +08:00
parent f8573d2715
commit 80e47a0ab9
6 changed files with 119 additions and 112 deletions

View File

@@ -14,45 +14,10 @@ namespace UI {
using EntityID = uint64_t; using EntityID = uint64_t;
constexpr EntityID INVALID_ENTITY_ID = 0; constexpr EntityID INVALID_ENTITY_ID = 0;
class Component { class Component;
public: class TransformComponent;
virtual ~Component() = default;
virtual std::string GetName() const = 0;
virtual void Awake() {}
virtual void Start() {}
virtual void Update(float deltaTime) {}
virtual void OnDestroy() {}
class Entity* GetEntity() const { return m_entity; }
bool IsEnabled() const { return m_enabled; }
void SetEnabled(bool enabled) { m_enabled = enabled; }
protected:
class Entity* m_entity = nullptr;
bool m_enabled = true;
friend class Entity;
};
class TransformComponent : public Component { class GameObject {
public:
float position[3] = {0.0f, 0.0f, 0.0f};
float rotation[3] = {0.0f, 0.0f, 0.0f};
float scale[3] = {1.0f, 1.0f, 1.0f};
std::string GetName() const override { return "Transform"; }
};
class MeshRendererComponent : public Component {
public:
std::string materialName = "Default-Material";
std::string meshName = "";
std::string GetName() const override { return "Mesh Renderer"; }
};
class Entity {
public: public:
EntityID id = INVALID_ENTITY_ID; EntityID id = INVALID_ENTITY_ID;
std::string name; std::string name;
@@ -60,16 +25,16 @@ public:
std::vector<EntityID> children; std::vector<EntityID> children;
std::vector<std::unique_ptr<Component>> components; std::vector<std::unique_ptr<Component>> components;
bool selected = false; bool selected = false;
template<typename T, typename... Args> template<typename T, typename... Args>
T* AddComponent(Args&&... args) { T* AddComponent(Args&&... args) {
auto comp = std::make_unique<T>(std::forward<Args>(args)...); auto comp = std::make_unique<T>(std::forward<Args>(args)...);
comp->m_entity = this; comp->m_gameObject = this;
T* ptr = comp.get(); T* ptr = comp.get();
components.push_back(std::move(comp)); components.push_back(std::move(comp));
return ptr; return ptr;
} }
template<typename T> template<typename T>
T* GetComponent() { T* GetComponent() {
for (auto& comp : components) { for (auto& comp : components) {
@@ -79,7 +44,7 @@ public:
} }
return nullptr; return nullptr;
} }
template<typename T> template<typename T>
std::vector<T*> GetComponents() { std::vector<T*> GetComponents() {
std::vector<T*> result; std::vector<T*> result;
@@ -90,6 +55,48 @@ public:
} }
return result; return result;
} }
TransformComponent* GetTransform() {
return GetComponent<TransformComponent>();
}
};
class Component {
public:
virtual ~Component() = default;
virtual std::string GetName() const = 0;
virtual void Awake() {}
virtual void Start() {}
virtual void Update(float deltaTime) {}
virtual void OnDestroy() {}
GameObject* GetGameObject() const { return m_gameObject; }
bool IsEnabled() const { return m_enabled; }
void SetEnabled(bool enabled) { m_enabled = enabled; }
protected:
GameObject* m_gameObject = nullptr;
bool m_enabled = true;
friend class GameObject;
};
class TransformComponent : public Component {
public:
float position[3] = {0.0f, 0.0f, 0.0f};
float rotation[3] = {0.0f, 0.0f, 0.0f};
float scale[3] = {1.0f, 1.0f, 1.0f};
std::string GetName() const override { return "Transform"; }
};
class MeshRendererComponent : public Component {
public:
std::string materialName = "Default-Material";
std::string meshName = "";
std::string GetName() const override { return "Mesh Renderer"; }
}; };
using ComponentInspectorFn = std::function<void(Component*)>; using ComponentInspectorFn = std::function<void(Component*)>;
@@ -105,7 +112,7 @@ public:
static ComponentRegistry instance; static ComponentRegistry instance;
return instance; return instance;
} }
template<typename T> template<typename T>
void RegisterComponent(const std::string& name, ComponentInspectorFn inspectorFn) { void RegisterComponent(const std::string& name, ComponentInspectorFn inspectorFn) {
m_inspectors[name] = {name, inspectorFn}; m_inspectors[name] = {name, inspectorFn};
@@ -113,7 +120,7 @@ public:
return std::make_unique<T>(); return std::make_unique<T>();
}; };
} }
ComponentInspectorInfo* GetInspector(const std::string& name) { ComponentInspectorInfo* GetInspector(const std::string& name) {
auto it = m_inspectors.find(name); auto it = m_inspectors.find(name);
if (it != m_inspectors.end()) { if (it != m_inspectors.end()) {
@@ -121,7 +128,7 @@ public:
} }
return nullptr; return nullptr;
} }
private: private:
ComponentRegistry() = default; ComponentRegistry() = default;
std::unordered_map<std::string, ComponentInspectorInfo> m_inspectors; std::unordered_map<std::string, ComponentInspectorInfo> m_inspectors;

View File

@@ -6,18 +6,18 @@ namespace UI {
EntityID SceneManager::CreateEntity(const std::string& name, EntityID parent) { EntityID SceneManager::CreateEntity(const std::string& name, EntityID parent) {
EntityID id = m_nextEntityId++; EntityID id = m_nextEntityId++;
Entity entity; GameObject entity;
entity.id = id; entity.id = id;
entity.name = name; entity.name = name;
entity.parent = parent; entity.parent = parent;
m_entities[id] = std::move(entity); m_entities[id] = std::move(entity);
if (parent != INVALID_ENTITY_ID) { if (parent != INVALID_ENTITY_ID) {
m_entities[parent].children.push_back(id); m_entities[parent].children.push_back(id);
} else { } else {
m_rootEntities.push_back(id); m_rootEntities.push_back(id);
} }
OnEntityCreated.Invoke(id); OnEntityCreated.Invoke(id);
return id; return id;
} }
@@ -25,14 +25,14 @@ EntityID SceneManager::CreateEntity(const std::string& name, EntityID parent) {
void SceneManager::DeleteEntity(EntityID id) { void SceneManager::DeleteEntity(EntityID id) {
auto it = m_entities.find(id); auto it = m_entities.find(id);
if (it == m_entities.end()) return; if (it == m_entities.end()) return;
Entity& entity = it->second; GameObject& entity = it->second;
std::vector<EntityID> childrenToDelete = entity.children; std::vector<EntityID> childrenToDelete = entity.children;
for (EntityID childId : childrenToDelete) { for (EntityID childId : childrenToDelete) {
DeleteEntity(childId); DeleteEntity(childId);
} }
if (entity.parent != INVALID_ENTITY_ID) { if (entity.parent != INVALID_ENTITY_ID) {
auto* parent = GetEntity(entity.parent); auto* parent = GetEntity(entity.parent);
if (parent) { if (parent) {
@@ -42,19 +42,19 @@ void SceneManager::DeleteEntity(EntityID id) {
} else { } else {
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end()); m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end());
} }
if (SelectionManager::Get().GetSelectedEntity() == id) { if (SelectionManager::Get().GetSelectedEntity() == id) {
SelectionManager::Get().ClearSelection(); SelectionManager::Get().ClearSelection();
} }
m_entities.erase(it); m_entities.erase(it);
OnEntityDeleted.Invoke(id); OnEntityDeleted.Invoke(id);
} }
ClipboardData SceneManager::CopyEntityRecursive(const Entity* entity) { ClipboardData SceneManager::CopyEntityRecursive(const GameObject* entity) {
ClipboardData data; ClipboardData data;
data.name = entity->name; data.name = entity->name;
for (const auto& comp : entity->components) { for (const auto& comp : entity->components) {
if (auto* transform = dynamic_cast<const TransformComponent*>(comp.get())) { if (auto* transform = dynamic_cast<const TransformComponent*>(comp.get())) {
auto newComp = std::make_unique<TransformComponent>(); auto newComp = std::make_unique<TransformComponent>();
@@ -70,28 +70,28 @@ ClipboardData SceneManager::CopyEntityRecursive(const Entity* entity) {
data.components.push_back(std::move(newComp)); data.components.push_back(std::move(newComp));
} }
} }
for (EntityID childId : entity->children) { for (EntityID childId : entity->children) {
const Entity* child = GetEntity(childId); const GameObject* child = GetEntity(childId);
if (child) { if (child) {
data.children.push_back(CopyEntityRecursive(child)); data.children.push_back(CopyEntityRecursive(child));
} }
} }
return data; return data;
} }
void SceneManager::CopyEntity(EntityID id) { void SceneManager::CopyEntity(EntityID id) {
const Entity* entity = GetEntity(id); const GameObject* entity = GetEntity(id);
if (!entity) return; if (!entity) return;
m_clipboard = CopyEntityRecursive(entity); m_clipboard = CopyEntityRecursive(entity);
} }
EntityID SceneManager::PasteEntityRecursive(const ClipboardData& data, EntityID parent) { EntityID SceneManager::PasteEntityRecursive(const ClipboardData& data, EntityID parent) {
EntityID newId = CreateEntity(data.name, parent); EntityID newId = CreateEntity(data.name, parent);
Entity* newEntity = GetEntity(newId); GameObject* newEntity = GetEntity(newId);
if (newEntity) { if (newEntity) {
newEntity->components.clear(); newEntity->components.clear();
for (const auto& comp : data.components) { for (const auto& comp : data.components) {
@@ -110,11 +110,11 @@ EntityID SceneManager::PasteEntityRecursive(const ClipboardData& data, EntityID
} }
} }
} }
for (const auto& childData : data.children) { for (const auto& childData : data.children) {
PasteEntityRecursive(childData, newId); PasteEntityRecursive(childData, newId);
} }
return newId; return newId;
} }
@@ -125,17 +125,17 @@ EntityID SceneManager::PasteEntity(EntityID parent) {
EntityID SceneManager::DuplicateEntity(EntityID id) { EntityID SceneManager::DuplicateEntity(EntityID id) {
CopyEntity(id); CopyEntity(id);
const Entity* entity = GetEntity(id); const GameObject* entity = GetEntity(id);
if (!entity) return INVALID_ENTITY_ID; if (!entity) return INVALID_ENTITY_ID;
return PasteEntity(entity->parent); return PasteEntity(entity->parent);
} }
void SceneManager::MoveEntity(EntityID id, EntityID newParent) { void SceneManager::MoveEntity(EntityID id, EntityID newParent) {
Entity* entity = GetEntity(id); GameObject* entity = GetEntity(id);
if (!entity || id == newParent) return; if (!entity || id == newParent) return;
if (entity->parent != INVALID_ENTITY_ID) { if (entity->parent != INVALID_ENTITY_ID) {
Entity* oldParent = GetEntity(entity->parent); GameObject* oldParent = GetEntity(entity->parent);
if (oldParent) { if (oldParent) {
auto& siblings = oldParent->children; auto& siblings = oldParent->children;
siblings.erase(std::remove(siblings.begin(), siblings.end(), id), siblings.end()); siblings.erase(std::remove(siblings.begin(), siblings.end(), id), siblings.end());
@@ -143,18 +143,18 @@ void SceneManager::MoveEntity(EntityID id, EntityID newParent) {
} else { } else {
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end()); m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end());
} }
entity->parent = newParent; entity->parent = newParent;
if (newParent != INVALID_ENTITY_ID) { if (newParent != INVALID_ENTITY_ID) {
Entity* newParentEntity = GetEntity(newParent); GameObject* newParentEntity = GetEntity(newParent);
if (newParentEntity) { if (newParentEntity) {
newParentEntity->children.push_back(id); newParentEntity->children.push_back(id);
} }
} else { } else {
m_rootEntities.push_back(id); m_rootEntities.push_back(id);
} }
OnEntityChanged.Invoke(id); OnEntityChanged.Invoke(id);
} }
@@ -163,24 +163,24 @@ void SceneManager::CreateDemoScene() {
m_rootEntities.clear(); m_rootEntities.clear();
m_nextEntityId = 1; m_nextEntityId = 1;
m_clipboard.reset(); m_clipboard.reset();
EntityID camera = CreateEntity("Main Camera"); EntityID camera = CreateEntity("Main Camera");
GetEntity(camera)->AddComponent<TransformComponent>(); GetEntity(camera)->AddComponent<TransformComponent>();
EntityID light = CreateEntity("Directional Light"); EntityID light = CreateEntity("Directional Light");
EntityID cube = CreateEntity("Cube"); EntityID cube = CreateEntity("Cube");
GetEntity(cube)->AddComponent<TransformComponent>(); GetEntity(cube)->AddComponent<TransformComponent>();
GetEntity(cube)->AddComponent<MeshRendererComponent>()->meshName = "Cube Mesh"; GetEntity(cube)->AddComponent<MeshRendererComponent>()->meshName = "Cube Mesh";
EntityID sphere = CreateEntity("Sphere"); EntityID sphere = CreateEntity("Sphere");
GetEntity(sphere)->AddComponent<TransformComponent>(); GetEntity(sphere)->AddComponent<TransformComponent>();
GetEntity(sphere)->AddComponent<MeshRendererComponent>()->meshName = "Sphere Mesh"; GetEntity(sphere)->AddComponent<MeshRendererComponent>()->meshName = "Sphere Mesh";
EntityID player = CreateEntity("Player"); EntityID player = CreateEntity("Player");
EntityID weapon = CreateEntity("Weapon", player); EntityID weapon = CreateEntity("Weapon", player);
OnSceneChanged.Invoke(); OnSceneChanged.Invoke();
} }
} }

View File

@@ -22,31 +22,31 @@ public:
static SceneManager instance; static SceneManager instance;
return instance; return instance;
} }
EntityID CreateEntity(const std::string& name, EntityID parent = INVALID_ENTITY_ID); EntityID CreateEntity(const std::string& name, EntityID parent = INVALID_ENTITY_ID);
Entity* GetEntity(EntityID id) { GameObject* GetEntity(EntityID id) {
auto it = m_entities.find(id); auto it = m_entities.find(id);
if (it != m_entities.end()) { if (it != m_entities.end()) {
return &it->second; return &it->second;
} }
return nullptr; return nullptr;
} }
const Entity* GetEntity(EntityID id) const { const GameObject* GetEntity(EntityID id) const {
auto it = m_entities.find(id); auto it = m_entities.find(id);
if (it != m_entities.end()) { if (it != m_entities.end()) {
return &it->second; return &it->second;
} }
return nullptr; return nullptr;
} }
const std::vector<EntityID>& GetRootEntities() const { const std::vector<EntityID>& GetRootEntities() const {
return m_rootEntities; return m_rootEntities;
} }
void DeleteEntity(EntityID id); void DeleteEntity(EntityID id);
void RenameEntity(EntityID id, const std::string& newName) { void RenameEntity(EntityID id, const std::string& newName) {
auto* entity = GetEntity(id); auto* entity = GetEntity(id);
if (entity) { if (entity) {
@@ -54,32 +54,32 @@ public:
OnEntityChanged.Invoke(id); OnEntityChanged.Invoke(id);
} }
} }
void CopyEntity(EntityID id); void CopyEntity(EntityID id);
EntityID PasteEntity(EntityID parent = INVALID_ENTITY_ID); EntityID PasteEntity(EntityID parent = INVALID_ENTITY_ID);
EntityID DuplicateEntity(EntityID id); EntityID DuplicateEntity(EntityID id);
void MoveEntity(EntityID id, EntityID newParent); void MoveEntity(EntityID id, EntityID newParent);
void CreateDemoScene(); void CreateDemoScene();
bool HasClipboardData() const { return m_clipboard.has_value(); } bool HasClipboardData() const { return m_clipboard.has_value(); }
XCEngine::Core::Event<EntityID> OnEntityCreated; XCEngine::Core::Event<EntityID> OnEntityCreated;
XCEngine::Core::Event<EntityID> OnEntityDeleted; XCEngine::Core::Event<EntityID> OnEntityDeleted;
XCEngine::Core::Event<EntityID> OnEntityChanged; XCEngine::Core::Event<EntityID> OnEntityChanged;
XCEngine::Core::Event<> OnSceneChanged; XCEngine::Core::Event<> OnSceneChanged;
private: private:
SceneManager() = default; SceneManager() = default;
ClipboardData CopyEntityRecursive(const Entity* entity); ClipboardData CopyEntityRecursive(const GameObject* entity);
EntityID PasteEntityRecursive(const ClipboardData& data, EntityID parent); EntityID PasteEntityRecursive(const ClipboardData& data, EntityID parent);
EntityID m_nextEntityId = 1; EntityID m_nextEntityId = 1;
std::unordered_map<EntityID, Entity> m_entities; std::unordered_map<EntityID, GameObject> m_entities;
std::vector<EntityID> m_rootEntities; std::vector<EntityID> m_rootEntities;
std::optional<ClipboardData> m_clipboard; std::optional<ClipboardData> m_clipboard;
}; };

View File

@@ -50,7 +50,7 @@ void HierarchyPanel::Render() {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_ID")) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_ID")) {
EntityID sourceId = *(const EntityID*)payload->Data; EntityID sourceId = *(const EntityID*)payload->Data;
if (sourceId != INVALID_ENTITY_ID) { if (sourceId != INVALID_ENTITY_ID) {
const Entity* sourceEntity = SceneManager::Get().GetEntity(sourceId); const GameObject* sourceEntity = SceneManager::Get().GetEntity(sourceId);
if (sourceEntity && sourceEntity->parent != INVALID_ENTITY_ID) { if (sourceEntity && sourceEntity->parent != INVALID_ENTITY_ID) {
SceneManager::Get().MoveEntity(sourceId, INVALID_ENTITY_ID); SceneManager::Get().MoveEntity(sourceId, INVALID_ENTITY_ID);
} }
@@ -71,7 +71,7 @@ void HierarchyPanel::RenderSearchBar() {
void HierarchyPanel::RenderEntity(EntityID id, const std::string& filter) { void HierarchyPanel::RenderEntity(EntityID id, const std::string& filter) {
auto& sceneManager = SceneManager::Get(); auto& sceneManager = SceneManager::Get();
Entity* entity = sceneManager.GetEntity(id); GameObject* entity = sceneManager.GetEntity(id);
if (!entity) return; if (!entity) return;
if (!filter.empty() && !PassesFilter(id, filter)) { if (!filter.empty() && !PassesFilter(id, filter)) {
@@ -156,7 +156,7 @@ void HierarchyPanel::RenderContextMenu(EntityID id) {
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("Rename", "F2")) { if (ImGui::MenuItem("Rename", "F2")) {
const Entity* entity = sceneManager.GetEntity(id); const GameObject* entity = sceneManager.GetEntity(id);
if (entity) { if (entity) {
m_renaming = true; m_renaming = true;
m_renamingEntity = id; m_renamingEntity = id;
@@ -239,7 +239,7 @@ void HierarchyPanel::HandleDragDrop(EntityID id) {
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
m_dragSource = id; m_dragSource = id;
ImGui::SetDragDropPayload("ENTITY_ID", &id, sizeof(EntityID)); ImGui::SetDragDropPayload("ENTITY_ID", &id, sizeof(EntityID));
const Entity* entity = sceneManager.GetEntity(id); const GameObject* entity = sceneManager.GetEntity(id);
if (entity) { if (entity) {
ImGui::Text("%s", entity->name.c_str()); ImGui::Text("%s", entity->name.c_str());
} }
@@ -250,8 +250,8 @@ void HierarchyPanel::HandleDragDrop(EntityID id) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_ID")) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_ID")) {
EntityID sourceId = *(const EntityID*)payload->Data; EntityID sourceId = *(const EntityID*)payload->Data;
if (sourceId != id && sourceId != INVALID_ENTITY_ID) { if (sourceId != id && sourceId != INVALID_ENTITY_ID) {
const Entity* targetEntity = sceneManager.GetEntity(id); const GameObject* targetEntity = sceneManager.GetEntity(id);
const Entity* sourceEntity = sceneManager.GetEntity(sourceId); const GameObject* sourceEntity = sceneManager.GetEntity(sourceId);
bool isValidMove = true; bool isValidMove = true;
EntityID checkParent = targetEntity ? targetEntity->parent : INVALID_ENTITY_ID; EntityID checkParent = targetEntity ? targetEntity->parent : INVALID_ENTITY_ID;
@@ -260,7 +260,7 @@ void HierarchyPanel::HandleDragDrop(EntityID id) {
isValidMove = false; isValidMove = false;
break; break;
} }
const Entity* parentEntity = sceneManager.GetEntity(checkParent); const GameObject* parentEntity = sceneManager.GetEntity(checkParent);
checkParent = parentEntity ? parentEntity->parent : INVALID_ENTITY_ID; checkParent = parentEntity ? parentEntity->parent : INVALID_ENTITY_ID;
} }
@@ -288,7 +288,7 @@ void HierarchyPanel::HandleKeyboardShortcuts() {
if (ImGui::IsKeyPressed(ImGuiKey_F2)) { if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
if (selectedId != INVALID_ENTITY_ID) { if (selectedId != INVALID_ENTITY_ID) {
const Entity* entity = sceneManager.GetEntity(selectedId); const GameObject* entity = sceneManager.GetEntity(selectedId);
if (entity) { if (entity) {
m_renaming = true; m_renaming = true;
m_renamingEntity = selectedId; m_renamingEntity = selectedId;
@@ -326,7 +326,7 @@ void HierarchyPanel::HandleKeyboardShortcuts() {
bool HierarchyPanel::PassesFilter(EntityID id, const std::string& filter) { bool HierarchyPanel::PassesFilter(EntityID id, const std::string& filter) {
auto& sceneManager = SceneManager::Get(); auto& sceneManager = SceneManager::Get();
const Entity* entity = sceneManager.GetEntity(id); const GameObject* entity = sceneManager.GetEntity(id);
if (!entity) return false; if (!entity) return false;
if (entity->name.find(filter) != std::string::npos) { if (entity->name.find(filter) != std::string::npos) {

View File

@@ -19,7 +19,7 @@ void InspectorPanel::Render() {
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None); ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
EntityID selectedId = SelectionManager::Get().GetSelectedEntity(); EntityID selectedId = SelectionManager::Get().GetSelectedEntity();
Entity* entity = SceneManager::Get().GetEntity(selectedId); GameObject* entity = SceneManager::Get().GetEntity(selectedId);
if (entity) { if (entity) {
RenderEntity(entity); RenderEntity(entity);
@@ -31,7 +31,7 @@ void InspectorPanel::Render() {
ImGui::End(); ImGui::End();
} }
void InspectorPanel::RenderEntity(Entity* entity) { void InspectorPanel::RenderEntity(GameObject* entity) {
ImGui::Text("%s", entity->name.c_str()); ImGui::Text("%s", entity->name.c_str());
ImGui::Separator(); ImGui::Separator();

View File

@@ -13,7 +13,7 @@ public:
void Render() override; void Render() override;
private: private:
void RenderEntity(Entity* entity); void RenderEntity(GameObject* entity);
void RenderComponent(Component* component); void RenderComponent(Component* component);
uint64_t m_selectionHandlerId = 0; uint64_t m_selectionHandlerId = 0;