From 498d202d6ee76d26003e6e03fdf9f5f6fcbf53a6 Mon Sep 17 00:00:00 2001 From: Rowland <975945824@qq.com> Date: Tue, 9 Jun 2026 10:49:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=92=AD=E6=94=BE=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + .../MetaCoreBuiltinCoreServicesModule.cpp | 427 ++++++++++++++- .../Private/MetaCoreBuiltinEditorModule.cpp | 196 ++++--- .../Private/MetaCoreEditorApp.cpp | 492 ++++++++++++++++-- .../Private/MetaCoreEditorCameraController.h | 2 +- .../Private/MetaCoreEditorContext.cpp | 57 +- .../Private/MetaCoreEditorServices.cpp | 32 ++ .../Public/MetaCoreEditor/MetaCoreEditorApp.h | 4 +- .../MetaCoreEditor/MetaCoreEditorContext.h | 7 +- .../MetaCoreEditor/MetaCoreEditorModule.h | 2 +- .../MetaCoreEditor/MetaCoreEditorServices.h | 30 +- .../MetaCoreScene/Private/MetaCoreScene.cpp | 13 + .../Private/MetaCoreSceneSerializer.cpp | 25 + .../Public/MetaCoreScene/MetaCoreComponents.h | 10 + .../Public/MetaCoreScene/MetaCoreGameObject.h | 2 + TestProject/Assets/Models/Cube.glb.mcmeta | 2 + TestProject/Assets/Models/Plane.glb.mcmeta | 2 + tests/MetaCoreSmokeTests.cpp | 340 ++++++++++-- 18 files changed, 1448 insertions(+), 196 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c80509..cd9a258 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -569,6 +569,7 @@ if(METACORE_BUILD_TESTS) add_executable(MetaCoreSmokeTests tests/MetaCoreSmokeTests.cpp + Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp ) target_link_libraries(MetaCoreSmokeTests diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp index 63e298d..4f6f33b 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp @@ -1671,6 +1671,72 @@ void MetaCoreDrawLightComponentInspector(MetaCoreEditorContext& editorContext, M } } +void MetaCoreDrawRotatorComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) { + return; + } + + const bool multiEdit = editorContext.GetSelectedObjectIds().size() > 1; + if (multiEdit) { + ImGui::TextDisabled("多选编辑 %zu 个对象", editorContext.GetSelectedObjectIds().size()); + } + + const auto sharedEnabled = MetaCoreGetSharedSelectedValue( + editorContext, + [](MetaCoreGameObject& selectedObject) -> std::optional { + return selectedObject.HasComponent() + ? std::optional(selectedObject.GetComponent().Enabled) + : std::nullopt; + }, + [](bool lhs, bool rhs) { return lhs == rhs; } + ); + bool enabled = sharedEnabled.value_or(gameObject.GetComponent().Enabled); + if (multiEdit && !sharedEnabled.has_value()) { + ImGui::TextDisabled("启用: 混合值"); + } else { + if (ImGui::Checkbox("启用##Rotator", &enabled)) { + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> bool* { + return selectedObject.HasComponent() + ? &selectedObject.GetComponent().Enabled + : nullptr; + }, + enabled + ); + } + } + + const auto sharedSpeed = MetaCoreGetSharedSelectedValue( + editorContext, + [](MetaCoreGameObject& selectedObject) -> std::optional { + return selectedObject.HasComponent() + ? std::optional(selectedObject.GetComponent().DegreesPerSecond) + : std::nullopt; + }, + [](float lhs, float rhs) { return MetaCoreNearlyEqual(lhs, rhs); } + ); + float degreesPerSecond = sharedSpeed.value_or(gameObject.GetComponent().DegreesPerSecond); + if (multiEdit && !sharedSpeed.has_value()) { + ImGui::TextDisabled("角速度: 混合值"); + } else { + ImGui::DragFloat("角速度 Y (度/秒)", °reesPerSecond, 1.0F, -1440.0F, 1440.0F); + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); + if (ImGui::IsItemEdited()) { + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> float* { + return selectedObject.HasComponent() + ? &selectedObject.GetComponent().DegreesPerSecond + : nullptr; + }, + degreesPerSecond + ); + } + } +} + void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { if (!gameObject.HasComponent()) { return; @@ -5738,7 +5804,7 @@ public: if (!gameObject.HasComponent()) { return false; } - gameObject.AddComponent(MetaCoreCameraComponent{}); + gameObject.GetComponent() = MetaCoreCameraComponent{}; return true; }, [](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional> { @@ -5752,7 +5818,11 @@ public: if (!MetaCoreDeserializeComponentValue(payload, component, registry)) { return false; } - gameObject.AddComponent(component); + if (gameObject.HasComponent()) { + gameObject.GetComponent() = component; + } else { + gameObject.AddComponent(component); + } return true; }, MetaCoreDrawCameraComponentInspector @@ -5779,7 +5849,7 @@ public: if (!gameObject.HasComponent()) { return false; } - gameObject.AddComponent(MetaCoreLightComponent{}); + gameObject.GetComponent() = MetaCoreLightComponent{}; return true; }, [](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional> { @@ -5793,11 +5863,60 @@ public: if (!MetaCoreDeserializeComponentValue(payload, component, registry)) { return false; } - gameObject.AddComponent(component); + if (gameObject.HasComponent()) { + gameObject.GetComponent() = component; + } else { + gameObject.AddComponent(component); + } return true; }, MetaCoreDrawLightComponentInspector }); + Descriptors_.push_back(MetaCoreComponentDescriptor{ + "Rotator", + "旋转器", + [](const MetaCoreGameObject& gameObject) { return gameObject.HasComponent(); }, + [](MetaCoreGameObject& gameObject) { + if (gameObject.HasComponent()) { + return false; + } + gameObject.AddComponent(MetaCoreRotatorComponent{}); + return true; + }, + [](MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) { + return false; + } + gameObject.RemoveComponent(); + return true; + }, + [](MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) { + return false; + } + gameObject.GetComponent() = MetaCoreRotatorComponent{}; + return true; + }, + [](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional> { + if (!gameObject.HasComponent()) { + return std::nullopt; + } + return MetaCoreSerializeComponentValue(gameObject.GetComponent(), registry); + }, + [](MetaCoreGameObject& gameObject, std::span payload, const MetaCoreTypeRegistry& registry) { + MetaCoreRotatorComponent component{}; + if (!MetaCoreDeserializeComponentValue(payload, component, registry)) { + return false; + } + if (gameObject.HasComponent()) { + gameObject.GetComponent() = component; + } else { + gameObject.AddComponent(component); + } + return true; + }, + MetaCoreDrawRotatorComponentInspector + }); Descriptors_.push_back(MetaCoreComponentDescriptor{ "MeshRenderer", "Mesh Renderer", @@ -5820,7 +5939,7 @@ public: if (!gameObject.HasComponent()) { return false; } - gameObject.AddComponent(MetaCoreMeshRendererComponent{}); + gameObject.GetComponent() = MetaCoreMeshRendererComponent{}; return true; }, [](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional> { @@ -5834,7 +5953,11 @@ public: if (!MetaCoreDeserializeComponentValue(payload, component, registry)) { return false; } - gameObject.AddComponent(component); + if (gameObject.HasComponent()) { + gameObject.GetComponent() = component; + } else { + gameObject.AddComponent(component); + } return true; }, MetaCoreDrawMeshRendererComponentInspector @@ -5908,11 +6031,295 @@ private: std::vector CopiedComponentPayload_{}; }; +class MetaCoreRotatorPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem { +public: + [[nodiscard]] std::string GetSystemId() const override { return "MetaCore.PlayMode.Rotator"; } + + void Update(MetaCoreEditorContext& editorContext, double deltaSeconds) override { + if (deltaSeconds <= 0.0) { + return; + } + + bool changed = false; + for (MetaCoreGameObject gameObject : editorContext.GetScene().GetGameObjects()) { + if (!gameObject.HasComponent() || + !gameObject.HasComponent()) { + continue; + } + + const MetaCoreRotatorComponent& rotator = gameObject.GetComponent(); + if (!rotator.Enabled || MetaCoreNearlyEqual(rotator.DegreesPerSecond, 0.0F)) { + continue; + } + + gameObject.GetComponent().RotationEulerDegrees.y += + rotator.DegreesPerSecond * static_cast(deltaSeconds); + changed = true; + } + + if (changed) { + editorContext.GetScene().IncrementRevision(); + } + } +}; + +class MetaCoreBuiltinPlayModeComponentSystemRegistry final : public MetaCoreIPlayModeComponentSystemRegistry { +public: + [[nodiscard]] std::string GetServiceId() const override { return "MetaCore.PlayModeComponentSystemRegistry"; } + + void Startup(MetaCoreEditorModuleRegistry& moduleRegistry) override { + (void)moduleRegistry; + Systems_.clear(); + RegisterSystem(std::make_shared()); + } + + void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) override { + (void)moduleRegistry; + Systems_.clear(); + } + + void RegisterSystem(std::shared_ptr system) override { + if (system == nullptr || system->GetSystemId().empty()) { + return; + } + + const std::string systemId = system->GetSystemId(); + const auto iterator = std::find_if(Systems_.begin(), Systems_.end(), [&](const auto& existingSystem) { + return existingSystem != nullptr && existingSystem->GetSystemId() == systemId; + }); + if (iterator != Systems_.end()) { + *iterator = std::move(system); + return; + } + + Systems_.push_back(std::move(system)); + } + + void ClearSystems() override { + Systems_.clear(); + } + + [[nodiscard]] std::vector> GetSystems() const override { + return Systems_; + } + +private: + std::vector> Systems_{}; +}; + class MetaCoreBuiltinPlayModeService final : public MetaCoreIPlayModeService { public: [[nodiscard]] std::string GetServiceId() const override { return "MetaCore.PlayMode"; } - [[nodiscard]] MetaCorePlayModeState GetState() const override { return MetaCorePlayModeState::Edit; } - [[nodiscard]] bool CanEnterPlayMode() const override { return false; } + [[nodiscard]] MetaCorePlayModeState GetState() const override { return State_; } + [[nodiscard]] bool CanEnterPlayMode() const override { return State_ == MetaCorePlayModeState::Edit; } + + void Startup(MetaCoreEditorModuleRegistry& moduleRegistry) override { + ComponentSystemRegistry_ = moduleRegistry.ResolveService(); + } + + void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) override { + (void)moduleRegistry; + ComponentSystemRegistry_.reset(); + State_ = MetaCorePlayModeState::Edit; + HasPrePlaySnapshot_ = false; + CommandRecordingSuppressed_ = false; + ElapsedTimeSeconds_ = 0.0; + FixedTimeAccumulatorSeconds_ = 0.0; + } + + [[nodiscard]] bool EnterPlayMode(MetaCoreEditorContext& editorContext) override { + if (State_ != MetaCorePlayModeState::Edit) { + return false; + } + + PrePlaySnapshot_ = editorContext.CaptureStateSnapshot(); + HasPrePlaySnapshot_ = true; + ElapsedTimeSeconds_ = 0.0; + FixedTimeAccumulatorSeconds_ = 0.0; + State_ = MetaCorePlayModeState::Playing; + + editorContext.BeginCommandRecordingSuppression(); + CommandRecordingSuppressed_ = true; + + DispatchOnPlayStart(editorContext); + RefreshSceneDirtyState(editorContext); + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "PlayMode", "已进入播放模式"); + return true; + } + + [[nodiscard]] bool ExitPlayMode(MetaCoreEditorContext& editorContext) override { + if (State_ == MetaCorePlayModeState::Edit) { + return false; + } + + DispatchOnPlayStop(editorContext); + State_ = MetaCorePlayModeState::Edit; + + if (CommandRecordingSuppressed_) { + editorContext.EndCommandRecordingSuppression(); + CommandRecordingSuppressed_ = false; + } + + if (HasPrePlaySnapshot_) { + const MetaCoreEditorStateSnapshot currentSnapshot = editorContext.CaptureStateSnapshot(); + (void)editorContext.CommitStateTransition("播放模式改动", PrePlaySnapshot_, currentSnapshot); + } else { + RefreshSceneDirtyState(editorContext); + } + + HasPrePlaySnapshot_ = false; + ElapsedTimeSeconds_ = 0.0; + FixedTimeAccumulatorSeconds_ = 0.0; + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "PlayMode", "已停止播放模式,播放期改动已保留"); + return true; + } + + [[nodiscard]] bool Pause() override { + if (State_ != MetaCorePlayModeState::Playing) { + return false; + } + + State_ = MetaCorePlayModeState::Paused; + return true; + } + + [[nodiscard]] bool Resume() override { + if (State_ != MetaCorePlayModeState::Paused) { + return false; + } + + State_ = MetaCorePlayModeState::Playing; + return true; + } + + [[nodiscard]] bool Step(MetaCoreEditorContext& editorContext, double deltaSeconds = 1.0 / 60.0) override { + if (State_ != MetaCorePlayModeState::Paused) { + return false; + } + + const double updateDeltaSeconds = NormalizeDeltaSeconds(deltaSeconds, FixedTimeStepSeconds_); + DispatchFixedUpdate(editorContext, FixedTimeStepSeconds_); + DispatchUpdate(editorContext, updateDeltaSeconds); + DispatchLateUpdate(editorContext, updateDeltaSeconds); + ElapsedTimeSeconds_ += updateDeltaSeconds; + RefreshSceneDirtyState(editorContext); + return true; + } + + void Tick(MetaCoreEditorContext& editorContext, double deltaSeconds) override { + if (State_ != MetaCorePlayModeState::Playing) { + return; + } + + const double frameDeltaSeconds = NormalizeDeltaSeconds(deltaSeconds, 0.0); + if (frameDeltaSeconds <= 0.0) { + return; + } + + ElapsedTimeSeconds_ += frameDeltaSeconds; + FixedTimeAccumulatorSeconds_ += frameDeltaSeconds; + + int fixedStepCount = 0; + while (FixedTimeAccumulatorSeconds_ >= FixedTimeStepSeconds_ && fixedStepCount < MaxFixedStepsPerFrame_) { + DispatchFixedUpdate(editorContext, FixedTimeStepSeconds_); + FixedTimeAccumulatorSeconds_ -= FixedTimeStepSeconds_; + ++fixedStepCount; + } + if (fixedStepCount == MaxFixedStepsPerFrame_ && FixedTimeAccumulatorSeconds_ >= FixedTimeStepSeconds_) { + FixedTimeAccumulatorSeconds_ = 0.0; + } + + DispatchUpdate(editorContext, frameDeltaSeconds); + DispatchLateUpdate(editorContext, frameDeltaSeconds); + RefreshSceneDirtyState(editorContext); + } + + [[nodiscard]] double GetElapsedTimeSeconds() const override { return ElapsedTimeSeconds_; } + [[nodiscard]] double GetFixedTimeStep() const override { return FixedTimeStepSeconds_; } + + void SetFixedTimeStep(double fixedTimeStepSeconds) override { + if (!std::isfinite(fixedTimeStepSeconds)) { + return; + } + FixedTimeStepSeconds_ = std::clamp(fixedTimeStepSeconds, 0.001, 1.0); + FixedTimeAccumulatorSeconds_ = std::min(FixedTimeAccumulatorSeconds_, FixedTimeStepSeconds_); + } + +private: + [[nodiscard]] std::vector> GetSystems() const { + return ComponentSystemRegistry_ != nullptr + ? ComponentSystemRegistry_->GetSystems() + : std::vector>{}; + } + + [[nodiscard]] double NormalizeDeltaSeconds(double deltaSeconds, double fallbackSeconds) const { + if (!std::isfinite(deltaSeconds) || deltaSeconds <= 0.0) { + deltaSeconds = fallbackSeconds; + } + if (deltaSeconds <= 0.0) { + return 0.0; + } + return std::min(deltaSeconds, MaxFrameDeltaSeconds_); + } + + void DispatchOnPlayStart(MetaCoreEditorContext& editorContext) { + for (const auto& system : GetSystems()) { + if (system != nullptr) { + system->OnPlayStart(editorContext); + } + } + } + + void DispatchFixedUpdate(MetaCoreEditorContext& editorContext, double fixedDeltaSeconds) { + for (const auto& system : GetSystems()) { + if (system != nullptr) { + system->FixedUpdate(editorContext, fixedDeltaSeconds); + } + } + } + + void DispatchUpdate(MetaCoreEditorContext& editorContext, double deltaSeconds) { + for (const auto& system : GetSystems()) { + if (system != nullptr) { + system->Update(editorContext, deltaSeconds); + } + } + } + + void DispatchLateUpdate(MetaCoreEditorContext& editorContext, double deltaSeconds) { + for (const auto& system : GetSystems()) { + if (system != nullptr) { + system->LateUpdate(editorContext, deltaSeconds); + } + } + } + + void DispatchOnPlayStop(MetaCoreEditorContext& editorContext) { + for (const auto& system : GetSystems()) { + if (system != nullptr) { + system->OnPlayStop(editorContext); + } + } + } + + void RefreshSceneDirtyState(MetaCoreEditorContext& editorContext) const { + if (const auto scenePersistenceService = + editorContext.GetModuleRegistry().ResolveService(); + scenePersistenceService != nullptr) { + scenePersistenceService->UpdateDirtyState(editorContext.GetScene().CaptureSnapshot()); + } + } + + std::shared_ptr ComponentSystemRegistry_{}; + MetaCorePlayModeState State_ = MetaCorePlayModeState::Edit; + MetaCoreEditorStateSnapshot PrePlaySnapshot_{}; + bool HasPrePlaySnapshot_ = false; + bool CommandRecordingSuppressed_ = false; + double ElapsedTimeSeconds_ = 0.0; + double FixedTimeStepSeconds_ = 1.0 / 60.0; + double FixedTimeAccumulatorSeconds_ = 0.0; + double MaxFrameDeltaSeconds_ = 0.1; + int MaxFixedStepsPerFrame_ = 8; }; std::optional MetaCoreBuiltinPrefabService::FindPrefabAsset(const MetaCoreAssetGuid& prefabAssetGuid) const { @@ -6029,6 +6436,7 @@ std::optional MetaCoreBuiltinPrefabService::CreatePrefabF data.Transform = gameObject.GetComponent(); if (gameObject.HasComponent()) data.Camera = gameObject.GetComponent(); if (gameObject.HasComponent()) data.Light = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.Rotator = gameObject.GetComponent(); if (gameObject.HasComponent()) data.MeshRenderer = gameObject.GetComponent(); prefabDataObjects.push_back(std::move(data)); } @@ -6090,6 +6498,7 @@ std::optional MetaCoreBuiltinPrefabService::InstantiatePrefabDocumen clonedObject.GetComponent() = sourceObject.Transform; if (sourceObject.Camera.has_value()) clonedObject.AddComponent(*sourceObject.Camera); if (sourceObject.Light.has_value()) clonedObject.AddComponent(*sourceObject.Light); + if (sourceObject.Rotator.has_value()) clonedObject.AddComponent(*sourceObject.Rotator); if (sourceObject.MeshRenderer.has_value()) clonedObject.AddComponent(*sourceObject.MeshRenderer); clonedObject.AddComponent(MetaCorePrefabInstanceMetadata{ @@ -6200,6 +6609,7 @@ bool MetaCoreBuiltinPrefabService::ApplySelectedPrefabInstance(MetaCoreEditorCon data.Transform = gameObject.GetComponent(); if (gameObject.HasComponent()) data.Camera = gameObject.GetComponent(); if (gameObject.HasComponent()) data.Light = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.Rotator = gameObject.GetComponent(); if (gameObject.HasComponent()) data.MeshRenderer = gameObject.GetComponent(); prefabDataObjects.push_back(std::move(data)); @@ -6964,6 +7374,7 @@ public: moduleRegistry.RegisterService(std::make_shared()); moduleRegistry.RegisterService(std::make_shared()); moduleRegistry.RegisterService(std::make_shared()); + moduleRegistry.RegisterService(std::make_shared()); moduleRegistry.RegisterService(std::make_shared()); if (const auto assetDatabase = moduleRegistry.ResolveService(); diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index bb7f42a..6781084 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -52,6 +52,7 @@ namespace { void MetaCoreLaunchPlayer(MetaCoreEditorContext& editorContext, MetaCoreIAssetDatabaseService& assetDatabaseService, MetaCoreIScenePersistenceService& scenePersistenceService); void MetaCorePollPlayerProcess(MetaCoreEditorContext& editorContext); +void CreateNewMaterial(MetaCoreEditorContext& editorContext, MetaCoreIAssetDatabaseService& assetDatabaseService); static std::string GeneratedResourceFilter_{}; static std::string GeneratedResourceKindFilter_{}; @@ -1911,7 +1912,7 @@ public: const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService(); const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); - if (ImGui::BeginMenu("文件")) { + if (ImGui::BeginMenu("项目")) { if (ImGui::MenuItem("新建项目...")) { if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) { const auto& project = assetDatabaseService->GetProjectDescriptor(); @@ -1934,16 +1935,13 @@ public: if (ImGui::MenuItem("保存场景", "Ctrl+S", false, scenePersistenceService != nullptr)) { MetaCoreHandleSaveScene(editorContext); } - if (ImGui::MenuItem("一键保存并运行", "F5", false, scenePersistenceService != nullptr && assetDatabaseService != nullptr && assetDatabaseService->HasProject())) { + if (ImGui::MenuItem("保存并运行", "F5", false, scenePersistenceService != nullptr && assetDatabaseService != nullptr && assetDatabaseService->HasProject())) { MetaCoreLaunchPlayer(editorContext, *assetDatabaseService, *scenePersistenceService); } - if (ImGui::MenuItem("刷新 Asset 数据库", nullptr, false, assetDatabaseService != nullptr)) { - MetaCoreHandleReloadAssets(editorContext); - } ImGui::Separator(); - if (ImGui::MenuItem("重置布局")) { - editorContext.SetDockLayoutBuilt(false); - } + ImGui::MenuItem("构建设置", nullptr, false, false); + ImGui::MenuItem("偏好设置", nullptr, false, false); + ImGui::Separator(); if (ImGui::MenuItem("退出")) { editorContext.GetWindow().RequestClose(); } @@ -1964,6 +1962,7 @@ public: } if (ImGui::BeginPopupModal("MetaCoreCreateProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextUnformatted("创建 MetaCore 项目。"); ImGui::InputText("项目目录", CreateProjectPathBuffer_, sizeof(CreateProjectPathBuffer_)); ImGui::InputText("项目名称", CreateProjectNameBuffer_, sizeof(CreateProjectNameBuffer_)); if (ImGui::Button("创建")) { @@ -1979,6 +1978,7 @@ public: } if (ImGui::BeginPopupModal("MetaCoreOpenProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextUnformatted("打开已有 MetaCore 项目。"); ImGui::InputText("项目路径", OpenProjectPathBuffer_, sizeof(OpenProjectPathBuffer_)); if (ImGui::Button("打开")) { MetaCoreOpenProject(editorContext, std::filesystem::path(OpenProjectPathBuffer_)); @@ -1992,7 +1992,7 @@ public: } if (ImGui::BeginPopupModal("MetaCoreImportResourcePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::TextUnformatted("输入外部文件或目录路径,导入到项目 Assets 目录。"); + ImGui::TextUnformatted("将外部文件或目录导入到项目 Assets 目录。"); ImGui::InputText("源路径", ImportSourcePathBuffer_, sizeof(ImportSourcePathBuffer_)); ImGui::InputText("目标目录", ImportTargetDirectoryBuffer_, sizeof(ImportTargetDirectoryBuffer_)); if (ImGui::Button("导入")) { @@ -2047,6 +2047,17 @@ public: ); OpenImportResourcePopup_ = true; } + if (ImGui::MenuItem("刷新资源数据库", nullptr, false, assetDatabaseService != nullptr)) { + MetaCoreHandleReloadAssets(editorContext); + } + ImGui::Separator(); + if (ImGui::BeginMenu("创建")) { + if (ImGui::MenuItem("材质", nullptr, false, assetDatabaseService != nullptr && assetDatabaseService->HasProject())) { + CreateNewMaterial(editorContext, *assetDatabaseService); + } + ImGui::EndMenu(); + } + ImGui::Separator(); if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { (void)MetaCoreCreatePrefabFromSelection(editorContext); } @@ -2057,17 +2068,20 @@ public: if (ImGui::MenuItem("创建空对象")) { (void)MetaCoreCreateObject(editorContext, "GameObject", false, std::nullopt); } - if (ImGui::MenuItem("创建立方体")) { - (void)MetaCoreCreateObject(editorContext, "Cube", true, std::nullopt); - } - if (ImGui::MenuItem("创建平面")) { - (void)MetaCoreCreateObject(editorContext, "Plane", true, std::nullopt); + if (ImGui::BeginMenu("3D 对象")) { + if (ImGui::MenuItem("立方体")) { + (void)MetaCoreCreateObject(editorContext, "Cube", true, std::nullopt); + } + if (ImGui::MenuItem("平面")) { + (void)MetaCoreCreateObject(editorContext, "Plane", true, std::nullopt); + } + ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::MenuItem("应用当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { + if (ImGui::MenuItem("应用 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { (void)MetaCoreApplySelectedPrefabInstance(editorContext); } - if (ImGui::MenuItem("还原当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { + if (ImGui::MenuItem("还原 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { (void)MetaCoreRevertSelectedPrefabInstance(editorContext); } ImGui::Separator(); @@ -2090,12 +2104,17 @@ public: bool& panelOpen = editorContext.GetModuleRegistry().AccessPanelOpenState(panelProvider->GetPanelId()); ImGui::MenuItem(panelProvider->GetPanelTitle().c_str(), nullptr, &panelOpen); } + ImGui::Separator(); + if (ImGui::MenuItem("重置布局")) { + editorContext.SetDockLayoutBuilt(false); + } ImGui::EndMenu(); } if (ImGui::BeginMenu("帮助")) { ImGui::MenuItem("MetaCore Editor", nullptr, false, false); - ImGui::MenuItem("当前运行模式:MetaCore 编辑器", nullptr, false, false); + ImGui::MenuItem("模式:MetaCore 编辑器", nullptr, false, false); + ImGui::MenuItem("布局参考 Infernux 编辑器", nullptr, false, false); ImGui::EndMenu(); } } @@ -4166,20 +4185,24 @@ public: ImGui::SetNextItemOpen(true, ImGuiCond_Once); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 3)); - if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.235F, 0.235F, 0.235F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.28F, 0.24F, 0.24F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.32F, 0.25F, 0.25F, 1.0F)); + if (ImGui::CollapsingHeader("变换##TransformHeader", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::PopStyleColor(3); ImGui::PopStyleVar(); if (hasTransformOverride) { ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "Overrides"); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "覆盖"); ImGui::SameLine(); - if (ImGui::SmallButton("Revert##TransformComponent")) { + if (ImGui::SmallButton("还原##TransformComponent")) { (void)MetaCoreRevertSelectedObjectComponentToPrefab(editorContext, "Transform"); } } ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 2)); const std::size_t selectedCount = editorContext.GetSelectedObjectIds().size(); if (selectedCount > 1) { - ImGui::TextDisabled("Multi-edit %zu objects", selectedCount); + ImGui::TextDisabled("多选编辑 %zu 个对象", selectedCount); } DrawVec3Control( "位置", @@ -4211,6 +4234,7 @@ public: ); ImGui::PopStyleVar(); } else { + ImGui::PopStyleColor(3); ImGui::PopStyleVar(); } } @@ -4296,13 +4320,13 @@ private: ImGui::Text("%s", label.c_str()); if (prefabValue.has_value() && !MetaCoreNearlyEqualVec3(values, *prefabValue)) { ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "Overrides"); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "覆盖"); ImGui::SameLine(); - if (ImGui::SmallButton(("Apply##" + label).c_str())) { + if (ImGui::SmallButton(("应用##" + label).c_str())) { (void)MetaCoreApplySelectedPrefabField(editorContext, "Transform", prefabFieldId); } ImGui::SameLine(); - if (ImGui::SmallButton(("Revert##" + label).c_str())) { + if (ImGui::SmallButton(("还原##" + label).c_str())) { (void)MetaCoreRevertSelectedTransformFieldToPrefab(editorContext, prefabFieldId); values = *prefabValue; } @@ -4310,7 +4334,7 @@ private: ImGui::NextColumn(); if (multiEdit && !sharedValue.has_value()) { - ImGui::TextDisabled("Mixed"); + ImGui::TextDisabled("混合"); } const float labelWidth = 14.0F; // Smaller labels @@ -4352,7 +4376,7 @@ private: } if (ImGui::BeginPopupContextItem("TransformContext")) { - if (ImGui::MenuItem("Reset")) { + if (ImGui::MenuItem("重置")) { MetaCoreApplyValueToSelectedObjects( editorContext, [&](MetaCoreGameObject& selectedObject) -> glm::vec3* { @@ -4383,26 +4407,33 @@ public: bool IsOpenByDefault() const override { return true; } void DrawPanel(MetaCoreEditorContext& editorContext) override { - - const std::size_t selectedCount = editorContext.GetSelectedObjectIds().size(); if (selectedCount == 0) { if (editorContext.HasSelectedAsset()) { const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); const auto packageService = editorContext.GetModuleRegistry().ResolveService(); const auto reflectionRegistry = editorContext.GetModuleRegistry().ResolveService(); + ImGui::BeginChild("RawDataModule", ImVec2(0.0F, 0.0F), ImGuiChildFlags_Borders); if (assetDatabaseService && packageService && reflectionRegistry) { MetaCoreDrawAssetInspector(editorContext, *assetDatabaseService, *packageService, *reflectionRegistry); + } else { + ImGui::TextDisabled("资源检查器不可用。"); } + ImGui::EndChild(); } else { + ImGui::BeginChild("PropertiesModule", ImVec2(0.0F, 0.0F), ImGuiChildFlags_Borders); ImGui::TextUnformatted("当前没有选中对象。"); + ImGui::TextDisabled("在层级面板选择对象,或在项目面板选择资源以查看详情。"); + ImGui::EndChild(); } return; } MetaCoreGameObject selectedObject = editorContext.GetSelectedGameObject(); if (!selectedObject) { + ImGui::BeginChild("PropertiesModule", ImVec2(0.0F, 0.0F), ImGuiChildFlags_Borders); ImGui::TextUnformatted("当前没有可编辑的主对象。"); + ImGui::EndChild(); return; } @@ -4412,11 +4443,10 @@ public: std::snprintf(NameBuffer_.data(), NameBuffer_.size(), "%s", selectedObject.GetName().c_str()); } - // GameObject Header (Unity Style) + ImGui::BeginChild("PropertiesModule", ImVec2(0.0F, 0.0F), ImGuiChildFlags_Borders); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4)); - // Active Checkbox + Cube Icon + Name bool active = true; bool hasActiveProp = false; if (selectedObject.HasComponent()) { @@ -4467,72 +4497,17 @@ public: } ImGui::SameLine(); - // Cube icon (small) - 使用 ImDrawList 绘制等角 3D 蓝色立方体以对标 Unity - { - float size = 12.0F; - ImVec2 p = ImGui::GetCursorScreenPos(); - p.y += (ImGui::GetFrameHeight() - size) * 0.5F - 1.0F; - ImDrawList* drawList = ImGui::GetWindowDrawList(); - - float h = size * 0.5F; - float dx = h * 0.866F; // 30度角的 x 偏移 - float dy = h * 0.5F; // 30度角的 y 偏移 - - ImVec2 c = ImVec2(p.x + dx, p.y + h); - ImVec2 p_top = ImVec2(c.x, c.y - h); - ImVec2 p_bottom = ImVec2(c.x, c.y + h); - ImVec2 p_left_top = ImVec2(c.x - dx, c.y - dy); - ImVec2 p_left_bottom = ImVec2(c.x - dx, c.y + dy); - ImVec2 p_right_top = ImVec2(c.x + dx, c.y - dy); - ImVec2 p_right_bottom = ImVec2(c.x + dx, c.y + dy); - - // 填充三个面,使用渐变色营造 3D 立体感 - ImVec2 top_face[4] = { p_top, p_right_top, c, p_left_top }; - drawList->AddConvexPolyFilled(top_face, 4, IM_COL32(110, 160, 230, 255)); - - ImVec2 left_face[4] = { p_left_top, c, p_bottom, p_left_bottom }; - drawList->AddConvexPolyFilled(left_face, 4, IM_COL32(75, 120, 190, 255)); - - ImVec2 right_face[4] = { p_right_top, p_right_bottom, p_bottom, c }; - drawList->AddConvexPolyFilled(right_face, 4, IM_COL32(50, 95, 160, 255)); - - // 绘制棱线增加精致感 - ImU32 lineColor = IM_COL32(230, 240, 255, 200); - drawList->AddLine(c, p_top, lineColor, 1.0F); - drawList->AddLine(c, p_bottom, lineColor, 1.0F); - drawList->AddLine(c, p_left_top, lineColor, 1.0F); - drawList->AddLine(c, p_right_top, lineColor, 1.0F); - drawList->AddLine(p_top, p_left_top, lineColor, 1.0F); - drawList->AddLine(p_top, p_right_top, lineColor, 1.0F); - drawList->AddLine(p_left_top, p_left_bottom, lineColor, 1.0F); - drawList->AddLine(p_right_top, p_right_bottom, lineColor, 1.0F); - drawList->AddLine(p_left_bottom, p_bottom, lineColor, 1.0F); - drawList->AddLine(p_right_bottom, p_bottom, lineColor, 1.0F); - - ImGui::Dummy(ImVec2(dx * 2.0F, size)); // 占位 - } - ImGui::SameLine(); - - // 扁平化名称输入框样式,移除突兀的蓝色边框,并与 Static 控件完美对齐 - float staticControlWidth = 85.0F; // "静态的" 文字及 checkbox 的总估算宽度 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.12F, 0.12F, 0.12F, 1.0F)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - staticControlWidth - 4.0F); + ImGui::SetNextItemWidth(-1.0F); if (ImGui::InputText("##Name", NameBuffer_.data(), NameBuffer_.size(), ImGuiInputTextFlags_EnterReturnsTrue)) { (void)MetaCoreRenameObject(editorContext, selectedObject.GetId(), NameBuffer_.data()); } ImGui::PopStyleVar(); ImGui::PopStyleColor(2); - - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - staticControlWidth); - static bool isStatic = false; - ImGui::TextDisabled("静态的"); ImGui::SameLine(); - ImGui::Checkbox("##Static", &isStatic); - // Tag and Layer float colWidth = ImGui::GetContentRegionAvail().x * 0.5F; ImGui::Columns(2, "TagLayerCols", false); ImGui::SetColumnWidth(0, colWidth); @@ -4547,12 +4522,12 @@ public: ImGui::Text("标签"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); - if (ImGui::Button("Untagged ▾##Tag", ImVec2(-1, 0))) { /* Menu */ } + if (ImGui::Button("未标记 ▾##Tag", ImVec2(-1, 0))) { /* Menu */ } ImGui::NextColumn(); ImGui::Text("图层"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); - if (ImGui::Button("Default ▾##Layer", ImVec2(-1, 0))) { /* Menu */ } + if (ImGui::Button("默认 ▾##Layer", ImVec2(-1, 0))) { /* Menu */ } ImGui::PopStyleVar(2); ImGui::PopStyleColor(4); @@ -4560,7 +4535,7 @@ public: ImGui::Columns(1); ImGui::PopStyleVar(2); if (selectedCount > 1) { - ImGui::TextDisabled("%zu objects selected", selectedCount); + ImGui::TextDisabled("已选中 %zu 个对象", selectedCount); } ImGui::Separator(); @@ -4586,18 +4561,18 @@ public: MetaCoreHasMeshRendererOverride(selectedObject, *prefabSourceObject); } - ImGui::Text("Prefab Instance%s", hasAnyOverride ? " [Overrides]" : ""); + ImGui::Text("Prefab 实例%s", hasAnyOverride ? " [覆盖]" : ""); ImGui::SameLine(); - if (ImGui::SmallButton("Apply")) { + if (ImGui::SmallButton("应用")) { (void)MetaCoreApplySelectedPrefabInstance(editorContext); } ImGui::SameLine(); - if (ImGui::SmallButton("Revert")) { + if (ImGui::SmallButton("还原")) { (void)MetaCoreRevertSelectedPrefabInstance(editorContext); } ImGui::TextDisabled( - "Asset: %s | RootId: %llu | PrefabObjectId: %llu", - prefabAsset.has_value() ? prefabAsset->RelativePath.generic_string().c_str() : "", + "资源: %s | 根对象: %llu | 源对象: %llu", + prefabAsset.has_value() ? prefabAsset->RelativePath.generic_string().c_str() : "<缺失 Prefab 资源>", static_cast(selectedObject.GetComponent().PrefabInstanceRootId), static_cast(selectedObject.GetComponent().PrefabObjectId) ); @@ -4635,7 +4610,21 @@ public: ImGui::PushID(descriptor.TypeId.c_str()); ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (ImGui::CollapsingHeader(descriptor.DisplayName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + std::string componentDisplayName = descriptor.DisplayName; + if (descriptor.TypeId == "Camera") { + componentDisplayName = "相机"; + } else if (descriptor.TypeId == "Light") { + componentDisplayName = "灯光"; + } else if (descriptor.TypeId == "MeshRenderer") { + componentDisplayName = "网格渲染器"; + } + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.235F, 0.235F, 0.235F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.28F, 0.24F, 0.24F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.32F, 0.25F, 0.25F, 1.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 3.0F)); + if (ImGui::CollapsingHeader(componentDisplayName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); bool hasPrefabOverride = false; if (prefabSourceObject != nullptr) { if (descriptor.TypeId == "Camera") { @@ -4649,18 +4638,18 @@ public: if (hasPrefabOverride) { ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "Overrides"); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "覆盖"); ImGui::SameLine(); - if (ImGui::SmallButton("Revert")) { + if (ImGui::SmallButton("还原")) { (void)MetaCoreRevertSelectedObjectComponentToPrefab(editorContext, descriptor.TypeId); } } ImGui::SameLine(); - if (ImGui::SmallButton("Reset")) { + if (ImGui::SmallButton("重置")) { (void)MetaCoreResetSelectedObjectComponent(editorContext, descriptor); } ImGui::SameLine(); - if (ImGui::SmallButton("Copy")) { + if (ImGui::SmallButton("复制")) { (void)MetaCoreCopySelectedObjectComponent(editorContext, descriptor); } ImGui::SameLine(); @@ -4668,14 +4657,14 @@ public: if (!canPasteComponent) { ImGui::BeginDisabled(); } - if (ImGui::SmallButton("Paste")) { + if (ImGui::SmallButton("粘贴")) { (void)MetaCorePasteSelectedObjectComponent(editorContext, descriptor); } if (!canPasteComponent) { ImGui::EndDisabled(); } ImGui::SameLine(); - if (ImGui::SmallButton("Remove")) { + if (ImGui::SmallButton("移除")) { (void)MetaCoreMutateSelectedObjectWithComponentDescriptor(editorContext, descriptor, false); ImGui::PopID(); continue; @@ -4684,6 +4673,9 @@ public: if (descriptor.DrawInspector) { descriptor.DrawInspector(editorContext, selectedObject); } + } else { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); } ImGui::PopID(); } @@ -4711,6 +4703,8 @@ public: } ImGui::EndPopup(); } + + ImGui::EndChild(); } private: diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp index 776b6df..efac278 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp @@ -23,6 +23,8 @@ #endif #define GLM_ENABLE_EXPERIMENTAL +#include +#include #include #include #include @@ -81,6 +83,352 @@ constexpr bool GMetaCoreEnableImGuizmo = true; constexpr bool GMetaCoreEnableImGuizmo = true; #endif +constexpr float GMetaCoreToolbarHeight = 34.0F; +constexpr float GMetaCoreStatusBarHeight = 24.0F; +constexpr ImVec4 GInfernuxAccent{0.922F, 0.341F, 0.341F, 1.0F}; +constexpr ImVec4 GInfernuxButtonIdle{0.18F, 0.18F, 0.18F, 1.0F}; +constexpr ImVec4 GInfernuxButtonDisabled{0.15F, 0.15F, 0.15F, 0.40F}; +constexpr ImVec4 GInfernuxPlayActive{0.20F, 0.45F, 0.30F, 1.0F}; +constexpr ImVec4 GInfernuxPauseActive{0.50F, 0.40F, 0.15F, 1.0F}; +constexpr ImVec4 GInfernuxStatusBarBg{0.13F, 0.13F, 0.13F, 1.0F}; +constexpr ImVec4 GInfernuxGhost{0.0F, 0.0F, 0.0F, 0.0F}; +constexpr ImVec4 GInfernuxGhostHovered{1.0F, 1.0F, 1.0F, 0.06F}; +constexpr ImVec4 GInfernuxGhostActive{1.0F, 1.0F, 1.0F, 0.10F}; + +void MetaCorePushFlatButtonStyle(const ImVec4& base) { + ImGui::PushStyleColor(ImGuiCol_Button, base); + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + ImVec4( + std::min(base.x + 0.06F, 1.0F), + std::min(base.y + 0.06F, 1.0F), + std::min(base.z + 0.06F, 1.0F), + base.w + ) + ); + ImGui::PushStyleColor( + ImGuiCol_ButtonActive, + ImVec4( + std::min(base.x + 0.12F, 1.0F), + std::min(base.y + 0.12F, 1.0F), + std::min(base.z + 0.12F, 1.0F), + base.w + ) + ); +} + +void MetaCorePushGhostButtonStyle() { + ImGui::PushStyleColor(ImGuiCol_Button, GInfernuxGhost); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, GInfernuxGhostHovered); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, GInfernuxGhostActive); +} + +[[nodiscard]] const char* MetaCoreGizmoOperationShortLabel(MetaCoreGizmoOperation operation) { + switch (operation) { + case MetaCoreGizmoOperation::None: return "选择"; + case MetaCoreGizmoOperation::Translate: return "移动"; + case MetaCoreGizmoOperation::Rotate: return "旋转"; + case MetaCoreGizmoOperation::Scale: return "缩放"; + default: return "工具"; + } +} + +[[nodiscard]] bool MetaCoreToolbarToggle(const char* label, bool selected, ImVec2 size = ImVec2(0.0F, 24.0F)) { + if (selected) { + MetaCorePushFlatButtonStyle(GInfernuxAccent); + } else { + MetaCorePushGhostButtonStyle(); + } + + const bool clicked = ImGui::Button(label, size); + ImGui::PopStyleColor(3); + return clicked; +} + +void MetaCoreDrawInfernuxToolbar(MetaCoreEditorContext& editorContext, const ImVec2& position, const ImVec2& size) { + ImGui::SetNextWindowPos(position, ImGuiCond_Always); + ImGui::SetNextWindowSize(size, ImGuiCond_Always); + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.16F, 0.16F, 0.16F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.16F, 0.16F, 0.16F, 1.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0F, 4.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0F, 4.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0F, 4.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0F); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + + constexpr ImGuiWindowFlags toolbarFlags = + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoBringToFrontOnFocus; + + if (ImGui::Begin("MetaCoreToolbar", nullptr, toolbarFlags)) { + const float windowWidth = ImGui::GetWindowWidth(); + + ImGui::AlignTextToFramePadding(); + ImGui::TextColored(GInfernuxAccent, "MetaCore"); + ImGui::SameLine(84.0F); + + if (MetaCoreToolbarToggle("选择", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::None)) { + editorContext.SetGizmoOperation(MetaCoreGizmoOperation::None); + } + ImGui::SameLine(); + if (MetaCoreToolbarToggle("移动", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) { + editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Translate); + } + ImGui::SameLine(); + if (MetaCoreToolbarToggle("旋转", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) { + editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Rotate); + } + ImGui::SameLine(); + if (MetaCoreToolbarToggle("缩放", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) { + editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Scale); + } + + ImGui::SameLine(0.0F, 10.0F); + if (MetaCoreToolbarToggle("局部", editorContext.GetGizmoMode() == MetaCoreGizmoMode::Local, ImVec2(52.0F, 24.0F))) { + editorContext.SetGizmoMode(MetaCoreGizmoMode::Local); + } + ImGui::SameLine(); + if (MetaCoreToolbarToggle("全局", editorContext.GetGizmoMode() == MetaCoreGizmoMode::Global, ImVec2(58.0F, 24.0F))) { + editorContext.SetGizmoMode(MetaCoreGizmoMode::Global); + } + + const auto playModeService = editorContext.GetModuleRegistry().ResolveService(); + const MetaCorePlayModeState playState = + playModeService != nullptr ? playModeService->GetState() : MetaCorePlayModeState::Edit; + const bool isPlaying = + playState == MetaCorePlayModeState::Playing || playState == MetaCorePlayModeState::Paused; + const bool isPaused = playState == MetaCorePlayModeState::Paused; + const bool canEnterPlayMode = + playModeService != nullptr && (playModeService->CanEnterPlayMode() || isPlaying); + + const float centerX = std::max(ImGui::GetCursorPosX() + 14.0F, (windowWidth - 180.0F) * 0.5F); + ImGui::SameLine(centerX); + + MetaCorePushFlatButtonStyle(isPlaying && !isPaused ? GInfernuxPlayActive : GInfernuxButtonIdle); + if (!canEnterPlayMode) { + ImGui::BeginDisabled(); + } + if (ImGui::Button(isPlaying ? "停止" : "播放", ImVec2(58.0F, 24.0F))) { + const bool ok = isPlaying + ? playModeService->ExitPlayMode(editorContext) + : playModeService->EnterPlayMode(editorContext); + if (!ok) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "PlayMode", "播放模式状态切换失败。"); + } + } + if (!canEnterPlayMode) { + ImGui::EndDisabled(); + } + ImGui::PopStyleColor(3); + + ImGui::SameLine(0.0F, 2.0F); + MetaCorePushFlatButtonStyle(isPaused ? GInfernuxPauseActive : (isPlaying ? GInfernuxButtonIdle : GInfernuxButtonDisabled)); + if (!isPlaying) { + ImGui::BeginDisabled(); + } + if (ImGui::Button(isPaused ? "继续" : "暂停", ImVec2(64.0F, 24.0F))) { + const bool ok = isPaused ? playModeService->Resume() : playModeService->Pause(); + if (!ok) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "PlayMode", "暂停状态切换失败。"); + } + } + if (!isPlaying) { + ImGui::EndDisabled(); + } + ImGui::PopStyleColor(3); + + ImGui::SameLine(0.0F, 2.0F); + MetaCorePushFlatButtonStyle(isPaused ? GInfernuxButtonIdle : GInfernuxButtonDisabled); + if (!isPaused) { + ImGui::BeginDisabled(); + } + if (ImGui::Button("单步", ImVec2(54.0F, 24.0F))) { + if (!playModeService->Step(editorContext)) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "PlayMode", "单步只在暂停状态下可用。"); + } + } + if (!isPaused) { + ImGui::EndDisabled(); + } + ImGui::PopStyleColor(3); + + const float rightX = std::max(ImGui::GetCursorPosX() + 16.0F, windowWidth - 198.0F); + if (rightX < windowWidth - 20.0F) { + ImGui::SameLine(rightX); + MetaCorePushGhostButtonStyle(); + if (ImGui::Button("小工具", ImVec2(72.0F, 24.0F))) { + ImGui::OpenPopup("##MetaCoreToolbarGizmos"); + } + ImGui::PopStyleColor(3); + + if (ImGui::BeginPopup("##MetaCoreToolbarGizmos")) { + bool showGrid = editorContext.GetShowViewportGrid(); + if (ImGui::Checkbox("显示网格", &showGrid)) { + editorContext.SetShowViewportGrid(showGrid); + } + bool showOrigin = editorContext.GetShowWorldOrigin(); + if (ImGui::Checkbox("显示原点", &showOrigin)) { + editorContext.SetShowWorldOrigin(showOrigin); + } + ImGui::Separator(); + ImGui::TextDisabled( + "工具: %s | 坐标: %s", + MetaCoreGizmoOperationShortLabel(editorContext.GetGizmoOperation()), + editorContext.GetGizmoMode() == MetaCoreGizmoMode::Local ? "局部" : "全局" + ); + ImGui::EndPopup(); + } + + ImGui::SameLine(0.0F, 4.0F); + MetaCorePushGhostButtonStyle(); + if (ImGui::Button("相机", ImVec2(72.0F, 24.0F))) { + ImGui::OpenPopup("##MetaCoreToolbarCamera"); + } + ImGui::PopStyleColor(3); + + if (ImGui::BeginPopup("##MetaCoreToolbarCamera")) { + const MetaCoreSceneView sceneView = editorContext.GetCameraController().BuildSceneView(); + ImGui::Text("视野: %.1f", sceneView.VerticalFieldOfViewDegrees); + ImGui::Text( + "位置: %.2f, %.2f, %.2f", + sceneView.CameraPosition.x, + sceneView.CameraPosition.y, + sceneView.CameraPosition.z + ); + ImGui::TextDisabled("快捷键: Q/W/E/R, Z, F, A, G, O"); + ImGui::EndPopup(); + } + } + } + ImGui::End(); + + ImGui::PopStyleVar(5); + ImGui::PopStyleColor(2); +} + +[[nodiscard]] ImVec4 MetaCoreStatusColor(MetaCoreLogLevel level) { + switch (level) { + case MetaCoreLogLevel::Warning: return ImVec4(0.89F, 0.71F, 0.30F, 1.0F); + case MetaCoreLogLevel::Error: return GInfernuxAccent; + case MetaCoreLogLevel::Info: + default: return ImVec4(0.82F, 0.82F, 0.85F, 1.0F); + } +} + +void MetaCoreDrawInfernuxStatusBar(MetaCoreEditorContext& editorContext, const ImVec2& position, const ImVec2& size) { + ImGui::SetNextWindowPos(position, ImGuiCond_Always); + ImGui::SetNextWindowSize(size, ImGuiCond_Always); + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + ImGui::PushStyleColor(ImGuiCol_WindowBg, GInfernuxStatusBarBg); + ImGui::PushStyleColor(ImGuiCol_ChildBg, GInfernuxStatusBarBg); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0F, 4.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0F, 0.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0F, 0.0F)); + + constexpr ImGuiWindowFlags statusFlags = + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoBringToFrontOnFocus; + + if (ImGui::Begin("MetaCoreStatusBar", nullptr, statusFlags)) { + const auto& entries = editorContext.GetLogService().GetEntries(); + const MetaCoreLogEntry* latestEntry = entries.empty() ? nullptr : &entries.back(); + const auto warningCount = static_cast(std::count_if(entries.begin(), entries.end(), [](const MetaCoreLogEntry& entry) { + return entry.Level == MetaCoreLogLevel::Warning; + })); + const auto errorCount = static_cast(std::count_if(entries.begin(), entries.end(), [](const MetaCoreLogEntry& entry) { + return entry.Level == MetaCoreLogLevel::Error; + })); + + const float windowWidth = ImGui::GetWindowWidth(); + const float leftZoneWidth = std::max(180.0F, windowWidth * 0.48F); + const std::string message = latestEntry != nullptr + ? "[" + latestEntry->Category + "] " + latestEntry->Message + : std::string("就绪"); + + MetaCorePushGhostButtonStyle(); + if (ImGui::Button(("##StatusOpenConsole" + message).c_str(), ImVec2(leftZoneWidth, 16.0F))) { + editorContext.GetModuleRegistry().AccessPanelOpenState("Console") = true; + } + ImGui::PopStyleColor(3); + + ImGui::SameLine(8.0F); + ImGui::PushStyleColor(ImGuiCol_Text, latestEntry != nullptr ? MetaCoreStatusColor(latestEntry->Level) : MetaCoreStatusColor(MetaCoreLogLevel::Info)); + ImGui::TextUnformatted(message.c_str()); + ImGui::PopStyleColor(); + + ImGui::SameLine(leftZoneWidth + 16.0F); + ImGui::PushStyleColor(ImGuiCol_Text, warningCount > 0 ? MetaCoreStatusColor(MetaCoreLogLevel::Warning) : ImVec4(0.35F, 0.35F, 0.35F, 1.0F)); + ImGui::Text("警告 %d", warningCount); + ImGui::PopStyleColor(); + + ImGui::SameLine(0.0F, 12.0F); + ImGui::PushStyleColor(ImGuiCol_Text, errorCount > 0 ? MetaCoreStatusColor(MetaCoreLogLevel::Error) : ImVec4(0.35F, 0.35F, 0.35F, 1.0F)); + ImGui::Text("错误 %d", errorCount); + ImGui::PopStyleColor(); + + ImGui::SameLine(0.0F, 16.0F); + const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); + const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService(); + if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) { + const auto& descriptor = assetDatabaseService->GetProjectDescriptor(); + ImGui::TextDisabled("%s | %zu 个资源", descriptor.Name.c_str(), assetDatabaseService->GetAssetRegistry().size()); + } else { + ImGui::TextDisabled("未打开项目"); + } + + ImGui::SameLine(0.0F, 16.0F); + if (scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene()) { + ImGui::TextDisabled( + "场景: %s%s", + scenePersistenceService->GetCurrentSceneDisplayName().c_str(), + scenePersistenceService->IsSceneDirty() ? "*" : "" + ); + } else { + ImGui::TextDisabled("场景: 未命名"); + } + + ImGui::SameLine(0.0F, 16.0F); + const auto playModeService = editorContext.GetModuleRegistry().ResolveService(); + const MetaCorePlayModeState playState = + playModeService != nullptr ? playModeService->GetState() : MetaCorePlayModeState::Edit; + switch (playState) { + case MetaCorePlayModeState::Playing: + ImGui::TextDisabled("模式: 播放 %.2fs", playModeService->GetElapsedTimeSeconds()); + break; + case MetaCorePlayModeState::Paused: + ImGui::TextDisabled("模式: 暂停 %.2fs", playModeService->GetElapsedTimeSeconds()); + break; + case MetaCorePlayModeState::Edit: + default: + ImGui::TextDisabled("模式: 编辑"); + break; + } + + ImGui::SameLine(0.0F, 16.0F); + const auto& selectedObjectIds = editorContext.GetSelectedObjectIds(); + if (selectedObjectIds.empty()) { + ImGui::TextDisabled("选中: 无"); + } else { + ImGui::TextDisabled("选中: %zu", selectedObjectIds.size()); + } + } + ImGui::End(); + + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(2); +} + [[nodiscard]] bool MetaCoreInstantiateProjectAssetToScene( MetaCoreEditorContext& editorContext, const MetaCoreProjectAssetDragDropPayload& payload @@ -439,22 +787,44 @@ void MetaCoreApplyEditorStyle() { ImGuiStyle& style = ImGui::GetStyle(); ImGui::StyleColorsDark(); style.WindowRounding = 2.0F; - style.FrameRounding = 2.0F; - style.TabRounding = 2.0F; + style.ChildRounding = 0.0F; + style.PopupRounding = 4.0F; + style.FrameRounding = 3.0F; + style.GrabRounding = 3.0F; + style.TabRounding = 3.0F; style.WindowBorderSize = 1.0F; - style.FramePadding = ImVec2(8.0F, 4.0F); - style.ItemSpacing = ImVec2(8.0F, 5.0F); - style.Colors[ImGuiCol_WindowBg] = ImVec4(0.11F, 0.12F, 0.14F, 0.97F); - style.Colors[ImGuiCol_TitleBg] = ImVec4(0.10F, 0.11F, 0.13F, 1.0F); - style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.14F, 0.15F, 0.17F, 1.0F); - style.Colors[ImGuiCol_Header] = ImVec4(0.17F, 0.20F, 0.24F, 1.0F); - style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.22F, 0.27F, 0.32F, 1.0F); - style.Colors[ImGuiCol_Button] = ImVec4(0.18F, 0.21F, 0.25F, 1.0F); - style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.23F, 0.28F, 0.33F, 1.0F); - style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.27F, 0.33F, 0.39F, 1.0F); - style.Colors[ImGuiCol_Tab] = ImVec4(0.12F, 0.14F, 0.16F, 1.0F); - style.Colors[ImGuiCol_TabSelected] = ImVec4(0.20F, 0.26F, 0.34F, 1.0F); - style.Colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.0F, 0.0F, 0.0F, 0.0F); + style.FrameBorderSize = 0.0F; + style.WindowPadding = ImVec2(8.0F, 8.0F); + style.FramePadding = ImVec2(6.0F, 4.0F); + style.ItemSpacing = ImVec2(6.0F, 4.0F); + style.IndentSpacing = 14.0F; + style.ScrollbarSize = 12.0F; + style.Colors[ImGuiCol_Text] = ImVec4(0.82F, 0.82F, 0.85F, 1.0F); + style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.50F, 0.50F, 0.50F, 1.0F); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.13F, 0.13F, 0.13F, 1.0F); + style.Colors[ImGuiCol_ChildBg] = ImVec4(0.13F, 0.13F, 0.13F, 1.0F); + style.Colors[ImGuiCol_PopupBg] = ImVec4(0.24F, 0.24F, 0.24F, 0.96F); + style.Colors[ImGuiCol_Border] = ImVec4(0.22F, 0.22F, 0.22F, 1.0F); + style.Colors[ImGuiCol_FrameBg] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.22F, 0.20F, 0.20F, 1.0F); + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.26F, 0.22F, 0.22F, 1.0F); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.12F, 0.12F, 0.12F, 1.0F); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.18F, 0.16F, 0.16F, 1.0F); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); + style.Colors[ImGuiCol_Header] = ImVec4(0.18F, 0.18F, 0.18F, 1.0F); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.28F, 0.24F, 0.24F, 1.0F); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.32F, 0.25F, 0.25F, 1.0F); + style.Colors[ImGuiCol_Button] = GInfernuxButtonIdle; + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.24F, 0.22F, 0.22F, 1.0F); + style.Colors[ImGuiCol_ButtonActive] = GInfernuxAccent; + style.Colors[ImGuiCol_Tab] = ImVec4(0.14F, 0.14F, 0.14F, 1.0F); + style.Colors[ImGuiCol_TabHovered] = ImVec4(0.28F, 0.24F, 0.24F, 1.0F); + style.Colors[ImGuiCol_TabSelected] = ImVec4(0.24F, 0.20F, 0.20F, 1.0F); + style.Colors[ImGuiCol_Separator] = ImVec4(0.22F, 0.22F, 0.22F, 1.0F); + style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.35F, 0.25F, 0.25F, 0.60F); + style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.40F, 0.28F, 0.28F, 0.80F); + style.Colors[ImGuiCol_DockingPreview] = ImVec4(0.922F, 0.341F, 0.341F, 0.45F); + style.Colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.10F, 0.10F, 0.10F, 1.0F); // Optimize ImGuizmo style for premium feel and remove hatching artifacts (black dashed lines). ImGuizmo::Style& gizmoStyle = ImGuizmo::GetStyle(); @@ -736,12 +1106,21 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) { } int MetaCoreEditorApp::Run() { + auto previousPlayModeTickTime = std::chrono::steady_clock::now(); while (!Window_.ShouldClose()) { Window_.BeginFrame(); if (const auto hotReloadService = ModuleRegistry_.ResolveService(); hotReloadService != nullptr) { hotReloadService->Tick(); } + const auto currentPlayModeTickTime = std::chrono::steady_clock::now(); + const double playModeDeltaSeconds = + std::chrono::duration(currentPlayModeTickTime - previousPlayModeTickTime).count(); + previousPlayModeTickTime = currentPlayModeTickTime; + if (const auto playModeService = ModuleRegistry_.ResolveService(); + playModeService != nullptr && EditorContext_ != nullptr) { + playModeService->Tick(*EditorContext_, playModeDeltaSeconds); + } #if defined(_WIN32) ImGui_ImplWin32_NewFrame(); @@ -834,8 +1213,38 @@ void MetaCoreEditorApp::DrawEditorFrame() { SceneInteractionService_.HandleShortcuts(*EditorContext_); const ImGuiViewport* mainViewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(mainViewport->WorkPos); - ImGui::SetNextWindowSize(mainViewport->WorkSize); + float menuBarHeight = ImGui::GetFrameHeight(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0F, 4.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0F, 4.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0F, 4.0F)); + ImGui::PushStyleColor(ImGuiCol_MenuBarBg, ImVec4(0.16F, 0.16F, 0.16F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.24F, 0.24F, 0.24F, 0.96F)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.28F, 0.24F, 0.24F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.32F, 0.25F, 0.25F, 1.0F)); + if (ImGui::BeginMainMenuBar()) { + menuBarHeight = ImGui::GetWindowHeight(); + for (const auto& menuProvider : ModuleRegistry_.GetMenuProviders()) { + menuProvider->DrawMenuBar(*EditorContext_); + } + ImGui::EndMainMenuBar(); + } + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(3); + + const ImVec2 toolbarPosition(mainViewport->Pos.x, mainViewport->Pos.y + menuBarHeight); + const ImVec2 toolbarSize(mainViewport->Size.x, GMetaCoreToolbarHeight); + const ImVec2 statusPosition(mainViewport->Pos.x, mainViewport->Pos.y + mainViewport->Size.y - GMetaCoreStatusBarHeight); + const ImVec2 statusSize(mainViewport->Size.x, GMetaCoreStatusBarHeight); + const ImVec2 dockPosition(mainViewport->Pos.x, toolbarPosition.y + GMetaCoreToolbarHeight); + const ImVec2 dockSize( + mainViewport->Size.x, + std::max(1.0F, statusPosition.y - dockPosition.y) + ); + + MetaCoreDrawInfernuxToolbar(*EditorContext_, toolbarPosition, toolbarSize); + + ImGui::SetNextWindowPos(dockPosition); + ImGui::SetNextWindowSize(dockSize); ImGui::SetNextWindowViewport(mainViewport->ID); constexpr ImGuiWindowFlags hostWindowFlags = @@ -846,7 +1255,7 @@ void MetaCoreEditorApp::DrawEditorFrame() { ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | - ImGuiWindowFlags_MenuBar; + ImGuiWindowFlags_NoDocking; ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F); @@ -854,32 +1263,13 @@ void MetaCoreEditorApp::DrawEditorFrame() { ImGui::Begin("MetaCoreMainDockSpace", nullptr, hostWindowFlags); ImGui::PopStyleVar(3); - if (ImGui::BeginMenuBar()) { - for (const auto& menuProvider : ModuleRegistry_.GetMenuProviders()) { - menuProvider->DrawMenuBar(*EditorContext_); - } - - // Center Play/Pause/Stop buttons - const float buttonWidth = 32.0F; - const float totalWidth = buttonWidth * 3 + ImGui::GetStyle().ItemSpacing.x * 2; - ImGui::SetCursorPosX((ImGui::GetWindowWidth() - totalWidth) * 0.5F); - - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2F, 0.2F, 0.2F, 0.0F)); - if (ImGui::Button(" > ", ImVec2(buttonWidth, 0))) { /* Play */ } - ImGui::SameLine(); - if (ImGui::Button(" ||", ImVec2(buttonWidth, 0))) { /* Pause */ } - ImGui::SameLine(); - if (ImGui::Button(" >>", ImVec2(buttonWidth, 0))) { /* Step */ } - ImGui::PopStyleColor(); - - ImGui::EndMenuBar(); - } - const ImGuiID dockSpaceId = ImGui::GetID("MetaCoreDockSpaceId"); ImGui::DockSpace(dockSpaceId, ImVec2(0.0F, 0.0F), ImGuiDockNodeFlags_PassthruCentralNode); - EnsureDefaultDockLayout(dockSpaceId); + EnsureDefaultDockLayout(dockSpaceId, dockSize); ImGui::End(); + MetaCoreDrawInfernuxStatusBar(*EditorContext_, statusPosition, statusSize); + for (const auto& panelProvider : ModuleRegistry_.GetPanelProviders()) { if (panelProvider->GetPanelId() == "Scene") { continue; @@ -927,16 +1317,20 @@ void MetaCoreEditorApp::DrawEditorFrame() { const float centralWidth = centralNode->Size.x > 1.0F ? centralNode->Size.x : 1.0F; const float centralHeight = centralNode->Size.y > 1.0F ? centralNode->Size.y : 1.0F; ImGui::SetNextWindowSize(ImVec2(centralWidth, 30.0F)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.14F, 0.14F, 0.14F, 1.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0F, 4.0F)); ImGui::Begin("ViewportTabs", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); static int activeTab = 0; // 0: Scene, 1: Game - if (ImGui::Selectable(" 场景 ", activeTab == 0, 0, ImVec2(60, 0))) activeTab = 0; + if (ImGui::Selectable(" 场景 ", activeTab == 0, 0, ImVec2(70, 0))) activeTab = 0; ImGui::SameLine(); - if (ImGui::Selectable(" 游戏 ", activeTab == 1, 0, ImVec2(60, 0))) activeTab = 1; + if (ImGui::Selectable(" 游戏 ", activeTab == 1, 0, ImVec2(70, 0))) activeTab = 1; ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); - constexpr float sceneToolbarHeight = 34.0F; + constexpr float sceneToolbarHeight = 0.0F; constexpr float tabHeaderHeight = 30.0F; MetaCoreSceneViewportState& viewportState = EditorContext_->GetSceneViewportState(); const ImVec2 mainViewportPosition = ImGui::GetMainViewport()->Pos; @@ -1108,24 +1502,24 @@ void MetaCoreEditorApp::DrawEditorFrame() { } } -void MetaCoreEditorApp::EnsureDefaultDockLayout(unsigned int dockSpaceId) { +void MetaCoreEditorApp::EnsureDefaultDockLayout(unsigned int dockSpaceId, const ImVec2& dockSize) { if (EditorContext_->HasDockLayoutBuilt()) { return; } ImGui::DockBuilderRemoveNode(dockSpaceId); ImGui::DockBuilderAddNode(dockSpaceId, ImGuiDockNodeFlags_DockSpace); - ImGui::DockBuilderSetNodeSize(dockSpaceId, ImGui::GetMainViewport()->Size); + ImGui::DockBuilderSetNodeSize(dockSpaceId, dockSize); ImGuiID mainNodeId = dockSpaceId; const ImGuiID leftNodeId = ImGui::DockBuilderSplitNode(mainNodeId, ImGuiDir_Left, 0.18F, nullptr, &mainNodeId); const ImGuiID rightNodeId = ImGui::DockBuilderSplitNode(mainNodeId, ImGuiDir_Right, 0.24F, nullptr, &mainNodeId); const ImGuiID bottomNodeId = ImGui::DockBuilderSplitNode(mainNodeId, ImGuiDir_Down, 0.28F, nullptr, &mainNodeId); - ImGui::DockBuilderDockWindow("\u5C42\u7EA7", leftNodeId); - ImGui::DockBuilderDockWindow("\u68C0\u67E5\u5668", rightNodeId); - ImGui::DockBuilderDockWindow("\u9879\u76EE", bottomNodeId); - ImGui::DockBuilderDockWindow("\u63A7\u5236\u53F0", bottomNodeId); + ImGui::DockBuilderDockWindow("层级", leftNodeId); + ImGui::DockBuilderDockWindow("检查器", rightNodeId); + ImGui::DockBuilderDockWindow("项目", bottomNodeId); + ImGui::DockBuilderDockWindow("控制台", bottomNodeId); ImGui::DockBuilderFinish(dockSpaceId); EditorContext_->SetDockLayoutBuilt(true); diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h index 38107f5..28633c7 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h @@ -8,7 +8,7 @@ namespace MetaCore { class MetaCoreInput; -struct MetaCoreGameObject; +class MetaCoreGameObject; class MetaCoreEditorCameraController { public: diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp index 9bafa02..3738e48 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp @@ -96,7 +96,8 @@ private: if (!MetaCoreNearlyEqual(lhs.Camera->FieldOfViewDegrees, rhs.Camera->FieldOfViewDegrees) || !MetaCoreNearlyEqual(lhs.Camera->NearClip, rhs.Camera->NearClip) || !MetaCoreNearlyEqual(lhs.Camera->FarClip, rhs.Camera->FarClip) || - lhs.Camera->IsPrimary != rhs.Camera->IsPrimary) { + lhs.Camera->IsPrimary != rhs.Camera->IsPrimary || + lhs.Camera->Enabled != rhs.Camera->Enabled) { return false; } } @@ -117,7 +118,18 @@ private: } if (lhs.Light.has_value()) { if (!MetaCoreNearlyEqualVec3(lhs.Light->Color, rhs.Light->Color) || - !MetaCoreNearlyEqual(lhs.Light->Intensity, rhs.Light->Intensity)) { + !MetaCoreNearlyEqual(lhs.Light->Intensity, rhs.Light->Intensity) || + lhs.Light->Enabled != rhs.Light->Enabled) { + return false; + } + } + + if (lhs.Rotator.has_value() != rhs.Rotator.has_value()) { + return false; + } + if (lhs.Rotator.has_value()) { + if (lhs.Rotator->Enabled != rhs.Rotator->Enabled || + !MetaCoreNearlyEqual(lhs.Rotator->DegreesPerSecond, rhs.Rotator->DegreesPerSecond)) { return false; } } @@ -350,6 +362,19 @@ MetaCoreEditorCommandService& MetaCoreEditorContext::GetCommandService() { retur const MetaCoreEditorCommandService& MetaCoreEditorContext::GetCommandService() const { return CommandService_; } bool MetaCoreEditorContext::ExecuteCommand(std::unique_ptr command) { + if (command == nullptr) { + return false; + } + + if (IsCommandRecordingSuppressed()) { + if (!command->Execute(*this)) { + return false; + } + NormalizeSelection(); + RefreshSceneDirtyState(); + return true; + } + if (!CommandService_.Execute(std::move(command), *this)) { return false; } @@ -360,6 +385,10 @@ bool MetaCoreEditorContext::ExecuteCommand(std::unique_ptr(label, beforeSnapshot, afterSnapshot, allowMerge))) { return false; } @@ -435,6 +474,20 @@ bool MetaCoreEditorContext::ExecuteSnapshotCommand(const std::string& label, con return CommitStateTransition(label, beforeSnapshot, afterSnapshot); } +void MetaCoreEditorContext::BeginCommandRecordingSuppression() { + ++CommandRecordingSuppressionDepth_; +} + +void MetaCoreEditorContext::EndCommandRecordingSuppression() { + if (CommandRecordingSuppressionDepth_ > 0) { + --CommandRecordingSuppressionDepth_; + } +} + +bool MetaCoreEditorContext::IsCommandRecordingSuppressed() const { + return CommandRecordingSuppressionDepth_ > 0; +} + void MetaCoreEditorContext::RequestRenameActiveObject() { PendingRenameObjectId_ = ActiveObjectId_; } diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorServices.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorServices.cpp index 0dabd69..f68cdee 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorServices.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorServices.cpp @@ -49,4 +49,36 @@ void MetaCoreIEditorService::Shutdown(MetaCoreEditorModuleRegistry& moduleRegist (void)moduleRegistry; } +void MetaCoreIPlayModeComponentSystem::OnPlayStart(MetaCoreEditorContext& editorContext) { + (void)editorContext; +} + +void MetaCoreIPlayModeComponentSystem::FixedUpdate( + MetaCoreEditorContext& editorContext, + double fixedDeltaSeconds +) { + (void)editorContext; + (void)fixedDeltaSeconds; +} + +void MetaCoreIPlayModeComponentSystem::Update( + MetaCoreEditorContext& editorContext, + double deltaSeconds +) { + (void)editorContext; + (void)deltaSeconds; +} + +void MetaCoreIPlayModeComponentSystem::LateUpdate( + MetaCoreEditorContext& editorContext, + double deltaSeconds +) { + (void)editorContext; + (void)deltaSeconds; +} + +void MetaCoreIPlayModeComponentSystem::OnPlayStop(MetaCoreEditorContext& editorContext) { + (void)editorContext; +} + } // namespace MetaCore diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorApp.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorApp.h index 723169a..cb5a4fc 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorApp.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorApp.h @@ -13,6 +13,8 @@ #include #include +struct ImVec2; + namespace MetaCore { class MetaCoreIModule; @@ -30,7 +32,7 @@ private: bool InitializeImGui(); void ShutdownImGui(); void DrawEditorFrame(); - void EnsureDefaultDockLayout(unsigned int dockSpaceId); + void EnsureDefaultDockLayout(unsigned int dockSpaceId, const ImVec2& dockSize); MetaCoreWindow Window_{}; MetaCoreRenderDevice RenderDevice_{}; diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h index c5a1989..e5f5d31 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h @@ -10,6 +10,7 @@ #include "MetaCoreScene/MetaCoreScene.h" #include +#include #include #include #include @@ -21,7 +22,7 @@ class MetaCoreWindow; class MetaCoreInput; class MetaCoreRenderDevice; class MetaCoreEditorViewportRenderer; -struct MetaCoreGameObject; +class MetaCoreGameObject; class MetaCoreEditorModuleRegistry; class MetaCoreEditorCameraController; @@ -160,6 +161,9 @@ public: const MetaCoreEditorStateSnapshot& afterSnapshot, bool allowMerge = false ); + void BeginCommandRecordingSuppression(); + void EndCommandRecordingSuppression(); + [[nodiscard]] bool IsCommandRecordingSuppressed() const; void RequestRenameActiveObject(); [[nodiscard]] MetaCoreId ConsumeRenameRequestObjectId(); void AddConsoleMessage(MetaCoreLogLevel level, const std::string& category, const std::string& message); @@ -204,6 +208,7 @@ private: bool ShowWorldOrigin_ = true; MetaCoreReparentTransformRule ReparentTransformRule_ = MetaCoreReparentTransformRule::KeepWorld; MetaCoreEditorCommandService CommandService_{}; + std::uint32_t CommandRecordingSuppressionDepth_ = 0; MetaCoreId PendingRenameObjectId_ = 0; bool DockLayoutBuilt_ = false; bool RuntimeDataConfigLoaded_ = false; diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorModule.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorModule.h index f09de88..c674d89 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorModule.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorModule.h @@ -11,7 +11,7 @@ namespace MetaCore { class MetaCoreEditorContext; -struct MetaCoreGameObject; +class MetaCoreGameObject; class MetaCoreIMenuProvider { public: diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h index 81d5e2a..125a47d 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h @@ -21,7 +21,7 @@ namespace MetaCore { class MetaCoreEditorContext; class MetaCoreEditorModuleRegistry; -struct MetaCoreGameObject; +class MetaCoreGameObject; enum class MetaCoreEditorEventType { ProjectLoaded = 0, @@ -312,10 +312,38 @@ enum class MetaCorePlayModeState { Paused }; +class MetaCoreIPlayModeComponentSystem { +public: + virtual ~MetaCoreIPlayModeComponentSystem() = default; + + [[nodiscard]] virtual std::string GetSystemId() const = 0; + virtual void OnPlayStart(MetaCoreEditorContext& editorContext); + virtual void FixedUpdate(MetaCoreEditorContext& editorContext, double fixedDeltaSeconds); + virtual void Update(MetaCoreEditorContext& editorContext, double deltaSeconds); + virtual void LateUpdate(MetaCoreEditorContext& editorContext, double deltaSeconds); + virtual void OnPlayStop(MetaCoreEditorContext& editorContext); +}; + +class MetaCoreIPlayModeComponentSystemRegistry : public MetaCoreIEditorService { +public: + virtual void RegisterSystem(std::shared_ptr system) = 0; + virtual void ClearSystems() = 0; + [[nodiscard]] virtual std::vector> GetSystems() const = 0; +}; + class MetaCoreIPlayModeService : public MetaCoreIEditorService { public: [[nodiscard]] virtual MetaCorePlayModeState GetState() const = 0; [[nodiscard]] virtual bool CanEnterPlayMode() const = 0; + [[nodiscard]] virtual bool EnterPlayMode(MetaCoreEditorContext& editorContext) = 0; + [[nodiscard]] virtual bool ExitPlayMode(MetaCoreEditorContext& editorContext) = 0; + [[nodiscard]] virtual bool Pause() = 0; + [[nodiscard]] virtual bool Resume() = 0; + [[nodiscard]] virtual bool Step(MetaCoreEditorContext& editorContext, double deltaSeconds = 1.0 / 60.0) = 0; + virtual void Tick(MetaCoreEditorContext& editorContext, double deltaSeconds) = 0; + [[nodiscard]] virtual double GetElapsedTimeSeconds() const = 0; + [[nodiscard]] virtual double GetFixedTimeStep() const = 0; + virtual void SetFixedTimeStep(double fixedTimeStepSeconds) = 0; }; } // namespace MetaCore diff --git a/Source/MetaCoreScene/Private/MetaCoreScene.cpp b/Source/MetaCoreScene/Private/MetaCoreScene.cpp index b22176f..2479ce5 100644 --- a/Source/MetaCoreScene/Private/MetaCoreScene.cpp +++ b/Source/MetaCoreScene/Private/MetaCoreScene.cpp @@ -66,6 +66,9 @@ std::vector MetaCoreScene::GetGameObjects() { for (auto [id, entity] : IdToEntity_) { objects.emplace_back(entity, this); } + std::sort(objects.begin(), objects.end(), [](const MetaCoreGameObject& lhs, const MetaCoreGameObject& rhs) { + return lhs.GetId() < rhs.GetId(); + }); return objects; } @@ -75,6 +78,9 @@ std::vector MetaCoreScene::GetGameObjects() const { for (auto [id, entity] : IdToEntity_) { objects.emplace_back(entity, const_cast(this)); } + std::sort(objects.begin(), objects.end(), [](const MetaCoreGameObject& lhs, const MetaCoreGameObject& rhs) { + return lhs.GetId() < rhs.GetId(); + }); return objects; } @@ -262,6 +268,9 @@ std::vector MetaCoreScene::DuplicateGameObjects(const std::vector()) clonedObject.AddComponent(sourceObject.GetComponent()); + + if (sourceObject.HasComponent()) + clonedObject.AddComponent(sourceObject.GetComponent()); if (sourceObject.HasComponent()) clonedObject.AddComponent(sourceObject.GetComponent()); @@ -410,6 +419,8 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const { data.MeshRenderer = obj.GetComponent(); if (obj.HasComponent()) data.Light = obj.GetComponent(); + if (obj.HasComponent()) + data.Rotator = obj.GetComponent(); if (obj.HasComponent()) data.PrefabInstance = obj.GetComponent(); if (obj.HasComponent()) @@ -436,6 +447,8 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) { obj.AddComponent(data.MeshRenderer.value()); if (data.Light.has_value()) obj.AddComponent(data.Light.value()); + if (data.Rotator.has_value()) + obj.AddComponent(data.Rotator.value()); if (data.PrefabInstance.has_value()) obj.AddComponent(data.PrefabInstance.value()); if (data.ModelRootTag.has_value()) diff --git a/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp b/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp index 507b330..667503d 100644 --- a/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp +++ b/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp @@ -105,9 +105,20 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo for (const auto& lightField : lightDesc->Fields) { if (lightField.Name == "Color") lightJson["Color"] = Vec3ToJson(obj.Light->Color); else if (lightField.Name == "Intensity") lightJson["Intensity"] = obj.Light->Intensity; + else if (lightField.Name == "Enabled") lightJson["Enabled"] = obj.Light->Enabled; } objJson["Light"] = lightJson; } + } else if (objField.Name == "Rotator" && obj.Rotator.has_value()) { + const MetaCoreStructDescriptor* rotatorDesc = registry.FindStruct(); + if (rotatorDesc) { + json rotatorJson = json::object(); + for (const auto& rotatorField : rotatorDesc->Fields) { + if (rotatorField.Name == "Enabled") rotatorJson["Enabled"] = obj.Rotator->Enabled; + else if (rotatorField.Name == "DegreesPerSecond") rotatorJson["DegreesPerSecond"] = obj.Rotator->DegreesPerSecond; + } + objJson["Rotator"] = rotatorJson; + } } else if (objField.Name == "Camera" && obj.Camera.has_value()) { const MetaCoreStructDescriptor* camDesc = registry.FindStruct(); if (camDesc) { @@ -117,6 +128,7 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo else if (camField.Name == "NearClip") camJson["NearClip"] = obj.Camera->NearClip; else if (camField.Name == "FarClip") camJson["FarClip"] = obj.Camera->FarClip; else if (camField.Name == "IsPrimary") camJson["IsPrimary"] = obj.Camera->IsPrimary; + else if (camField.Name == "Enabled") camJson["Enabled"] = obj.Camera->Enabled; } objJson["Camera"] = camJson; } @@ -211,9 +223,21 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me for (const auto& lightField : lightDesc->Fields) { if (lightField.Name == "Color" && lightJson.contains("Color")) light.Color = JsonToVec3(lightJson["Color"]); else if (lightField.Name == "Intensity" && lightJson.contains("Intensity")) light.Intensity = lightJson["Intensity"].get(); + else if (lightField.Name == "Enabled" && lightJson.contains("Enabled")) light.Enabled = lightJson["Enabled"].get(); } obj.Light = light; } + } else if (objField.Name == "Rotator" && objJson.contains("Rotator")) { + const auto& rotatorJson = objJson["Rotator"]; + const MetaCoreStructDescriptor* rotatorDesc = registry.FindStruct(); + if (rotatorDesc) { + MetaCoreRotatorComponent rotator; + for (const auto& rotatorField : rotatorDesc->Fields) { + if (rotatorField.Name == "Enabled" && rotatorJson.contains("Enabled")) rotator.Enabled = rotatorJson["Enabled"].get(); + else if (rotatorField.Name == "DegreesPerSecond" && rotatorJson.contains("DegreesPerSecond")) rotator.DegreesPerSecond = rotatorJson["DegreesPerSecond"].get(); + } + obj.Rotator = rotator; + } } else if (objField.Name == "Camera" && objJson.contains("Camera")) { const auto& camJson = objJson["Camera"]; const MetaCoreStructDescriptor* camDesc = registry.FindStruct(); @@ -224,6 +248,7 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me else if (camField.Name == "NearClip" && camJson.contains("NearClip")) cam.NearClip = camJson["NearClip"].get(); else if (camField.Name == "FarClip" && camJson.contains("FarClip")) cam.FarClip = camJson["FarClip"].get(); else if (camField.Name == "IsPrimary" && camJson.contains("IsPrimary")) cam.IsPrimary = camJson["IsPrimary"].get(); + else if (camField.Name == "Enabled" && camJson.contains("Enabled")) cam.Enabled = camJson["Enabled"].get(); } obj.Camera = cam; } diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h index 5626ec8..0b00b42 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h @@ -132,6 +132,16 @@ struct MetaCoreLightComponent { bool Enabled = true; }; +MC_STRUCT() +struct MetaCoreRotatorComponent { + MC_GENERATED_BODY() + + MC_PROPERTY() + bool Enabled = true; + MC_PROPERTY() + float DegreesPerSecond = 90.0F; +}; + MC_STRUCT() struct MetaCoreModelRootTag { MC_GENERATED_BODY() diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h index 80f9cc0..eb00bd3 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h @@ -129,6 +129,8 @@ struct MetaCoreGameObjectData { std::optional PrefabInstance; MC_PROPERTY() std::optional ModelRootTag; + MC_PROPERTY() + std::optional Rotator; }; } // namespace MetaCore diff --git a/TestProject/Assets/Models/Cube.glb.mcmeta b/TestProject/Assets/Models/Cube.glb.mcmeta index 2bda765..ba82544 100644 --- a/TestProject/Assets/Models/Cube.glb.mcmeta +++ b/TestProject/Assets/Models/Cube.glb.mcmeta @@ -10,12 +10,14 @@ "guid": "9cd50d41-df30-26ff-da7e-e01bb57f7135", "index": 0, "name": "Cube", + "stable_import_key": "", "type": "mesh" }, { "guid": "20a8eca7-ae5b-8ed9-c7a8-b64b59567283", "index": 0, "name": "Cube_Material", + "stable_import_key": "", "type": "material" } ] diff --git a/TestProject/Assets/Models/Plane.glb.mcmeta b/TestProject/Assets/Models/Plane.glb.mcmeta index eda4aa7..c70c88b 100644 --- a/TestProject/Assets/Models/Plane.glb.mcmeta +++ b/TestProject/Assets/Models/Plane.glb.mcmeta @@ -10,12 +10,14 @@ "guid": "804bca6d-b9ea-f7af-3f0a-f078d5de0d45", "index": 0, "name": "Plane", + "stable_import_key": "", "type": "mesh" }, { "guid": "6c337003-dc9a-da7c-d685-6d27c673046a", "index": 0, "name": "Plane_Material", + "stable_import_key": "", "type": "material" } ] diff --git a/tests/MetaCoreSmokeTests.cpp b/tests/MetaCoreSmokeTests.cpp index d7eac31..ef0f200 100644 --- a/tests/MetaCoreSmokeTests.cpp +++ b/tests/MetaCoreSmokeTests.cpp @@ -35,6 +35,14 @@ #include #include +#if !defined(_WIN32) +#include +#include +#include +#include +#endif + +#if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif @@ -43,9 +51,22 @@ #endif #include #include +#endif namespace { +#if !defined(_WIN32) +int _putenv_s(const char* name, const char* value) { + if (name == nullptr) { + return -1; + } + if (value == nullptr || value[0] == '\0') { + return unsetenv(name); + } + return setenv(name, value, 1); +} +#endif + void MetaCoreExpect(bool condition, const char* message) { if (!condition) { std::cerr << "MetaCoreSmokeTests failed: " << message << '\n'; @@ -53,6 +74,49 @@ void MetaCoreExpect(bool condition, const char* message) { } } +[[nodiscard]] std::filesystem::path MetaCoreTestRepositoryRoot() { + return std::filesystem::path(__FILE__).parent_path().parent_path(); +} + +#if defined(_WIN32) +using MetaCoreTestSocket = SOCKET; +using MetaCoreTestSocketLength = int; +constexpr MetaCoreTestSocket MetaCoreTestInvalidSocket = INVALID_SOCKET; +constexpr int MetaCoreTestSocketError = SOCKET_ERROR; +#else +using MetaCoreTestSocket = int; +using MetaCoreTestSocketLength = socklen_t; +constexpr MetaCoreTestSocket MetaCoreTestInvalidSocket = -1; +constexpr int MetaCoreTestSocketError = -1; +constexpr int SD_SEND = SHUT_WR; +#endif + +[[nodiscard]] bool MetaCoreTestInitializeSocketApi() { +#if defined(_WIN32) + WSADATA wsaData{}; + return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0; +#else + return true; +#endif +} + +void MetaCoreTestCleanupSocketApi() { +#if defined(_WIN32) + WSACleanup(); +#endif +} + +void MetaCoreTestCloseSocket(MetaCoreTestSocket socketHandle) { + if (socketHandle == MetaCoreTestInvalidSocket) { + return; + } +#if defined(_WIN32) + closesocket(socketHandle); +#else + close(socketHandle); +#endif +} + void MetaCoreExpectVec3Near(const glm::vec3& actual, const glm::vec3& expected, const char* message) { const auto nearlyEqual = [](float lhs, float rhs) { return std::abs(lhs - rhs) <= 0.0001F; @@ -91,6 +155,35 @@ public: void DrawPanel(MetaCore::MetaCoreEditorContext&) override {} }; +class MetaCoreCountingPlayModeSystem final : public MetaCore::MetaCoreIPlayModeComponentSystem { +public: + [[nodiscard]] std::string GetSystemId() const override { return "MetaCore.Tests.CountingPlayModeSystem"; } + + void OnPlayStart(MetaCore::MetaCoreEditorContext&) override { ++StartCount; } + void FixedUpdate(MetaCore::MetaCoreEditorContext&, double fixedDeltaSeconds) override { + ++FixedUpdateCount; + LastFixedDeltaSeconds = fixedDeltaSeconds; + } + void Update(MetaCore::MetaCoreEditorContext&, double deltaSeconds) override { + ++UpdateCount; + LastUpdateDeltaSeconds = deltaSeconds; + } + void LateUpdate(MetaCore::MetaCoreEditorContext&, double deltaSeconds) override { + ++LateUpdateCount; + LastLateDeltaSeconds = deltaSeconds; + } + void OnPlayStop(MetaCore::MetaCoreEditorContext&) override { ++StopCount; } + + int StartCount = 0; + int FixedUpdateCount = 0; + int UpdateCount = 0; + int LateUpdateCount = 0; + int StopCount = 0; + double LastFixedDeltaSeconds = 0.0; + double LastUpdateDeltaSeconds = 0.0; + double LastLateDeltaSeconds = 0.0; +}; + void MetaCoreTestSceneEditApi() { MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene(); const std::size_t originalCount = scene.GetGameObjects().size(); @@ -479,30 +572,204 @@ void MetaCoreTestComponentRegistryDescriptors() { const auto* transformDescriptor = componentRegistry->FindDescriptor("Transform"); const auto* cameraDescriptor = componentRegistry->FindDescriptor("Camera"); const auto* lightDescriptor = componentRegistry->FindDescriptor("Light"); + const auto* rotatorDescriptor = componentRegistry->FindDescriptor("Rotator"); const auto* meshRendererDescriptor = componentRegistry->FindDescriptor("MeshRenderer"); std::cout << "[CHECK] transformDescriptor: " << transformDescriptor << std::endl; std::cout << "[CHECK] cameraDescriptor: " << cameraDescriptor << std::endl; std::cout << "[CHECK] lightDescriptor: " << lightDescriptor << std::endl; + std::cout << "[CHECK] rotatorDescriptor: " << rotatorDescriptor << std::endl; std::cout << "[CHECK] meshRendererDescriptor: " << meshRendererDescriptor << std::endl; if (transformDescriptor) std::cout << "[CHECK] transform DrawInspector: " << static_cast(transformDescriptor->DrawInspector) << std::endl; if (cameraDescriptor) std::cout << "[CHECK] camera DrawInspector: " << static_cast(cameraDescriptor->DrawInspector) << std::endl; if (lightDescriptor) std::cout << "[CHECK] light DrawInspector: " << static_cast(lightDescriptor->DrawInspector) << std::endl; + if (rotatorDescriptor) std::cout << "[CHECK] rotator DrawInspector: " << static_cast(rotatorDescriptor->DrawInspector) << std::endl; if (meshRendererDescriptor) std::cout << "[CHECK] meshRenderer DrawInspector: " << static_cast(meshRendererDescriptor->DrawInspector) << std::endl; MetaCoreExpect(transformDescriptor != nullptr, "Transform descriptor 应存在"); MetaCoreExpect(cameraDescriptor != nullptr, "Camera descriptor 应存在"); MetaCoreExpect(lightDescriptor != nullptr, "Light descriptor 应存在"); + MetaCoreExpect(rotatorDescriptor != nullptr, "Rotator descriptor 应存在"); MetaCoreExpect(meshRendererDescriptor != nullptr, "MeshRenderer descriptor 应存在"); MetaCoreExpect(!transformDescriptor->DrawInspector, "Transform 应继续由独立 InspectorDrawer 处理"); MetaCoreExpect(static_cast(cameraDescriptor->DrawInspector), "Camera descriptor 应提供 drawer"); MetaCoreExpect(static_cast(lightDescriptor->DrawInspector), "Light descriptor 应提供 drawer"); + MetaCoreExpect(static_cast(rotatorDescriptor->DrawInspector), "Rotator descriptor 应提供 drawer"); MetaCoreExpect(static_cast(meshRendererDescriptor->DrawInspector), "MeshRenderer descriptor 应提供 drawer"); coreServicesModule->Shutdown(moduleRegistry); moduleRegistry.ShutdownServices(); } +void MetaCoreTestPlayModeStateMachineAndLifecycle() { + _putenv_s("METACORE_PROJECT_PATH", ""); + + MetaCore::MetaCoreWindow window; + MetaCore::MetaCoreRenderDevice renderDevice; + MetaCore::MetaCoreEditorViewportRenderer viewportRenderer; + MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene(); + MetaCore::MetaCoreLogService logService; + MetaCore::MetaCoreEditorModuleRegistry moduleRegistry; + auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule(); + coreServicesModule->Startup(moduleRegistry); + + MetaCore::MetaCoreEditorContext editorContext( + window, + renderDevice, + viewportRenderer, + scene, + logService, + moduleRegistry + ); + + const auto playMode = moduleRegistry.ResolveService(); + const auto componentSystemRegistry = + moduleRegistry.ResolveService(); + MetaCoreExpect(playMode != nullptr, "应解析到 PlayModeService"); + MetaCoreExpect(componentSystemRegistry != nullptr, "应解析到 PlayModeComponentSystemRegistry"); + + auto countingSystem = std::make_shared(); + componentSystemRegistry->RegisterSystem(countingSystem); + + MetaCoreExpect(playMode->GetState() == MetaCore::MetaCorePlayModeState::Edit, "播放模式初始应为 Edit"); + MetaCoreExpect(playMode->CanEnterPlayMode(), "Edit 状态应允许进入播放"); + MetaCoreExpect(playMode->EnterPlayMode(editorContext), "应能进入播放模式"); + MetaCoreExpect(playMode->GetState() == MetaCore::MetaCorePlayModeState::Playing, "进入后应为 Playing"); + MetaCoreExpect(countingSystem->StartCount == 1, "OnPlayStart 应只调用一次"); + + playMode->Tick(editorContext, 1.0 / 60.0); + MetaCoreExpect(countingSystem->FixedUpdateCount == 1, "播放 Tick 应执行一次 FixedUpdate"); + MetaCoreExpect(countingSystem->UpdateCount == 1, "播放 Tick 应执行一次 Update"); + MetaCoreExpect(countingSystem->LateUpdateCount == 1, "播放 Tick 应执行一次 LateUpdate"); + MetaCoreExpect(std::abs(countingSystem->LastFixedDeltaSeconds - (1.0 / 60.0)) < 0.0001, "FixedUpdate delta 应为固定步长"); + + MetaCoreExpect(playMode->Pause(), "应能暂停播放"); + MetaCoreExpect(playMode->GetState() == MetaCore::MetaCorePlayModeState::Paused, "暂停后应为 Paused"); + playMode->Tick(editorContext, 1.0 / 60.0); + MetaCoreExpect(countingSystem->UpdateCount == 1, "暂停 Tick 不应执行 Update"); + MetaCoreExpect(countingSystem->LateUpdateCount == 1, "暂停 Tick 不应执行 LateUpdate"); + + MetaCoreExpect(playMode->Step(editorContext), "暂停时应能单步"); + MetaCoreExpect(countingSystem->FixedUpdateCount == 2, "单步应执行一次 FixedUpdate"); + MetaCoreExpect(countingSystem->UpdateCount == 2, "单步应执行一次 Update"); + MetaCoreExpect(countingSystem->LateUpdateCount == 2, "单步应执行一次 LateUpdate"); + + MetaCoreExpect(playMode->Resume(), "应能继续播放"); + MetaCoreExpect(playMode->GetState() == MetaCore::MetaCorePlayModeState::Playing, "继续后应回到 Playing"); + MetaCoreExpect(playMode->ExitPlayMode(editorContext), "应能退出播放模式"); + MetaCoreExpect(playMode->GetState() == MetaCore::MetaCorePlayModeState::Edit, "退出后应回到 Edit"); + MetaCoreExpect(countingSystem->StopCount == 1, "OnPlayStop 应只调用一次"); + + coreServicesModule->Shutdown(moduleRegistry); + moduleRegistry.ShutdownServices(); +} + +void MetaCoreTestPlayModeRotatorRetainsChangesAsSingleUndoStep() { + _putenv_s("METACORE_PROJECT_PATH", ""); + + MetaCore::MetaCoreWindow window; + MetaCore::MetaCoreRenderDevice renderDevice; + MetaCore::MetaCoreEditorViewportRenderer viewportRenderer; + MetaCore::MetaCoreScene scene; + MetaCore::MetaCoreGameObject spinner = scene.CreateGameObject("Spinner"); + spinner.AddComponent().DegreesPerSecond = 60.0F; + const MetaCore::MetaCoreId spinnerId = spinner.GetId(); + + MetaCore::MetaCoreLogService logService; + MetaCore::MetaCoreEditorModuleRegistry moduleRegistry; + auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule(); + coreServicesModule->Startup(moduleRegistry); + + MetaCore::MetaCoreEditorContext editorContext( + window, + renderDevice, + viewportRenderer, + scene, + logService, + moduleRegistry + ); + + const auto playMode = moduleRegistry.ResolveService(); + const auto scenePersistence = moduleRegistry.ResolveService(); + MetaCoreExpect(playMode != nullptr, "应解析到 PlayModeService"); + MetaCoreExpect(scenePersistence != nullptr, "应解析到 ScenePersistenceService"); + MetaCoreExpect(editorContext.GetCommandService().GetUndoCount() == 0, "播放前 Undo 栈应为空"); + + MetaCoreExpect(playMode->EnterPlayMode(editorContext), "应能进入播放模式"); + for (int frameIndex = 0; frameIndex < 5; ++frameIndex) { + playMode->Tick(editorContext, 0.1); + } + MetaCore::MetaCoreGameObject updatedSpinner = scene.FindGameObject(spinnerId); + MetaCoreExpectVec3Near( + updatedSpinner.GetComponent().RotationEulerDegrees, + glm::vec3(0.0F, 30.0F, 0.0F), + "Rotator 播放 0.5 秒后应旋转 30 度" + ); + + MetaCoreExpect(playMode->Pause(), "应能暂停播放"); + playMode->Tick(editorContext, 0.5); + updatedSpinner = scene.FindGameObject(spinnerId); + MetaCoreExpectVec3Near( + updatedSpinner.GetComponent().RotationEulerDegrees, + glm::vec3(0.0F, 30.0F, 0.0F), + "暂停期间 Rotator 不应继续旋转" + ); + + MetaCoreExpect(playMode->Step(editorContext), "暂停后应能单步"); + updatedSpinner = scene.FindGameObject(spinnerId); + MetaCoreExpectVec3Near( + updatedSpinner.GetComponent().RotationEulerDegrees, + glm::vec3(0.0F, 31.0F, 0.0F), + "单步应按固定步长推进 Rotator" + ); + + const bool changedDuringPlay = editorContext.ExecuteSnapshotCommand("播放期手动改动", [&]() { + MetaCore::MetaCoreGameObject object = scene.FindGameObject(spinnerId); + if (!object) { + return false; + } + object.GetComponent().Position.x = 2.0F; + scene.IncrementRevision(); + return true; + }); + MetaCoreExpect(changedDuringPlay, "播放期间应允许 Inspector/编辑命令改变场景"); + MetaCoreExpect(editorContext.GetCommandService().GetUndoCount() == 0, "播放期间改动不应单独写入 Undo 栈"); + + MetaCoreExpect(playMode->ExitPlayMode(editorContext), "应能停止播放"); + MetaCoreExpect(playMode->GetState() == MetaCore::MetaCorePlayModeState::Edit, "停止后应回到 Edit"); + MetaCoreExpect(editorContext.GetCommandService().GetUndoCount() == 1, "停止后播放期差异应合并为一个 Undo 步"); + MetaCoreExpect(scenePersistence->IsSceneDirty(), "停止并保留播放改动后场景应标记为脏"); + + updatedSpinner = scene.FindGameObject(spinnerId); + MetaCoreExpectVec3Near( + updatedSpinner.GetComponent().RotationEulerDegrees, + glm::vec3(0.0F, 31.0F, 0.0F), + "停止后 Rotator 结果应保留" + ); + MetaCoreExpectVec3Near( + updatedSpinner.GetComponent().Position, + glm::vec3(2.0F, 0.0F, 0.0F), + "停止后播放期手动改动应保留" + ); + + MetaCoreExpect(editorContext.UndoCommand(), "Undo 应能回到播放前快照"); + updatedSpinner = scene.FindGameObject(spinnerId); + MetaCoreExpectVec3Near( + updatedSpinner.GetComponent().RotationEulerDegrees, + glm::vec3(0.0F, 0.0F, 0.0F), + "Undo 后 Rotator 旋转应回到播放前" + ); + MetaCoreExpectVec3Near( + updatedSpinner.GetComponent().Position, + glm::vec3(0.0F, 0.0F, 0.0F), + "Undo 后播放期手动改动应回到播放前" + ); + MetaCoreExpect(updatedSpinner.HasComponent(), "Undo 后 Rotator 组件应保留"); + + coreServicesModule->Shutdown(moduleRegistry); + moduleRegistry.ShutdownServices(); +} + void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() { MetaCore::MetaCoreScene scene; @@ -2503,37 +2770,36 @@ void MetaCoreTestRuntimeDataConfigValidationRejectsMissingTcpPort() { } void MetaCoreTestTcpRuntimeDataSourceAdapterReadsSocketStream() { - WSADATA wsaData{}; - MetaCoreExpect(WSAStartup(MAKEWORD(2, 2), &wsaData) == 0, "Smoke test 应能初始化 Winsock"); + MetaCoreExpect(MetaCoreTestInitializeSocketApi(), "Smoke test 应能初始化 socket API"); - SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - MetaCoreExpect(listenSocket != INVALID_SOCKET, "Smoke test 应能创建监听 socket"); + MetaCoreTestSocket listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + MetaCoreExpect(listenSocket != MetaCoreTestInvalidSocket, "Smoke test 应能创建监听 socket"); sockaddr_in address{}; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); address.sin_port = 0; - MetaCoreExpect(bind(listenSocket, reinterpret_cast(&address), sizeof(address)) != SOCKET_ERROR, "Smoke test bind 应成功"); - MetaCoreExpect(listen(listenSocket, 1) != SOCKET_ERROR, "Smoke test listen 应成功"); + MetaCoreExpect(bind(listenSocket, reinterpret_cast(&address), sizeof(address)) != MetaCoreTestSocketError, "Smoke test bind 应成功"); + MetaCoreExpect(listen(listenSocket, 1) != MetaCoreTestSocketError, "Smoke test listen 应成功"); - int addressLength = sizeof(address); + MetaCoreTestSocketLength addressLength = sizeof(address); MetaCoreExpect( - getsockname(listenSocket, reinterpret_cast(&address), &addressLength) != SOCKET_ERROR, + getsockname(listenSocket, reinterpret_cast(&address), &addressLength) != MetaCoreTestSocketError, "Smoke test getsockname 应成功" ); const unsigned short port = ntohs(address.sin_port); std::thread serverThread([listenSocket]() { - SOCKET clientSocket = accept(listenSocket, nullptr, nullptr); - if (clientSocket != INVALID_SOCKET) { + MetaCoreTestSocket clientSocket = accept(listenSocket, nullptr, nullptr); + if (clientSocket != MetaCoreTestInvalidSocket) { const char* payload = "cube.visible bool true\n" "cube.position vec3 1.0 2.0 3.0\n"; - send(clientSocket, payload, static_cast(std::strlen(payload)), 0); + send(clientSocket, payload, std::strlen(payload), 0); shutdown(clientSocket, SD_SEND); - closesocket(clientSocket); + MetaCoreTestCloseSocket(clientSocket); } - closesocket(listenSocket); + MetaCoreTestCloseSocket(listenSocket); }); MetaCore::MetaCoreTcpRuntimeDataSourceAdapter adapter; @@ -2559,14 +2825,14 @@ void MetaCoreTestTcpRuntimeDataSourceAdapterReadsSocketStream() { MetaCoreExpect(updates[1].DataPointId == "cube.position", "Tcp adapter 第二条更新应正确"); MetaCoreExpect(updates[1].Value.Type == MetaCore::MetaCoreRuntimeValueType::Vec3, "Tcp adapter 类型应正确"); serverThread.join(); - WSACleanup(); + MetaCoreTestCleanupSocketApi(); return; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } serverThread.join(); - WSACleanup(); + MetaCoreTestCleanupSocketApi(); MetaCoreExpect(false, "Tcp adapter 应在轮询内收到更新"); } @@ -2703,23 +2969,27 @@ void MetaCoreTestUiDocumentSerialization() { } void MetaCoreTestDecoupledSnapshotSync() { + const std::filesystem::path projectRoot = MetaCoreTestRepositoryRoot() / "TestProject"; + const std::filesystem::path modelRelativePath = std::filesystem::path("Assets") / "Models" / "Cube.glb"; + const std::string modelRelativePathString = modelRelativePath.generic_string(); + // 1. 创建默认场景并收集核心对象 ID MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene(); - // 动态添加一个 box1.glb,测试需要它作为网格对象进行解耦同步验证 - MetaCore::MetaCoreGameObject cubeObj = scene.CreateGameObject("box1.glb"); + // 动态添加一个模型对象,测试需要它作为网格对象进行解耦同步验证 + MetaCore::MetaCoreGameObject cubeObj = scene.CreateGameObject("Cube.glb"); auto& meshRenderer = cubeObj.AddComponent(); meshRenderer.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset; - meshRenderer.SourceModelPath = "Assets/Models/box1.glb"; - meshRenderer.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid("Assets/Models/box1.glb"); - cubeObj.AddComponent(MetaCore::MetaCoreModelRootTag{ "Assets/Models/box1.glb" }); + meshRenderer.SourceModelPath = modelRelativePathString; + meshRenderer.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid(modelRelativePathString); + cubeObj.AddComponent(MetaCore::MetaCoreModelRootTag{ modelRelativePathString }); cubeObj.GetComponent().Position = glm::vec3(0.0F, 0.5F, 0.0F); MetaCore::MetaCoreId cubeId = 0; MetaCore::MetaCoreId lightId = 0; MetaCore::MetaCoreId cameraId = 0; for (const MetaCore::MetaCoreGameObject& object : scene.GetGameObjects()) { - if (object.GetName() == "box1.glb") { + if (object.GetName() == "Cube.glb") { cubeId = object.GetId(); } if (object.GetName() == "Directional Light") { @@ -2757,7 +3027,7 @@ void MetaCoreTestDecoupledSnapshotSync() { MetaCoreExpect(winOk, "测试窗口初始化应成功"); MetaCore::MetaCoreFilamentSceneBridge bridge; - bridge.SetProjectRootPath("d:/MetaCore/SandboxProject"); + bridge.SetProjectRootPath(projectRoot); bool bridgeOk = bridge.Initialize(window); MetaCoreExpect(bridgeOk, "测试桥接器初始化应成功"); @@ -2861,25 +3131,29 @@ void MetaCoreTestDecoupledSnapshotSync() { } void MetaCoreTestNestedModelHierarchyTransformSync() { + const std::filesystem::path projectRoot = MetaCoreTestRepositoryRoot() / "TestProject"; + const std::filesystem::path modelRelativePath = std::filesystem::path("Assets") / "Models" / "Cube.glb"; + const std::string modelRelativePathString = modelRelativePath.generic_string(); + // 1. 创建测试场景,建立嵌套模型和父子变换结构 MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene(); - // 模拟根模型 (box1.glb) + // 模拟根模型 MetaCore::MetaCoreGameObject parentModel = scene.CreateGameObject("ParentModel"); auto& parentMesh = parentModel.AddComponent(); parentMesh.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset; - parentMesh.SourceModelPath = "Assets/Models/box1.glb"; - parentMesh.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid("Assets/Models/box1.glb"); - parentModel.AddComponent(MetaCore::MetaCoreModelRootTag{ "Assets/Models/box1.glb" }); + parentMesh.SourceModelPath = modelRelativePathString; + parentMesh.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid(modelRelativePathString); + parentModel.AddComponent(MetaCore::MetaCoreModelRootTag{ modelRelativePathString }); parentModel.GetComponent().Position = glm::vec3(0.0F, 1.0F, 0.0F); - // 模拟子模型 (嵌套相同的 box1.glb,其 parent 为 parentModel) + // 模拟子模型 (嵌套相同模型,其 parent 为 parentModel) MetaCore::MetaCoreGameObject childModel = scene.CreateGameObject("ChildModel", parentModel.GetId()); auto& childMesh = childModel.AddComponent(); childMesh.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset; - childMesh.SourceModelPath = "Assets/Models/box1.glb"; - childMesh.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid("Assets/Models/box1.glb"); - childModel.AddComponent(MetaCore::MetaCoreModelRootTag{ "Assets/Models/box1.glb" }); + childMesh.SourceModelPath = modelRelativePathString; + childMesh.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid(modelRelativePathString); + childModel.AddComponent(MetaCore::MetaCoreModelRootTag{ modelRelativePathString }); childModel.GetComponent().Position = glm::vec3(2.0F, 0.0F, 0.0F); // 局部偏移 x + 2.0 MetaCore::MetaCoreSceneRenderSync renderSync; @@ -2901,7 +3175,7 @@ void MetaCoreTestNestedModelHierarchyTransformSync() { MetaCoreExpect(winOk, "测试窗口初始化成功"); MetaCore::MetaCoreFilamentSceneBridge bridge; - bridge.SetProjectRootPath("d:/MetaCore/SandboxProject"); + bridge.SetProjectRootPath(projectRoot); bool bridgeOk = bridge.Initialize(window); MetaCoreExpect(bridgeOk, "测试桥接器初始化成功"); @@ -3319,6 +3593,10 @@ int main() { MetaCoreTestAssetDatabaseMovePathUpdatesProjectDescriptor(); std::cout << "[RUN] MetaCoreTestComponentRegistryDescriptors..." << std::endl; MetaCoreTestComponentRegistryDescriptors(); + std::cout << "[RUN] MetaCoreTestPlayModeStateMachineAndLifecycle..." << std::endl; + MetaCoreTestPlayModeStateMachineAndLifecycle(); + std::cout << "[RUN] MetaCoreTestPlayModeRotatorRetainsChangesAsSingleUndoStep..." << std::endl; + MetaCoreTestPlayModeRotatorRetainsChangesAsSingleUndoStep(); std::cout << "[RUN] MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot..." << std::endl; MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot(); std::cout << "[RUN] MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson..." << std::endl;