#include "character/CharacterInputRouter.h" #include #include #include 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