重构架构:引入EventSystem、EntityID、SelectionManager
- 新增 Event 系统,支持面板间解耦通信 - 用 EntityID 替代裸指针,解决悬空指针问题 - 拆分 SelectionManager 管理选择状态 - SceneManager 使用 EntityID 和 unordered_map 存储 - HierarchyPanel/InspectorPanel 使用事件系统
This commit is contained in:
48
ui/src/Core/Event.h
Normal file
48
ui/src/Core/Event.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace UI {
|
||||
|
||||
template<typename... Args>
|
||||
class Event {
|
||||
public:
|
||||
using HandlerID = size_t;
|
||||
using Handler = std::function<void(Args...)>;
|
||||
|
||||
HandlerID Subscribe(Handler handler) {
|
||||
HandlerID id = m_nextId++;
|
||||
m_handlers.emplace_back(id, std::move(handler));
|
||||
return id;
|
||||
}
|
||||
|
||||
void Unsubscribe(HandlerID id) {
|
||||
m_handlers.erase(
|
||||
std::remove_if(m_handlers.begin(), m_handlers.end(),
|
||||
[id](const auto& pair) { return pair.first == id; }),
|
||||
m_handlers.end()
|
||||
);
|
||||
}
|
||||
|
||||
void Invoke(Args... args) {
|
||||
for (const auto& pair : m_handlers) {
|
||||
pair.second(args...);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(Args... args) {
|
||||
Invoke(args...);
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
m_handlers.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
HandlerID m_nextId = 0;
|
||||
std::vector<std::pair<HandlerID, Handler>> m_handlers;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -3,9 +3,15 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
|
||||
namespace UI {
|
||||
|
||||
using EntityID = uint64_t;
|
||||
constexpr EntityID INVALID_ENTITY = 0;
|
||||
|
||||
class Component {
|
||||
public:
|
||||
virtual ~Component() = default;
|
||||
@@ -29,16 +35,14 @@ public:
|
||||
std::string GetName() const override { return "Mesh Renderer"; }
|
||||
};
|
||||
|
||||
class GameObject {
|
||||
public:
|
||||
struct Entity {
|
||||
EntityID id = INVALID_ENTITY;
|
||||
std::string name;
|
||||
std::vector<GameObject> children;
|
||||
EntityID parent = INVALID_ENTITY;
|
||||
std::vector<EntityID> children;
|
||||
std::vector<std::unique_ptr<Component>> components;
|
||||
bool selected = false;
|
||||
|
||||
GameObject() = default;
|
||||
explicit GameObject(const std::string& n) : name(n) {}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T* AddComponent(Args&&... args) {
|
||||
auto comp = std::make_unique<T>(std::forward<Args>(args)...);
|
||||
@@ -58,4 +62,40 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
using ComponentInspectorFn = std::function<void(Component*)>;
|
||||
|
||||
struct ComponentInspectorInfo {
|
||||
std::string name;
|
||||
ComponentInspectorFn renderFn;
|
||||
};
|
||||
|
||||
class ComponentRegistry {
|
||||
public:
|
||||
static ComponentRegistry& Get() {
|
||||
static ComponentRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RegisterComponent(const std::string& name, ComponentInspectorFn inspectorFn) {
|
||||
m_inspectors[name] = {name, inspectorFn};
|
||||
m_factories[name] = []() -> std::unique_ptr<Component> {
|
||||
return std::make_unique<T>();
|
||||
};
|
||||
}
|
||||
|
||||
ComponentInspectorInfo* GetInspector(const std::string& name) {
|
||||
auto it = m_inspectors.find(name);
|
||||
if (it != m_inspectors.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ComponentRegistry() = default;
|
||||
std::unordered_map<std::string, ComponentInspectorInfo> m_inspectors;
|
||||
std::unordered_map<std::string, std::function<std::unique_ptr<Component>()>> m_factories;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -2,46 +2,4 @@
|
||||
|
||||
namespace UI {
|
||||
|
||||
SceneManager& SceneManager::Get() {
|
||||
static SceneManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void SceneManager::SetSelectedObject(GameObject* obj) {
|
||||
if (m_selectedObject) {
|
||||
m_selectedObject->selected = false;
|
||||
}
|
||||
m_selectedObject = obj;
|
||||
if (m_selectedObject) {
|
||||
m_selectedObject->selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::CreateDemoScene() {
|
||||
m_rootObjects.clear();
|
||||
|
||||
GameObject mainCamera("Main Camera");
|
||||
mainCamera.AddComponent<TransformComponent>();
|
||||
|
||||
GameObject directionalLight("Directional Light");
|
||||
|
||||
GameObject cube("Cube");
|
||||
cube.AddComponent<TransformComponent>();
|
||||
cube.AddComponent<MeshRendererComponent>()->meshName = "Cube Mesh";
|
||||
|
||||
GameObject sphere("Sphere");
|
||||
sphere.AddComponent<TransformComponent>();
|
||||
sphere.AddComponent<MeshRendererComponent>()->meshName = "Sphere Mesh";
|
||||
|
||||
GameObject player("Player");
|
||||
GameObject weapon("Weapon");
|
||||
player.children.push_back(std::move(weapon));
|
||||
|
||||
m_rootObjects.push_back(std::move(mainCamera));
|
||||
m_rootObjects.push_back(std::move(directionalLight));
|
||||
m_rootObjects.push_back(std::move(cube));
|
||||
m_rootObjects.push_back(std::move(sphere));
|
||||
m_rootObjects.push_back(std::move(player));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +1,119 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/GameObject.h"
|
||||
#include "SelectionManager.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class SceneManager {
|
||||
public:
|
||||
static SceneManager& Get();
|
||||
static SceneManager& Get() {
|
||||
static SceneManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::vector<GameObject>& GetRootObjects() { return m_rootObjects; }
|
||||
GameObject* GetSelectedObject() { return m_selectedObject; }
|
||||
void SetSelectedObject(GameObject* obj);
|
||||
EntityID CreateEntity(const std::string& name, EntityID parent = INVALID_ENTITY) {
|
||||
EntityID id = m_nextEntityId++;
|
||||
Entity entity;
|
||||
entity.id = id;
|
||||
entity.name = name;
|
||||
entity.parent = parent;
|
||||
m_entities[id] = std::move(entity);
|
||||
|
||||
if (parent != INVALID_ENTITY) {
|
||||
m_entities[parent].children.push_back(id);
|
||||
} else {
|
||||
m_rootEntities.push_back(id);
|
||||
}
|
||||
|
||||
OnEntityCreated.Invoke(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
void CreateDemoScene();
|
||||
Entity* GetEntity(EntityID id) {
|
||||
auto it = m_entities.find(id);
|
||||
if (it != m_entities.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Entity* GetEntity(EntityID id) const {
|
||||
auto it = m_entities.find(id);
|
||||
if (it != m_entities.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<EntityID>& GetRootEntities() const {
|
||||
return m_rootEntities;
|
||||
}
|
||||
|
||||
void DeleteEntity(EntityID id) {
|
||||
auto it = m_entities.find(id);
|
||||
if (it == m_entities.end()) return;
|
||||
|
||||
Entity& entity = it->second;
|
||||
|
||||
for (EntityID childId : entity.children) {
|
||||
DeleteEntity(childId);
|
||||
}
|
||||
|
||||
if (entity.parent != INVALID_ENTITY) {
|
||||
auto* parent = GetEntity(entity.parent);
|
||||
if (parent) {
|
||||
auto& siblings = parent->children;
|
||||
siblings.erase(std::remove(siblings.begin(), siblings.end(), id), siblings.end());
|
||||
}
|
||||
} else {
|
||||
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end());
|
||||
}
|
||||
|
||||
if (SelectionManager::Get().GetSelectedEntity() == id) {
|
||||
SelectionManager::Get().ClearSelection();
|
||||
}
|
||||
|
||||
m_entities.erase(it);
|
||||
OnEntityDeleted.Invoke(id);
|
||||
}
|
||||
|
||||
void CreateDemoScene() {
|
||||
m_entities.clear();
|
||||
m_rootEntities.clear();
|
||||
m_nextEntityId = 1;
|
||||
|
||||
EntityID camera = CreateEntity("Main Camera");
|
||||
GetEntity(camera)->AddComponent<TransformComponent>();
|
||||
|
||||
EntityID light = CreateEntity("Directional Light");
|
||||
|
||||
EntityID cube = CreateEntity("Cube");
|
||||
GetEntity(cube)->AddComponent<TransformComponent>();
|
||||
GetEntity(cube)->AddComponent<MeshRendererComponent>()->meshName = "Cube Mesh";
|
||||
|
||||
EntityID sphere = CreateEntity("Sphere");
|
||||
GetEntity(sphere)->AddComponent<TransformComponent>();
|
||||
GetEntity(sphere)->AddComponent<MeshRendererComponent>()->meshName = "Sphere Mesh";
|
||||
|
||||
EntityID player = CreateEntity("Player");
|
||||
EntityID weapon = CreateEntity("Weapon", player);
|
||||
|
||||
OnSceneChanged.Invoke();
|
||||
}
|
||||
|
||||
Event<EntityID> OnEntityCreated;
|
||||
Event<EntityID> OnEntityDeleted;
|
||||
Event<> OnSceneChanged;
|
||||
|
||||
private:
|
||||
SceneManager() = default;
|
||||
|
||||
std::vector<GameObject> m_rootObjects;
|
||||
GameObject* m_selectedObject = nullptr;
|
||||
EntityID m_nextEntityId = 1;
|
||||
std::unordered_map<EntityID, Entity> m_entities;
|
||||
std::vector<EntityID> m_rootEntities;
|
||||
};
|
||||
|
||||
}
|
||||
38
ui/src/Managers/SelectionManager.h
Normal file
38
ui/src/Managers/SelectionManager.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/GameObject.h"
|
||||
#include "Core/Event.h"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class SelectionManager {
|
||||
public:
|
||||
static SelectionManager& Get() {
|
||||
static SelectionManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
EntityID GetSelectedEntity() const { return m_selectedEntity; }
|
||||
|
||||
void SetSelectedEntity(EntityID id) {
|
||||
m_selectedEntity = id;
|
||||
OnSelectionChanged.Invoke(id);
|
||||
}
|
||||
|
||||
void ClearSelection() {
|
||||
SetSelectedEntity(INVALID_ENTITY);
|
||||
}
|
||||
|
||||
bool IsSelected(EntityID id) const {
|
||||
return m_selectedEntity == id;
|
||||
}
|
||||
|
||||
Event<EntityID> OnSelectionChanged;
|
||||
|
||||
private:
|
||||
SelectionManager() = default;
|
||||
EntityID m_selectedEntity = INVALID_ENTITY;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,44 +1,55 @@
|
||||
#include "HierarchyPanel.h"
|
||||
#include "Managers/SceneManager.h"
|
||||
#include "Core/GameObject.h"
|
||||
#include "Managers/SelectionManager.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") {
|
||||
SceneManager::Get().CreateDemoScene();
|
||||
|
||||
m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](EntityID) {
|
||||
});
|
||||
}
|
||||
|
||||
HierarchyPanel::~HierarchyPanel() {
|
||||
SelectionManager::Get().OnSelectionChanged.Unsubscribe(m_selectionHandlerId);
|
||||
}
|
||||
|
||||
void HierarchyPanel::Render() {
|
||||
ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None);
|
||||
|
||||
for (auto& obj : SceneManager::Get().GetRootObjects()) {
|
||||
RenderGameObject(obj);
|
||||
for (EntityID id : SceneManager::Get().GetRootEntities()) {
|
||||
RenderEntity(id);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderGameObject(GameObject& obj) {
|
||||
void HierarchyPanel::RenderEntity(EntityID id) {
|
||||
auto& sceneManager = SceneManager::Get();
|
||||
const Entity* entity = sceneManager.GetEntity(id);
|
||||
if (!entity) return;
|
||||
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
|
||||
|
||||
if (obj.children.empty()) {
|
||||
if (entity->children.empty()) {
|
||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
||||
}
|
||||
|
||||
if (obj.selected) {
|
||||
if (SelectionManager::Get().IsSelected(id)) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
|
||||
bool isOpen = ImGui::TreeNodeEx(obj.name.c_str(), flags);
|
||||
bool isOpen = ImGui::TreeNodeEx(entity->name.c_str(), flags);
|
||||
|
||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
|
||||
SceneManager::Get().SetSelectedObject(&obj);
|
||||
SelectionManager::Get().SetSelectedEntity(id);
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
for (auto& child : obj.children) {
|
||||
RenderGameObject(child);
|
||||
for (EntityID childId : entity->children) {
|
||||
RenderEntity(childId);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
#include "Core/Event.h"
|
||||
#include "Core/GameObject.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class HierarchyPanel : public Panel {
|
||||
public:
|
||||
HierarchyPanel();
|
||||
~HierarchyPanel();
|
||||
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RenderGameObject(class GameObject& obj);
|
||||
void RenderEntity(EntityID id);
|
||||
|
||||
Event<EntityID>::HandlerID m_selectionHandlerId = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,31 +1,27 @@
|
||||
#include "InspectorPanel.h"
|
||||
#include "Managers/SceneManager.h"
|
||||
#include "Core/GameObject.h"
|
||||
#include "Managers/SelectionManager.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
InspectorPanel::InspectorPanel() : Panel("Inspector") {}
|
||||
InspectorPanel::InspectorPanel() : Panel("Inspector") {
|
||||
m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](EntityID) {
|
||||
});
|
||||
}
|
||||
|
||||
InspectorPanel::~InspectorPanel() {
|
||||
SelectionManager::Get().OnSelectionChanged.Unsubscribe(m_selectionHandlerId);
|
||||
}
|
||||
|
||||
void InspectorPanel::Render() {
|
||||
ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None);
|
||||
|
||||
GameObject* selected = SceneManager::Get().GetSelectedObject();
|
||||
EntityID selectedId = SelectionManager::Get().GetSelectedEntity();
|
||||
Entity* entity = SceneManager::Get().GetEntity(selectedId);
|
||||
|
||||
if (selected) {
|
||||
ImGui::Text("%s", selected->name.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
auto transform = selected->GetComponent<TransformComponent>();
|
||||
if (transform) {
|
||||
RenderTransformSection(transform);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
auto meshRenderer = selected->GetComponent<MeshRendererComponent>();
|
||||
if (meshRenderer) {
|
||||
RenderMeshRendererSection(meshRenderer);
|
||||
}
|
||||
if (entity) {
|
||||
RenderEntity(entity);
|
||||
} else {
|
||||
ImGui::Text("No object selected");
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Select an object in Hierarchy");
|
||||
@@ -34,42 +30,51 @@ void InspectorPanel::Render() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderTransformSection(TransformComponent* transform) {
|
||||
if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent(10.0f);
|
||||
|
||||
ImGui::Text("Position");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Position", transform->position, 0.1f);
|
||||
|
||||
ImGui::Text("Rotation");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Rotation", transform->rotation, 1.0f);
|
||||
|
||||
ImGui::Text("Scale");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Scale", transform->scale, 0.1f);
|
||||
|
||||
ImGui::Unindent(10.0f);
|
||||
void InspectorPanel::RenderEntity(Entity* entity) {
|
||||
ImGui::Text("%s", entity->name.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
for (auto& component : entity->components) {
|
||||
RenderComponent(component.get());
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderMeshRendererSection(MeshRendererComponent* meshRenderer) {
|
||||
if (ImGui::CollapsingHeader("Mesh Renderer", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
void InspectorPanel::RenderComponent(Component* component) {
|
||||
if (!component) return;
|
||||
|
||||
const char* name = component->GetName().c_str();
|
||||
|
||||
if (ImGui::CollapsingHeader(name, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent(10.0f);
|
||||
|
||||
ImGui::Text("Material");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::InputText("##Material", meshRenderer->materialName.data(), meshRenderer->materialName.capacity());
|
||||
|
||||
ImGui::Text("Mesh");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::InputText("##Mesh", meshRenderer->meshName.data(), meshRenderer->meshName.capacity());
|
||||
if (auto* transform = dynamic_cast<TransformComponent*>(component)) {
|
||||
ImGui::Text("Position");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Position", transform->position, 0.1f);
|
||||
|
||||
ImGui::Text("Rotation");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Rotation", transform->rotation, 1.0f);
|
||||
|
||||
ImGui::Text("Scale");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Scale", transform->scale, 0.1f);
|
||||
}
|
||||
else if (auto* meshRenderer = dynamic_cast<MeshRendererComponent*>(component)) {
|
||||
ImGui::Text("Material");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::InputText("##Material", meshRenderer->materialName.data(), meshRenderer->materialName.capacity());
|
||||
|
||||
ImGui::Text("Mesh");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::InputText("##Mesh", meshRenderer->meshName.data(), meshRenderer->meshName.capacity());
|
||||
}
|
||||
|
||||
ImGui::Unindent(10.0f);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
#include "Core/Event.h"
|
||||
#include "Core/GameObject.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class InspectorPanel : public Panel {
|
||||
public:
|
||||
InspectorPanel();
|
||||
~InspectorPanel();
|
||||
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RenderTransformSection(class TransformComponent* transform);
|
||||
void RenderMeshRendererSection(class MeshRendererComponent* meshRenderer);
|
||||
void RenderEntity(Entity* entity);
|
||||
void RenderComponent(Component* component);
|
||||
|
||||
Event<EntityID>::HandlerID m_selectionHandlerId = 0;
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user