新增 CharacterShadowActor 类用于处理角色阴影的渲染 在 CharacterObject 中实现阴影的同步和渲染逻辑 移除 GameDebugActor 中不再使用的合成纹理预览代码 添加 EnsureCompositeTextureReady 方法确保纹理准备就绪
262 lines
11 KiB
C++
262 lines
11 KiB
C++
#pragma once
|
||
|
||
#include "character/CharacterActionLibrary.h"
|
||
#include "character/CharacterAnimation.h"
|
||
#include "character/CharacterDataLoader.h"
|
||
#include "character/CharacterEquipmentManager.h"
|
||
#include "character/CharacterInputRouter.h"
|
||
#include "character/CharacterShadowActor.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 {
|
||
|
||
class GameMap;
|
||
|
||
/// @brief 角色系统的主聚合对象。
|
||
///
|
||
/// 这个类可以理解为“角色外壳”:
|
||
/// - 输入事件先进入 `OnEvent()`,再由 `CharacterInputRouter` 转成统一命令
|
||
/// - `OnUpdate()` 把命令整理成意图,交给 `CharacterStateMachine` 决策
|
||
/// - `CharacterMotor` 负责推进逻辑世界坐标
|
||
/// - 最后通过 `Actor` 和 `CharacterAnimation` 把结果表现到屏幕上
|
||
///
|
||
/// 所以阅读这个类时,可以重点抓住两条主线:
|
||
/// 1. 数据如何从输入流到状态机
|
||
/// 2. 状态机如何把逻辑结果投影成最终动画和位置
|
||
class CharacterObject : public Actor {
|
||
public:
|
||
CharacterObject() = default;
|
||
~CharacterObject() override;
|
||
|
||
/// @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 Update(float deltaTime) override;
|
||
void OnUpdate(float deltaTime) override;
|
||
void OnAdded(Actor* parent) override;
|
||
void PrepareRenderFrame();
|
||
|
||
/// @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<Texture> 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();
|
||
void SyncShadowAttachment();
|
||
void SyncShadowPresentation();
|
||
void DetachShadowActor();
|
||
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;
|
||
|
||
/// ???? normal ???????????
|
||
RefPtr<CharacterShadowActor> shadowActor_ = nullptr;
|
||
GameMap* shadowAttachedMap_ = nullptr;
|
||
|
||
/// 缓存上一帧 deltaTime,方便状态和脚本系统查询。
|
||
float lastDeltaTime_ = 0.0f;
|
||
|
||
/// 是否允许从事件系统接收输入;AI/剧情接管时可临时关闭。
|
||
bool inputEnabled_ = true;
|
||
|
||
friend class CharacterStateMachine;
|
||
};
|
||
|
||
} // namespace frostbite2D
|