feat(character): 实现角色状态机与输入路由系统
新增角色状态机框架,包含空闲、移动、跳跃、攻击等状态 添加输入路由组件,统一处理键盘和手柄输入 引入动作库管理角色动作定义,支持PVF脚本配置 重构角色对象,整合运动器、状态机和输入处理 修复Actor子节点排序时的迭代安全问题
This commit is contained in:
@@ -4,10 +4,18 @@
|
||||
namespace frostbite2D {
|
||||
|
||||
bool CharacterObject::Construction(int jobId) {
|
||||
EnableEventReceive();
|
||||
RemoveAllChildren();
|
||||
animationManager_ = nullptr;
|
||||
config_.reset();
|
||||
currentAction_.clear();
|
||||
actionLibrary_ = CharacterActionLibrary();
|
||||
commandBuffer_.Clear();
|
||||
currentIntent_ = CharacterIntent();
|
||||
stateMachine_.Reset();
|
||||
motor_ = CharacterMotor();
|
||||
status_ = CharacterStatus();
|
||||
lastDeltaTime_ = 0.0f;
|
||||
|
||||
auto config = character::loadCharacterConfig(jobId);
|
||||
if (!config) {
|
||||
@@ -21,6 +29,9 @@ bool CharacterObject::Construction(int jobId) {
|
||||
direction_ = 1;
|
||||
config_ = *config;
|
||||
equipmentManager_.Init(config_->baseJobConfig);
|
||||
if (auto actionLibrary = loadCharacterActionLibrary(*config_)) {
|
||||
actionLibrary_ = *actionLibrary;
|
||||
}
|
||||
|
||||
animationManager_ = MakePtr<CharacterAnimation>();
|
||||
if (!animationManager_->Init(this, *config_, equipmentManager_)) {
|
||||
@@ -29,13 +40,16 @@ bool CharacterObject::Construction(int jobId) {
|
||||
}
|
||||
AddChild(animationManager_);
|
||||
|
||||
if (config_->animationPath.count("rest") > 0) {
|
||||
if (const auto* idleAction = actionLibrary_.GetDefaultAction()) {
|
||||
PlayAnimationTag(idleAction->animationTag);
|
||||
} else if (config_->animationPath.count("rest") > 0) {
|
||||
SetAction("rest");
|
||||
} else if (!config_->animationPath.empty()) {
|
||||
SetAction(config_->animationPath.begin()->first);
|
||||
}
|
||||
|
||||
SetDirection(1);
|
||||
SetWorldPosition({});
|
||||
SetFacing(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,8 +68,113 @@ void CharacterObject::SetDirection(int direction) {
|
||||
}
|
||||
|
||||
void CharacterObject::SetCharacterPosition(const Vec2& pos) {
|
||||
SetPosition(pos);
|
||||
motor_.SetGroundPosition(pos);
|
||||
SetWorldPosition(motor_.position);
|
||||
}
|
||||
|
||||
void CharacterObject::SetWorldPosition(const CharacterWorldPosition& pos) {
|
||||
motor_.position = pos;
|
||||
// 逻辑世界坐标是 x/y/z,真正渲染时再投影到屏幕坐标。
|
||||
SetPosition(pos.ToScreenPosition());
|
||||
// 地面 y 继续决定同层对象前后遮挡顺序。
|
||||
SetZOrder(static_cast<int>(pos.y));
|
||||
}
|
||||
|
||||
void CharacterObject::PushCommand(const CharacterCommand& command) {
|
||||
commandBuffer_.Submit(command);
|
||||
}
|
||||
|
||||
void CharacterObject::ApplyHit(const HitContext& hit) {
|
||||
if (status_.invincible) {
|
||||
return;
|
||||
}
|
||||
if (status_.superArmor && !hit.ignoreInterrupt) {
|
||||
return;
|
||||
}
|
||||
if (!hit.ignoreInterrupt && !stateMachine_.CanBeInterrupted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CharacterActionDefinition* hurtAction = FindAction(hit.hurtAction);
|
||||
if (!hurtAction) {
|
||||
hurtAction = FindAction("hurt_light");
|
||||
}
|
||||
|
||||
motor_.verticalVelocity = hit.launchZVelocity;
|
||||
if (hit.launchZVelocity > 0.0f) {
|
||||
motor_.grounded = false;
|
||||
}
|
||||
stateMachine_.ForceHurt(hurtAction);
|
||||
PlayAnimationTag(hurtAction ? hurtAction->animationTag
|
||||
: ResolveAnimationTag("damage", "rest"));
|
||||
}
|
||||
|
||||
void CharacterObject::OnUpdate(float deltaTime) {
|
||||
// 角色主循环顺序固定为:
|
||||
// 输入采样 -> 缓冲推进 -> 意图生成 -> 状态机决策 -> motor 推进 -> 投影到屏幕。
|
||||
lastDeltaTime_ = deltaTime;
|
||||
commandBuffer_.Advance(deltaTime);
|
||||
inputRouter_.EmitCommands(commandBuffer_);
|
||||
currentIntent_ = commandBuffer_.BuildIntent();
|
||||
stateMachine_.Update(*this, commandBuffer_, currentIntent_, deltaTime);
|
||||
motor_.Update(deltaTime);
|
||||
SetWorldPosition(motor_.position);
|
||||
SetFacing(motor_.facing);
|
||||
}
|
||||
|
||||
bool CharacterObject::OnKeyDown(const KeyEvent& event) {
|
||||
if (event.isRepeat()) {
|
||||
return false;
|
||||
}
|
||||
return inputRouter_.OnKeyDown(event);
|
||||
}
|
||||
|
||||
bool CharacterObject::OnKeyUp(const KeyEvent& event) {
|
||||
return inputRouter_.OnKeyUp(event.getKeyCode());
|
||||
}
|
||||
|
||||
bool CharacterObject::OnJoystickAxis(const JoystickEvent& event) {
|
||||
return inputRouter_.OnJoystickAxis(static_cast<const JoystickAxisEvent&>(event));
|
||||
}
|
||||
|
||||
bool CharacterObject::OnJoystickButtonDown(const JoystickEvent& event) {
|
||||
return inputRouter_.OnJoystickButtonDown(
|
||||
static_cast<const JoystickButtonDownEvent&>(event));
|
||||
}
|
||||
|
||||
bool CharacterObject::OnJoystickButtonUp(const JoystickEvent& event) {
|
||||
return inputRouter_.OnJoystickButtonUp(
|
||||
static_cast<const JoystickButtonUpEvent&>(event));
|
||||
}
|
||||
|
||||
const CharacterActionDefinition* CharacterObject::FindAction(
|
||||
const std::string& actionId) const {
|
||||
return actionLibrary_.FindAction(actionId);
|
||||
}
|
||||
|
||||
std::string CharacterObject::ResolveAnimationTag(
|
||||
const std::string& preferred,
|
||||
const std::string& fallback) const {
|
||||
// 逻辑动作与资源动作是两层名字:优先选动作定义里的标签,不存在再回退。
|
||||
if (animationManager_ && animationManager_->HasAction(preferred)) {
|
||||
return preferred;
|
||||
}
|
||||
if (animationManager_ && animationManager_->HasAction(fallback)) {
|
||||
return fallback;
|
||||
}
|
||||
if (config_ && !config_->animationPath.empty()) {
|
||||
return config_->animationPath.begin()->first;
|
||||
}
|
||||
return preferred;
|
||||
}
|
||||
|
||||
void CharacterObject::PlayAnimationTag(const std::string& actionTag) {
|
||||
SetAction(actionTag);
|
||||
}
|
||||
|
||||
void CharacterObject::SetFacing(int direction) {
|
||||
motor_.facing = direction >= 0 ? 1 : -1;
|
||||
SetDirection(motor_.facing);
|
||||
}
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
||||
Reference in New Issue
Block a user