Unify input behind Engine::Input facade; fix Ctrl+P and Pause button

New Engine/Core/Input.h/.cpp: static input facade with a Key/MouseButton
enum (values map to GLFW key codes internally) so scripts and game logic
no longer touch GLFW directly - window backend becomes swappable and the
facade is the surface to expose when embedding scripting later. Wired up
in Application's constructor; Camera (editor camera WASD/RMB/MMB), the
WASDMove script and SandboxApp key handling now query Input only.

Ctrl+P hotkey fix: the !m_Playing guard made the hotkey dead exactly
when it was needed - after pressing Play. Now toggles both ways.

Toolbar Pause button now reads "Resume" while paused (tooltip follows).
This commit is contained in:
2026-10-05 11:34:27 +08:00
parent c083ee5cb7
commit 641b255cb2
7 changed files with 161 additions and 29 deletions
+8 -7
View File
@@ -26,8 +26,6 @@
#include <imgui_internal.h> // DockBuilder*(内部 API)
#include <ImGuizmo.h>
#include <GLFW/glfw3.h>
#include <filesystem>
#include <fstream>
@@ -283,8 +281,9 @@ void Editor::DrawMenuBar()
void Editor::DrawToolbar()
{
const ImGuiIO& io = ImGui::GetIO();
// Ctrl+P:文本输入中(改名/路径框)不触发,防打字误切 PIE
if (!m_Playing && io.KeyCtrl && !io.WantTextInput &&
// Ctrl+P 全局热键:编辑态进 PIE,运行态停回编辑器(双向)。
// 文本输入中(改名/路径框)不触发,防打字误切
if (io.KeyCtrl && !io.WantTextInput &&
ImGui::IsKeyPressed(ImGuiKey_P, false))
{
TogglePlay();
@@ -293,12 +292,14 @@ void Editor::DrawToolbar()
// 按钮加大触感(工具栏行高 = 一个按钮高;DockSpace avail 自适应)
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 4.0f));
const ImGuiStyle& st = ImGui::GetStyle();
const char* pauseLabel = m_Paused ? "Resume" : "Pause";
// Play 组宽度 → 水平居中(编辑态 1 钮 / 运行态 2 钮)
float groupW = 0.0f;
if (m_Playing)
{
groupW = ImGui::CalcTextSize("Pause").x + ImGui::CalcTextSize("Stop").x +
groupW = ImGui::CalcTextSize(pauseLabel).x +
ImGui::CalcTextSize("Stop").x +
4.0f * st.FramePadding.x + st.ItemSpacing.x;
}
else
@@ -309,7 +310,7 @@ void Editor::DrawToolbar()
if (m_Playing)
{
// Pause:暂停中亮黄常驻(UE 按下态),运行中暗黄
// Pause/Resume:暂停中亮黄常驻(UE 按下态),运行中暗黄
ImGui::PushStyleColor(ImGuiCol_Button,
m_Paused ? ImVec4(0.85f, 0.62f, 0.12f, 1.0f)
: ImVec4(0.55f, 0.42f, 0.10f, 1.0f));
@@ -318,7 +319,7 @@ void Editor::DrawToolbar()
: ImVec4(0.65f, 0.50f, 0.14f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(0.95f, 0.76f, 0.24f, 1.0f));
if (ImGui::Button("Pause"))
if (ImGui::Button(pauseLabel))
{
TogglePause();
}
+5 -4
View File
@@ -24,8 +24,8 @@
#include <Engine/Renderer/Renderer.h>
#include <Engine/Renderer/EditorCamera.h>
#include <GLFW/glfw3.h>
#include <Engine/Core/Input.h>
#include "Editor.h"
#include "EditorContext.h"
@@ -94,11 +94,12 @@ namespace
void OnKeyPress(const Event& event) override
{
if (event.key == GLFW_KEY_GRAVE_ACCENT)
// 键码统一走 Input::Key(App 层零 GLFW 依赖)
if (event.key == static_cast<int>(Input::Key::GraveAccent))
{
SetImGuiEnabled(!GetImGuiEnabled());
}
if (event.key == GLFW_KEY_F1)
if (event.key == static_cast<int>(Input::Key::F1))
{
STATS.PrintComparison();
}
+9 -8
View File
@@ -12,15 +12,16 @@
#include <Engine/Scripting/Script.h>
#include <Engine/Core/Application.h>
#include <Engine/Core/Window.h>
#include <Engine/Core/Input.h>
#include <Engine/Renderer/Renderer.h>
#include <Engine/Renderer/EditorCamera.h>
#include <Engine/Physics/PhysicsWorld.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
using namespace Engine; // Input 门面(统一输入入口)
namespace {
class WASDMove final : public Engine::Scripting::ScriptBehavior {
@@ -29,7 +30,8 @@ namespace {
void OnUpdate(float dt) override {
// 位移方向 = 视线水平投影(W 前进 / A 左 / D 右),UE Scene
// view 移动惯例;Y 锁 0(升降/跳跃归物理或将来扩展)
// view 移动惯例;Y 锁 0(升降/跳跃归物理或将来扩展)。
// 键鼠查询统一走 Input 门面(脚本层零 GLFW 依赖)
const glm::quat cq = App().GetRenderer()
.GetEditorCamera()
.GetTransform()
@@ -37,12 +39,11 @@ namespace {
const glm::vec3 fwd = cq * glm::vec3(0.0f, 0.0f, -1.0f);
const glm::vec3 right = cq * glm::vec3(1.0f, 0.0f, 0.0f);
auto& win = App().GetWindow();
glm::vec3 move(0.0f);
if (win.IsKeyPressed(GLFW_KEY_W)) move += fwd;
if (win.IsKeyPressed(GLFW_KEY_S)) move -= fwd;
if (win.IsKeyPressed(GLFW_KEY_D)) move += right;
if (win.IsKeyPressed(GLFW_KEY_A)) move -= right;
if (Input::IsKeyDown(Input::Key::W)) move += fwd;
if (Input::IsKeyDown(Input::Key::S)) move -= fwd;
if (Input::IsKeyDown(Input::Key::D)) move += right;
if (Input::IsKeyDown(Input::Key::A)) move -= right;
move.y = 0.0f;
if (glm::length(move) > 1e-4f) {
move = glm::normalize(move);
+89
View File
@@ -0,0 +1,89 @@
//
// Input.h:统一输入门面(脚本/游戏逻辑唯一入口)。
//
// 目的:
// 1. 上层代码(脚本、SandboxApp、编辑器相机)不再直接依赖 GLFW——
// 只依赖本枚举与门面,Window 后端将来可换(SDL/平台原生)
// 2. 嵌入脚本时暴露的也是这一套(Lua/Python 绑定包一层即可)
//
// 键码值 = GLFW 物理键码(glf3.h 标准值),强转 int 可直接传给
// Window::IsKeyPressed;但调用方不得依赖这一点。
//
// 语义:轮询接口沿用 Window 的门控规则——UI 捕获键盘(编辑器文本框
// 输入中)时 IsKeyDown 返回 false,游戏侧静默;鼠标捕获同理。
//
#pragma once
#include <glm/glm.hpp>
namespace Engine {
class Window;
namespace Input {
enum class Key : int {
// ---- 可打印区(值 = ASCII)----
Space = 32,
Apostrophe = 39,
Comma = 44,
Minus = 45,
Period = 46,
Slash = 47,
Num0 = 48, Num1 = 49, Num2 = 50, Num3 = 51, Num4 = 52,
Num5 = 53, Num6 = 54, Num7 = 55, Num8 = 56, Num9 = 57,
Semicolon = 59,
Equal = 61,
A = 65, B = 66, C = 67, D = 68, E = 69, F = 70, G = 71,
H = 72, I = 73, J = 74, K = 75, L = 76, M = 77, N = 78,
O = 79, P = 80, Q = 81, R = 82, S = 83, T = 84, U = 85,
V = 86, W = 87, X = 88, Y = 89, Z = 90,
BracketLeft = 91,
Backslash = 92,
BracketRight = 93,
GraveAccent = 96,
// ---- 控制键(值 = GLFW 键码)----
Escape = 256,
Enter = 257,
Tab = 258,
Backspace = 259,
Insert = 260,
Delete = 261,
Right = 262,
Left = 263,
Down = 264,
Up = 265,
PageUp = 266,
PageDown = 267,
Home = 268,
End = 269,
F1 = 290, F2 = 291, F3 = 292, F4 = 293, F5 = 294, F6 = 295,
F7 = 296, F8 = 297, F9 = 298, F10 = 299, F11 = 300,
F12 = 301,
// ---- 修饰键 ----
LeftShift = 340,
LeftControl = 341,
LeftAlt = 342,
LeftSuper = 343,
RightShift = 344,
RightControl = 345,
RightAlt = 346,
RightSuper = 347,
};
enum class MouseButton : int {
Left = 0, // GLFW_MOUSE_BUTTON_1
Right = 1,
Middle = 2,
};
// 装配:Application 构造时自动调用(后端 Window 就绪后)
void Init(const Window& window);
// 轮询查询(UI 捕获对应输入时返回 false——游戏侧静默)
bool IsKeyDown(Key key);
bool IsMouseButtonDown(MouseButton button);
}
} // namespace Engine
+5
View File
@@ -4,6 +4,7 @@
#include <Engine/Core/Application.h>
#include <Engine/Core/Window.h>
#include <Engine/Core/Input.h>
#include <Engine/Core/Log.h>
#include <Engine/ECS/Scene.h>
#include <Engine/Physics/PhysicsWorld.h>
@@ -39,6 +40,10 @@ namespace Engine {
// 资产层不感知 Vulkan 具体类。
// 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块)
m_Assets = CreateScope<Assets::AssetManager>(m_Renderer->GetAssetUploader());
// 统一输入门面装配:脚本/游戏逻辑经 Input 查询键鼠,不直接
// 依赖 Window 后端(GLFW 解耦)
Input::Init(*m_Window);
}
Application::~Application()
+35
View File
@@ -0,0 +1,35 @@
//
// Input.cpp:统一输入门面实现(转发 Window 后端轮询接口)。
//
#include "Engine/Core/Input.h"
#include "Engine/Core/Window.h"
namespace Engine {
namespace Input {
namespace {
// Application 生命周期内有效(成员析构序:Input 无状态依赖,
// 仅 Application::Run 期间被查询)
const Window* s_Window = nullptr;
}
void Init(const Window& window)
{
s_Window = &window;
}
bool IsKeyDown(const Key key)
{
return s_Window != nullptr &&
s_Window->IsKeyPressed(static_cast<int>(key));
}
bool IsMouseButtonDown(const MouseButton button)
{
return s_Window != nullptr &&
s_Window->IsMouseButtonPressed(static_cast<int>(button));
}
}
} // namespace Engine
+10 -10
View File
@@ -10,7 +10,7 @@
#include <cmath>
#include "GLFW/glfw3.h"
#include "Engine/Core/Input.h" // 键查询走统一输入门面(不依赖 GLFW)
namespace Engine {
@@ -35,9 +35,9 @@ namespace Engine {
// 光标型交互互斥共用捕获;编辑器模式 flyRequiresRMB=true 时
// 移动键仅 RMB 按住生效(否则与 Gizmo W/E/R 热键冲突)----
const bool wantLook = m_MouseLookEnabled &&
window.IsMouseButtonPressed(GLFW_MOUSE_BUTTON_RIGHT);
Input::IsMouseButtonDown(Input::MouseButton::Right);
const bool wantOrbit = m_MouseLookEnabled &&
window.IsMouseButtonPressed(GLFW_MOUSE_BUTTON_MIDDLE);
Input::IsMouseButtonDown(Input::MouseButton::Middle);
if (wantLook || wantOrbit)
{
@@ -70,7 +70,7 @@ namespace Engine {
if (wantOrbit)
{
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT))
if (Input::IsKeyDown(Input::Key::LeftShift))
{
// ---- 屏幕平面平移:内容跟手(拖右 → 相机左移、
// 拖下 → 相机上移),pivot 同步平移保持环绕点的
@@ -120,12 +120,12 @@ namespace Engine {
if (wantLook || !flyRequiresRMB)
{
const f32 speed = m_MoveSpeed * deltaTime;
if (window.IsKeyPressed(GLFW_KEY_W)) { transform.SetPosition(transform.GetPosition() + forward * speed); }
if (window.IsKeyPressed(GLFW_KEY_S)) { transform.SetPosition(transform.GetPosition() - forward * speed); }
if (window.IsKeyPressed(GLFW_KEY_A)) { transform.SetPosition(transform.GetPosition() - right * speed); }
if (window.IsKeyPressed(GLFW_KEY_D)) { transform.SetPosition(transform.GetPosition() + right * speed); }
if (window.IsKeyPressed(GLFW_KEY_SPACE)) { transform.SetPosition(transform.GetPosition() + glm::vec3(0, speed, 0)); }
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) { transform.SetPosition(transform.GetPosition() - glm::vec3(0, speed, 0)); }
if (Input::IsKeyDown(Input::Key::W)) { transform.SetPosition(transform.GetPosition() + forward * speed); }
if (Input::IsKeyDown(Input::Key::S)) { transform.SetPosition(transform.GetPosition() - forward * speed); }
if (Input::IsKeyDown(Input::Key::A)) { transform.SetPosition(transform.GetPosition() - right * speed); }
if (Input::IsKeyDown(Input::Key::D)) { transform.SetPosition(transform.GetPosition() + right * speed); }
if (Input::IsKeyDown(Input::Key::Space)) { transform.SetPosition(transform.GetPosition() + glm::vec3(0, speed, 0)); }
if (Input::IsKeyDown(Input::Key::LeftShift)) { transform.SetPosition(transform.GetPosition() - glm::vec3(0, speed, 0)); }
}
// ---- 滚轮推拉:有 pivot 时改环绕距离(指数步进,近处精细/