feat(animation): 添加动画状态回调支持

refactor(character): 重构角色动作处理逻辑

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

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

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

refactor(movement): 重构移动系统支持行走/奔跑模式
This commit is contained in:
2026-04-04 14:45:41 +08:00
parent 1200cf0181
commit 5e80df040b
29 changed files with 1251 additions and 513 deletions

View File

@@ -1,45 +1,115 @@
#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:
left_ = true;
return true;
return handleDirectionPress(
KeyboardMoveDirection::Left, left_, leftLastTapTimestamp_);
case KeyCode::d:
case KeyCode::Right:
right_ = true;
return true;
return handleDirectionPress(
KeyboardMoveDirection::Right, right_, rightLastTapTimestamp_);
case KeyCode::w:
case KeyCode::Up:
up_ = true;
return true;
return handleDirectionPress(
KeyboardMoveDirection::Up, up_, upLastTapTimestamp_);
case KeyCode::s:
case KeyCode::Down:
down_ = true;
return handleDirectionPress(
KeyboardMoveDirection::Down, down_, downLastTapTimestamp_);
case KeyCode::x:
QueueAction("attack");
return true;
case KeyCode::j:
attackPressed_ = true;
return true;
case KeyCode::k:
case KeyCode::c:
jumpPressed_ = true;
return true;
case KeyCode::l:
skill1Pressed_ = true;
case KeyCode::z:
QueueAction("skill_1");
return true;
case KeyCode::LShift:
dashPressed_ = true;
QueueAction("dash");
return true;
default:
return false;
@@ -47,23 +117,25 @@ bool CharacterInputRouter::OnKeyDown(const KeyEvent& event) {
}
bool CharacterInputRouter::OnKeyUp(const KeyUpEvent& event) {
auto handleDirectionRelease = [&](bool& pressedFlag) {
pressedFlag = false;
RefreshKeyboardMoveMode();
return true;
};
switch (event.getKeyCode()) {
case KeyCode::a:
case KeyCode::Left:
left_ = false;
return true;
return handleDirectionRelease(left_);
case KeyCode::d:
case KeyCode::Right:
right_ = false;
return true;
return handleDirectionRelease(right_);
case KeyCode::w:
case KeyCode::Up:
up_ = false;
return true;
return handleDirectionRelease(up_);
case KeyCode::s:
case KeyCode::Down:
down_ = false;
return true;
return handleDirectionRelease(down_);
default:
return false;
}
@@ -88,14 +160,14 @@ bool CharacterInputRouter::OnJoystickButtonDown(
jumpPressed_ = true;
return true;
case SDL_CONTROLLER_BUTTON_X:
attackPressed_ = true;
QueueAction("attack");
return true;
case SDL_CONTROLLER_BUTTON_B:
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
skill1Pressed_ = true;
QueueAction("skill_1");
return true;
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
dashPressed_ = true;
QueueAction("dash");
return true;
default:
return false;
@@ -110,23 +182,41 @@ bool CharacterInputRouter::OnJoystickButtonUp(
void CharacterInputRouter::EmitCommands(CharacterCommandBuffer& commandBuffer) {
Vec2 moveAxis(leftStickX_, leftStickY_);
if (moveAxis.lengthSquared() < 0.0001f) {
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});
commandBuffer.Submit(
{CharacterCommandType::Move, moveAxis, 0.0f, moveMode, std::string()});
if (jumpPressed_) {
commandBuffer.Submit({CharacterCommandType::JumpPressed, Vec2::Zero(), 0.0f});
commandBuffer.Submit(
{CharacterCommandType::JumpPressed, Vec2::Zero(), 0.0f,
CharacterMoveMode::None, std::string()});
}
if (attackPressed_) {
commandBuffer.Submit({CharacterCommandType::AttackPressed, Vec2::Zero(), 0.0f});
}
if (skill1Pressed_) {
commandBuffer.Submit({CharacterCommandType::Skill1Pressed, Vec2::Zero(), 0.0f});
}
if (dashPressed_) {
commandBuffer.Submit({CharacterCommandType::DashPressed, Vec2::Zero(), 0.0f});
for (const auto& actionId : actionRequests_) {
commandBuffer.Submit(
{CharacterCommandType::ActionPressed, Vec2::Zero(), 0.0f,
CharacterMoveMode::None, actionId});
}
ClearFrameState();
@@ -134,9 +224,7 @@ void CharacterInputRouter::EmitCommands(CharacterCommandBuffer& commandBuffer) {
void CharacterInputRouter::ClearFrameState() {
jumpPressed_ = false;
attackPressed_ = false;
skill1Pressed_ = false;
dashPressed_ = false;
actionRequests_.clear();
}
} // namespace frostbite2D