播放更新
This commit is contained in:
parent
c8b8e79dd7
commit
498d202d6e
@ -569,6 +569,7 @@ if(METACORE_BUILD_TESTS)
|
||||
|
||||
add_executable(MetaCoreSmokeTests
|
||||
tests/MetaCoreSmokeTests.cpp
|
||||
Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(MetaCoreSmokeTests
|
||||
|
||||
@ -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 (度/秒)", °reesPerSecond, 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>();
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreInput;
|
||||
struct MetaCoreGameObject;
|
||||
class MetaCoreGameObject;
|
||||
|
||||
class MetaCoreEditorCameraController {
|
||||
public:
|
||||
|
||||
@ -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_;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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_{};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreEditorContext;
|
||||
struct MetaCoreGameObject;
|
||||
class MetaCoreGameObject;
|
||||
|
||||
class MetaCoreIMenuProvider {
|
||||
public:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -129,6 +129,8 @@ struct MetaCoreGameObjectData {
|
||||
std::optional<MetaCorePrefabInstanceMetadata> PrefabInstance;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreModelRootTag> ModelRootTag;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreRotatorComponent> Rotator;
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user