refactor(character): 重构角色动作与动画系统

- 移除自动回退动作生成逻辑,改为严格检查动作定义
- 增加动作资源缺失时的详细错误报告机制
- 统一输入事件处理接口,优化角色对象生命周期管理
- 改进动画标签管理,移除隐式回退逻辑
- 增强状态机对无效动作的处理能力
This commit is contained in:
2026-04-04 05:53:07 +08:00
parent f64180ebed
commit 1200cf0181
13 changed files with 380 additions and 202 deletions

View File

@@ -4,6 +4,7 @@
#include <frostbite2D/resource/script_parser.h>
#include <algorithm>
#include <cctype>
#include <sstream>
#include <vector>
namespace frostbite2D {
@@ -227,9 +228,6 @@ void CharacterCommandBuffer::Clear() {
bool CharacterActionLibrary::LoadForConfig(const character::CharacterConfig& config) {
actions_.clear();
// 先填一套最小可运行的默认动作,保证资源或 PVF 缺失时角色仍能工作。
BuildFallbackActions(config);
// 再尝试用 PVF 动作逻辑覆盖默认值。
TryLoadPvfActionScripts(config);
return !actions_.empty();
}
@@ -247,95 +245,22 @@ const CharacterActionDefinition* CharacterActionLibrary::GetDefaultAction() cons
return FindAction("idle");
}
void CharacterActionLibrary::BuildFallbackActions(
const character::CharacterConfig& config) {
auto animationTagOrDefault = [&config](const std::string& actionId,
const std::string& fallback) {
if (config.animationPath.count(actionId) > 0) {
return actionId;
std::string CharacterActionLibrary::DescribeActionIds() const {
if (actions_.empty()) {
return "<none>";
}
std::ostringstream stream;
bool first = true;
for (const auto& [actionId, definition] : actions_) {
(void)definition;
if (!first) {
stream << ", ";
}
if (config.animationPath.count(fallback) > 0) {
return fallback;
}
if (!config.animationPath.empty()) {
return config.animationPath.begin()->first;
}
return fallback;
};
CharacterActionDefinition idle;
idle.actionId = "idle";
idle.animationTag = animationTagOrDefault("rest", "rest");
idle.totalFrames = 1;
idle.loop = true;
idle.canMove = true;
addOrReplaceAction(actions_, idle);
CharacterActionDefinition move;
move.actionId = "move";
move.animationTag = animationTagOrDefault("run", animationTagOrDefault("walk", "rest"));
move.totalFrames = 1;
move.loop = true;
move.canMove = true;
addOrReplaceAction(actions_, move);
CharacterActionDefinition jump;
jump.actionId = "jump";
jump.animationTag = animationTagOrDefault("jump", "rest");
jump.totalFrames = 18;
jump.canTurn = true;
addOrReplaceAction(actions_, jump);
CharacterActionDefinition fall;
fall.actionId = "fall";
fall.animationTag = animationTagOrDefault("jump", "rest");
fall.totalFrames = 1;
fall.loop = true;
fall.canTurn = true;
addOrReplaceAction(actions_, fall);
CharacterActionDefinition landing;
landing.actionId = "landing";
landing.animationTag = animationTagOrDefault("rest", "rest");
landing.totalFrames = 6;
addOrReplaceAction(actions_, landing);
CharacterActionDefinition attack1;
attack1.actionId = "attack_1";
attack1.animationTag = animationTagOrDefault("attack1", animationTagOrDefault("attack", "rest"));
attack1.totalFrames = 24;
attack1.canMove = false;
attack1.canTurn = false;
attack1.canBeInterrupted = true;
attack1.cancelRules.push_back({"attack_2", 12, 22, true, false});
addOrReplaceAction(actions_, attack1);
CharacterActionDefinition attack2;
attack2.actionId = "attack_2";
attack2.animationTag = animationTagOrDefault("attack2", attack1.animationTag);
attack2.totalFrames = 28;
attack2.canMove = false;
attack2.canTurn = false;
attack2.canBeInterrupted = true;
addOrReplaceAction(actions_, attack2);
CharacterActionDefinition skill1;
skill1.actionId = "skill_1";
skill1.animationTag = animationTagOrDefault("skill1", attack1.animationTag);
skill1.totalFrames = 36;
skill1.canMove = false;
skill1.canTurn = false;
skill1.canBeInterrupted = false;
addOrReplaceAction(actions_, skill1);
CharacterActionDefinition hurt;
hurt.actionId = "hurt_light";
hurt.animationTag = animationTagOrDefault("damage", animationTagOrDefault("hurt", "rest"));
hurt.totalFrames = 18;
hurt.canMove = false;
hurt.canTurn = false;
hurt.canBeInterrupted = true;
addOrReplaceAction(actions_, hurt);
stream << actionId;
first = false;
}
return stream.str();
}
bool CharacterActionLibrary::TryLoadPvfActionScripts(
@@ -413,12 +338,6 @@ bool CharacterActionLibrary::TryLoadPvfActionScripts(
}
}
if (definition.animationTag.empty()) {
// 如果动作脚本没有显式指定动画标签,则尝试用同名动作映射。
if (config.animationPath.count(actionId) > 0) {
definition.animationTag = actionId;
}
}
addOrReplaceAction(actions_, std::move(definition));
}