Files
Frostbite2D/Game/src/character/CharacterInputRouter.cpp
Lenheart 5e80df040b feat(animation): 添加动画状态回调支持
refactor(character): 重构角色动作处理逻辑

feat(swordman): 实现剑士基础攻击和技能1处理

refactor(state): 优化状态机与动作上下文管理

feat(input): 改进输入系统支持动作请求队列

refactor(movement): 重构移动系统支持行走/奔跑模式
2026-04-04 14:45:41 +08:00

231 lines
6.6 KiB
C++

#include "character/CharacterInputRouter.h"
#include <SDL2/SDL_gamecontroller.h>
#include <algorithm>
#include <cmath>
namespace frostbite2D {
namespace {
constexpr std::uint32_t kKeyboardRunDoubleTapWindowMs = 250;
constexpr float kAnalogRunThreshold = 0.75f;
float applyDeadZone(float value, float deadZone) {
return std::fabs(value) >= deadZone ? value : 0.0f;
}
} // namespace
void CharacterInputRouter::QueueAction(const std::string& actionId) {
if (actionId.empty()) {
return;
}
if (std::find(actionRequests_.begin(), actionRequests_.end(), actionId)
== actionRequests_.end()) {
actionRequests_.push_back(actionId);
}
}
bool CharacterInputRouter::IsDirectionPressed(KeyboardMoveDirection direction) const {
switch (direction) {
case KeyboardMoveDirection::Left:
return left_;
case KeyboardMoveDirection::Right:
return right_;
case KeyboardMoveDirection::Up:
return up_;
case KeyboardMoveDirection::Down:
return down_;
default:
return false;
}
}
bool CharacterInputRouter::HasDirectionalInput() const {
return left_ || right_ || up_ || down_;
}
void CharacterInputRouter::RefreshKeyboardMoveMode() {
if (!HasDirectionalInput()) {
keyboardMoveMode_ = CharacterMoveMode::None;
runDirection_ = KeyboardMoveDirection::None;
return;
}
if (runDirection_ != KeyboardMoveDirection::None && IsDirectionPressed(runDirection_)) {
keyboardMoveMode_ = CharacterMoveMode::Run;
return;
}
runDirection_ = KeyboardMoveDirection::None;
keyboardMoveMode_ = CharacterMoveMode::Walk;
}
bool CharacterInputRouter::OnKeyDown(const KeyEvent& event) {
auto handleDirectionPress = [&](KeyboardMoveDirection direction,
bool& pressedFlag,
std::uint32_t& lastTapTimestamp) {
pressedFlag = true;
std::uint32_t now = event.getTimestamp();
bool isDoubleTap = lastTapTimestamp > 0
&& now >= lastTapTimestamp
&& now - lastTapTimestamp <= kKeyboardRunDoubleTapWindowMs;
lastTapTimestamp = now;
if (isDoubleTap) {
keyboardMoveMode_ = CharacterMoveMode::Run;
runDirection_ = direction;
} else if (keyboardMoveMode_ != CharacterMoveMode::Run
|| !IsDirectionPressed(runDirection_)) {
keyboardMoveMode_ = CharacterMoveMode::Walk;
runDirection_ = KeyboardMoveDirection::None;
}
return true;
};
switch (event.getKeyCode()) {
case KeyCode::a:
case KeyCode::Left:
return handleDirectionPress(
KeyboardMoveDirection::Left, left_, leftLastTapTimestamp_);
case KeyCode::d:
case KeyCode::Right:
return handleDirectionPress(
KeyboardMoveDirection::Right, right_, rightLastTapTimestamp_);
case KeyCode::w:
case KeyCode::Up:
return handleDirectionPress(
KeyboardMoveDirection::Up, up_, upLastTapTimestamp_);
case KeyCode::s:
case KeyCode::Down:
return handleDirectionPress(
KeyboardMoveDirection::Down, down_, downLastTapTimestamp_);
case KeyCode::x:
QueueAction("attack");
return true;
case KeyCode::c:
jumpPressed_ = true;
return true;
case KeyCode::z:
QueueAction("skill_1");
return true;
case KeyCode::LShift:
QueueAction("dash");
return true;
default:
return false;
}
}
bool CharacterInputRouter::OnKeyUp(const KeyUpEvent& event) {
auto handleDirectionRelease = [&](bool& pressedFlag) {
pressedFlag = false;
RefreshKeyboardMoveMode();
return true;
};
switch (event.getKeyCode()) {
case KeyCode::a:
case KeyCode::Left:
return handleDirectionRelease(left_);
case KeyCode::d:
case KeyCode::Right:
return handleDirectionRelease(right_);
case KeyCode::w:
case KeyCode::Up:
return handleDirectionRelease(up_);
case KeyCode::s:
case KeyCode::Down:
return handleDirectionRelease(down_);
default:
return false;
}
}
bool CharacterInputRouter::OnJoystickAxis(const JoystickAxisEvent& event) {
if (event.getAxis() == SDL_CONTROLLER_AXIS_LEFTX) {
leftStickX_ = applyDeadZone(event.getNormalizedValue(), joystickDeadZone_);
return true;
}
if (event.getAxis() == SDL_CONTROLLER_AXIS_LEFTY) {
leftStickY_ = applyDeadZone(event.getNormalizedValue(), joystickDeadZone_);
return true;
}
return false;
}
bool CharacterInputRouter::OnJoystickButtonDown(
const JoystickButtonDownEvent& event) {
switch (event.getButton()) {
case SDL_CONTROLLER_BUTTON_A:
jumpPressed_ = true;
return true;
case SDL_CONTROLLER_BUTTON_X:
QueueAction("attack");
return true;
case SDL_CONTROLLER_BUTTON_B:
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
QueueAction("skill_1");
return true;
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
QueueAction("dash");
return true;
default:
return false;
}
}
bool CharacterInputRouter::OnJoystickButtonUp(
const JoystickButtonUpEvent& event) {
(void)event;
return false;
}
void CharacterInputRouter::EmitCommands(CharacterCommandBuffer& commandBuffer) {
Vec2 moveAxis(leftStickX_, leftStickY_);
CharacterMoveMode moveMode = CharacterMoveMode::None;
float stickMagnitudeSquared = moveAxis.lengthSquared();
if (stickMagnitudeSquared >= joystickDeadZone_ * joystickDeadZone_) {
float stickMagnitude = std::sqrt(stickMagnitudeSquared);
if (stickMagnitude > 1.0f) {
moveAxis = moveAxis.normalized();
stickMagnitude = 1.0f;
}
moveMode = stickMagnitude >= kAnalogRunThreshold
? CharacterMoveMode::Run
: CharacterMoveMode::Walk;
} else {
moveAxis.x = (right_ ? 1.0f : 0.0f) - (left_ ? 1.0f : 0.0f);
moveAxis.y = (down_ ? 1.0f : 0.0f) - (up_ ? 1.0f : 0.0f);
if (moveAxis.lengthSquared() > 1.0f) {
moveAxis = moveAxis.normalized();
}
if (moveAxis.lengthSquared() >= 0.0001f) {
moveMode = keyboardMoveMode_ == CharacterMoveMode::Run
? CharacterMoveMode::Run
: CharacterMoveMode::Walk;
}
}
commandBuffer.Submit(
{CharacterCommandType::Move, moveAxis, 0.0f, moveMode, std::string()});
if (jumpPressed_) {
commandBuffer.Submit(
{CharacterCommandType::JumpPressed, Vec2::Zero(), 0.0f,
CharacterMoveMode::None, std::string()});
}
for (const auto& actionId : actionRequests_) {
commandBuffer.Submit(
{CharacterCommandType::ActionPressed, Vec2::Zero(), 0.0f,
CharacterMoveMode::None, actionId});
}
ClearFrameState();
}
void CharacterInputRouter::ClearFrameState() {
jumpPressed_ = false;
actionRequests_.clear();
}
} // namespace frostbite2D