修复编辑器相机与 game 形态黑屏;中文字体与 Q 选择热键

FPS 式视角合成(yaw 世界轴/pitch 本地轴,永不 roll);
RMB 自由视角后环绕 pivot 失效待重建(修 Focus 后 MMB/滚轮
瞬移锁回);SceneRenderer 默认 kDisplayDirect(game 形态
Blit 空转+清屏黑屏根因);swapchain 优先 Opaque 合成;
ImGui 加载思源黑体(CJK 常用集);Q=纯选择不出杆;
新增 InputMap 动作映射
This commit is contained in:
2026-10-08 17:02:39 +08:00
parent 36b1fc0566
commit 26e222a9cf
15 changed files with 730 additions and 50 deletions
+32
View File
@@ -18,6 +18,7 @@
#include <glm/glm.hpp>
#include "Engine/Core/Types.h"
#include "Engine/Input/InputAction.h"
namespace Engine {
@@ -103,6 +104,37 @@ namespace Engine {
// 滚轮增量(上行 = +1,一档 ≈1):读后清零。UI 捕获鼠标
//(面板悬停)时不累积——滚轮归 ImGui
f32 ConsumeScrollDelta();
// ---- 输入动作层(具名动作,键鼠+手柄混绑;InputMap 藏在 cpp)----
// 采样:主循环单点调用 Poll(物理/脚本/OnUpdate 之前),整帧
// 一致。active=false(GameFlow 非 Running)时全部动作静默清零。
// 默认绑定表在 Init 时内建(Move=Axis2D、Jump=Button)
// 装配期(编辑器/游戏配置阶段调用;运行中 Define 也可,索引重建)
void DefineAction(const std::string& name, ActionKind kind);
// 追加绑定:绑定到具名动作(动作不存在则忽略)
void BindAction(const std::string& name, const ActionBinding& b);
// 就地重绑定(bindingIdx = 该动作绑定表下标;下帧生效)
bool RebindAction(const std::string& name, size_t bindingIdx,
const ActionBinding& b);
// 持久化(yaml;Load 文件不存在静默失败保持现状表)
bool SaveActionMap(const std::string& path);
bool LoadActionMap(const std::string& path);
// 每帧采样(Application::Run 专用单点;上层勿调)
void Poll(bool active);
// 查询(按动作名;未定义动作返回 false/0)。Button 用
// Pressed/Down/Released 边沿语义,Axis 用 Value/Value2
bool ActionPressed(const std::string& name);
bool ActionDown(const std::string& name);
bool ActionReleased(const std::string& name);
f32 ActionValue(const std::string& name);
glm::vec2 ActionValue2(const std::string& name);
// 手柄连接态(Poll 时更新)
bool IsGamepadConnected();
}
} // namespace Engine
+15
View File
@@ -17,6 +17,16 @@ struct GLFWwindow;
namespace Engine {
// 手柄状态快照(POD,PollGamepad 每帧填充)。
// 值 = GLFW 标准游戏布局:axes 下标即 GLFW_GAMEPAD_AXIS_*(左摇杆 X/Y、
// 右摇杆 X/Y、LT、RT),buttons 下标即 GLFW_GAMEPAD_BUTTON_*(A=0…),
// 调用方不得依赖具体数值(GLFW 头语义)
struct GamepadState {
bool connected = false;
f32 axes[6] = {};
bool buttons[15] = {};
};
class Window {
public:
Window(const std::string& title, u32 width, u32 height);
@@ -76,6 +86,11 @@ namespace Engine {
void SetCursorCaptured(bool captured);
bool IsCursorCaptured() const { return m_CursorCaptured; }
// 手柄轮询(JOYSTICK_1 标准游戏布局):连接且带标准映射时填充
// state 并返回 true;否则置 connected=false 全零并返回 false。
// 无分配(GLFW 平台态拉取),每帧调用成本可忽略
bool PollGamepad(GamepadState& state) const;
private:
+70
View File
@@ -0,0 +1,70 @@
//
// InputAction.h:输入动作模型类型。
//
// 动作层把"游戏语义"(Move/Jump)与"物理输入源"(键/鼠标/手柄)解耦:
// - 动作(Action)= 具名游戏语义,Button(按下沿)/Axis1D/Axis2D
// - 绑定(Binding)= 一个输入源到动作贡献的映射(code = GLFW 标准码)
// 上层(脚本/玩法系统)只认动作名,换键位/换手柄不改代码。
//
// code 值约定 = GLFW 标准码(同 Input::Key 惯例:Key 即 glf3.h 键码,
// GamepadButton 即 GLFW_GAMEPAD_BUTTON_*,GamepadAxis 即
// GLFW_GAMEPAD_AXIS_*);调用方不得依赖具体数值。
//
// component:Axis2D 动作中该绑定贡献哪个分量(0=X,1=Y)。
// 例如 W → {Key, KEY_W, scale=-1, component=1}(Y 分量负方向 = 前进),
// 左摇杆 Y 轴 → {GamepadAxis, AXIS_LEFT_Y, scale=+1, component=1}
//(GLFW 摇杆推上为负,与 W 的 -1 语义自然对齐)。
//
#ifndef HELLOVULKAN_INPUTACTION_H
#define HELLOVULKAN_INPUTACTION_H
#include <glm/glm.hpp>
#include <string>
#include <vector>
#include "Engine/Core/Types.h"
namespace Engine {
namespace Input {
// 动作类型
enum class ActionKind : u8 {
Button, // 0/1 按下态(Down/Pressed/Released 边沿查询)
Axis1D, // 标量 [-1,1](键绑定 scale 加权、手柄轴原始值×scale)
Axis2D, // 平面向量(2D 径向死区 + 单位圆钳制)
};
// 绑定源类型
enum class SourceKind : u8 {
Key, // 键盘(code = GLFW 键码)
MouseButton, // 鼠标键(0=左 1=右 2=中)
GamepadButton, // 手柄按钮(0-14 标准布局)
GamepadAxis, // 手柄轴(0-5:左摇杆 X/Y、右摇杆 X/Y、LT、RT)
};
// 单条绑定:一个输入源对一个动作的贡献
struct ActionBinding {
SourceKind kind;
i32 code; // 见头注释(GLFW 标准码)
f32 scale; // 贡献权重(方向:键 ±1;轴 ±1)
u8 component = 0; // 仅 Axis2D 消费:0=X 分量,1=Y 分量
};
// 动作实例:定义 + 绑定表 + 双帧值(边沿派生用,见 InputMap)
struct InputAction {
std::string name;
ActionKind kind = ActionKind::Button;
std::vector<ActionBinding> bindings;
// prev/cur 双帧值(Button/Axis1D 用 x;Axis2D 用 xy)。
// Button 语义:Down = |cur.x|>0.5,Pressed/Released = cur/prev 边沿
glm::vec2 prev{0.0f, 0.0f};
glm::vec2 cur{0.0f, 0.0f};
};
} // namespace Input
} // namespace Engine
#endif //HELLOVULKAN_INPUTACTION_H
+88
View File
@@ -0,0 +1,88 @@
//
// InputMap.h:动作表(定义/绑定/采样/重绑定/持久化)。
//
// 装配期(DefineAction/Bind/SetDefaults/LoadYaml)与运行期(Poll + 查询)
// 分离:名字→下标哈希在 Define 期建好,Poll 只写值槽位——热路径零堆分配。
//
// 采样语义(Poll):
// - active=false(GameFlow 非 Running):全部动作值清零且 prev 同步清零,
// 恢复激活后按住的键产生一次 Pressed 边(符合游戏语义,无脏边)
// - active=true:键鼠经 Window 轮询(自带 UI 捕获门控),手柄经
// Window::PollGamepad;连接翻转打一条日志
// - Axis2D:各绑定按 component 分量加权求和 → 2D 径向死区(0.15) →
// 超出单位圆时归一化(斜向不超速)
//
#ifndef HELLOVULKAN_INPUTMAP_H
#define HELLOVULKAN_INPUTMAP_H
#include <glm/glm.hpp>
#include <string>
#include <unordered_map>
#include <vector>
#include "Engine/Core/Types.h"
#include "Engine/Input/InputAction.h"
namespace Engine {
class Window;
namespace Input {
class InputMap {
public:
// ---- 装配期 ----
// 定义动作(返回动作 id = 下标;重名忽略并返回已有 id)
size_t DefineAction(const std::string& name, ActionKind kind);
// 追加绑定(动作 id 非法时忽略)
void Bind(size_t id, ActionBinding b);
// 就地重绑定(下帧采样即生效)。成功返回 true
bool Rebind(size_t id, size_t bindingIdx, ActionBinding b);
// 内建默认表:Move(Axis2D)=WASD+方向键+左摇杆、Jump(Button)=Space+A
void SetDefaults();
// 清空全部动作与索引(LoadYaml 重建用)
void Clear();
// ---- 运行期 ----
// 每帧采样(主循环单点调用:物理/脚本/OnUpdate 之前)
void Poll(const Window& win, bool active);
bool Down(size_t id) const; // Button: 按住;Axis: |值|>0.5
bool Pressed(size_t id) const; // 本帧按下边沿
bool Released(size_t id) const; // 本帧松开边沿
f32 Value(size_t id) const; // Axis1D;Button 返回 0/1
glm::vec2 Value2(size_t id) const;// Axis2D;其他 kind 返回 (x,0)
// 手柄连接态(Poll 时更新;连接翻转打 INFO)
bool IsGamepadConnected() const { return m_GamepadConnected; }
// ---- 持久化(yaml)----
// 格式见 InputMap.cpp;保存失败(目录不存在等)返回 false
bool SaveYaml(const std::string& path) const;
// 文件不存在静默失败返回 false(保持现状表);存在则
// SetDefaults 后按 yaml 覆盖同名动作的绑定表
bool LoadYaml(const std::string& path);
// ---- 查询辅助 ----
size_t Count() const { return m_Actions.size(); }
// 名字→id;不存在返回 npos
static constexpr size_t npos = static_cast<size_t>(-1);
size_t Find(const std::string& name) const;
private:
void PollGamepadConnection(bool connected);
std::vector<InputAction> m_Actions;
std::unordered_map<std::string, size_t> m_ByName;
bool m_GamepadConnected = false;
};
} // namespace Input
} // namespace Engine
#endif //HELLOVULKAN_INPUTMAP_H
@@ -236,6 +236,7 @@ namespace Engine {
// UniformBuffer——PhysicsDebugPass::PrepareFrame 上传,pass 内绑定)
Scope<UniformBuffer> m_PhysicsLineVB;
Scope<UniformBuffer> m_PhysicsTriVB;
Scope<UniformBuffer> m_DebugLineVB; // 通用调试源(Engine::Debug 服务)
// Editor gizmo 图元 ring VB(editor 专属,game 构建不分配):
// 图标 billboard / 视锥线框 / 拾取代理壳三流——代理壳由
// HitProxyPass 经 AttachGizmoProxy 消费(同 VB 换 shader 重画)
@@ -205,7 +205,11 @@ namespace Engine {
bool m_BloomEnabled = true;
bool m_LodEnabled = true; // 关 = 全部强制 LOD0(选级跳过)
int m_DebugView = 0;
int m_DisplayMode = kDisplayViewport; // Editor 视口为默认形态
// 引擎默认 = 游戏直渲形态(与 Renderer::m_DesiredDisplayMode 的
// 默认一致)。编辑器形态在 App OnInit 显式切 Viewport——若这里
// 默认 Viewport,game 形态没人调 SetDisplayMode:Blit 恒空转 +
// ImGuiPass 清屏 → 纯黑屏(game 形态黑屏的根因)
int m_DisplayMode = kDisplayDirect;
bool m_GameView = false; // PIE 内嵌播放(Editor Play/Stop 驱动)
#ifdef HELLO_EDITOR