From c9e459c1791ea853bf0dddcd2f51340ebddf7469 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 25 Mar 2026 01:23:08 +0800 Subject: [PATCH] Editor: UI panels and GameObject updates --- editor/CMakeLists.txt | 3 +- editor/src/Application.cpp | 78 ++-------- editor/src/Application.h | 23 +-- editor/src/Layers/EditorLayer.cpp | 112 ++++++++++++++ editor/src/Layers/EditorLayer.h | 48 ++++++ editor/src/Managers/SceneManager.cpp | 10 +- editor/src/Managers/SceneManager.h | 6 +- editor/src/Managers/SelectionManager.h | 56 ++++++- editor/src/main.cpp | 2 +- editor/src/panels/HierarchyPanel.cpp | 141 +++++++++++------- editor/src/panels/HierarchyPanel.h | 9 +- editor/src/panels/InspectorPanel.cpp | 100 +++++++++++-- editor/src/panels/InspectorPanel.h | 7 +- editor/src/panels/Panel.cpp | 3 +- editor/src/panels/Panel.h | 12 +- .../include/XCEngine/Components/GameObject.h | 15 ++ engine/include/XCEngine/Core/Layer.h | 26 ++++ engine/include/XCEngine/Core/LayerStack.h | 100 +++++++++++++ engine/src/Components/GameObject.cpp | 29 ++++ imgui.ini | 55 +++++++ minimal.ppm | Bin 0 -> 2764816 bytes 21 files changed, 651 insertions(+), 184 deletions(-) create mode 100644 editor/src/Layers/EditorLayer.cpp create mode 100644 editor/src/Layers/EditorLayer.h create mode 100644 engine/include/XCEngine/Core/Layer.h create mode 100644 engine/include/XCEngine/Core/LayerStack.h create mode 100644 imgui.ini create mode 100644 minimal.ppm diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 9dbb5c59..f19dd03c 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(${PROJECT_NAME} WIN32 src/panels/InspectorPanel.cpp src/panels/ConsolePanel.cpp src/panels/ProjectPanel.cpp + src/Layers/EditorLayer.cpp ${IMGUI_SOURCES} ) @@ -61,7 +62,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE d3d12.lib dxgi.lib d3dcompiler.lib - ${CMAKE_SOURCE_DIR}/engine/build/Release/XCEngine.lib + ${CMAKE_CURRENT_SOURCE_DIR}/../engine/build/Release/XCEngine.lib ) set_target_properties(${PROJECT_NAME} PROPERTIES diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp index 9dcfef22..21ddd136 100644 --- a/editor/src/Application.cpp +++ b/editor/src/Application.cpp @@ -1,4 +1,5 @@ #include "Application.h" +#include "Layers/EditorLayer.h" #include #include #include @@ -45,14 +46,6 @@ bool Application::Initialize(HWND hwnd) { m_srvHeap->GetCPUDescriptorHandleForHeapStart(), m_srvHeap->GetGPUDescriptorHandleForHeapStart()); - m_menuBar = std::make_unique(); - m_hierarchyPanel = std::make_unique(); - m_sceneViewPanel = std::make_unique(); - m_gameViewPanel = std::make_unique(); - m_inspectorPanel = std::make_unique(); - m_consolePanel = std::make_unique(); - m_projectPanel = std::make_unique(); - wchar_t exePath[MAX_PATH]; GetModuleFileNameW(nullptr, exePath, MAX_PATH); std::wstring exeDirW(exePath); @@ -66,12 +59,18 @@ bool Application::Initialize(HWND hwnd) { exeDir.resize(len - 1); WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, &exeDir[0], len, nullptr, nullptr); } - m_projectPanel->Initialize(exeDir); + + m_editorLayer = new EditorLayer(); + m_editorLayer->SetProjectPath(exeDir); + m_layerStack.pushLayer(std::unique_ptr(m_editorLayer)); + m_layerStack.onAttach(); return true; } void Application::Shutdown() { + m_layerStack.onDetach(); + ImGui_ImplDX12_Shutdown(); ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(); @@ -93,8 +92,7 @@ void Application::Render() { ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); - SetupDockspace(); - RenderUI(); + m_layerStack.onImGuiRender(); ImGui::Render(); @@ -243,63 +241,5 @@ void Application::CleanupRenderTarget() { } } -void Application::SetupDockspace() { - static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_NoWindowMenuButton; - - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; - - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->Pos); - ImGui::SetNextWindowSize(viewport->Size); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; - windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::Begin("MainDockspace", nullptr, windowFlags); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(2); - - ImGuiID dockspaceId = ImGui::GetID("MyDockspace"); - ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags); - - static bool firstTime = true; - if (firstTime) { - firstTime = false; - ImGui::DockBuilderRemoveNode(dockspaceId); - ImGui::DockBuilderAddNode(dockspaceId, dockspaceFlags | ImGuiDockNodeFlags_DockSpace); - ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->Size); - - ImGuiID dockMain = dockspaceId; - ImGuiID dockBottom = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.25f, nullptr, &dockMain); - ImGuiID dockLeft = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.15f, nullptr, &dockMain); - ImGuiID dockRight = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, nullptr, &dockMain); - - ImGui::DockBuilderDockWindow("Hierarchy", dockLeft); - ImGui::DockBuilderDockWindow("Scene", dockMain); - ImGui::DockBuilderDockWindow("Game", dockMain); - ImGui::DockBuilderDockWindow("Inspector", dockRight); - ImGui::DockBuilderDockWindow("Console", dockBottom); - ImGui::DockBuilderDockWindow("Project", dockBottom); - - ImGui::DockBuilderFinish(dockspaceId); - } - - ImGui::End(); -} - -void Application::RenderUI() { - m_menuBar->Render(); - - m_hierarchyPanel->Render(); - m_sceneViewPanel->Render(); - m_gameViewPanel->Render(); - m_inspectorPanel->Render(); - m_consolePanel->Render(); - m_projectPanel->Render(); -} - } } \ No newline at end of file diff --git a/editor/src/Application.h b/editor/src/Application.h index bb3c3972..4000b7af 100644 --- a/editor/src/Application.h +++ b/editor/src/Application.h @@ -5,19 +5,15 @@ #include #include +#include + #include "Theme.h" -#include "panels/Panel.h" -#include "panels/MenuBar.h" -#include "panels/HierarchyPanel.h" -#include "panels/SceneViewPanel.h" -#include "panels/GameViewPanel.h" -#include "panels/InspectorPanel.h" -#include "panels/ConsolePanel.h" -#include "panels/ProjectPanel.h" namespace XCEngine { namespace Editor { +class EditorLayer; + class Application { public: static Application& Get(); @@ -34,8 +30,6 @@ private: bool CreateDevice(); bool CreateRenderTarget(); void CleanupRenderTarget(); - void SetupDockspace(); - void RenderUI(); HWND m_hwnd = nullptr; int m_width = 1280; @@ -54,13 +48,8 @@ private: UINT m_rtvDescriptorSize = 0; UINT m_frameIndex = 0; - std::unique_ptr m_menuBar; - std::unique_ptr m_hierarchyPanel; - std::unique_ptr m_sceneViewPanel; - std::unique_ptr m_gameViewPanel; - std::unique_ptr m_inspectorPanel; - std::unique_ptr m_consolePanel; - std::unique_ptr m_projectPanel; + Core::LayerStack m_layerStack; + EditorLayer* m_editorLayer = nullptr; }; } diff --git a/editor/src/Layers/EditorLayer.cpp b/editor/src/Layers/EditorLayer.cpp new file mode 100644 index 00000000..7ac2301d --- /dev/null +++ b/editor/src/Layers/EditorLayer.cpp @@ -0,0 +1,112 @@ +#include "EditorLayer.h" +#include "panels/MenuBar.h" +#include "panels/HierarchyPanel.h" +#include "panels/SceneViewPanel.h" +#include "panels/GameViewPanel.h" +#include "panels/InspectorPanel.h" +#include "panels/ConsolePanel.h" +#include "panels/ProjectPanel.h" +#include "Managers/SelectionManager.h" +#include "Managers/SceneManager.h" +#include +#include + +namespace XCEngine { +namespace Editor { + +EditorLayer::EditorLayer() : Layer("Editor") {} + +void EditorLayer::onAttach() { + m_menuBar = std::make_unique(); + m_hierarchyPanel = std::make_unique(); + m_sceneViewPanel = std::make_unique(); + m_gameViewPanel = std::make_unique(); + m_inspectorPanel = std::make_unique(); + m_consolePanel = std::make_unique(); + m_projectPanel = std::make_unique(); + + m_projectPanel->Initialize(m_projectPath); + + auto& selection = SelectionManager::Get(); + selection.OnSelectionChanged.Subscribe([](uint64_t id) { + }); +} + +void EditorLayer::onDetach() { +} + +void EditorLayer::onUpdate(float dt) { +} + +void EditorLayer::onEvent(void* event) { +} + +void EditorLayer::onImGuiRender() { + setupDockspace(); + renderAllPanels(); +} + +void EditorLayer::SetProjectPath(const std::string& path) { + m_projectPath = path; +} + +void EditorLayer::setupDockspace() { + static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_NoWindowMenuButton; + + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("MainDockspace", nullptr, windowFlags); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(2); + + ImGuiID dockspaceId = ImGui::GetID("MyDockspace"); + ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags); + + static bool firstTime = true; + if (firstTime) { + firstTime = false; + ImGui::DockBuilderRemoveNode(dockspaceId); + ImGui::DockBuilderAddNode(dockspaceId, dockspaceFlags | ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->Size); + + ImGuiID dockMain = dockspaceId; + ImGuiID dockBottom = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.25f, nullptr, &dockMain); + ImGuiID dockLeft = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.15f, nullptr, &dockMain); + ImGuiID dockRight = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, nullptr, &dockMain); + + ImGui::DockBuilderDockWindow("Hierarchy", dockLeft); + ImGui::DockBuilderDockWindow("Scene", dockMain); + ImGui::DockBuilderDockWindow("Game", dockMain); + ImGui::DockBuilderDockWindow("Inspector", dockRight); + ImGui::DockBuilderDockWindow("Console", dockBottom); + ImGui::DockBuilderDockWindow("Project", dockBottom); + + ImGui::DockBuilderFinish(dockspaceId); + } + + ImGui::End(); +} + +void EditorLayer::renderAllPanels() { + m_menuBar->Render(); + + m_hierarchyPanel->Render(); + m_sceneViewPanel->Render(); + m_gameViewPanel->Render(); + m_inspectorPanel->Render(); + m_consolePanel->Render(); + m_projectPanel->Render(); +} + +} +} diff --git a/editor/src/Layers/EditorLayer.h b/editor/src/Layers/EditorLayer.h new file mode 100644 index 00000000..a975dc00 --- /dev/null +++ b/editor/src/Layers/EditorLayer.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + +namespace XCEngine { +namespace Editor { + +class MenuBar; +class HierarchyPanel; +class SceneViewPanel; +class GameViewPanel; +class InspectorPanel; +class ConsolePanel; +class ProjectPanel; + +class EditorLayer : public Core::Layer { +public: + EditorLayer(); + ~EditorLayer() override = default; + + void onAttach() override; + void onDetach() override; + void onUpdate(float dt) override; + void onEvent(void* event) override; + void onImGuiRender() override; + + void SetProjectPath(const std::string& path); + +private: + void setupDockspace(); + void renderAllPanels(); + + std::string m_projectPath; + + std::unique_ptr m_menuBar; + std::unique_ptr m_hierarchyPanel; + std::unique_ptr m_sceneViewPanel; + std::unique_ptr m_gameViewPanel; + std::unique_ptr m_inspectorPanel; + std::unique_ptr m_consolePanel; + std::unique_ptr m_projectPanel; +}; + +} +} diff --git a/editor/src/Managers/SceneManager.cpp b/editor/src/Managers/SceneManager.cpp index 075b0d30..83adf52a 100644 --- a/editor/src/Managers/SceneManager.cpp +++ b/editor/src/Managers/SceneManager.cpp @@ -48,7 +48,9 @@ EditorSceneManager::ClipboardData EditorSceneManager::CopyEntityRecursive(const data.name = entity->GetName(); if (auto* transform = entity->GetComponent<::XCEngine::Components::TransformComponent>()) { - // Transform 数据会被复制 + data.localPosition = transform->GetLocalPosition(); + data.localRotation = transform->GetLocalRotation(); + data.localScale = transform->GetLocalScale(); } for (auto* child : entity->GetChildren()) { @@ -75,6 +77,12 @@ void EditorSceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) { ::XCEngine::Components::GameObject* newEntity = m_scene->CreateGameObject(data.name, parentObj); + if (auto* transform = newEntity->GetComponent<::XCEngine::Components::TransformComponent>()) { + transform->SetLocalPosition(data.localPosition); + transform->SetLocalRotation(data.localRotation); + transform->SetLocalScale(data.localScale); + } + if (parentObj == nullptr) { m_rootEntities.push_back(newEntity); } diff --git a/editor/src/Managers/SceneManager.h b/editor/src/Managers/SceneManager.h index 5194769c..c0086dc4 100644 --- a/editor/src/Managers/SceneManager.h +++ b/editor/src/Managers/SceneManager.h @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include @@ -61,7 +63,9 @@ private: struct ClipboardData { std::string name; - std::vector> components; + Math::Vector3 localPosition = Math::Vector3::Zero(); + Math::Quaternion localRotation = Math::Quaternion::Identity(); + Math::Vector3 localScale = Math::Vector3::One(); std::vector children; }; diff --git a/editor/src/Managers/SelectionManager.h b/editor/src/Managers/SelectionManager.h index 376f4236..6b1468f2 100644 --- a/editor/src/Managers/SelectionManager.h +++ b/editor/src/Managers/SelectionManager.h @@ -1,6 +1,7 @@ #pragma once -#include +#include +#include #include #include @@ -15,26 +16,65 @@ public: return instance; } - ::XCEngine::Components::GameObject* GetSelectedEntity() const { return m_selectedEntity; } - void SetSelectedEntity(::XCEngine::Components::GameObject* entity) { - m_selectedEntity = entity; + if (m_selectedEntities.empty()) { + m_selectedEntities.push_back(entity); + } else { + m_selectedEntities[0] = entity; + m_selectedEntities.resize(1); + } OnSelectionChanged.Invoke(entity ? entity->GetID() : 0); } - void ClearSelection() { - SetSelectedEntity(nullptr); + void SetSelectedEntities(const std::vector<::XCEngine::Components::GameObject*>& entities) { + m_selectedEntities = entities; + if (!entities.empty()) { + OnSelectionChanged.Invoke(entities.back()->GetID()); + } else { + OnSelectionChanged.Invoke(0); + } } + void AddToSelection(::XCEngine::Components::GameObject* entity) { + if (entity && !IsSelected(entity)) { + m_selectedEntities.push_back(entity); + OnSelectionChanged.Invoke(entity->GetID()); + } + } + + void RemoveFromSelection(::XCEngine::Components::GameObject* entity) { + auto it = std::find(m_selectedEntities.begin(), m_selectedEntities.end(), entity); + if (it != m_selectedEntities.end()) { + m_selectedEntities.erase(it); + OnSelectionChanged.Invoke(entity ? entity->GetID() : 0); + } + } + + void ClearSelection() { + m_selectedEntities.clear(); + OnSelectionChanged.Invoke(0); + } + + ::XCEngine::Components::GameObject* GetSelectedEntity() const { + return m_selectedEntities.empty() ? nullptr : m_selectedEntities.back(); + } + + const std::vector<::XCEngine::Components::GameObject*>& GetSelectedEntities() const { + return m_selectedEntities; + } + + bool HasSelection() const { return !m_selectedEntities.empty(); } + size_t GetSelectionCount() const { return m_selectedEntities.size(); } + bool IsSelected(::XCEngine::Components::GameObject* entity) const { - return m_selectedEntity == entity; + return std::find(m_selectedEntities.begin(), m_selectedEntities.end(), entity) != m_selectedEntities.end(); } ::XCEngine::Core::Event OnSelectionChanged; private: SelectionManager() = default; - ::XCEngine::Components::GameObject* m_selectedEntity = nullptr; + std::vector<::XCEngine::Components::GameObject*> m_selectedEntities; }; } diff --git a/editor/src/main.cpp b/editor/src/main.cpp index 582e7036..00643c65 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -93,4 +93,4 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { return 0; } return DefWindowProcW(hWnd, msg, wParam, lParam); -} \ No newline at end of file +} diff --git a/editor/src/panels/HierarchyPanel.cpp b/editor/src/panels/HierarchyPanel.cpp index 0ef21551..20e51186 100644 --- a/editor/src/panels/HierarchyPanel.cpp +++ b/editor/src/panels/HierarchyPanel.cpp @@ -1,6 +1,8 @@ #include "HierarchyPanel.h" #include "Managers/SceneManager.h" #include "Managers/SelectionManager.h" +#include +#include #include #include @@ -31,8 +33,8 @@ void HierarchyPanel::Render() { ImGui::BeginChild("EntityList"); - for (auto* entity : EditorSceneManager::Get().GetRootEntities()) { - RenderEntity(entity, filter); + for (auto* gameObject : EditorSceneManager::Get().GetRootEntities()) { + RenderEntity(gameObject, filter); } if (ImGui::IsWindowHovered() && ImGui::IsMouseDown(0) && !ImGui::IsAnyItemHovered()) { @@ -49,9 +51,9 @@ void HierarchyPanel::Render() { ImGui::InvisibleButton("##DragTarget", ImVec2(-1, -1)); if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) { - ::XCEngine::Components::GameObject* sourceEntity = *(::XCEngine::Components::GameObject**)payload->Data; - if (sourceEntity && sourceEntity->GetParent() != nullptr) { - EditorSceneManager::Get().MoveEntity(sourceEntity->GetID(), 0); + ::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data; + if (sourceGameObject && sourceGameObject->GetParent() != nullptr) { + EditorSceneManager::Get().MoveEntity(sourceGameObject->GetID(), 0); } } ImGui::EndDragDropTarget(); @@ -67,26 +69,26 @@ void HierarchyPanel::RenderSearchBar() { ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer)); } -void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* entity, const std::string& filter) { - if (!entity) return; +void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter) { + if (!gameObject) return; - if (!filter.empty() && !PassesFilter(entity, filter)) { + if (!filter.empty() && !PassesFilter(gameObject, filter)) { return; } - ImGui::PushID(static_cast(entity->GetID())); + ImGui::PushID(static_cast(gameObject->GetID())); ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth; - if (entity->GetChildCount() == 0) { + if (gameObject->GetChildCount() == 0) { flags |= ImGuiTreeNodeFlags_Leaf; } - if (SelectionManager::Get().IsSelected(entity)) { + if (SelectionManager::Get().IsSelected(gameObject)) { flags |= ImGuiTreeNodeFlags_Selected; } - if (m_renaming && m_renamingEntity == entity) { + if (m_renaming && m_renamingEntity == gameObject) { if (m_renameJustStarted) { ImGui::SetKeyboardFocusHere(); m_renameJustStarted = false; @@ -95,7 +97,7 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* entity, co ImGui::SetNextItemWidth(-1); if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { if (strlen(m_renameBuffer) > 0) { - EditorSceneManager::Get().RenameEntity(entity->GetID(), m_renameBuffer); + EditorSceneManager::Get().RenameEntity(gameObject->GetID(), m_renameBuffer); } m_renaming = false; m_renamingEntity = nullptr; @@ -103,35 +105,42 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* entity, co if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) { if (strlen(m_renameBuffer) > 0) { - EditorSceneManager::Get().RenameEntity(entity->GetID(), m_renameBuffer); + EditorSceneManager::Get().RenameEntity(gameObject->GetID(), m_renameBuffer); } m_renaming = false; m_renamingEntity = nullptr; } } else { - bool isOpen = ImGui::TreeNodeEx(entity->GetName().c_str(), flags); + bool isOpen = ImGui::TreeNodeEx((void*)gameObject->GetUUID(), flags, "%s", gameObject->GetName().c_str()); if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) { - SelectionManager::Get().SetSelectedEntity(entity); + ImGuiIO& io = ImGui::GetIO(); + if (io.KeyCtrl) { + if (!SelectionManager::Get().IsSelected(gameObject)) { + SelectionManager::Get().AddToSelection(gameObject); + } + } else { + SelectionManager::Get().SetSelectedEntity(gameObject); + } } if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { m_renaming = true; - m_renamingEntity = entity; - strcpy_s(m_renameBuffer, entity->GetName().c_str()); + m_renamingEntity = gameObject; + strcpy_s(m_renameBuffer, gameObject->GetName().c_str()); m_renameJustStarted = true; } - HandleDragDrop(entity); + HandleDragDrop(gameObject); if (ImGui::BeginPopupContextItem("EntityContextMenu")) { - RenderContextMenu(entity); + RenderContextMenu(gameObject); ImGui::EndPopup(); } if (isOpen) { - for (size_t i = 0; i < entity->GetChildCount(); i++) { - RenderEntity(entity->GetChild(i), filter); + for (size_t i = 0; i < gameObject->GetChildCount(); i++) { + RenderEntity(gameObject->GetChild(i), filter); } ImGui::TreePop(); } @@ -140,44 +149,54 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* entity, co ImGui::PopID(); } -void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* entity) { +void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameObject) { auto& sceneManager = EditorSceneManager::Get(); auto& selectionManager = SelectionManager::Get(); if (ImGui::BeginMenu("Create")) { - RenderCreateMenu(entity); + RenderCreateMenu(gameObject); ImGui::EndMenu(); } + if (gameObject != nullptr && ImGui::MenuItem("Create Child")) { + auto* child = sceneManager.CreateEntity("GameObject", gameObject); + selectionManager.SetSelectedEntity(child); + } + ImGui::Separator(); + if (gameObject != nullptr && gameObject->GetParent() != nullptr) { + if (ImGui::MenuItem("Detach from Parent")) { + gameObject->DetachFromParent(); + } + } + if (ImGui::MenuItem("Rename", "F2")) { - if (entity) { + if (gameObject) { m_renaming = true; - m_renamingEntity = entity; - strcpy_s(m_renameBuffer, entity->GetName().c_str()); + m_renamingEntity = gameObject; + strcpy_s(m_renameBuffer, gameObject->GetName().c_str()); m_renameJustStarted = true; } } if (ImGui::MenuItem("Delete", "Delete")) { - sceneManager.DeleteEntity(entity->GetID()); + sceneManager.DeleteEntity(gameObject->GetID()); } ImGui::Separator(); if (ImGui::MenuItem("Copy", "Ctrl+C")) { - sceneManager.CopyEntity(entity->GetID()); + sceneManager.CopyEntity(gameObject->GetID()); } if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) { - sceneManager.PasteEntity(entity->GetID()); + sceneManager.PasteEntity(gameObject->GetID()); } if (ImGui::MenuItem("Duplicate", "Ctrl+D")) { - uint64_t newId = sceneManager.DuplicateEntity(entity->GetID()); + uint64_t newId = sceneManager.DuplicateEntity(gameObject->GetID()); if (newId != 0) { - // Selection handled via callback } } } @@ -225,24 +244,23 @@ void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent } } -void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* entity) { +void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObject) { auto& sceneManager = EditorSceneManager::Get(); if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - m_dragSource = entity; - ImGui::SetDragDropPayload("ENTITY_PTR", &entity, sizeof(::XCEngine::Components::GameObject*)); - ImGui::Text("%s", entity->GetName().c_str()); + ImGui::SetDragDropPayload("ENTITY_PTR", &gameObject, sizeof(::XCEngine::Components::GameObject*)); + ImGui::Text("%s", gameObject->GetName().c_str()); ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) { - ::XCEngine::Components::GameObject* sourceEntity = *(::XCEngine::Components::GameObject**)payload->Data; - if (sourceEntity != entity && sourceEntity != nullptr) { + ::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data; + if (sourceGameObject != gameObject && sourceGameObject != nullptr) { bool isValidMove = true; - ::XCEngine::Components::GameObject* checkParent = entity; + ::XCEngine::Components::GameObject* checkParent = gameObject; while (checkParent != nullptr) { - if (checkParent == sourceEntity) { + if (checkParent == sourceGameObject) { isValidMove = false; break; } @@ -250,7 +268,16 @@ void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* entity) } if (isValidMove) { - sceneManager.MoveEntity(sourceEntity->GetID(), entity->GetID()); + auto* srcTransform = sourceGameObject->GetTransform(); + Math::Vector3 worldPos = srcTransform->GetPosition(); + Math::Quaternion worldRot = srcTransform->GetRotation(); + Math::Vector3 worldScale = srcTransform->GetScale(); + + sceneManager.MoveEntity(sourceGameObject->GetID(), gameObject->GetID()); + + srcTransform->SetPosition(worldPos); + srcTransform->SetRotation(worldRot); + srcTransform->SetScale(worldScale); } } } @@ -262,20 +289,20 @@ void HierarchyPanel::HandleKeyboardShortcuts() { auto& sceneManager = EditorSceneManager::Get(); auto& selectionManager = SelectionManager::Get(); - ::XCEngine::Components::GameObject* selectedEntity = selectionManager.GetSelectedEntity(); + ::XCEngine::Components::GameObject* selectedGameObject = selectionManager.GetSelectedEntity(); if (ImGui::IsWindowFocused()) { if (ImGui::IsKeyPressed(ImGuiKey_Delete)) { - if (selectedEntity != nullptr) { - sceneManager.DeleteEntity(selectedEntity->GetID()); + if (selectedGameObject != nullptr) { + sceneManager.DeleteEntity(selectedGameObject->GetID()); } } if (ImGui::IsKeyPressed(ImGuiKey_F2)) { - if (selectedEntity != nullptr) { + if (selectedGameObject != nullptr) { m_renaming = true; - m_renamingEntity = selectedEntity; - strcpy_s(m_renameBuffer, selectedEntity->GetName().c_str()); + m_renamingEntity = selectedGameObject; + strcpy_s(m_renameBuffer, selectedGameObject->GetName().c_str()); m_renameJustStarted = true; } } @@ -283,35 +310,35 @@ void HierarchyPanel::HandleKeyboardShortcuts() { ImGuiIO& io = ImGui::GetIO(); if (io.KeyCtrl) { if (ImGui::IsKeyPressed(ImGuiKey_C)) { - if (selectedEntity != nullptr) { - sceneManager.CopyEntity(selectedEntity->GetID()); + if (selectedGameObject != nullptr) { + sceneManager.CopyEntity(selectedGameObject->GetID()); } } if (ImGui::IsKeyPressed(ImGuiKey_V)) { if (sceneManager.HasClipboardData()) { - sceneManager.PasteEntity(selectedEntity ? selectedEntity->GetID() : 0); + sceneManager.PasteEntity(selectedGameObject ? selectedGameObject->GetID() : 0); } } if (ImGui::IsKeyPressed(ImGuiKey_D)) { - if (selectedEntity != nullptr) { - sceneManager.DuplicateEntity(selectedEntity->GetID()); + if (selectedGameObject != nullptr) { + sceneManager.DuplicateEntity(selectedGameObject->GetID()); } } } } } -bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* entity, const std::string& filter) { - if (!entity) return false; +bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) { + if (!gameObject) return false; - if (entity->GetName().find(filter) != std::string::npos) { + if (gameObject->GetName().find(filter) != std::string::npos) { return true; } - for (size_t i = 0; i < entity->GetChildCount(); i++) { - if (PassesFilter(entity->GetChild(i), filter)) { + for (size_t i = 0; i < gameObject->GetChildCount(); i++) { + if (PassesFilter(gameObject->GetChild(i), filter)) { return true; } } diff --git a/editor/src/panels/HierarchyPanel.h b/editor/src/panels/HierarchyPanel.h index a9a9099a..01ff6c65 100644 --- a/editor/src/panels/HierarchyPanel.h +++ b/editor/src/panels/HierarchyPanel.h @@ -15,12 +15,12 @@ public: private: void RenderSearchBar(); - void RenderEntity(::XCEngine::Components::GameObject* entity, const std::string& filter); - void RenderContextMenu(::XCEngine::Components::GameObject* entity); + void RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter); + void RenderContextMenu(::XCEngine::Components::GameObject* gameObject); void RenderCreateMenu(::XCEngine::Components::GameObject* parent); - void HandleDragDrop(::XCEngine::Components::GameObject* entity); + void HandleDragDrop(::XCEngine::Components::GameObject* gameObject); void HandleKeyboardShortcuts(); - bool PassesFilter(::XCEngine::Components::GameObject* entity, const std::string& filter); + bool PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter); uint64_t m_selectionHandlerId = 0; @@ -29,7 +29,6 @@ private: ::XCEngine::Components::GameObject* m_renamingEntity = nullptr; char m_renameBuffer[256] = ""; bool m_renameJustStarted = false; - ::XCEngine::Components::GameObject* m_dragSource = nullptr; }; } diff --git a/editor/src/panels/InspectorPanel.cpp b/editor/src/panels/InspectorPanel.cpp index 8d782c45..885ef375 100644 --- a/editor/src/panels/InspectorPanel.cpp +++ b/editor/src/panels/InspectorPanel.cpp @@ -10,6 +10,7 @@ namespace Editor { InspectorPanel::InspectorPanel() : Panel("Inspector") { m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](uint64_t) { + m_selectedGameObject = SelectionManager::Get().GetSelectedEntity(); }); } @@ -20,10 +21,10 @@ InspectorPanel::~InspectorPanel() { void InspectorPanel::Render() { ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None); - ::XCEngine::Components::GameObject* entity = SelectionManager::Get().GetSelectedEntity(); + m_selectedGameObject = SelectionManager::Get().GetSelectedEntity(); - if (entity) { - RenderEntity(entity); + if (m_selectedGameObject) { + RenderGameObject(m_selectedGameObject); } else { ImGui::Text("No object selected"); ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Select an object in Hierarchy"); @@ -32,27 +33,80 @@ void InspectorPanel::Render() { ImGui::End(); } -void InspectorPanel::RenderEntity(::XCEngine::Components::GameObject* entity) { - ImGui::Text("%s", entity->GetName().c_str()); - ImGui::Separator(); +void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameObject) { + char nameBuffer[256]; + strcpy_s(nameBuffer, gameObject->GetName().c_str()); + ImGui::InputText("##Name", nameBuffer, sizeof(nameBuffer)); + if (ImGui::IsItemDeactivatedAfterEdit()) { + gameObject->SetName(nameBuffer); + } - auto components = entity->GetComponents<::XCEngine::Components::Component>(); + ImGui::SameLine(); + if (ImGui::Button("Add Component")) { + ImGui::OpenPopup("AddComponent"); + } + + RenderAddComponentPopup(gameObject); + + auto components = gameObject->GetComponents<::XCEngine::Components::Component>(); for (auto* component : components) { - RenderComponent(component); - ImGui::Separator(); + RenderComponent(component, gameObject); } } -void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component) { +void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) { + if (!ImGui::BeginPopup("AddComponent")) { + return; + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 10.0f)); + + ImGui::SeparatorText("Components"); + + if (ImGui::MenuItem("Transform", nullptr, false, !gameObject->GetComponent<::XCEngine::Components::TransformComponent>())) { + gameObject->AddComponent<::XCEngine::Components::TransformComponent>(); + ImGui::CloseCurrentPopup(); + } + + ImGui::SeparatorText("Other"); + ImGui::TextDisabled("No more components available"); + + ImGui::PopStyleVar(); + ImGui::EndPopup(); +} + +void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) { if (!component) return; const char* name = component->GetName().c_str(); - std::string headerId = std::string(name) + "##" + std::to_string(reinterpret_cast(component)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 2}); - if (ImGui::CollapsingHeader(headerId.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Indent(10.0f); - + ImGuiTreeNodeFlags flags = + ImGuiTreeNodeFlags_DefaultOpen | + ImGuiTreeNodeFlags_Framed | + ImGuiTreeNodeFlags_SpanAvailWidth | + ImGuiTreeNodeFlags_FramePadding | + ImGuiTreeNodeFlags_AllowOverlap; + + bool open = ImGui::TreeNodeEx((void*)typeid(*component).hash_code(), flags, "%s", name); + + ImGui::PopStyleVar(); + + bool removeComponent = false; + if (ImGui::BeginPopupContextItem("ComponentSettings")) { + if (ImGui::MenuItem("Remove Component")) { + removeComponent = true; + } + ImGui::EndPopup(); + } + + if (removeComponent) { + RemoveComponentByType(component, gameObject); + return; + } + + if (open) { if (auto* transform = dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) { ::XCEngine::Math::Vector3 position = transform->GetLocalPosition(); ::XCEngine::Math::Vector3 rotation = transform->GetLocalEulerAngles(); @@ -71,7 +125,23 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen } } - ImGui::Unindent(10.0f); + ImGui::TreePop(); + } +} + +void InspectorPanel::RemoveComponentByType(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) { + if (!component || !gameObject) return; + + if (dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) { + return; + } + + auto components = gameObject->GetComponents<::XCEngine::Components::Component>(); + for (auto* comp : components) { + if (comp == component) { + gameObject->RemoveComponent<::XCEngine::Components::Component>(); + break; + } } } diff --git a/editor/src/panels/InspectorPanel.h b/editor/src/panels/InspectorPanel.h index 6cbca1d0..9921f6bb 100644 --- a/editor/src/panels/InspectorPanel.h +++ b/editor/src/panels/InspectorPanel.h @@ -15,10 +15,13 @@ public: void Render() override; private: - void RenderEntity(::XCEngine::Components::GameObject* entity); - void RenderComponent(::XCEngine::Components::Component* component); + void RenderGameObject(::XCEngine::Components::GameObject* gameObject); + void RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject); + void RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject); + void RemoveComponentByType(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject); uint64_t m_selectionHandlerId = 0; + ::XCEngine::Components::GameObject* m_selectedGameObject = nullptr; }; } diff --git a/editor/src/panels/Panel.cpp b/editor/src/panels/Panel.cpp index 95ec7a32..8bb8aaa3 100644 --- a/editor/src/panels/Panel.cpp +++ b/editor/src/panels/Panel.cpp @@ -2,5 +2,6 @@ namespace XCEngine { namespace Editor { + +} } -} \ No newline at end of file diff --git a/editor/src/panels/Panel.h b/editor/src/panels/Panel.h index fdbb8c8f..ba3a987c 100644 --- a/editor/src/panels/Panel.h +++ b/editor/src/panels/Panel.h @@ -1,27 +1,27 @@ #pragma once +#include #include -#include namespace XCEngine { namespace Editor { -class Panel { +class Panel : public Core::Layer { public: - Panel(const std::string& name) : m_name(name), m_isOpen(true) {} + Panel(const std::string& name) : Layer(name), m_isOpen(true) {} virtual ~Panel() = default; virtual void Render() = 0; - const std::string& GetName() const { return m_name; } bool IsOpen() const { return m_isOpen; } void SetOpen(bool open) { m_isOpen = open; } void Toggle() { m_isOpen = !m_isOpen; } + void onImGuiRender() override { Render(); } + protected: - std::string m_name; bool m_isOpen; }; } -} \ No newline at end of file +} diff --git a/engine/include/XCEngine/Components/GameObject.h b/engine/include/XCEngine/Components/GameObject.h index efe85c3a..6974a1d7 100644 --- a/engine/include/XCEngine/Components/GameObject.h +++ b/engine/include/XCEngine/Components/GameObject.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace XCEngine { namespace Components { @@ -24,9 +25,12 @@ public: ~GameObject(); ID GetID() const { return m_id; } + uint64_t GetUUID() const { return m_uuid; } const std::string& GetName() const { return m_name; } void SetName(const std::string& name) { m_name = name; } + void DetachFromParent(); + Scene* GetScene() const { return m_scene; } TransformComponent* GetTransform() { return m_transform; } @@ -83,6 +87,16 @@ public: return result; } + template + void RemoveComponent() { + for (auto it = m_components.begin(); it != m_components.end(); ++it) { + if (T* casted = dynamic_cast(it->get())) { + m_components.erase(it); + return; + } + } + } + template T* GetComponentInChildren() { T* comp = GetComponent(); @@ -154,6 +168,7 @@ public: private: ID m_id = INVALID_ID; + uint64_t m_uuid = 0; std::string m_name; bool m_activeSelf = true; diff --git a/engine/include/XCEngine/Core/Layer.h b/engine/include/XCEngine/Core/Layer.h new file mode 100644 index 00000000..8473bdde --- /dev/null +++ b/engine/include/XCEngine/Core/Layer.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Core { + +class Layer { +public: + Layer(const std::string& name = "Layer") : m_name(name) {} + virtual ~Layer() = default; + + virtual void onAttach() {} + virtual void onDetach() {} + virtual void onUpdate(float dt) {} + virtual void onEvent(void* event) {} + virtual void onImGuiRender() {} + + const std::string& getName() const { return m_name; } + +protected: + std::string m_name; +}; + +} +} diff --git a/engine/include/XCEngine/Core/LayerStack.h b/engine/include/XCEngine/Core/LayerStack.h new file mode 100644 index 00000000..f331cfff --- /dev/null +++ b/engine/include/XCEngine/Core/LayerStack.h @@ -0,0 +1,100 @@ +#pragma once + +#include "Layer.h" +#include +#include +#include + +namespace XCEngine { +namespace Core { + +class LayerStack { +public: + LayerStack() = default; + ~LayerStack() = default; + + void pushLayer(std::unique_ptr layer); + void pushOverlay(std::unique_ptr overlay); + void popLayer(Layer* layer); + void popOverlay(Layer* overlay); + + void onUpdate(float dt); + void onEvent(void* event); + void onImGuiRender(); + void onAttach(); + void onDetach(); + + std::vector>::iterator begin() { return m_layers.begin(); } + std::vector>::iterator end() { return m_layers.end(); } + std::vector>::reverse_iterator rbegin() { return m_layers.rbegin(); } + std::vector>::reverse_iterator rend() { return m_layers.rend(); } + std::vector>::const_iterator cbegin() const { return m_layers.begin(); } + std::vector>::const_iterator cend() const { return m_layers.end(); } + std::vector>::const_reverse_iterator crbegin() const { return m_layers.crbegin(); } + std::vector>::const_reverse_iterator crend() const { return m_layers.crend(); } + +private: + std::vector> m_layers; + unsigned int m_layerInsertIndex = 0; +}; + +inline void LayerStack::pushLayer(std::unique_ptr layer) { + m_layers.emplace(m_layers.begin() + m_layerInsertIndex, std::move(layer)); + m_layerInsertIndex++; +} + +inline void LayerStack::pushOverlay(std::unique_ptr overlay) { + m_layers.emplace_back(std::move(overlay)); +} + +inline void LayerStack::popLayer(Layer* layer) { + auto it = std::find_if(m_layers.begin(), m_layers.begin() + m_layerInsertIndex, + [layer](const std::unique_ptr& ptr) { return ptr.get() == layer; }); + if (it != m_layers.begin() + m_layerInsertIndex) { + (*it)->onDetach(); + m_layers.erase(it); + m_layerInsertIndex--; + } +} + +inline void LayerStack::popOverlay(Layer* overlay) { + auto it = std::find_if(m_layers.begin() + m_layerInsertIndex, m_layers.end(), + [overlay](const std::unique_ptr& ptr) { return ptr.get() == overlay; }); + if (it != m_layers.end()) { + (*it)->onDetach(); + m_layers.erase(it); + } +} + +inline void LayerStack::onUpdate(float dt) { + for (auto& layer : m_layers) { + layer->onUpdate(dt); + } +} + +inline void LayerStack::onEvent(void* event) { + for (auto& layer : m_layers) { + layer->onEvent(event); + } +} + +inline void LayerStack::onImGuiRender() { + for (auto& layer : m_layers) { + layer->onImGuiRender(); + } +} + +inline void LayerStack::onAttach() { + for (auto& layer : m_layers) { + layer->onAttach(); + } +} + +inline void LayerStack::onDetach() { + for (auto& layer : m_layers) { + layer->onDetach(); + } +} + +} +} diff --git a/engine/src/Components/GameObject.cpp b/engine/src/Components/GameObject.cpp index 157699ba..cad22f2a 100644 --- a/engine/src/Components/GameObject.cpp +++ b/engine/src/Components/GameObject.cpp @@ -10,6 +10,10 @@ GameObject::ID GameObject::s_nextID = 1; GameObject::GameObject() : m_name("GameObject") { m_id = s_nextID++; + static std::random_device rd; + static std::mt19937_64 gen(rd()); + static std::uniform_int_distribution dis(1, UINT64_MAX); + m_uuid = dis(gen); m_transform = new TransformComponent(); m_transform->m_gameObject = this; } @@ -17,6 +21,10 @@ GameObject::GameObject() GameObject::GameObject(const std::string& name) : m_name(name) { m_id = s_nextID++; + static std::random_device rd; + static std::mt19937_64 gen(rd()); + static std::uniform_int_distribution dis(1, UINT64_MAX); + m_uuid = dis(gen); m_transform = new TransformComponent(); m_transform->m_gameObject = this; } @@ -88,6 +96,27 @@ void GameObject::DetachChildren() { 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(); + } +} + void GameObject::SetActive(bool active) { if (m_activeSelf != active) { m_activeSelf = active; diff --git a/imgui.ini b/imgui.ini new file mode 100644 index 00000000..75f563a1 --- /dev/null +++ b/imgui.ini @@ -0,0 +1,55 @@ +[Window][Hierarchy] +Pos=0,24 +Size=189,485 +Collapsed=0 +DockId=0x00000003,0 + +[Window][Scene] +Pos=191,24 +Size=594,485 +Collapsed=0 +DockId=0x00000005,0 + +[Window][Game] +Pos=191,24 +Size=594,485 +Collapsed=0 +DockId=0x00000005,1 + +[Window][Inspector] +Pos=787,24 +Size=533,485 +Collapsed=0 +DockId=0x00000006,0 + +[Window][Console] +Pos=0,511 +Size=1320,170 +Collapsed=0 +DockId=0x00000002,0 + +[Window][Project] +Pos=0,511 +Size=1320,170 +Collapsed=0 +DockId=0x00000002,1 + +[Window][MainDockspace] +Pos=0,0 +Size=1320,681 +Collapsed=0 + +[Window][Debug##Default] +Pos=60,60 +Size=400,400 +Collapsed=0 + +[Docking][Data] +DockSpace ID=0x8D32E0A4 Window=0x1C358F53 Pos=0,24 Size=1320,657 Split=Y + DockNode ID=0x00000001 Parent=0x8D32E0A4 SizeRef=1264,509 Split=X + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=189,509 Selected=0xBABDAE5E + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1073,509 Split=X Selected=0xE601B12F + DockNode ID=0x00000005 Parent=0x00000004 SizeRef=538,485 CentralNode=1 Selected=0xE601B12F + DockNode ID=0x00000006 Parent=0x00000004 SizeRef=533,485 Selected=0x36DC96AB + DockNode ID=0x00000002 Parent=0x8D32E0A4 SizeRef=1264,170 Selected=0x9C21DE82 + diff --git a/minimal.ppm b/minimal.ppm new file mode 100644 index 0000000000000000000000000000000000000000..2217d8f07a547a8e2b5ef4b64f14bbab3d65cb0a GIT binary patch literal 2764816 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 z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* i1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r#(@C*8F^0t literal 0 HcmV?d00001