完成输入管理器1
This commit is contained in:
@@ -40,6 +40,7 @@ add_executable(${TARGET}
|
||||
src/engine/resource/font_manager.cpp
|
||||
src/engine/render/renderer.cpp
|
||||
src/engine/render/camera.cpp
|
||||
src/engine/input/input_manager.cpp
|
||||
)
|
||||
|
||||
# 链接库
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../resource/resource_manager.h"
|
||||
#include "../render/renderer.h"
|
||||
#include "../render/camera.h"
|
||||
#include "../input/input_manager.h"
|
||||
#include "config.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
@@ -27,7 +28,8 @@ void GameApp::run() {
|
||||
while (is_running_) {
|
||||
time_->update();
|
||||
float delta_time = time_->getDeltaTime();
|
||||
|
||||
input_manager_->update(); // 每帧首先更新输入管理器
|
||||
|
||||
handleEvents();
|
||||
update(delta_time);
|
||||
render();
|
||||
@@ -46,6 +48,7 @@ bool GameApp::init() {
|
||||
if (!initResourceManager()) return false;
|
||||
if (!initRenderer()) return false;
|
||||
if (!initCamera()) return false;
|
||||
if (!initInputManager()) return false;
|
||||
|
||||
// 测试资源管理器
|
||||
testResourceManager();
|
||||
@@ -56,12 +59,13 @@ bool GameApp::init() {
|
||||
}
|
||||
|
||||
void GameApp::handleEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
is_running_ = false;
|
||||
}
|
||||
if (input_manager_->shouldQuit()) {
|
||||
spdlog::trace("GameApp 收到来自 InputManager 的退出请求。");
|
||||
is_running_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
testInputManager();
|
||||
}
|
||||
|
||||
void GameApp::update(float /* delta_time */) {
|
||||
@@ -185,6 +189,17 @@ bool GameApp::initCamera() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameApp::initInputManager()
|
||||
{
|
||||
try {
|
||||
input_manager_ = std::make_unique<engine::input::InputManager>(sdl_renderer_, config_.get());
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("初始化输入管理器失败: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
spdlog::trace("输入管理器初始化成功。");
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- 测试用函数 ---
|
||||
|
||||
@@ -224,4 +239,31 @@ void GameApp::testCamera()
|
||||
if (key_state[SDL_SCANCODE_RIGHT]) camera_->move(glm::vec2(1, 0));
|
||||
}
|
||||
|
||||
void GameApp::testInputManager()
|
||||
{
|
||||
std::vector<std::string> actions = {
|
||||
"move_up",
|
||||
"move_down",
|
||||
"move_left",
|
||||
"move_right",
|
||||
"jump",
|
||||
"attack",
|
||||
"pause",
|
||||
"MouseLeftClick",
|
||||
"MouseRightClick"
|
||||
};
|
||||
|
||||
for (const auto& action : actions) {
|
||||
if (input_manager_->isActionPressed(action)) {
|
||||
spdlog::info(" {} 按下 ", action);
|
||||
}
|
||||
if (input_manager_->isActionReleased(action)) {
|
||||
spdlog::info(" {} 抬起 ", action);
|
||||
}
|
||||
if (input_manager_->isActionDown(action)) {
|
||||
spdlog::info(" {} 按下中 ", action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace engine::core
|
||||
@@ -14,6 +14,10 @@ class Renderer;
|
||||
class Camera;
|
||||
}
|
||||
|
||||
namespace engine::input {
|
||||
class InputManager;
|
||||
}
|
||||
|
||||
namespace engine::core { // 命名空间的最佳实践:与文件路径一致
|
||||
class Time;
|
||||
class Config;
|
||||
@@ -33,6 +37,7 @@ private:
|
||||
std::unique_ptr<engine::render::Renderer> renderer_;
|
||||
std::unique_ptr<engine::render::Camera> camera_;
|
||||
std::unique_ptr<engine::core::Config> config_;
|
||||
std::unique_ptr<engine::input::InputManager> input_manager_;
|
||||
public:
|
||||
GameApp();
|
||||
~GameApp();
|
||||
@@ -62,11 +67,13 @@ private:
|
||||
[[nodiscard]] bool initResourceManager();
|
||||
[[nodiscard]] bool initRenderer();
|
||||
[[nodiscard]] bool initCamera();
|
||||
[[nodiscard]] bool initInputManager();
|
||||
|
||||
// 测试用函数
|
||||
void testResourceManager();
|
||||
void testRenderer();
|
||||
void testCamera();
|
||||
void testInputManager();
|
||||
};
|
||||
|
||||
} // namespace engine::core
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
#include "input_manager.h"
|
||||
#include "../core/config.h"
|
||||
#include <stdexcept>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
|
||||
namespace engine::input {
|
||||
|
||||
InputManager::InputManager(SDL_Renderer* sdl_renderer, const engine::core::Config* config): sdl_renderer_(sdl_renderer) {
|
||||
if (!sdl_renderer_) {
|
||||
spdlog::error("输入管理器: SDL_Renderer 为空指针");
|
||||
throw std::runtime_error("输入管理器: SDL_Renderer 为空指针");
|
||||
}
|
||||
initializeMappings(config);
|
||||
// 获取初始鼠标位置
|
||||
float x, y;
|
||||
SDL_GetMouseState(&x, &y);
|
||||
mouse_position_ = {x, y};
|
||||
spdlog::trace("初始鼠标位置: ({}, {})", mouse_position_.x, mouse_position_.y);
|
||||
}
|
||||
|
||||
// --- 更新和事件处理 ---
|
||||
|
||||
void InputManager::update() {
|
||||
// 1. 根据上一帧的值更新默认的动作状态
|
||||
for (auto& [action_name, state] : action_states_) {
|
||||
if (state == ActionState::PRESSED_THIS_FRAME) {
|
||||
state = ActionState::HELD_DOWN; // 当某个键按下不动时,并不会生成SDL_Event。
|
||||
} else if (state == ActionState::RELEASED_THIS_FRAME) {
|
||||
state = ActionState::INACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 处理所有待处理的 SDL 事件 (这将设定 action_states_ 的值)
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
processEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::processEvent(const SDL_Event& event) {
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
SDL_Scancode scancode = event.key.scancode; // 获取按键的scancode
|
||||
bool is_down = event.key.down;
|
||||
bool is_repeat = event.key.repeat;
|
||||
|
||||
auto it = scancode_to_actions_map_.find(scancode);
|
||||
if (it != scancode_to_actions_map_.end()) { // 如果按键有对应的action
|
||||
const std::vector<std::string>& associated_actions = it->second;
|
||||
for (const std::string& action_name : associated_actions) {
|
||||
updateActionState(action_name, is_down, is_repeat); // 更新action状态
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
Uint8 button = event.button.button; // 获取鼠标按钮
|
||||
bool is_down = event.button.down;
|
||||
auto it = mouse_button_to_actions_map_.find(button);
|
||||
if (it != mouse_button_to_actions_map_.end()) { // 如果鼠标按钮有对应的action
|
||||
const std::vector<std::string>& associated_actions = it->second;
|
||||
for (const std::string& action_name : associated_actions) {
|
||||
// 鼠标事件不考虑repeat, 所以第三个参数传false
|
||||
updateActionState(action_name, is_down, false); // 更新action状态
|
||||
}
|
||||
}
|
||||
// 在点击时更新鼠标位置
|
||||
mouse_position_ = {event.button.x, event.button.y};
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_MOUSE_MOTION: // 处理鼠标运动
|
||||
mouse_position_ = {event.motion.x, event.motion.y};
|
||||
break;
|
||||
case SDL_EVENT_QUIT:
|
||||
should_quit_ = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 状态查询方法 ---
|
||||
|
||||
bool InputManager::isActionDown(const std::string& action_name) const {
|
||||
// C++17 引入的 “带有初始化语句的 if 语句”
|
||||
if (auto it = action_states_.find(action_name); it != action_states_.end()) {
|
||||
return it->second == ActionState::PRESSED_THIS_FRAME || it->second == ActionState::HELD_DOWN;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputManager::isActionPressed(const std::string& action_name) const {
|
||||
if (auto it = action_states_.find(action_name); it != action_states_.end()) {
|
||||
return it->second == ActionState::PRESSED_THIS_FRAME;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputManager::isActionReleased(const std::string& action_name) const {
|
||||
if (auto it = action_states_.find(action_name); it != action_states_.end()) {
|
||||
return it->second == ActionState::RELEASED_THIS_FRAME;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputManager::shouldQuit() const {
|
||||
return should_quit_;
|
||||
}
|
||||
|
||||
void InputManager::setShouldQuit(bool should_quit)
|
||||
{
|
||||
should_quit_ = should_quit;
|
||||
}
|
||||
|
||||
glm::vec2 InputManager::getMousePosition() const
|
||||
{
|
||||
return mouse_position_;
|
||||
}
|
||||
|
||||
glm::vec2 InputManager::getLogicalMousePosition() const
|
||||
{
|
||||
glm::vec2 logical_pos;
|
||||
// 通过窗口坐标获取渲染坐标(逻辑坐标)
|
||||
SDL_RenderCoordinatesFromWindow(sdl_renderer_, mouse_position_.x, mouse_position_.y, &logical_pos.x, &logical_pos.y);
|
||||
return logical_pos;
|
||||
}
|
||||
|
||||
// --- 初始化输入映射 ---
|
||||
|
||||
void InputManager::initializeMappings(const engine::core::Config* config) {
|
||||
spdlog::trace("初始化输入映射...");
|
||||
if (!config) {
|
||||
spdlog::error("输入管理器: Config 为空指针");
|
||||
throw std::runtime_error("输入管理器: Config 为空指针");
|
||||
}
|
||||
actions_to_keyname_map_ = config->input_mappings_; // 获取配置中的输入映射(动作 -> 按键名称)
|
||||
scancode_to_actions_map_.clear();
|
||||
mouse_button_to_actions_map_.clear();
|
||||
action_states_.clear();
|
||||
|
||||
// 如果配置中没有定义鼠标按钮动作(通常不需要配置),则添加默认映射, 用于 UI
|
||||
if (actions_to_keyname_map_.find("MouseLeftClick") == actions_to_keyname_map_.end()) {
|
||||
spdlog::debug("配置中没有定义 'MouseLeftClick' 动作,添加默认映射到 'MouseLeft'.");
|
||||
actions_to_keyname_map_["MouseLeftClick"] = {"MouseLeft"}; // 如果缺失则添加默认映射
|
||||
}
|
||||
if (actions_to_keyname_map_.find("MouseRightClick") == actions_to_keyname_map_.end()) {
|
||||
spdlog::debug("配置中没有定义 'MouseRightClick' 动作,添加默认映射到 'MouseRight'.");
|
||||
actions_to_keyname_map_["MouseRightClick"] = {"MouseRight"}; // 如果缺失则添加默认映射
|
||||
}
|
||||
// 遍历 动作 -> 按键名称 的映射
|
||||
for (const auto& [action_name, key_names] : actions_to_keyname_map_) {
|
||||
// 每个动作对应一个动作状态,初始化为 INACTIVE
|
||||
action_states_[action_name] = ActionState::INACTIVE;
|
||||
spdlog::trace("映射动作: {}", action_name);
|
||||
// 设置 "按键 -> 动作" 的映射
|
||||
for (const std::string& key_name : key_names) {
|
||||
SDL_Scancode scancode = scancodeFromString(key_name); // 尝试根据按键名称获取scancode
|
||||
Uint8 mouse_button = mouseButtonUint8FromString(key_name); // 尝试根据按键名称获取鼠标按钮
|
||||
// 未来可添加其它输入类型 ...
|
||||
|
||||
if (scancode != SDL_SCANCODE_UNKNOWN) { // 如果scancode有效,则将action添加到scancode_to_actions_map_中
|
||||
scancode_to_actions_map_[scancode].push_back(action_name);
|
||||
spdlog::trace(" 映射按键: {} (Scancode: {}) 到动作: {}", key_name, static_cast<int>(scancode), action_name);
|
||||
} else if (mouse_button != 0) { // 如果鼠标按钮有效,则将action添加到mouse_button_to_actions_map_中
|
||||
mouse_button_to_actions_map_[mouse_button].push_back(action_name);
|
||||
spdlog::trace(" 映射鼠标按钮: {} (Button ID: {}) 到动作: {}", key_name, static_cast<int>(mouse_button), action_name);
|
||||
// else if: 未来可添加其它输入类型 ...
|
||||
} else {
|
||||
spdlog::warn("输入映射警告: 未知键或按钮名称 '{}' 用于动作 '{}'.", key_name, action_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
spdlog::trace("输入映射初始化完成.");
|
||||
}
|
||||
|
||||
// --- 工具函数 ---
|
||||
// 将字符串名称转换为 SDL_Scancode
|
||||
SDL_Scancode InputManager::scancodeFromString(const std::string& key_name) {
|
||||
return SDL_GetScancodeFromName(key_name.c_str());
|
||||
}
|
||||
|
||||
// 将鼠标按钮名称字符串转换为 SDL 按钮 Uint8 值
|
||||
Uint8 InputManager::mouseButtonUint8FromString(const std::string& button_name) {
|
||||
if (button_name == "MouseLeft") return SDL_BUTTON_LEFT;
|
||||
if (button_name == "MouseMiddle") return SDL_BUTTON_MIDDLE;
|
||||
if (button_name == "MouseRight") return SDL_BUTTON_RIGHT;
|
||||
// SDL 还定义了 SDL_BUTTON_X1 和 SDL_BUTTON_X2
|
||||
if (button_name == "MouseX1") return SDL_BUTTON_X1;
|
||||
if (button_name == "MouseX2") return SDL_BUTTON_X2;
|
||||
return 0; // 0 不是有效的按钮值,表示无效
|
||||
}
|
||||
|
||||
void InputManager::updateActionState(const std::string& action_name, bool is_input_active, bool is_repeat_event) {
|
||||
auto it = action_states_.find(action_name);
|
||||
if (it == action_states_.end()) {
|
||||
spdlog::warn("尝试更新未注册的动作状态: {}", action_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_input_active) { // 输入被激活 (按下)
|
||||
if (is_repeat_event) {
|
||||
it->second = ActionState::HELD_DOWN;
|
||||
} else { // 非重复的按下事件
|
||||
it->second = ActionState::PRESSED_THIS_FRAME;
|
||||
}
|
||||
} else { // 输入被释放 (松开)
|
||||
it->second = ActionState::RELEASED_THIS_FRAME;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace engine::input
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <SDL3/SDL_render.h>
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
namespace engine::core {
|
||||
class Config;
|
||||
}
|
||||
|
||||
namespace engine::input {
|
||||
|
||||
enum class ActionState {
|
||||
INACTIVE, ///< @brief 动作未激活
|
||||
PRESSED_THIS_FRAME, ///< @brief 动作在本帧刚刚被按下
|
||||
HELD_DOWN, ///< @brief 动作被持续按下
|
||||
RELEASED_THIS_FRAME ///< @brief 动作在本帧刚刚被释放
|
||||
};
|
||||
|
||||
class InputManager final {
|
||||
private:
|
||||
SDL_Renderer* sdl_renderer_; ///< @brief 用于获取逻辑坐标的 SDL_Renderer 指针
|
||||
std::unordered_map<std::string, std::vector<std::string>> actions_to_keyname_map_; ///< @brief 存储动作名称到按键名称列表的映射
|
||||
std::unordered_map<SDL_Scancode, std::vector<std::string>> scancode_to_actions_map_;///< @brief 从键盘(Scancode)到关联的动作名称列表
|
||||
std::unordered_map<Uint8, std::vector<std::string>> mouse_button_to_actions_map_; ///< @brief 从鼠标按钮 (Uint8) 到关联的动作名称列表
|
||||
|
||||
std::unordered_map<std::string, ActionState> action_states_; ///< @brief 存储每个动作的当前状态
|
||||
|
||||
bool should_quit_ = false; ///< @brief 退出标志
|
||||
glm::vec2 mouse_position_; ///< @brief 鼠标位置 (针对屏幕坐标)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param sdl_renderer 指向 SDL_Renderer 的指针
|
||||
* @param config 配置对象
|
||||
* @throws std::runtime_error 如果任一指针为 nullptr。
|
||||
*/
|
||||
InputManager(SDL_Renderer* sdl_renderer, const engine::core::Config* config);
|
||||
|
||||
void update(); ///< @brief 更新输入状态,每轮循环最先调用
|
||||
|
||||
|
||||
// 动作状态检查
|
||||
bool isActionDown(const std::string& action_name) const; ///< @brief 动作当前是否触发 (持续按下或本帧按下)
|
||||
bool isActionPressed(const std::string& action_name) const; ///< @brief 动作是否在本帧刚刚按下
|
||||
bool isActionReleased(const std::string& action_name) const; ///< @brief 动作是否在本帧刚刚释放
|
||||
|
||||
bool shouldQuit() const; ///< @brief 查询退出状态
|
||||
void setShouldQuit(bool should_quit); ///< @brief 设置退出状态
|
||||
|
||||
glm::vec2 getMousePosition() const; ///< @brief 获取鼠标位置 (屏幕坐标)
|
||||
glm::vec2 getLogicalMousePosition() const; ///< @brief 获取鼠标位置 (逻辑坐标)
|
||||
|
||||
private:
|
||||
void processEvent(const SDL_Event& event); ///< @brief 处理 SDL 事件(将按键转换为动作状态)
|
||||
void initializeMappings(const engine::core::Config* config); ///< @brief 根据 Config配置初始化映射表
|
||||
|
||||
void updateActionState(const std::string& action_name, bool is_input_active, bool is_repeat_event); ///< @brief 辅助更新动作状态
|
||||
SDL_Scancode scancodeFromString(const std::string& key_name); ///< @brief 将字符串键名转换为 SDL_Scancode
|
||||
Uint8 mouseButtonUint8FromString(const std::string& button_name); ///< @brief 将字符串按钮名转换为 SDL_Button
|
||||
};
|
||||
|
||||
} // namespace engine::input
|
||||
Reference in New Issue
Block a user