From 5ca5ac2a1eb612715cd5703bdc4c18ed3e6a4a2b Mon Sep 17 00:00:00 2001 From: atdunbg Date: Mon, 5 Oct 2026 01:20:00 +0800 Subject: [PATCH] Editor: group Details parameters and reorganize Stats panel Details: multi-parameter components (RigidBody/Joint/CharacterController/ Animation) now use SeparatorText category headers in UE Details style; all parameter labels normalized to Pascal Case. Stats: rearranged into View / Scene / Rendering / Physics / Performance collapsing groups. Clear Color moved to Scene, Debug View to Rendering, GPU Timings + Frame merged into Performance; bare top-level widgets and the now-obsolete Environment group removed. --- App/editor/InspectorPanel.cpp | 150 ++++++++++++++++++++-------------- App/editor/StatsPanel.cpp | 114 ++++++++++++-------------- 2 files changed, 141 insertions(+), 123 deletions(-) diff --git a/App/editor/InspectorPanel.cpp b/App/editor/InspectorPanel.cpp index f9bb54e..16b0f1a 100644 --- a/App/editor/InspectorPanel.cpp +++ b/App/editor/InspectorPanel.cpp @@ -263,7 +263,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) } } glm::vec3 pos = t->GetPosition(); - if (DragVec3Row("position", pos, 0.05f)) + if (DragVec3Row("Position", pos, 0.05f)) { t->SetPosition(pos); } @@ -315,7 +315,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) glm::vec3 rot = s_RotMirror; // "%.1f" = UE Details 默认显示精度。mirror 值直显(无 wrap、 // 无限累计);Ctrl+Click 手输全精度写入 - if (DragVec3Row("rotation", rot, 0.5f, -3.4e38f, 3.4e38f, + if (DragVec3Row("Rotation", rot, 0.5f, -3.4e38f, 3.4e38f, "%.1f", &editingRot)) { t->SetRotation(rot); @@ -323,7 +323,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) } s_RotEditingLast = editingRot; glm::vec3 scl = t->GetScale(); - if (DragVec3Row("scale", scl, 0.02f, 0.001f, 100.0f)) + if (DragVec3Row("Scale", scl, 0.02f, 0.001f, 100.0f)) { t->SetScale(scl); } @@ -426,20 +426,20 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ImGui::BeginDisabled(true); } int type = static_cast(light->type); - if (ImGui::Combo("type", &type, "Directional\0Point\0")) + if (ImGui::Combo("Type", &type, "Directional\0Point\0")) { light->type = static_cast(type); } - ImGui::ColorEdit3("color", &light->color.x); - ImGui::DragFloat("intensity", &light->color.a, 0.05f, 0.0f, 100.0f); + ImGui::ColorEdit3("Color", &light->color.x); + ImGui::DragFloat("Intensity", &light->color.a, 0.05f, 0.0f, 100.0f); if (light->type == Engine::ECS::LightComponent::Type::Directional) { - ImGui::SliderFloat3("direction", &light->direction.x, -1.0f, 1.0f); - ImGui::Checkbox("castShadow", &light->castShadow); + ImGui::SliderFloat3("Direction", &light->direction.x, -1.0f, 1.0f); + ImGui::Checkbox("Cast Shadows", &light->castShadow); } else { - ImGui::DragFloat("radius", &light->radius, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Radius", &light->radius, 0.1f, 0.0f, 100.0f); ImGui::TextDisabled("(position = entity Transform)"); } if (!light->active) @@ -513,7 +513,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) { ImGui::BeginDisabled(true); } - ImGui::ColorEdit4("ambient", &sl->ambientColor.x); + ImGui::ColorEdit4("Ambient", &sl->ambientColor.x); if (!sl->active) { ImGui::EndDisabled(); @@ -535,7 +535,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) { ImGui::BeginDisabled(true); } - if (ImGui::Checkbox("primary", &cam->primary) && cam->primary) + if (ImGui::Checkbox("Primary", &cam->primary) && cam->primary) { // primary 互斥:启用本相机时清掉其余实体的 primary //(引擎取第一个 primary 作为游戏形态渲染视角) @@ -590,42 +590,46 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) "! conflicts with Character Controller " "(disable or remove one)"); } + // ---- Body:类型与碰撞体选择(变更 → PhysicsWorld 重建 body)---- + ImGui::SeparatorText("Body"); int type = static_cast(rb->type); - if (ImGui::Combo("type", &type, "Static\0Dynamic\0Kinematic\0")) + if (ImGui::Combo("Type", &type, "Static\0Dynamic\0Kinematic\0")) { rb->type = static_cast(type); } int collider = static_cast(rb->collider); - if (ImGui::Combo("collider", &collider, + if (ImGui::Combo("Collider", &collider, "Box\0Sphere\0Mesh\0Capsule\0Cylinder\0Compound\0")) { rb->collider = static_cast(collider); } + // ---- Shape:碰撞体尺寸(随类型切换;× Transform 缩放烘焙)---- + ImGui::SeparatorText("Shape"); if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Box) { - ImGui::DragFloat3("halfExtents", &rb->boxHalfExtents.x, 0.05f, 0.01f, 500.0f); + ImGui::DragFloat3("Half Extents", &rb->boxHalfExtents.x, 0.05f, 0.01f, 500.0f); } else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Sphere) { - ImGui::DragFloat("radius", &rb->sphereRadius, 0.05f, 0.01f, 500.0f); + ImGui::DragFloat("Radius", &rb->sphereRadius, 0.05f, 0.01f, 500.0f); } else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Capsule) { // 半高 = 圆柱段半高(不含半球端盖),总高 = 2*(halfHeight + radius) - ImGui::DragFloat("radius", &rb->capsuleRadius, 0.05f, 0.01f, 500.0f); - ImGui::DragFloat("halfHeight", &rb->capsuleHalfHeight, 0.05f, 0.0f, 500.0f); + ImGui::DragFloat("Radius", &rb->capsuleRadius, 0.05f, 0.01f, 500.0f); + ImGui::DragFloat("Half Height", &rb->capsuleHalfHeight, 0.05f, 0.0f, 500.0f); } else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Cylinder) { - ImGui::DragFloat("radius", &rb->cylinderRadius, 0.05f, 0.01f, 500.0f); - ImGui::DragFloat("halfHeight", &rb->cylinderHalfHeight, 0.05f, 0.01f, 500.0f); + ImGui::DragFloat("Radius", &rb->cylinderRadius, 0.05f, 0.01f, 500.0f); + ImGui::DragFloat("Half Height", &rb->cylinderHalfHeight, 0.05f, 0.01f, 500.0f); } else if (rb->collider == Engine::ECS::RigidBodyComponent::ColliderType::Compound) { // 子形状列表:尺寸/位置变更 → PhysicsWorld 重建 body(既有模式) - ImGui::Text("children: %d", static_cast(rb->compound.size())); + ImGui::Text("Children: %d", static_cast(rb->compound.size())); ImGui::SameLine(); - if (ImGui::Button("+ add")) + if (ImGui::Button("+ Add")) { rb->compound.emplace_back(); } @@ -636,24 +640,24 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ImGui::PushID(i); ImGui::Separator(); int sh = static_cast(c.shape); - if (ImGui::Combo("shape", &sh, "Box\0Sphere\0Capsule\0")) + if (ImGui::Combo("Shape", &sh, "Box\0Sphere\0Capsule\0")) { c.shape = static_cast(sh); } if (c.shape == Engine::ECS::RigidBodyComponent::CompoundChild::Shape::Box) { - ImGui::DragFloat3("halfExtents", &c.boxHalfExtents.x, 0.05f, 0.01f, 500.0f); + ImGui::DragFloat3("Half Extents", &c.boxHalfExtents.x, 0.05f, 0.01f, 500.0f); } else { - ImGui::DragFloat("radius", &c.radius, 0.05f, 0.01f, 500.0f); + ImGui::DragFloat("Radius", &c.radius, 0.05f, 0.01f, 500.0f); if (c.shape == Engine::ECS::RigidBodyComponent::CompoundChild::Shape::Capsule) { - ImGui::DragFloat("halfHeight", &c.capsuleHalfHeight, 0.05f, 0.0f, 500.0f); + ImGui::DragFloat("Half Height", &c.capsuleHalfHeight, 0.05f, 0.0f, 500.0f); } } - ImGui::DragFloat3("offset", &c.position.x, 0.05f); - if (ImGui::Button("remove")) + ImGui::DragFloat3("Offset", &c.position.x, 0.05f); + if (ImGui::Button("Remove")) { remove = i; } @@ -670,20 +674,22 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) // Dynamic=凸包近似),无尺寸可调 ImGui::TextDisabled("follows MeshRenderer geometry"); } - ImGui::DragFloat("mass", &rb->mass, 0.1f, 0.1f, 1000.0f, "%.1f"); - ImGui::SliderFloat("gravityScale", &rb->gravityScale, -1.0f, 5.0f); - ImGui::SliderFloat("friction", &rb->friction, 0.0f, 2.0f); - ImGui::SliderFloat("restitution", &rb->restitution, 0.0f, 1.0f); - ImGui::Checkbox("isTrigger", &rb->isTrigger); + // ---- Simulation:动态体物理参数 ---- + ImGui::SeparatorText("Simulation"); + ImGui::DragFloat("Mass", &rb->mass, 0.1f, 0.1f, 1000.0f, "%.1f"); + ImGui::SliderFloat("Gravity Scale", &rb->gravityScale, -1.0f, 5.0f); + ImGui::SliderFloat("Friction", &rb->friction, 0.0f, 2.0f); + ImGui::SliderFloat("Restitution", &rb->restitution, 0.0f, 1.0f); + ImGui::Checkbox("Is Trigger", &rb->isTrigger); + // ---- Debug:遥测与驱动调试(冲量瞬态不序列化)---- if (rb->bodyValid && rb->type == Engine::ECS::RigidBodyComponent::BodyType::Dynamic) { + ImGui::SeparatorText("Debug"); ImGui::Text("vel: %.2f, %.2f, %.2f m/s", rb->velocity.x, rb->velocity.y, rb->velocity.z); - - // 驱动调试(冲量瞬态不序列化):直接推一把看响应 static glm::vec3 s_debugImpulse{0.0f, 5.0f, 0.0f}; - ImGui::DragFloat3("impulse", &s_debugImpulse.x, 0.1f); + ImGui::DragFloat3("Impulse", &s_debugImpulse.x, 0.1f); if (ImGui::Button("Apply Impulse")) { ctx.app->GetPhysics().AddImpulse( @@ -718,8 +724,10 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), "! requires a RigidBody on this entity"); } + // ---- Constraint:类型与目标体 ---- + ImGui::SeparatorText("Constraint"); int type = static_cast(jc->type); - if (ImGui::Combo("type", &type, "Fixed\0Hinge\0Ball\0")) + if (ImGui::Combo("Type", &type, "Fixed\0Hinge\0Ball\0")) { jc->type = static_cast(type); } @@ -745,21 +753,25 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) items.push_back('\0'); }); items.push_back('\0'); // ImGui Combo 双 \0 结尾 - if (ImGui::Combo("target", ¤t, items.c_str()) && current >= 0) + if (ImGui::Combo("Target", ¤t, items.c_str()) && current >= 0) { jc->target = uuids[static_cast(current)]; } - ImGui::DragFloat3("anchor A", &jc->anchorA.x, 0.05f); - ImGui::DragFloat3("anchor B", &jc->anchorB.x, 0.05f); + // ---- Anchors:各体局部系锚点 ---- + ImGui::SeparatorText("Anchors"); + ImGui::DragFloat3("Anchor A", &jc->anchorA.x, 0.05f); + ImGui::DragFloat3("Anchor B", &jc->anchorB.x, 0.05f); + // ---- Hinge:转轴与角度限位(度)---- if (jc->type == Engine::ECS::JointComponent::Type::Hinge) { - ImGui::DragFloat3("hinge axis", &jc->hingeAxis.x, 0.05f); - ImGui::Checkbox("enable limit", &jc->enableLimit); + ImGui::SeparatorText("Hinge"); + ImGui::DragFloat3("Hinge Axis", &jc->hingeAxis.x, 0.05f); + ImGui::Checkbox("Enable Limit", &jc->enableLimit); if (jc->enableLimit) { - ImGui::DragFloat("min deg", &jc->limitMin, 1.0f, -180.0f, 0.0f); - ImGui::DragFloat("max deg", &jc->limitMax, 1.0f, 0.0f, 180.0f); + ImGui::DragFloat("Limit Min", &jc->limitMin, 1.0f, -180.0f, 0.0f); + ImGui::DragFloat("Limit Max", &jc->limitMax, 1.0f, 0.0f, 180.0f); } } if (!jc->active) @@ -790,16 +802,20 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) "! conflicts with RigidBody " "(disable or remove one)"); } - // 实体原点 = 脚底(胶囊底端),总高 = 2*(halfHeight + radius) - ImGui::DragFloat("radius", &cc->radius, 0.02f, 0.05f, 5.0f); - ImGui::DragFloat("halfHeight", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f); - ImGui::DragFloat("mass", &cc->mass, 1.0f, 1.0f, 500.0f, "%.0f"); - ImGui::DragFloat("max slope deg", &cc->maxSlopeAngle, 1.0f, 1.0f, 89.0f); - ImGui::DragFloat("step height", &cc->stepHeight, 0.02f, 0.0f, 2.0f); + // ---- Body:胶囊体(实体原点 = 脚底,总高 = 2*(halfHeight + radius))---- + ImGui::SeparatorText("Body"); + ImGui::DragFloat("Radius", &cc->radius, 0.02f, 0.05f, 5.0f); + ImGui::DragFloat("Half Height", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f); + ImGui::DragFloat("Mass", &cc->mass, 1.0f, 1.0f, 500.0f, "%.0f"); + // ---- Movement:扫掠运动学参数 ---- + ImGui::SeparatorText("Movement"); + ImGui::DragFloat("Max Slope (deg)", &cc->maxSlopeAngle, 1.0f, 1.0f, 89.0f); + ImGui::DragFloat("Step Height", &cc->stepHeight, 0.02f, 0.0f, 2.0f); - // 遥测(只读):运行时状态在 PhysicsWorld 的 CharacterController + // ---- Debug:遥测(只读,运行时状态在 PhysicsWorld)---- if (auto& phy = ctx.app->GetPhysics(); phy.IsEnabled()) { + ImGui::SeparatorText("Debug"); const uint32_t bits = static_cast(entt::to_integral(selected)); ImGui::Text("grounded: %s", @@ -980,10 +996,16 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), "! requires a MeshRenderer component"); } - else if (model != nullptr) + else if (model == nullptr) { - // 动画资产槽:列全部模型容器文件(无法静态区分"模型 - // fbx"与"纯动画 fbx",一并列出,无 clip 的会警告跳过) + ImGui::TextDisabled("(no model assigned)"); + } + else + { + // ---- Clips:动画资产槽(列全部模型容器文件——无法静态 + // 区分"模型 fbx"与"纯动画 fbx",一并列出,无 clip 的会 + // 警告跳过)---- + ImGui::SeparatorText("Clips"); const auto& files = AssetModelFiles(); std::vector opts; opts.push_back(""); @@ -1016,9 +1038,15 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) .string() .c_str()); } + if (!model->HasAnimations()) + { + ImGui::TextDisabled("(no animation clips on model)"); + } } if (model != nullptr && model->HasAnimations()) { + // ---- Playback:播放控制(切换 clip 触发 crossfade)---- + ImGui::SeparatorText("Playback"); const auto& clips = model->GetClips(); std::vector clipNames; clipNames.reserve(clips.size()); @@ -1027,16 +1055,16 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) clipNames.push_back(c.name.c_str()); } int cur = anim->clipIndex; - if (ImGui::Combo("clip", &cur, clipNames.data(), + if (ImGui::Combo("Clip", &cur, clipNames.data(), static_cast(clipNames.size()))) { anim->Play(cur); // 触发 crossfade(fade 秒) } - ImGui::Checkbox("playing", &anim->playing); + ImGui::Checkbox("Playing", &anim->playing); ImGui::SameLine(); - ImGui::Checkbox("loop", &anim->loop); - ImGui::SliderFloat("speed", &anim->speed, -2.0f, 2.0f); - ImGui::SliderFloat("fade", &anim->fadeDuration, 0.0f, 1.0f); + ImGui::Checkbox("Loop", &anim->loop); + ImGui::SliderFloat("Speed", &anim->speed, -2.0f, 2.0f); + ImGui::SliderFloat("Fade", &anim->fadeDuration, 0.0f, 1.0f); if (anim->clipIndex >= 0 && anim->clipIndex < static_cast(clips.size())) { @@ -1046,7 +1074,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) // 动画数据表:每个 clip 的时长与有效轨道数(attach 进来的 // 外部动画在此核对骨骼匹配结果——轨道数明显偏少 = 骨骼 // 命名不同源被跳过) - if (ImGui::TreeNode("clip data")) + if (ImGui::TreeNode("Clip Data")) { ImGui::Columns(3, nullptr, false); ImGui::SetColumnWidth(0, 150.0f); @@ -1083,10 +1111,6 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ImGui::TreePop(); } } - else - { - ImGui::TextDisabled("(no animation clips on model)"); - } if (!anim->active) { ImGui::EndDisabled(); diff --git a/App/editor/StatsPanel.cpp b/App/editor/StatsPanel.cpp index cb2fe35..eb365d8 100644 --- a/App/editor/StatsPanel.cpp +++ b/App/editor/StatsPanel.cpp @@ -1,5 +1,8 @@ // // StatsPanel.cpp:遥测与全局设置实现(原 main.cpp Scene Debug 搬迁)。 +// 面板按 View / Scene / Rendering / Physics / Performance 分组—— +// 环境贴图/环境光已实体化(Skybox/SkyLight 组件,Details 配置), +// 此处只留场景级 ClearColor。 // #include "StatsPanel.h" @@ -19,12 +22,23 @@ void StatsPanel::Draw(const EditorContext& ctx) auto& renderer = *ctx.renderer; auto& app = *ctx.app; - // ---- Camera:视图相机位姿(只读遥测;跟随渲染源分派)---- + // ---- View:看哪里(渲染目标)+ 相机位姿(跟随渲染源分派)---- + if (ImGui::CollapsingHeader("View", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ctx.renderer->GetDisplayMode() == kDisplayViewport && ctx.editorCamera) + static const char* displayTarget[] = { + "Fullscreen", + "Viewport", + }; + int mode = renderer.GetDisplayMode(); + if (ImGui::Combo("Display Target", &mode, displayTarget, + IM_ARRAYSIZE(displayTarget))) + { + renderer.SetDisplayMode(mode); + } + if (renderer.GetDisplayMode() == kDisplayViewport && ctx.editorCamera) { const glm::vec3& pos = ctx.editorCamera->GetTransform().GetPosition(); - ImGui::Text("Editor camera pos"); + ImGui::Text("Editor Camera Pos"); ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z); } else @@ -34,7 +48,7 @@ void StatsPanel::Draw(const EditorContext& ctx) { // world 位姿(相机实体可能挂父层级下) const glm::vec3& pos = camT->GetWorldPosition(); - ImGui::Text("Game camera pos"); + ImGui::Text("Game Camera Pos"); ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z); } else @@ -42,35 +56,26 @@ void StatsPanel::Draw(const EditorContext& ctx) ImGui::TextDisabled("(no primary camera)"); } } - ImGui::Separator(); } - ImGui::Separator(); - static const char* displayTarget[] = { - "Fullscreen", - "Viewport", - }; - int mode = renderer.GetDisplayMode(); - if (ImGui::Combo("##DisplayTarget", &mode, displayTarget, IM_ARRAYSIZE(displayTarget))) { - renderer.SetDisplayMode(mode); - } - - ImGui::Separator(); - - // ---- Scene:暂停模拟(引擎 Time 门面:动画/物理统一冻结)---- + // ---- Scene:模拟控制 + 场景级设置 ---- + if (ImGui::CollapsingHeader("Scene", ImGuiTreeNodeFlags_DefaultOpen)) { + // 暂停模拟(引擎 Time 门面:动画/物理统一冻结) bool paused = Time::TimeScale() == 0.0f; if (ImGui::Checkbox("Pause Simulation", &paused)) { Time::SetTimeScale(paused ? 0.0f : 1.0f); } + ImGui::ColorEdit4("Clear Color", + glm::value_ptr(ctx.scene->GetSettings().ClearColor)); } // ---- Rendering:渲染管线参数与开关 ---- if (ImGui::CollapsingHeader("Rendering", ImGuiTreeNodeFlags_DefaultOpen)) { f32 exposure = renderer.GetExposure(); - if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f)) + if (ImGui::SliderFloat("Exposure", &exposure, 0.1f, 4.0f)) { renderer.SetExposure(exposure); } @@ -89,12 +94,12 @@ void StatsPanel::Draw(const EditorContext& ctx) if (bloom) { f32 threshold = renderer.GetBloomThreshold(); - if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f)) + if (ImGui::SliderFloat("Threshold", &threshold, 0.0f, 2.0f)) { renderer.SetBloomThreshold(threshold); } f32 strength = renderer.GetBloomStrength(); - if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f)) + if (ImGui::SliderFloat("Strength", &strength, 0.0f, 2.0f)) { renderer.SetBloomStrength(strength); } @@ -118,14 +123,6 @@ void StatsPanel::Draw(const EditorContext& ctx) { renderer.SetLodEnabled(lod); // 关 = 恒绘原网格 } - } - - // ---- Environment:清屏色 + 调试视图(决定"看什么")。 - // 环境贴图/环境光已迁为实体组件(Skybox/SkyLight),在 Details 配 ---- - if (ImGui::CollapsingHeader("Environment", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::ColorEdit4("ClearColor", - glm::value_ptr(ctx.scene->GetSettings().ClearColor)); // 调试视图:单看某个 pass 的中间输出(类 UE pass debug) const auto& viewNames = renderer.GetDebugViewNames(); @@ -137,13 +134,13 @@ void StatsPanel::Draw(const EditorContext& ctx) } } - // ---- Physics Debug:物理调试可视化(collider/约束/重力/速度/加速度)---- - if (ImGui::CollapsingHeader("Physics Debug")) + // ---- Physics:调试可视化(collider/约束/重力/速度/加速度)---- + if (ImGui::CollapsingHeader("Physics")) { auto& physics = app.GetPhysics(); u32 bits = physics.GetDebugDrawBits(); bool master = bits != 0; - if (ImGui::Checkbox("Enable##PhysDebug", &master)) + if (ImGui::Checkbox("Debug Draw##Phys", &master)) { bits = master ? kPhyDbgDefault : 0; } @@ -169,9 +166,32 @@ void StatsPanel::Draw(const EditorContext& ctx) ImGui::Text("contacts (frame): %zu", physics.GetEvents().size()); } - // ---- GPU Timings:只读遥测(默认折叠,不占面板高度)---- - if (ImGui::CollapsingHeader("GPU Timings")) + // ---- Performance:帧率控制 + GPU 遥测(默认折叠,不占面板高度)---- + if (ImGui::CollapsingHeader("Performance")) { + bool vsync = app.GetVSync(); + if (ImGui::Checkbox("VSync", &vsync)) + { + app.SetVSync(vsync); + if (!vsync) + { + const f32 target = app.GetTargetFps(); + app.SetTargetFps(target); + } + } + if (!vsync) + { + f32 target = app.GetTargetFps(); + if (ImGui::SliderFloat("Target FPS", &target, 24.0f, 360.0f, "%.0f")) + { + app.SetTargetFps(target); + } + } + ImGui::Text("fps: %.1f frame: %llu", + 1.0f / Time::Current().UnscaledDt(), + static_cast(app.GetFrameCount())); + + ImGui::Separator(); ImGui::Text("instances: %u main / %u shadow", renderer.GetMainInstanceCount(), renderer.GetShadowInstanceCount()); @@ -188,31 +208,5 @@ void StatsPanel::Draw(const EditorContext& ctx) } } - // ---- Frame:帧率控制与显示 ---- - if (ImGui::CollapsingHeader("Frame", ImGuiTreeNodeFlags_DefaultOpen)) - { - bool vsync = app.GetVSync(); - if (ImGui::Checkbox("VSync", &vsync)) - { - app.SetVSync(vsync); - if (!vsync) - { - const f32 target = app.GetTargetFps(); - app.SetTargetFps(target); - } - } - if (!vsync) - { - f32 target = app.GetTargetFps(); - if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f")) - { - app.SetTargetFps(target); - } - } - ImGui::Text("fps: %.1f frame: %llu", - 1.0f / Time::Current().UnscaledDt(), - static_cast(app.GetFrameCount())); - } - ImGui::End(); }