播放更新

This commit is contained in:
Rowland 2026-06-09 10:49:51 +08:00
parent c8b8e79dd7
commit 498d202d6e
18 changed files with 1448 additions and 196 deletions

View File

@ -569,6 +569,7 @@ if(METACORE_BUILD_TESTS)
add_executable(MetaCoreSmokeTests
tests/MetaCoreSmokeTests.cpp
Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp
)
target_link_libraries(MetaCoreSmokeTests

View File

@ -1671,6 +1671,72 @@ void MetaCoreDrawLightComponentInspector(MetaCoreEditorContext& editorContext, M
}
}
void MetaCoreDrawRotatorComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
if (!gameObject.HasComponent<MetaCoreRotatorComponent>()) {
return;
}
const bool multiEdit = editorContext.GetSelectedObjectIds().size() > 1;
if (multiEdit) {
ImGui::TextDisabled("多选编辑 %zu 个对象", editorContext.GetSelectedObjectIds().size());
}
const auto sharedEnabled = MetaCoreGetSharedSelectedValue<bool>(
editorContext,
[](MetaCoreGameObject& selectedObject) -> std::optional<bool> {
return selectedObject.HasComponent<MetaCoreRotatorComponent>()
? std::optional<bool>(selectedObject.GetComponent<MetaCoreRotatorComponent>().Enabled)
: std::nullopt;
},
[](bool lhs, bool rhs) { return lhs == rhs; }
);
bool enabled = sharedEnabled.value_or(gameObject.GetComponent<MetaCoreRotatorComponent>().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<MetaCoreRotatorComponent>()
? &selectedObject.GetComponent<MetaCoreRotatorComponent>().Enabled
: nullptr;
},
enabled
);
}
}
const auto sharedSpeed = MetaCoreGetSharedSelectedValue<float>(
editorContext,
[](MetaCoreGameObject& selectedObject) -> std::optional<float> {
return selectedObject.HasComponent<MetaCoreRotatorComponent>()
? std::optional<float>(selectedObject.GetComponent<MetaCoreRotatorComponent>().DegreesPerSecond)
: std::nullopt;
},
[](float lhs, float rhs) { return MetaCoreNearlyEqual(lhs, rhs); }
);
float degreesPerSecond = sharedSpeed.value_or(gameObject.GetComponent<MetaCoreRotatorComponent>().DegreesPerSecond);
if (multiEdit && !sharedSpeed.has_value()) {
ImGui::TextDisabled("角速度: 混合值");
} else {
ImGui::DragFloat("角速度 Y (度/秒)", &degreesPerSecond, 1.0F, -1440.0F, 1440.0F);
MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true);
if (ImGui::IsItemEdited()) {
MetaCoreApplyValueToSelectedObjects(
editorContext,
[](MetaCoreGameObject& selectedObject) -> float* {
return selectedObject.HasComponent<MetaCoreRotatorComponent>()
? &selectedObject.GetComponent<MetaCoreRotatorComponent>().DegreesPerSecond
: nullptr;
},
degreesPerSecond
);
}
}
}
void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
return;
@ -5738,7 +5804,7 @@ public:
if (!gameObject.HasComponent<MetaCoreCameraComponent>()) {
return false;
}
gameObject.AddComponent<MetaCoreCameraComponent>(MetaCoreCameraComponent{});
gameObject.GetComponent<MetaCoreCameraComponent>() = MetaCoreCameraComponent{};
return true;
},
[](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional<std::vector<std::byte>> {
@ -5752,7 +5818,11 @@ public:
if (!MetaCoreDeserializeComponentValue(payload, component, registry)) {
return false;
}
gameObject.AddComponent<MetaCoreCameraComponent>(component);
if (gameObject.HasComponent<MetaCoreCameraComponent>()) {
gameObject.GetComponent<MetaCoreCameraComponent>() = component;
} else {
gameObject.AddComponent<MetaCoreCameraComponent>(component);
}
return true;
},
MetaCoreDrawCameraComponentInspector
@ -5779,7 +5849,7 @@ public:
if (!gameObject.HasComponent<MetaCoreLightComponent>()) {
return false;
}
gameObject.AddComponent<MetaCoreLightComponent>(MetaCoreLightComponent{});
gameObject.GetComponent<MetaCoreLightComponent>() = MetaCoreLightComponent{};
return true;
},
[](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional<std::vector<std::byte>> {
@ -5793,11 +5863,60 @@ public:
if (!MetaCoreDeserializeComponentValue(payload, component, registry)) {
return false;
}
gameObject.AddComponent<MetaCoreLightComponent>(component);
if (gameObject.HasComponent<MetaCoreLightComponent>()) {
gameObject.GetComponent<MetaCoreLightComponent>() = component;
} else {
gameObject.AddComponent<MetaCoreLightComponent>(component);
}
return true;
},
MetaCoreDrawLightComponentInspector
});
Descriptors_.push_back(MetaCoreComponentDescriptor{
"Rotator",
"旋转器",
[](const MetaCoreGameObject& gameObject) { return gameObject.HasComponent<MetaCoreRotatorComponent>(); },
[](MetaCoreGameObject& gameObject) {
if (gameObject.HasComponent<MetaCoreRotatorComponent>()) {
return false;
}
gameObject.AddComponent<MetaCoreRotatorComponent>(MetaCoreRotatorComponent{});
return true;
},
[](MetaCoreGameObject& gameObject) {
if (!gameObject.HasComponent<MetaCoreRotatorComponent>()) {
return false;
}
gameObject.RemoveComponent<MetaCoreRotatorComponent>();
return true;
},
[](MetaCoreGameObject& gameObject) {
if (!gameObject.HasComponent<MetaCoreRotatorComponent>()) {
return false;
}
gameObject.GetComponent<MetaCoreRotatorComponent>() = MetaCoreRotatorComponent{};
return true;
},
[](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional<std::vector<std::byte>> {
if (!gameObject.HasComponent<MetaCoreRotatorComponent>()) {
return std::nullopt;
}
return MetaCoreSerializeComponentValue(gameObject.GetComponent<MetaCoreRotatorComponent>(), registry);
},
[](MetaCoreGameObject& gameObject, std::span<const std::byte> payload, const MetaCoreTypeRegistry& registry) {
MetaCoreRotatorComponent component{};
if (!MetaCoreDeserializeComponentValue(payload, component, registry)) {
return false;
}
if (gameObject.HasComponent<MetaCoreRotatorComponent>()) {
gameObject.GetComponent<MetaCoreRotatorComponent>() = component;
} else {
gameObject.AddComponent<MetaCoreRotatorComponent>(component);
}
return true;
},
MetaCoreDrawRotatorComponentInspector
});
Descriptors_.push_back(MetaCoreComponentDescriptor{
"MeshRenderer",
"Mesh Renderer",
@ -5820,7 +5939,7 @@ public:
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
return false;
}
gameObject.AddComponent<MetaCoreMeshRendererComponent>(MetaCoreMeshRendererComponent{});
gameObject.GetComponent<MetaCoreMeshRendererComponent>() = MetaCoreMeshRendererComponent{};
return true;
},
[](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional<std::vector<std::byte>> {
@ -5834,7 +5953,11 @@ public:
if (!MetaCoreDeserializeComponentValue(payload, component, registry)) {
return false;
}
gameObject.AddComponent<MetaCoreMeshRendererComponent>(component);
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
gameObject.GetComponent<MetaCoreMeshRendererComponent>() = component;
} else {
gameObject.AddComponent<MetaCoreMeshRendererComponent>(component);
}
return true;
},
MetaCoreDrawMeshRendererComponentInspector
@ -5908,11 +6031,295 @@ private:
std::vector<std::byte> 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<MetaCoreRotatorComponent>() ||
!gameObject.HasComponent<MetaCoreTransformComponent>()) {
continue;
}
const MetaCoreRotatorComponent& rotator = gameObject.GetComponent<MetaCoreRotatorComponent>();
if (!rotator.Enabled || MetaCoreNearlyEqual(rotator.DegreesPerSecond, 0.0F)) {
continue;
}
gameObject.GetComponent<MetaCoreTransformComponent>().RotationEulerDegrees.y +=
rotator.DegreesPerSecond * static_cast<float>(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<MetaCoreRotatorPlayModeComponentSystem>());
}
void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) override {
(void)moduleRegistry;
Systems_.clear();
}
void RegisterSystem(std::shared_ptr<MetaCoreIPlayModeComponentSystem> 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<std::shared_ptr<MetaCoreIPlayModeComponentSystem>> GetSystems() const override {
return Systems_;
}
private:
std::vector<std::shared_ptr<MetaCoreIPlayModeComponentSystem>> 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<MetaCoreIPlayModeComponentSystemRegistry>();
}
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<std::shared_ptr<MetaCoreIPlayModeComponentSystem>> GetSystems() const {
return ComponentSystemRegistry_ != nullptr
? ComponentSystemRegistry_->GetSystems()
: std::vector<std::shared_ptr<MetaCoreIPlayModeComponentSystem>>{};
}
[[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<MetaCoreIScenePersistenceService>();
scenePersistenceService != nullptr) {
scenePersistenceService->UpdateDirtyState(editorContext.GetScene().CaptureSnapshot());
}
}
std::shared_ptr<MetaCoreIPlayModeComponentSystemRegistry> 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<MetaCoreAssetRecord> MetaCoreBuiltinPrefabService::FindPrefabAsset(const MetaCoreAssetGuid& prefabAssetGuid) const {
@ -6029,6 +6436,7 @@ std::optional<std::filesystem::path> MetaCoreBuiltinPrefabService::CreatePrefabF
data.Transform = gameObject.GetComponent<MetaCoreTransformComponent>();
if (gameObject.HasComponent<MetaCoreCameraComponent>()) data.Camera = gameObject.GetComponent<MetaCoreCameraComponent>();
if (gameObject.HasComponent<MetaCoreLightComponent>()) data.Light = gameObject.GetComponent<MetaCoreLightComponent>();
if (gameObject.HasComponent<MetaCoreRotatorComponent>()) data.Rotator = gameObject.GetComponent<MetaCoreRotatorComponent>();
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) data.MeshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
prefabDataObjects.push_back(std::move(data));
}
@ -6090,6 +6498,7 @@ std::optional<MetaCoreId> MetaCoreBuiltinPrefabService::InstantiatePrefabDocumen
clonedObject.GetComponent<MetaCoreTransformComponent>() = sourceObject.Transform;
if (sourceObject.Camera.has_value()) clonedObject.AddComponent<MetaCoreCameraComponent>(*sourceObject.Camera);
if (sourceObject.Light.has_value()) clonedObject.AddComponent<MetaCoreLightComponent>(*sourceObject.Light);
if (sourceObject.Rotator.has_value()) clonedObject.AddComponent<MetaCoreRotatorComponent>(*sourceObject.Rotator);
if (sourceObject.MeshRenderer.has_value()) clonedObject.AddComponent<MetaCoreMeshRendererComponent>(*sourceObject.MeshRenderer);
clonedObject.AddComponent<MetaCorePrefabInstanceMetadata>(MetaCorePrefabInstanceMetadata{
@ -6200,6 +6609,7 @@ bool MetaCoreBuiltinPrefabService::ApplySelectedPrefabInstance(MetaCoreEditorCon
data.Transform = gameObject.GetComponent<MetaCoreTransformComponent>();
if (gameObject.HasComponent<MetaCoreCameraComponent>()) data.Camera = gameObject.GetComponent<MetaCoreCameraComponent>();
if (gameObject.HasComponent<MetaCoreLightComponent>()) data.Light = gameObject.GetComponent<MetaCoreLightComponent>();
if (gameObject.HasComponent<MetaCoreRotatorComponent>()) data.Rotator = gameObject.GetComponent<MetaCoreRotatorComponent>();
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) data.MeshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
prefabDataObjects.push_back(std::move(data));
@ -6964,6 +7374,7 @@ public:
moduleRegistry.RegisterService<MetaCoreISceneEditingService>(std::make_shared<MetaCoreBuiltinSceneEditingService>());
moduleRegistry.RegisterService<MetaCoreIPrefabService>(std::make_shared<MetaCoreBuiltinPrefabService>());
moduleRegistry.RegisterService<MetaCoreIComponentTypeRegistry>(std::make_shared<MetaCoreBuiltinComponentTypeRegistry>());
moduleRegistry.RegisterService<MetaCoreIPlayModeComponentSystemRegistry>(std::make_shared<MetaCoreBuiltinPlayModeComponentSystemRegistry>());
moduleRegistry.RegisterService<MetaCoreIPlayModeService>(std::make_shared<MetaCoreBuiltinPlayModeService>());
if (const auto assetDatabase = moduleRegistry.ResolveService<MetaCoreIAssetDatabaseService>();

View File

@ -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<MetaCoreIScenePersistenceService>();
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
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<MetaCoreIAssetDatabaseService>();
const auto packageService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIPackageService>();
const auto reflectionRegistry = editorContext.GetModuleRegistry().ResolveService<MetaCoreIReflectionRegistry>();
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<MetaCoreMeshRendererComponent>()) {
@ -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() : "<Missing Prefab Asset>",
"资源: %s | 根对象: %llu | 源对象: %llu",
prefabAsset.has_value() ? prefabAsset->RelativePath.generic_string().c_str() : "<缺失 Prefab 资源>",
static_cast<unsigned long long>(selectedObject.GetComponent<MetaCorePrefabInstanceMetadata>().PrefabInstanceRootId),
static_cast<unsigned long long>(selectedObject.GetComponent<MetaCorePrefabInstanceMetadata>().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:

View File

@ -23,6 +23,8 @@
#endif
#define GLM_ENABLE_EXPERIMENTAL
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <cstdio>
#include <filesystem>
@ -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<MetaCoreIPlayModeService>();
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<int>(std::count_if(entries.begin(), entries.end(), [](const MetaCoreLogEntry& entry) {
return entry.Level == MetaCoreLogLevel::Warning;
}));
const auto errorCount = static_cast<int>(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<MetaCoreIAssetDatabaseService>();
const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIScenePersistenceService>();
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<MetaCoreIPlayModeService>();
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<MetaCoreIAssetHotReloadService>();
hotReloadService != nullptr) {
hotReloadService->Tick();
}
const auto currentPlayModeTickTime = std::chrono::steady_clock::now();
const double playModeDeltaSeconds =
std::chrono::duration<double>(currentPlayModeTickTime - previousPlayModeTickTime).count();
previousPlayModeTickTime = currentPlayModeTickTime;
if (const auto playModeService = ModuleRegistry_.ResolveService<MetaCoreIPlayModeService>();
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);

View File

@ -8,7 +8,7 @@
namespace MetaCore {
class MetaCoreInput;
struct MetaCoreGameObject;
class MetaCoreGameObject;
class MetaCoreEditorCameraController {
public:

View File

@ -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<MetaCoreIEditorCommand> 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<MetaCoreIEditorComman
}
bool MetaCoreEditorContext::UndoCommand() {
if (IsCommandRecordingSuppressed()) {
return false;
}
if (!CommandService_.Undo(*this)) {
return false;
}
@ -371,6 +400,10 @@ bool MetaCoreEditorContext::UndoCommand() {
}
bool MetaCoreEditorContext::RedoCommand() {
if (IsCommandRecordingSuppressed()) {
return false;
}
if (!CommandService_.Redo(*this)) {
return false;
}
@ -417,6 +450,12 @@ bool MetaCoreEditorContext::CommitStateTransition(
return false;
}
if (IsCommandRecordingSuppressed()) {
NormalizeSelection();
RefreshSceneDirtyState();
return true;
}
if (!CommandService_.PushExecuted(std::make_unique<MetaCoreSnapshotEditorCommand>(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_;
}

View File

@ -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

View File

@ -13,6 +13,8 @@
#include <memory>
#include <vector>
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_{};

View File

@ -10,6 +10,7 @@
#include "MetaCoreScene/MetaCoreScene.h"
#include <functional>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <string>
@ -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;

View File

@ -11,7 +11,7 @@
namespace MetaCore {
class MetaCoreEditorContext;
struct MetaCoreGameObject;
class MetaCoreGameObject;
class MetaCoreIMenuProvider {
public:

View File

@ -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<MetaCoreIPlayModeComponentSystem> system) = 0;
virtual void ClearSystems() = 0;
[[nodiscard]] virtual std::vector<std::shared_ptr<MetaCoreIPlayModeComponentSystem>> 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

View File

@ -66,6 +66,9 @@ std::vector<MetaCoreGameObject> 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<MetaCoreGameObject> MetaCoreScene::GetGameObjects() const {
for (auto [id, entity] : IdToEntity_) {
objects.emplace_back(entity, const_cast<MetaCoreScene*>(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<MetaCoreId> MetaCoreScene::DuplicateGameObjects(const std::vector<Me
if (sourceObject.HasComponent<MetaCoreLightComponent>())
clonedObject.AddComponent<MetaCoreLightComponent>(sourceObject.GetComponent<MetaCoreLightComponent>());
if (sourceObject.HasComponent<MetaCoreRotatorComponent>())
clonedObject.AddComponent<MetaCoreRotatorComponent>(sourceObject.GetComponent<MetaCoreRotatorComponent>());
if (sourceObject.HasComponent<MetaCorePrefabInstanceMetadata>())
clonedObject.AddComponent<MetaCorePrefabInstanceMetadata>(sourceObject.GetComponent<MetaCorePrefabInstanceMetadata>());
@ -410,6 +419,8 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
data.MeshRenderer = obj.GetComponent<MetaCoreMeshRendererComponent>();
if (obj.HasComponent<MetaCoreLightComponent>())
data.Light = obj.GetComponent<MetaCoreLightComponent>();
if (obj.HasComponent<MetaCoreRotatorComponent>())
data.Rotator = obj.GetComponent<MetaCoreRotatorComponent>();
if (obj.HasComponent<MetaCorePrefabInstanceMetadata>())
data.PrefabInstance = obj.GetComponent<MetaCorePrefabInstanceMetadata>();
if (obj.HasComponent<MetaCoreModelRootTag>())
@ -436,6 +447,8 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
obj.AddComponent<MetaCoreMeshRendererComponent>(data.MeshRenderer.value());
if (data.Light.has_value())
obj.AddComponent<MetaCoreLightComponent>(data.Light.value());
if (data.Rotator.has_value())
obj.AddComponent<MetaCoreRotatorComponent>(data.Rotator.value());
if (data.PrefabInstance.has_value())
obj.AddComponent<MetaCorePrefabInstanceMetadata>(data.PrefabInstance.value());
if (data.ModelRootTag.has_value())

View File

@ -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<MetaCoreRotatorComponent>();
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<MetaCoreCameraComponent>();
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<float>();
else if (lightField.Name == "Enabled" && lightJson.contains("Enabled")) light.Enabled = lightJson["Enabled"].get<bool>();
}
obj.Light = light;
}
} else if (objField.Name == "Rotator" && objJson.contains("Rotator")) {
const auto& rotatorJson = objJson["Rotator"];
const MetaCoreStructDescriptor* rotatorDesc = registry.FindStruct<MetaCoreRotatorComponent>();
if (rotatorDesc) {
MetaCoreRotatorComponent rotator;
for (const auto& rotatorField : rotatorDesc->Fields) {
if (rotatorField.Name == "Enabled" && rotatorJson.contains("Enabled")) rotator.Enabled = rotatorJson["Enabled"].get<bool>();
else if (rotatorField.Name == "DegreesPerSecond" && rotatorJson.contains("DegreesPerSecond")) rotator.DegreesPerSecond = rotatorJson["DegreesPerSecond"].get<float>();
}
obj.Rotator = rotator;
}
} else if (objField.Name == "Camera" && objJson.contains("Camera")) {
const auto& camJson = objJson["Camera"];
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
@ -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<float>();
else if (camField.Name == "FarClip" && camJson.contains("FarClip")) cam.FarClip = camJson["FarClip"].get<float>();
else if (camField.Name == "IsPrimary" && camJson.contains("IsPrimary")) cam.IsPrimary = camJson["IsPrimary"].get<bool>();
else if (camField.Name == "Enabled" && camJson.contains("Enabled")) cam.Enabled = camJson["Enabled"].get<bool>();
}
obj.Camera = cam;
}

View File

@ -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()

View File

@ -129,6 +129,8 @@ struct MetaCoreGameObjectData {
std::optional<MetaCorePrefabInstanceMetadata> PrefabInstance;
MC_PROPERTY()
std::optional<MetaCoreModelRootTag> ModelRootTag;
MC_PROPERTY()
std::optional<MetaCoreRotatorComponent> Rotator;
};
} // namespace MetaCore

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

@ -35,6 +35,14 @@
#include <thread>
#include <unordered_map>
#if !defined(_WIN32)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
@ -43,9 +51,22 @@
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#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<bool>(transformDescriptor->DrawInspector) << std::endl;
if (cameraDescriptor) std::cout << "[CHECK] camera DrawInspector: " << static_cast<bool>(cameraDescriptor->DrawInspector) << std::endl;
if (lightDescriptor) std::cout << "[CHECK] light DrawInspector: " << static_cast<bool>(lightDescriptor->DrawInspector) << std::endl;
if (rotatorDescriptor) std::cout << "[CHECK] rotator DrawInspector: " << static_cast<bool>(rotatorDescriptor->DrawInspector) << std::endl;
if (meshRendererDescriptor) std::cout << "[CHECK] meshRenderer DrawInspector: " << static_cast<bool>(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<bool>(cameraDescriptor->DrawInspector), "Camera descriptor 应提供 drawer");
MetaCoreExpect(static_cast<bool>(lightDescriptor->DrawInspector), "Light descriptor 应提供 drawer");
MetaCoreExpect(static_cast<bool>(rotatorDescriptor->DrawInspector), "Rotator descriptor 应提供 drawer");
MetaCoreExpect(static_cast<bool>(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<MetaCore::MetaCoreIPlayModeService>();
const auto componentSystemRegistry =
moduleRegistry.ResolveService<MetaCore::MetaCoreIPlayModeComponentSystemRegistry>();
MetaCoreExpect(playMode != nullptr, "应解析到 PlayModeService");
MetaCoreExpect(componentSystemRegistry != nullptr, "应解析到 PlayModeComponentSystemRegistry");
auto countingSystem = std::make_shared<MetaCoreCountingPlayModeSystem>();
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<MetaCore::MetaCoreRotatorComponent>().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<MetaCore::MetaCoreIPlayModeService>();
const auto scenePersistence = moduleRegistry.ResolveService<MetaCore::MetaCoreIScenePersistenceService>();
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<MetaCore::MetaCoreTransformComponent>().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<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees,
glm::vec3(0.0F, 30.0F, 0.0F),
"暂停期间 Rotator 不应继续旋转"
);
MetaCoreExpect(playMode->Step(editorContext), "暂停后应能单步");
updatedSpinner = scene.FindGameObject(spinnerId);
MetaCoreExpectVec3Near(
updatedSpinner.GetComponent<MetaCore::MetaCoreTransformComponent>().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<MetaCore::MetaCoreTransformComponent>().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<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees,
glm::vec3(0.0F, 31.0F, 0.0F),
"停止后 Rotator 结果应保留"
);
MetaCoreExpectVec3Near(
updatedSpinner.GetComponent<MetaCore::MetaCoreTransformComponent>().Position,
glm::vec3(2.0F, 0.0F, 0.0F),
"停止后播放期手动改动应保留"
);
MetaCoreExpect(editorContext.UndoCommand(), "Undo 应能回到播放前快照");
updatedSpinner = scene.FindGameObject(spinnerId);
MetaCoreExpectVec3Near(
updatedSpinner.GetComponent<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees,
glm::vec3(0.0F, 0.0F, 0.0F),
"Undo 后 Rotator 旋转应回到播放前"
);
MetaCoreExpectVec3Near(
updatedSpinner.GetComponent<MetaCore::MetaCoreTransformComponent>().Position,
glm::vec3(0.0F, 0.0F, 0.0F),
"Undo 后播放期手动改动应回到播放前"
);
MetaCoreExpect(updatedSpinner.HasComponent<MetaCore::MetaCoreRotatorComponent>(), "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<sockaddr*>(&address), sizeof(address)) != SOCKET_ERROR, "Smoke test bind 应成功");
MetaCoreExpect(listen(listenSocket, 1) != SOCKET_ERROR, "Smoke test listen 应成功");
MetaCoreExpect(bind(listenSocket, reinterpret_cast<sockaddr*>(&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<sockaddr*>(&address), &addressLength) != SOCKET_ERROR,
getsockname(listenSocket, reinterpret_cast<sockaddr*>(&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<int>(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<MetaCore::MetaCoreMeshRendererComponent>();
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>(MetaCore::MetaCoreModelRootTag{ "Assets/Models/box1.glb" });
meshRenderer.SourceModelPath = modelRelativePathString;
meshRenderer.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid(modelRelativePathString);
cubeObj.AddComponent<MetaCore::MetaCoreModelRootTag>(MetaCore::MetaCoreModelRootTag{ modelRelativePathString });
cubeObj.GetComponent<MetaCore::MetaCoreTransformComponent>().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<MetaCore::MetaCoreMeshRendererComponent>();
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>(MetaCore::MetaCoreModelRootTag{ "Assets/Models/box1.glb" });
parentMesh.SourceModelPath = modelRelativePathString;
parentMesh.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid(modelRelativePathString);
parentModel.AddComponent<MetaCore::MetaCoreModelRootTag>(MetaCore::MetaCoreModelRootTag{ modelRelativePathString });
parentModel.GetComponent<MetaCore::MetaCoreTransformComponent>().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<MetaCore::MetaCoreMeshRendererComponent>();
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>(MetaCore::MetaCoreModelRootTag{ "Assets/Models/box1.glb" });
childMesh.SourceModelPath = modelRelativePathString;
childMesh.SourceModelAssetGuid = MetaCore::MetaCoreAssetRegistry::Get().ResolvePathToGuid(modelRelativePathString);
childModel.AddComponent<MetaCore::MetaCoreModelRootTag>(MetaCore::MetaCoreModelRootTag{ modelRelativePathString });
childModel.GetComponent<MetaCore::MetaCoreTransformComponent>().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;