#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 #include #include #include #include #include namespace frostbite2D { class GameMap; /// @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; bool HasCompositeTexture() const; Ptr GetCompositeTexture() const; Vec2 GetCompositeTextureSize() const; Vec2 GetCompositeOriginInTexture() const; Vec2 GetCompositeGroundAnchorInTexture() const; uint64 GetCompositeTextureVersion() 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); GameMap* FindOwningMap() const; void ApplyMapMovementConstraints(const CharacterWorldPosition& previousPosition); void QueueMapTransitionIfNeeded(); void SyncActorPositionFromWorld(); 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 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 animationManager_ = nullptr; /// 缓存上一帧 deltaTime,方便状态和脚本系统查询。 float lastDeltaTime_ = 0.0f; /// 是否允许从事件系统接收输入;AI/剧情接管时可临时关闭。 bool inputEnabled_ = true; friend class CharacterStateMachine; }; } // namespace frostbite2D