camera: UE Scene-view semantics — RMB fly, MMB focus-orbit, wheel dolly, F focus

Key conflict fix (UE semantics): while RMB is held the keyboard belongs
to the camera, so WASD no longer collides with the gizmo W/E/R tool
hotkeys. Editor mode gates fly movement behind RMB; game mode keeps
always-on WASD. Viewport tool hotkeys (W/E/R/T/F) are suppressed while
RMB is held.

Focus orbit (pivot): MMB drag rotates the camera around a pivot keeping
distance; first orbit picks a pivot 5m ahead on the view ray. Shift+MMB
pans in screen plane (camera and pivot move together). Mouse wheel
dollies toward/away from the pivot with exponential steps (no pivot:
move along view ray). F focuses the selected entity (frame-selection):
pivot set to its position, camera slides along the view ray; orbit
distance persists.

Window gains a polled scroll-delta channel (ConsumeScrollDelta) that
respects ImGui mouse capture; Camera::Update mutates orbit state (no
longer const).
This commit is contained in:
2026-10-03 13:02:55 +08:00
parent 9ff08651f0
commit ffe33e9967
6 changed files with 178 additions and 45 deletions
+27 -15
View File
@@ -125,26 +125,38 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
else size.y = avail.x / aspect;
ImGui::Image((ImTextureID) (intptr_t) sceneTex, size);
// ---- W/E/R 切换 Gizmo 模式(UE 语义:W 平移 / E 旋转 /
// R 缩放),T 切换 local/world 操纵空间。仅 Viewport 持焦时
// 生效——输入框打字(WantTextInput)由 BlockingKeyboard 拦
// 游戏侧,但 ImGui 自身仍收键,故必须限定持焦面板,避免在
// Inspector 拖数值时误切 ----
if (out.focused && !ImGui::GetIO().WantTextInput) {
if (ImGui::IsKeyPressed(ImGuiKey_W)) gizmo.op = ImGuizmo::TRANSLATE;
if (ImGui::IsKeyPressed(ImGuiKey_E)) gizmo.op = ImGuizmo::ROTATE;
if (ImGui::IsKeyPressed(ImGuiKey_R)) gizmo.op = ImGuizmo::SCALE;
if (ImGui::IsKeyPressed(ImGuiKey_T)) gizmo.localSpace = !gizmo.localSpace;
}
// ---- Gizmo 操纵杆(ImGui drawlist 叠加,无 GPU pass)----
// 选中实体 + 有 Transform 才出杆;矩阵分解直接写回
// TransformComponent(与 Inspector 同源,下一帧数值自动跟随)
// ---- 工具热键(UE 语义):W/E/R 切 Gizmo 模式、T 切
// local/world、F 焦点选中实体。仅 Viewport 持焦时生效——
// 输入框打字(WantTextInput)由 BlockingKeyboard 拦游戏侧,
// 但 ImGui 自身仍收键,故必须限定持焦面板。RMB 按住 = 相机
// 飞行模式(键盘归相机),工具热键全部抑制——W 前进与
// W 平移 Gizmo 的键位冲突就此解耦 ----
auto &reg = ctx.scene->GetRegistry();
ECS::TransformComponent *tr =
selected != entt::null
? reg.try_get<ECS::TransformComponent>(selected)
: nullptr;
if (out.focused && !ImGui::GetIO().WantTextInput &&
!ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
if (ImGui::IsKeyPressed(ImGuiKey_W)) gizmo.op = ImGuizmo::TRANSLATE;
if (ImGui::IsKeyPressed(ImGuiKey_E)) gizmo.op = ImGuizmo::ROTATE;
if (ImGui::IsKeyPressed(ImGuiKey_R)) gizmo.op = ImGuizmo::SCALE;
if (ImGui::IsKeyPressed(ImGuiKey_T)) gizmo.localSpace = !gizmo.localSpace;
if (ImGui::IsKeyPressed(ImGuiKey_F) && tr != nullptr) {
// F = 焦点选中实体(frame-selection):环绕 pivot 设
// 为选中实体位置,相机沿视线平移对准(环绕距离沿用
// 当前值,滚轮可调)
ECS::TransformComponent *camT = nullptr;
if (auto *camComp = ctx.scene->GetPrimaryCamera(&camT);
camComp != nullptr && camT != nullptr) {
camComp->camera.FocusOn(*camT, tr->GetPosition());
}
}
}
// ---- Gizmo 操纵杆(ImGui drawlist 叠加,无 GPU pass)----
// 选中实体 + 有 Transform 才出杆;矩阵分解直接写回
// TransformComponent(与 Inspector 同源,下一帧数值自动跟随)
if (tr != nullptr) {
// 相机矩阵:primary 相机 view + Vulkan proj(转 GL 形式),
// 参数与渲染侧一致(aspect = 渲染分辨率、near/far 0.1/100)
+6 -3
View File
@@ -201,12 +201,15 @@ namespace
void OnUpdate(const FrameTime& frameTime) override
{
// 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)。
// 编辑器相机走真实轨:暂停(timeScale=0)时仍可飞行观察
// 相机(RMB 飞行 / MMB 焦点环绕 / 滚轮推拉;位姿读写实体
// Transform)。编辑器相机走真实轨:暂停(timeScale=0)时仍
// 可飞行观察。编辑器模式移动键需 RMB 按住(UE 语义:飞行时
// 键盘归相机,W/E/R 归 Gizmo)
m_Camera.GetComponent<CameraComponent>()
.camera.Update(GetWindow(),
m_Camera.GetComponent<TransformComponent>(),
frameTime.UnscaledDt());
frameTime.UnscaledDt(),
GetRenderer().GetDisplayMode() == kDisplayViewport);
// 旋转动画:编辑 Transform 分量后 Compose 重算 world。
// 动画时钟 = Time::Elapsed()(scaled 累计,暂停自动冻结——