refactor(character): 重构角色动作处理逻辑 feat(swordman): 实现剑士基础攻击和技能1处理 refactor(state): 优化状态机与动作上下文管理 feat(input): 改进输入系统支持动作请求队列 refactor(movement): 重构移动系统支持行走/奔跑模式
231 lines
6.6 KiB
C++
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
|