refactor(character): 重构角色动作与动画系统
- 移除自动回退动作生成逻辑,改为严格检查动作定义 - 增加动作资源缺失时的详细错误报告机制 - 统一输入事件处理接口,优化角色对象生命周期管理 - 改进动画标签管理,移除隐式回退逻辑 - 增强状态机对无效动作的处理能力
This commit is contained in:
@@ -8,15 +8,15 @@
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/// 管理角色动作定义。优先从 PVF 动作脚本读取,缺失时回退到内置默认动作。
|
||||
/// 管理角色动作定义。优先从 PVF 动作脚本读取,不再自动生成回退动作。
|
||||
class CharacterActionLibrary {
|
||||
public:
|
||||
bool LoadForConfig(const character::CharacterConfig& config);
|
||||
const CharacterActionDefinition* FindAction(const std::string& actionId) const;
|
||||
const CharacterActionDefinition* GetDefaultAction() const;
|
||||
std::string DescribeActionIds() const;
|
||||
|
||||
private:
|
||||
void BuildFallbackActions(const character::CharacterConfig& config);
|
||||
bool TryLoadPvfActionScripts(const character::CharacterConfig& config);
|
||||
|
||||
std::map<std::string, CharacterActionDefinition> actions_;
|
||||
|
||||
@@ -21,12 +21,13 @@ public:
|
||||
const character::CharacterConfig& config,
|
||||
const CharacterEquipmentManager& equipmentManager);
|
||||
|
||||
void SetAction(const std::string& actionName);
|
||||
bool SetAction(const std::string& actionName);
|
||||
void SetDirection(int direction);
|
||||
const std::string& GetCurrentAction() const { return currentActionTag_; }
|
||||
bool HasAction(const std::string& actionName) const {
|
||||
return actionAnimations_.count(actionName) > 0;
|
||||
}
|
||||
std::string DescribeAvailableActions() const;
|
||||
|
||||
private:
|
||||
static std::string FormatImgPath(std::string path, Animation::ReplaceData data);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace frostbite2D {
|
||||
class CharacterInputRouter {
|
||||
public:
|
||||
bool OnKeyDown(const KeyEvent& event);
|
||||
bool OnKeyUp(KeyCode keyCode);
|
||||
bool OnKeyUp(const KeyUpEvent& event);
|
||||
bool OnJoystickAxis(const JoystickAxisEvent& event);
|
||||
bool OnJoystickButtonDown(const JoystickButtonDownEvent& event);
|
||||
bool OnJoystickButtonUp(const JoystickButtonUpEvent& event);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "character/CharacterInputRouter.h"
|
||||
#include "character/CharacterStateMachine.h"
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/event/joystick_event.h>
|
||||
#include <frostbite2D/event/key_event.h>
|
||||
#include <optional>
|
||||
@@ -14,26 +15,76 @@
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/// @brief 角色系统的主聚合对象。
|
||||
///
|
||||
/// 这个类可以理解为“角色外壳”:
|
||||
/// - 输入事件先进入 `OnEvent()`,再由 `CharacterInputRouter` 转成统一命令
|
||||
/// - `OnUpdate()` 把命令整理成意图,交给 `CharacterStateMachine` 决策
|
||||
/// - `CharacterMotor` 负责推进逻辑世界坐标
|
||||
/// - 最后通过 `Actor` 和 `CharacterAnimation` 把结果表现到屏幕上
|
||||
///
|
||||
/// 所以阅读这个类时,可以重点抓住两条主线:
|
||||
/// 1. 数据如何从输入流到状态机
|
||||
/// 2. 状态机如何把逻辑结果投影成最终动画和位置
|
||||
class CharacterObject : public Actor {
|
||||
public:
|
||||
CharacterObject() = default;
|
||||
~CharacterObject() override = default;
|
||||
|
||||
/// @brief 二段初始化角色。
|
||||
/// @param jobId 职业 id,用来加载职业配置、动作定义和动画资源。
|
||||
/// @return 初始化成功返回 true。
|
||||
bool Construction(int jobId);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 表现与外部驱动入口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// @brief 直接切换当前播放的资源动作标签。
|
||||
///
|
||||
/// 这里操作的是“动画表现层名字”,不一定等于逻辑动作 id。
|
||||
void SetAction(const std::string& actionName);
|
||||
|
||||
/// @brief 设置朝向,并同步到动画镜像。
|
||||
/// @param direction 大于等于 0 视为朝右,小于 0 视为朝左。
|
||||
void SetDirection(int direction);
|
||||
|
||||
/// @brief 只改地面平面上的 x/y,再重新投影到屏幕。
|
||||
void SetCharacterPosition(const Vec2& pos);
|
||||
|
||||
/// @brief 写入逻辑世界坐标,并把它转换成屏幕坐标和遮挡顺序。
|
||||
void SetWorldPosition(const CharacterWorldPosition& pos);
|
||||
|
||||
/// @brief 向输入缓冲区手动压入一条命令。
|
||||
///
|
||||
/// 一般用于 AI、脚本或测试代码模拟输入,而不是直接发 SDL 事件。
|
||||
void PushCommand(const CharacterCommand& command);
|
||||
|
||||
/// @brief 处理一次受击请求。
|
||||
///
|
||||
/// 这里会先检查无敌、霸体、当前动作是否允许被打断,再决定是否强制切到受击状态。
|
||||
void ApplyHit(const HitContext& hit);
|
||||
|
||||
/// @brief 开关输入处理。
|
||||
///
|
||||
/// 关闭后角色仍会继续更新状态和运动,只是不再消费外部输入事件。
|
||||
void SetInputEnabled(bool enabled) { inputEnabled_ = enabled; }
|
||||
bool IsInputEnabled() const { return inputEnabled_; }
|
||||
|
||||
/// @brief 角色每帧主循环。
|
||||
///
|
||||
/// 顺序固定为:命令缓冲推进 -> 采样实时输入 -> 生成意图 -> 状态机更新
|
||||
/// -> motor 推进 -> 投影到 Actor 坐标。
|
||||
void OnUpdate(float deltaTime) override;
|
||||
bool OnKeyDown(const KeyEvent& event) override;
|
||||
bool OnKeyUp(const KeyEvent& event) override;
|
||||
bool OnJoystickAxis(const JoystickEvent& event) override;
|
||||
bool OnJoystickButtonDown(const JoystickEvent& event) override;
|
||||
bool OnJoystickButtonUp(const JoystickEvent& event) override;
|
||||
|
||||
/// @brief 统一输入入口。
|
||||
///
|
||||
/// `Actor` 底层会把不同 SDL 事件派发到这里;本类再把它们转交给 `CharacterInputRouter`。
|
||||
bool OnEvent(const Event& event) override;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 运行时查询接口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int GetJobId() const { return jobId_; }
|
||||
int GetGrowType() const { return growType_; }
|
||||
@@ -46,13 +97,37 @@ public:
|
||||
CharacterMotor& GetMotorMutable() { return motor_; }
|
||||
CharacterCommandBuffer& GetCommandBufferMutable() { return commandBuffer_; }
|
||||
float GetLastDeltaTime() const { return lastDeltaTime_; }
|
||||
|
||||
/// @brief 按逻辑动作 id 查询动作定义。
|
||||
const CharacterActionDefinition* FindAction(const std::string& actionId) const;
|
||||
std::string ResolveAnimationTag(const std::string& preferred,
|
||||
const std::string& fallback) const;
|
||||
|
||||
/// @brief 严格查询逻辑动作;不存在时会输出详细错误并请求退出程序。
|
||||
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);
|
||||
|
||||
/// @brief 输出角色动作/动画的致命错误,并请求应用退出。
|
||||
void ReportFatalCharacterError(const char* phase,
|
||||
const char* reason,
|
||||
const std::string& requestedActionId = std::string(),
|
||||
const std::string& requestedAnimationTag = std::string())
|
||||
const;
|
||||
|
||||
/// @brief 触发状态生命周期钩子。
|
||||
///
|
||||
/// 当前为空实现,预留给后续脚本系统接管 `on_enter/on_update/on_exit`。
|
||||
void RunStateHook(const std::string& phase,
|
||||
CharacterStateId stateId,
|
||||
const CharacterActionDefinition* action);
|
||||
|
||||
/// @brief 同步逻辑朝向到 motor 与动画。
|
||||
void SetFacing(int direction);
|
||||
|
||||
const character::CharacterConfig* GetConfig() const {
|
||||
@@ -61,21 +136,58 @@ public:
|
||||
const CharacterEquipmentManager& GetEquipmentManager() const { return equipmentManager_; }
|
||||
|
||||
private:
|
||||
bool SetActionStrict(const std::string& actionName,
|
||||
const char* phase,
|
||||
const std::string& requestedActionId);
|
||||
std::string DescribeConfiguredAnimationTags() const;
|
||||
|
||||
/// 职业 id,用于加载职业配置、动作表和资源。
|
||||
int jobId_ = -1;
|
||||
|
||||
/// 转职/成长阶段,当前项目里先保留这个槽位。
|
||||
int growType_ = -1;
|
||||
|
||||
/// 表现层朝向:1 表示右,-1 表示左。
|
||||
int direction_ = 1;
|
||||
|
||||
/// 当前正在播放的资源动作标签,不一定等于逻辑 actionId。
|
||||
std::string currentAction_;
|
||||
|
||||
/// 角色静态配置,包含职业、动画路径等加载结果。
|
||||
std::optional<character::CharacterConfig> config_;
|
||||
|
||||
/// 负责拼装装备部件,决定分层 Avatar 的表现资源。
|
||||
CharacterEquipmentManager equipmentManager_;
|
||||
|
||||
/// 动作定义表:把逻辑 actionId 映射到帧数据、取消规则、动画标签等。
|
||||
CharacterActionLibrary actionLibrary_;
|
||||
|
||||
/// 输入适配层,把键盘/手柄事件收敛成统一命令。
|
||||
CharacterInputRouter inputRouter_;
|
||||
|
||||
/// 输入缓冲区,支持预输入和连段窗口。
|
||||
CharacterCommandBuffer commandBuffer_;
|
||||
|
||||
/// 当前帧从命令缓冲整理出的“角色意图”。
|
||||
CharacterIntent currentIntent_;
|
||||
|
||||
/// 状态调度器,负责状态切换、动作进入和帧事件推进。
|
||||
CharacterStateMachine stateMachine_;
|
||||
|
||||
/// 纯运动数据:位置、速度、落地状态和朝向都在这里维护。
|
||||
CharacterMotor motor_;
|
||||
|
||||
/// 战斗附加状态,如霸体、无敌、控制锁定。
|
||||
CharacterStatus status_;
|
||||
|
||||
/// 真正负责播放角色分层动画的 Actor 子节点。
|
||||
RefPtr<CharacterAnimation> animationManager_ = nullptr;
|
||||
|
||||
/// 缓存上一帧 deltaTime,方便状态和脚本系统查询。
|
||||
float lastDeltaTime_ = 0.0f;
|
||||
|
||||
/// 是否允许从事件系统接收输入;AI/剧情接管时可临时关闭。
|
||||
bool inputEnabled_ = true;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
||||
@@ -57,9 +57,7 @@ protected:
|
||||
void ChangeState(CharacterStateMachine& machine,
|
||||
CharacterStateContext& context,
|
||||
CharacterStateId nextState) const;
|
||||
void PlayActionById(CharacterObject& owner,
|
||||
const std::string& actionId,
|
||||
const std::string& fallbackTag = "rest") const;
|
||||
void PlayActionById(CharacterObject& owner, const std::string& actionId) const;
|
||||
void InvokeHook(CharacterStateMachine& machine,
|
||||
CharacterObject& owner,
|
||||
const CharacterActionDefinition* action,
|
||||
|
||||
Reference in New Issue
Block a user