feat(animation): 添加动画状态回调支持
refactor(character): 重构角色动作处理逻辑 feat(swordman): 实现剑士基础攻击和技能1处理 refactor(state): 优化状态机与动作上下文管理 feat(input): 改进输入系统支持动作请求队列 refactor(movement): 重构移动系统支持行走/奔跑模式
This commit is contained in:
@@ -8,7 +8,11 @@
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/// 管理角色动作定义。优先从 PVF 动作脚本读取,不再自动生成回退动作。
|
||||
/// 管理角色显式注册的逻辑动作。
|
||||
///
|
||||
/// 动画配置只负责提供资源标签,不再自动推导哪些逻辑动作存在。
|
||||
/// 基础状态动作和职业技能入口都在代码中显式注册,让技能实现类自己决定
|
||||
/// 当前要播放哪个 ani。
|
||||
class CharacterActionLibrary {
|
||||
public:
|
||||
bool LoadForConfig(const character::CharacterConfig& config);
|
||||
@@ -17,7 +21,7 @@ public:
|
||||
std::string DescribeActionIds() const;
|
||||
|
||||
private:
|
||||
bool TryLoadPvfActionScripts(const character::CharacterConfig& config);
|
||||
bool BuildExplicitActionRegistry(const character::CharacterConfig& config);
|
||||
|
||||
std::map<std::string, CharacterActionDefinition> actions_;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
@@ -10,9 +13,13 @@ namespace frostbite2D {
|
||||
enum class CharacterCommandType {
|
||||
Move,
|
||||
JumpPressed,
|
||||
AttackPressed,
|
||||
Skill1Pressed,
|
||||
DashPressed,
|
||||
ActionPressed,
|
||||
};
|
||||
|
||||
enum class CharacterMoveMode {
|
||||
None,
|
||||
Walk,
|
||||
Run,
|
||||
};
|
||||
|
||||
/// 单条输入命令。移动命令携带轴值,按钮命令只关心触发时机。
|
||||
@@ -20,16 +27,17 @@ struct CharacterCommand {
|
||||
CharacterCommandType type = CharacterCommandType::Move;
|
||||
Vec2 moveAxis = Vec2::Zero();
|
||||
float timestamp = 0.0f;
|
||||
CharacterMoveMode moveMode = CharacterMoveMode::None;
|
||||
std::string actionId;
|
||||
};
|
||||
|
||||
/// 角色在当前帧想要执行的意图,供状态机消费。
|
||||
struct CharacterIntent {
|
||||
float moveX = 0.0f;
|
||||
float moveY = 0.0f;
|
||||
CharacterMoveMode moveMode = CharacterMoveMode::None;
|
||||
bool wantJump = false;
|
||||
bool wantAttack = false;
|
||||
bool wantSkill1 = false;
|
||||
bool wantDash = false;
|
||||
std::vector<std::string> requestedActions;
|
||||
};
|
||||
|
||||
/// 主状态机的大状态,普攻和技能统一走 Action。
|
||||
@@ -59,7 +67,9 @@ struct CharacterWorldPosition {
|
||||
struct CharacterMotor {
|
||||
CharacterWorldPosition position;
|
||||
Vec2 groundVelocity = Vec2::Zero();
|
||||
Vec2 forcedSlideVelocity = Vec2::Zero();
|
||||
float verticalVelocity = 0.0f;
|
||||
float forcedSlideRemainingTime = 0.0f;
|
||||
bool grounded = true;
|
||||
int facing = 1;
|
||||
float moveSpeed = 220.0f;
|
||||
@@ -85,6 +95,20 @@ struct CharacterMotor {
|
||||
}
|
||||
|
||||
void StopGroundMovement() { groundVelocity = Vec2::Zero(); }
|
||||
void ClearForcedSlide() {
|
||||
forcedSlideVelocity = Vec2::Zero();
|
||||
forcedSlideRemainingTime = 0.0f;
|
||||
}
|
||||
|
||||
void StartForcedSlide(const Vec2& direction, float distance, float duration) {
|
||||
if (distance <= 0.0f || duration <= 0.0f || direction.lengthSquared() < 0.0001f) {
|
||||
ClearForcedSlide();
|
||||
return;
|
||||
}
|
||||
|
||||
forcedSlideVelocity = direction.normalized() * (distance / duration);
|
||||
forcedSlideRemainingTime = duration;
|
||||
}
|
||||
|
||||
void Jump() {
|
||||
grounded = false;
|
||||
@@ -97,6 +121,17 @@ struct CharacterMotor {
|
||||
void Update(float deltaTime) {
|
||||
position.x += groundVelocity.x * deltaTime;
|
||||
position.y += groundVelocity.y * deltaTime;
|
||||
if (forcedSlideRemainingTime > 0.0f && forcedSlideVelocity.lengthSquared() > 0.0f) {
|
||||
float slideDeltaTime = deltaTime < forcedSlideRemainingTime
|
||||
? deltaTime
|
||||
: forcedSlideRemainingTime;
|
||||
position.x += forcedSlideVelocity.x * slideDeltaTime;
|
||||
position.y += forcedSlideVelocity.y * slideDeltaTime;
|
||||
forcedSlideRemainingTime -= slideDeltaTime;
|
||||
if (forcedSlideRemainingTime <= 0.0f) {
|
||||
ClearForcedSlide();
|
||||
}
|
||||
}
|
||||
|
||||
if (!grounded || position.z > 0.0f || verticalVelocity > 0.0f) {
|
||||
position.z += verticalVelocity * deltaTime;
|
||||
@@ -116,51 +151,166 @@ struct CharacterMotor {
|
||||
}
|
||||
};
|
||||
|
||||
/// 帧事件是动作定义驱动逻辑的钩子,后续可扩展命中框与位移曲线。
|
||||
enum class CharacterFrameEventType {
|
||||
OpenHitWindow,
|
||||
CloseHitWindow,
|
||||
OpenInputBuffer,
|
||||
CloseInputBuffer,
|
||||
SetVelocityXY,
|
||||
SetVelocityZ,
|
||||
AnimationEvent,
|
||||
FinishAction,
|
||||
};
|
||||
|
||||
/// 单个动作帧上的逻辑事件。
|
||||
struct CharacterFrameEventDefinition {
|
||||
int frame = 0;
|
||||
CharacterFrameEventType type = CharacterFrameEventType::AnimationEvent;
|
||||
Vec2 velocityXY = Vec2::Zero();
|
||||
float velocityZ = 0.0f;
|
||||
std::string stringValue;
|
||||
};
|
||||
|
||||
/// 取消规则决定动作在什么窗口内可以接下一个动作。
|
||||
struct CharacterCancelRuleDefinition {
|
||||
std::string targetAction;
|
||||
int beginFrame = 0;
|
||||
int endFrame = 0;
|
||||
bool requireGrounded = false;
|
||||
bool requireAirborne = false;
|
||||
};
|
||||
|
||||
/// 逻辑动作定义。actionId 是玩法动作名,animationTag 是资源动作名。
|
||||
/// 逻辑动作定义。这里只保留显式注册的逻辑入口名。
|
||||
///
|
||||
/// 动画播放统一由状态类/技能类直接调用 `PlayAnimationTag()` 控制,
|
||||
/// 不再通过动作定义反查 animationTag。
|
||||
struct CharacterActionDefinition {
|
||||
std::string actionId;
|
||||
std::string animationTag;
|
||||
int totalFrames = 1;
|
||||
bool loop = false;
|
||||
bool canMove = false;
|
||||
bool canTurn = true;
|
||||
bool canBeInterrupted = true;
|
||||
int inputBufferOpenFrame = 0;
|
||||
float moveSpeedScale = 0.0f;
|
||||
std::vector<CharacterCancelRuleDefinition> cancelRules;
|
||||
std::vector<CharacterFrameEventDefinition> frameEvents;
|
||||
};
|
||||
|
||||
using CharacterRuntimeValue = std::variant<bool, int, float, std::string, Vec2>;
|
||||
|
||||
class CharacterRuntimeBlackboard {
|
||||
public:
|
||||
void SetBool(const std::string& key, bool value);
|
||||
void SetInt(const std::string& key, int value);
|
||||
void SetFloat(const std::string& key, float value);
|
||||
void SetString(const std::string& key, const std::string& value);
|
||||
void SetVec2(const std::string& key, const Vec2& value);
|
||||
|
||||
bool TryGetBool(const std::string& key, bool& outValue) const;
|
||||
bool TryGetInt(const std::string& key, int& outValue) const;
|
||||
bool TryGetFloat(const std::string& key, float& outValue) const;
|
||||
bool TryGetString(const std::string& key, std::string& outValue) const;
|
||||
bool TryGetVec2(const std::string& key, Vec2& outValue) const;
|
||||
|
||||
bool Has(const std::string& key) const;
|
||||
void Remove(const std::string& key);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void SetValue(const std::string& key, T&& value) {
|
||||
values_[key] = std::forward<T>(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool TryGetValue(const std::string& key, T& outValue) const {
|
||||
auto it = values_.find(key);
|
||||
if (it == values_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const T* value = std::get_if<T>(&it->second);
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = *value;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, CharacterRuntimeValue> values_;
|
||||
};
|
||||
|
||||
struct CharacterActionContextData {
|
||||
bool valid = false;
|
||||
std::string requestedActionId;
|
||||
std::string sourceActionId;
|
||||
CharacterStateId sourceStateId = CharacterStateId::Idle;
|
||||
bool hasSourceStateId = false;
|
||||
CharacterRuntimeBlackboard values;
|
||||
|
||||
void Clear() {
|
||||
valid = false;
|
||||
requestedActionId.clear();
|
||||
sourceActionId.clear();
|
||||
sourceStateId = CharacterStateId::Idle;
|
||||
hasSourceStateId = false;
|
||||
values.Clear();
|
||||
}
|
||||
|
||||
void SetSourceStateId(CharacterStateId stateId) {
|
||||
sourceStateId = stateId;
|
||||
hasSourceStateId = true;
|
||||
}
|
||||
|
||||
void SetBool(const std::string& key, bool value) { values.SetBool(key, value); }
|
||||
void SetInt(const std::string& key, int value) { values.SetInt(key, value); }
|
||||
void SetFloat(const std::string& key, float value) { values.SetFloat(key, value); }
|
||||
void SetString(const std::string& key, const std::string& value) {
|
||||
values.SetString(key, value);
|
||||
}
|
||||
void SetVec2(const std::string& key, const Vec2& value) { values.SetVec2(key, value); }
|
||||
|
||||
bool TryGetBool(const std::string& key, bool& outValue) const {
|
||||
return values.TryGetBool(key, outValue);
|
||||
}
|
||||
bool TryGetInt(const std::string& key, int& outValue) const {
|
||||
return values.TryGetInt(key, outValue);
|
||||
}
|
||||
bool TryGetFloat(const std::string& key, float& outValue) const {
|
||||
return values.TryGetFloat(key, outValue);
|
||||
}
|
||||
bool TryGetString(const std::string& key, std::string& outValue) const {
|
||||
return values.TryGetString(key, outValue);
|
||||
}
|
||||
bool TryGetVec2(const std::string& key, Vec2& outValue) const {
|
||||
return values.TryGetVec2(key, outValue);
|
||||
}
|
||||
|
||||
bool Has(const std::string& key) const { return values.Has(key); }
|
||||
void Remove(const std::string& key) { values.Remove(key); }
|
||||
};
|
||||
|
||||
inline void CharacterRuntimeBlackboard::SetBool(const std::string& key, bool value) {
|
||||
SetValue(key, value);
|
||||
}
|
||||
|
||||
inline void CharacterRuntimeBlackboard::SetInt(const std::string& key, int value) {
|
||||
SetValue(key, value);
|
||||
}
|
||||
|
||||
inline void CharacterRuntimeBlackboard::SetFloat(const std::string& key, float value) {
|
||||
SetValue(key, value);
|
||||
}
|
||||
|
||||
inline void CharacterRuntimeBlackboard::SetString(const std::string& key,
|
||||
const std::string& value) {
|
||||
SetValue(key, value);
|
||||
}
|
||||
|
||||
inline void CharacterRuntimeBlackboard::SetVec2(const std::string& key, const Vec2& value) {
|
||||
SetValue(key, value);
|
||||
}
|
||||
|
||||
inline bool CharacterRuntimeBlackboard::TryGetBool(const std::string& key,
|
||||
bool& outValue) const {
|
||||
return TryGetValue(key, outValue);
|
||||
}
|
||||
|
||||
inline bool CharacterRuntimeBlackboard::TryGetInt(const std::string& key, int& outValue) const {
|
||||
return TryGetValue(key, outValue);
|
||||
}
|
||||
|
||||
inline bool CharacterRuntimeBlackboard::TryGetFloat(const std::string& key,
|
||||
float& outValue) const {
|
||||
return TryGetValue(key, outValue);
|
||||
}
|
||||
|
||||
inline bool CharacterRuntimeBlackboard::TryGetString(const std::string& key,
|
||||
std::string& outValue) const {
|
||||
return TryGetValue(key, outValue);
|
||||
}
|
||||
|
||||
inline bool CharacterRuntimeBlackboard::TryGetVec2(const std::string& key,
|
||||
Vec2& outValue) const {
|
||||
return TryGetValue(key, outValue);
|
||||
}
|
||||
|
||||
inline bool CharacterRuntimeBlackboard::Has(const std::string& key) const {
|
||||
return values_.find(key) != values_.end();
|
||||
}
|
||||
|
||||
inline void CharacterRuntimeBlackboard::Remove(const std::string& key) {
|
||||
values_.erase(key);
|
||||
}
|
||||
|
||||
inline void CharacterRuntimeBlackboard::Clear() {
|
||||
values_.clear();
|
||||
}
|
||||
|
||||
/// 运行期状态标记,承载霸体、无敌等战斗属性。
|
||||
struct CharacterStatus {
|
||||
bool superArmor = false;
|
||||
@@ -183,15 +333,20 @@ public:
|
||||
CharacterIntent BuildIntent() const;
|
||||
bool HasBuffered(CharacterCommandType type) const;
|
||||
void Consume(CharacterCommandType type);
|
||||
bool HasBufferedAction(const std::string& actionId) const;
|
||||
void ConsumeAction(const std::string& actionId);
|
||||
void ClearBufferedActions();
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
struct BufferedButton {
|
||||
CharacterCommandType type = CharacterCommandType::AttackPressed;
|
||||
CharacterCommandType type = CharacterCommandType::ActionPressed;
|
||||
std::string actionId;
|
||||
float age = 0.0f;
|
||||
};
|
||||
|
||||
Vec2 moveAxis_ = Vec2::Zero();
|
||||
CharacterMoveMode moveMode_ = CharacterMoveMode::None;
|
||||
std::vector<BufferedButton> buttons_;
|
||||
float bufferWindowSeconds_ = 0.2f;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
#include <frostbite2D/animation/animation.h>
|
||||
#include <frostbite2D/base/RefPtr.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -16,6 +17,8 @@ class CharacterObject;
|
||||
class CharacterAnimation : public Actor {
|
||||
public:
|
||||
using ActionAnimationList = std::map<std::string, std::vector<RefPtr<Animation>>>;
|
||||
using ActionFrameFlagCallback = std::function<void(int)>;
|
||||
using ActionEndCallback = std::function<void()>;
|
||||
|
||||
bool Init(CharacterObject* parent,
|
||||
const character::CharacterConfig& config,
|
||||
@@ -28,9 +31,19 @@ public:
|
||||
return actionAnimations_.count(actionName) > 0;
|
||||
}
|
||||
std::string DescribeAvailableActions() const;
|
||||
bool IsCurrentActionFinished() const;
|
||||
int GetCurrentFrameIndex() const;
|
||||
int GetCurrentFrameCount() const;
|
||||
float GetCurrentNormalizedProgress() const;
|
||||
animation::AniFrame GetCurrentFrameInfo() const;
|
||||
void SetActionFrameFlagCallback(ActionFrameFlagCallback callback);
|
||||
void SetActionEndCallback(ActionEndCallback callback);
|
||||
|
||||
private:
|
||||
static std::string FormatImgPath(std::string path, Animation::ReplaceData data);
|
||||
Animation* GetPrimaryAnimation(const std::string& actionName) const;
|
||||
Animation* GetCurrentPrimaryAnimation() const;
|
||||
void RefreshRuntimeCallbacks();
|
||||
|
||||
void CreateAnimationBySlot(const std::string& actionName,
|
||||
const std::string& slotName,
|
||||
@@ -42,6 +55,8 @@ private:
|
||||
ActionAnimationList actionAnimations_;
|
||||
std::string currentActionTag_;
|
||||
int direction_ = 1;
|
||||
ActionFrameFlagCallback actionFrameFlagCallback_;
|
||||
ActionEndCallback actionEndCallback_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include "character/CharacterActionTypes.h"
|
||||
#include <frostbite2D/event/joystick_event.h>
|
||||
#include <frostbite2D/event/key_event.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
@@ -19,14 +22,31 @@ public:
|
||||
void ClearFrameState();
|
||||
|
||||
private:
|
||||
enum class KeyboardMoveDirection {
|
||||
None,
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
};
|
||||
|
||||
void QueueAction(const std::string& actionId);
|
||||
bool IsDirectionPressed(KeyboardMoveDirection direction) const;
|
||||
bool HasDirectionalInput() const;
|
||||
void RefreshKeyboardMoveMode();
|
||||
|
||||
bool left_ = false;
|
||||
bool right_ = false;
|
||||
bool up_ = false;
|
||||
bool down_ = false;
|
||||
bool jumpPressed_ = false;
|
||||
bool attackPressed_ = false;
|
||||
bool skill1Pressed_ = false;
|
||||
bool dashPressed_ = false;
|
||||
std::vector<std::string> actionRequests_;
|
||||
CharacterMoveMode keyboardMoveMode_ = CharacterMoveMode::None;
|
||||
KeyboardMoveDirection runDirection_ = KeyboardMoveDirection::None;
|
||||
std::uint32_t leftLastTapTimestamp_ = 0;
|
||||
std::uint32_t rightLastTapTimestamp_ = 0;
|
||||
std::uint32_t upLastTapTimestamp_ = 0;
|
||||
std::uint32_t downLastTapTimestamp_ = 0;
|
||||
float leftStickX_ = 0.0f;
|
||||
float leftStickY_ = 0.0f;
|
||||
float joystickDeadZone_ = 0.2f;
|
||||
|
||||
@@ -97,6 +97,26 @@ public:
|
||||
CharacterMotor& GetMotorMutable() { return motor_; }
|
||||
CharacterCommandBuffer& GetCommandBufferMutable() { return commandBuffer_; }
|
||||
float GetLastDeltaTime() const { return lastDeltaTime_; }
|
||||
const CharacterRuntimeBlackboard& GetRuntimeBlackboard() const { return runtimeBlackboard_; }
|
||||
CharacterRuntimeBlackboard& GetRuntimeBlackboardMutable() { return runtimeBlackboard_; }
|
||||
|
||||
/// @brief Prepare one-shot data for the next action transition.
|
||||
///
|
||||
/// The returned context is consumed by the next successful `EnterAction()`.
|
||||
CharacterActionContextData& PreparePendingActionContext(
|
||||
const std::string& requestedActionId,
|
||||
const std::string& sourceActionId = std::string(),
|
||||
CharacterStateId sourceStateId = CharacterStateId::Idle);
|
||||
void ClearPendingActionContext();
|
||||
bool HasPendingActionContext() const { return pendingActionContext_.valid; }
|
||||
const CharacterActionContextData* GetPendingActionContext() const;
|
||||
CharacterActionContextData* GetPendingActionContextMutable();
|
||||
|
||||
/// @brief Read transition metadata for the currently running action.
|
||||
const CharacterActionContextData* GetCurrentActionContext() const;
|
||||
CharacterActionContextData* GetCurrentActionContextMutable();
|
||||
bool HasCurrentActionContext() const { return currentActionContext_.valid; }
|
||||
void ClearCurrentActionContext();
|
||||
|
||||
/// @brief 按逻辑动作 id 查询动作定义。
|
||||
const CharacterActionDefinition* FindAction(const std::string& actionId) const;
|
||||
@@ -105,13 +125,24 @@ public:
|
||||
const CharacterActionDefinition* RequireAction(const std::string& actionId,
|
||||
const char* phase) const;
|
||||
|
||||
/// @brief 按动作定义播放动画;缺少 animationTag 或资源不存在时会输出详细错误并退出。
|
||||
bool PlayActionDefinition(const CharacterActionDefinition& action, const char* phase);
|
||||
|
||||
/// @brief 播放某个动画标签。
|
||||
///
|
||||
/// 当前只是对 `SetAction()` 的语义包装,方便状态机表达“播放动作标签”。
|
||||
void PlayAnimationTag(const std::string& actionTag);
|
||||
bool HasAnimationTag(const std::string& actionTag) const;
|
||||
|
||||
/// @brief 查询当前动作主动画是否已经播放完成。
|
||||
bool IsCurrentAnimationFinished() const;
|
||||
|
||||
/// @brief 读取当前动作主动画的帧信息,方便状态类/脚本做运行时判断。
|
||||
int GetCurrentAnimationFrameIndex() const;
|
||||
int GetCurrentAnimationFrameCount() const;
|
||||
float GetCurrentAnimationProgressNormalized() const;
|
||||
animation::AniFrame GetCurrentAnimationFrameInfo() const;
|
||||
|
||||
/// @brief 绑定当前动作主动画的运行时回调,供后续脚本系统接入。
|
||||
void SetAnimationFrameFlagCallback(CharacterAnimation::ActionFrameFlagCallback callback);
|
||||
void SetAnimationEndCallback(CharacterAnimation::ActionEndCallback callback);
|
||||
|
||||
/// @brief 输出角色动作/动画的致命错误,并请求应用退出。
|
||||
void ReportFatalCharacterError(const char* phase,
|
||||
@@ -136,6 +167,9 @@ public:
|
||||
const CharacterEquipmentManager& GetEquipmentManager() const { return equipmentManager_; }
|
||||
|
||||
private:
|
||||
void CommitPendingActionContext(const std::string& defaultRequestedActionId,
|
||||
const std::string& defaultSourceActionId,
|
||||
CharacterStateId defaultSourceStateId);
|
||||
bool SetActionStrict(const std::string& actionName,
|
||||
const char* phase,
|
||||
const std::string& requestedActionId);
|
||||
@@ -159,7 +193,7 @@ private:
|
||||
/// 负责拼装装备部件,决定分层 Avatar 的表现资源。
|
||||
CharacterEquipmentManager equipmentManager_;
|
||||
|
||||
/// 动作定义表:把逻辑 actionId 映射到帧数据、取消规则、动画标签等。
|
||||
/// 动作定义表:保存显式注册的逻辑动作入口。
|
||||
CharacterActionLibrary actionLibrary_;
|
||||
|
||||
/// 输入适配层,把键盘/手柄事件收敛成统一命令。
|
||||
@@ -171,7 +205,16 @@ private:
|
||||
/// 当前帧从命令缓冲整理出的“角色意图”。
|
||||
CharacterIntent currentIntent_;
|
||||
|
||||
/// 状态调度器,负责状态切换、动作进入和帧事件推进。
|
||||
/// Character-wide shared runtime data for skills, buffs, and future scripts.
|
||||
CharacterRuntimeBlackboard runtimeBlackboard_;
|
||||
|
||||
/// One-shot payload prepared before switching into the next action.
|
||||
CharacterActionContextData pendingActionContext_;
|
||||
|
||||
/// Metadata attached to the action currently running inside Action state.
|
||||
CharacterActionContextData currentActionContext_;
|
||||
|
||||
/// 状态调度器,负责状态切换与动作进入。
|
||||
CharacterStateMachine stateMachine_;
|
||||
|
||||
/// 纯运动数据:位置、速度、落地状态和朝向都在这里维护。
|
||||
@@ -188,6 +231,8 @@ private:
|
||||
|
||||
/// 是否允许从事件系统接收输入;AI/剧情接管时可临时关闭。
|
||||
bool inputEnabled_ = true;
|
||||
|
||||
friend class CharacterStateMachine;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
||||
@@ -47,18 +47,17 @@ private:
|
||||
const char* phase) const;
|
||||
bool TryStartAction(CharacterObject& owner,
|
||||
CharacterCommandBuffer& commandBuffer,
|
||||
CharacterCommandType commandType,
|
||||
const std::string& requestedActionId,
|
||||
const std::string& actionId);
|
||||
bool StartAction(CharacterObject& owner,
|
||||
const std::string& requestedActionId,
|
||||
const std::string& actionId);
|
||||
bool TryStartRegisteredAction(CharacterStateContext& context);
|
||||
void ApplyFrameEvents(CharacterObject& owner, int previousFrame, int currentFrame);
|
||||
|
||||
CharacterStateId currentState_ = CharacterStateId::Idle;
|
||||
std::string currentActionId_;
|
||||
const CharacterActionDefinition* currentAction_ = nullptr;
|
||||
float stateTime_ = 0.0f;
|
||||
float actionFrameProgress_ = 0.0f;
|
||||
int actionFrame_ = 0;
|
||||
float landingTimer_ = 0.0f;
|
||||
StateRegistry stateRegistry_;
|
||||
|
||||
friend class CharacterStateBase;
|
||||
|
||||
@@ -57,21 +57,26 @@ protected:
|
||||
void ChangeState(CharacterStateMachine& machine,
|
||||
CharacterStateContext& context,
|
||||
CharacterStateId nextState) const;
|
||||
void PlayActionById(CharacterObject& owner, const std::string& actionId) const;
|
||||
void InvokeHook(CharacterStateMachine& machine,
|
||||
CharacterObject& owner,
|
||||
const CharacterActionDefinition* action,
|
||||
const char* phase) const;
|
||||
bool TryStartAction(CharacterStateMachine& machine,
|
||||
CharacterStateContext& context,
|
||||
CharacterCommandType commandType,
|
||||
const std::string& actionId) const;
|
||||
bool TryStartAction(CharacterStateMachine& machine,
|
||||
CharacterStateContext& context,
|
||||
const std::string& requestedActionId,
|
||||
const std::string& actionId) const;
|
||||
bool StartAction(CharacterStateMachine& machine,
|
||||
CharacterStateContext& context,
|
||||
const std::string& actionId) const;
|
||||
bool StartAction(CharacterStateMachine& machine,
|
||||
CharacterStateContext& context,
|
||||
const std::string& requestedActionId,
|
||||
const std::string& actionId) const;
|
||||
bool TryStartRegisteredAction(CharacterStateMachine& machine,
|
||||
CharacterStateContext& context) const;
|
||||
void ApplyFrameEvents(CharacterStateMachine& machine,
|
||||
CharacterObject& owner,
|
||||
int previousFrame,
|
||||
int currentFrame) const;
|
||||
|
||||
const CharacterActionDefinition* GetCurrentAction(const CharacterStateMachine& machine) const;
|
||||
const std::string& GetCurrentActionId(const CharacterStateMachine& machine) const;
|
||||
@@ -81,9 +86,6 @@ protected:
|
||||
void ClearCurrentAction(CharacterStateMachine& machine) const;
|
||||
|
||||
float& StateTime(CharacterStateMachine& machine) const;
|
||||
float& ActionFrameProgress(CharacterStateMachine& machine) const;
|
||||
int& ActionFrame(CharacterStateMachine& machine) const;
|
||||
float& LandingTimer(CharacterStateMachine& machine) const;
|
||||
|
||||
private:
|
||||
CharacterStateId stateId_ = CharacterStateId::Idle;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "character/states/CharacterStateBase.h"
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
struct SwordmanActionUpdateResult {
|
||||
enum class Type {
|
||||
Running,
|
||||
Finished,
|
||||
Transition,
|
||||
};
|
||||
|
||||
static SwordmanActionUpdateResult Running() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static SwordmanActionUpdateResult Finished() {
|
||||
SwordmanActionUpdateResult result;
|
||||
result.type = Type::Finished;
|
||||
return result;
|
||||
}
|
||||
|
||||
static SwordmanActionUpdateResult TransitionTo(const std::string& requestedActionId,
|
||||
bool requireBufferedInput = true) {
|
||||
SwordmanActionUpdateResult result;
|
||||
result.type = Type::Transition;
|
||||
result.requestedActionId = requestedActionId;
|
||||
result.requireBufferedInput = requireBufferedInput;
|
||||
return result;
|
||||
}
|
||||
|
||||
Type type = Type::Running;
|
||||
std::string requestedActionId;
|
||||
bool requireBufferedInput = true;
|
||||
};
|
||||
|
||||
class ISwordmanActionHandler {
|
||||
public:
|
||||
virtual ~ISwordmanActionHandler() = default;
|
||||
|
||||
virtual const std::string& GetActionId() const = 0;
|
||||
virtual void OnEnter(CharacterStateContext& context,
|
||||
const CharacterActionDefinition& action) = 0;
|
||||
virtual SwordmanActionUpdateResult OnUpdate(CharacterStateContext& context,
|
||||
const CharacterActionDefinition& action) = 0;
|
||||
virtual void OnExit(CharacterStateContext& context,
|
||||
const CharacterActionDefinition& action) {
|
||||
(void)context;
|
||||
(void)action;
|
||||
}
|
||||
};
|
||||
|
||||
class SwordmanActionHandlerBase : public ISwordmanActionHandler {
|
||||
public:
|
||||
explicit SwordmanActionHandlerBase(std::string actionId);
|
||||
~SwordmanActionHandlerBase() override = default;
|
||||
|
||||
const std::string& GetActionId() const override { return actionId_; }
|
||||
|
||||
protected:
|
||||
std::string actionId_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "character/states/CharacterStateBase.h"
|
||||
#include "character/states/jobs/swordman/SwordmanActionHandler.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
@@ -18,6 +22,11 @@ public:
|
||||
void OnExit(CharacterStateMachine& machine,
|
||||
CharacterStateContext& context,
|
||||
CharacterStateId nextState) override;
|
||||
|
||||
private:
|
||||
ISwordmanActionHandler* FindHandler(const std::string& actionId) const;
|
||||
|
||||
std::unordered_map<std::string, std::unique_ptr<ISwordmanActionHandler>> handlers_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "character/states/jobs/swordman/SwordmanActionHandler.h"
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class SwordmanBasicAttackHandler : public SwordmanActionHandlerBase {
|
||||
public:
|
||||
SwordmanBasicAttackHandler();
|
||||
|
||||
void OnEnter(CharacterStateContext& context,
|
||||
const CharacterActionDefinition& action) override;
|
||||
SwordmanActionUpdateResult OnUpdate(CharacterStateContext& context,
|
||||
const CharacterActionDefinition& action) override;
|
||||
void OnExit(CharacterStateContext& context,
|
||||
const CharacterActionDefinition& action) override;
|
||||
|
||||
private:
|
||||
void BeginComboStep(CharacterStateContext& context, int comboStep);
|
||||
|
||||
int comboStep_ = 0;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "character/states/jobs/swordman/SwordmanActionHandler.h"
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class SwordmanSkill1Handler : public SwordmanActionHandlerBase {
|
||||
public:
|
||||
SwordmanSkill1Handler();
|
||||
|
||||
void OnEnter(CharacterStateContext& context,
|
||||
const CharacterActionDefinition& action) override;
|
||||
SwordmanActionUpdateResult OnUpdate(CharacterStateContext& context,
|
||||
const CharacterActionDefinition& action) override;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
Reference in New Issue
Block a user