Files
Frostbite2D/Game/include/character/CharacterObject.h
Lenheart 5e80df040b feat(animation): 添加动画状态回调支持
refactor(character): 重构角色动作处理逻辑

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

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

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

refactor(movement): 重构移动系统支持行走/奔跑模式
2026-04-04 14:45:41 +08:00

239 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "character/CharacterActionLibrary.h"
#include "character/CharacterAnimation.h"
#include "character/CharacterDataLoader.h"
#include "character/CharacterEquipmentManager.h"
#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>
#include <string>
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;
/// @brief 统一输入入口。
///
/// `Actor` 底层会把不同 SDL 事件派发到这里;本类再把它们转交给 `CharacterInputRouter`。
bool OnEvent(const Event& event) override;
// ---------------------------------------------------------------------------
// 运行时查询接口
// ---------------------------------------------------------------------------
int GetJobId() const { return jobId_; }
int GetGrowType() const { return growType_; }
int GetDirection() const { return direction_; }
const std::string& GetCurrentAction() const { return currentAction_; }
CharacterStateId GetState() const { return stateMachine_.GetState(); }
const CharacterIntent& GetCurrentIntent() const { return currentIntent_; }
const CharacterWorldPosition& GetWorldPosition() const { return motor_.position; }
const CharacterMotor& GetMotor() const { return motor_; }
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;
/// @brief 严格查询逻辑动作;不存在时会输出详细错误并请求退出程序。
const CharacterActionDefinition* RequireAction(const std::string& actionId,
const char* phase) const;
/// @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,
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 {
return config_ ? &config_.value() : nullptr;
}
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);
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_;
/// 动作定义表:保存显式注册的逻辑动作入口。
CharacterActionLibrary actionLibrary_;
/// 输入适配层,把键盘/手柄事件收敛成统一命令。
CharacterInputRouter inputRouter_;
/// 输入缓冲区,支持预输入和连段窗口。
CharacterCommandBuffer commandBuffer_;
/// 当前帧从命令缓冲整理出的“角色意图”。
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_;
/// 纯运动数据:位置、速度、落地状态和朝向都在这里维护。
CharacterMotor motor_;
/// 战斗附加状态,如霸体、无敌、控制锁定。
CharacterStatus status_;
/// 真正负责播放角色分层动画的 Actor 子节点。
RefPtr<CharacterAnimation> animationManager_ = nullptr;
/// 缓存上一帧 deltaTime方便状态和脚本系统查询。
float lastDeltaTime_ = 0.0f;
/// 是否允许从事件系统接收输入AI/剧情接管时可临时关闭。
bool inputEnabled_ = true;
friend class CharacterStateMachine;
};
} // namespace frostbite2D