refactor(character): 重构角色动作与动画系统
- 移除自动回退动作生成逻辑,改为严格检查动作定义 - 增加动作资源缺失时的详细错误报告机制 - 统一输入事件处理接口,优化角色对象生命周期管理 - 改进动画标签管理,移除隐式回退逻辑 - 增强状态机对无效动作的处理能力
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
|
||||
namespace frostbite2D {
|
||||
namespace {
|
||||
@@ -51,12 +52,6 @@ bool CharacterAnimation::Init(CharacterObject* parent,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionAnimations_.count("rest") > 0) {
|
||||
SetAction("rest");
|
||||
} else {
|
||||
SetAction(actionAnimations_.begin()->first);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -129,13 +124,37 @@ void CharacterAnimation::CreateAnimationBySlot(
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimation::SetAction(const std::string& actionName) {
|
||||
std::string CharacterAnimation::DescribeAvailableActions() const {
|
||||
if (actionAnimations_.empty()) {
|
||||
return "<none>";
|
||||
}
|
||||
|
||||
std::ostringstream stream;
|
||||
bool first = true;
|
||||
for (const auto& [actionName, animations] : actionAnimations_) {
|
||||
(void)animations;
|
||||
if (!first) {
|
||||
stream << ", ";
|
||||
}
|
||||
stream << actionName;
|
||||
first = false;
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
bool CharacterAnimation::SetAction(const std::string& actionName) {
|
||||
auto nextIt = actionAnimations_.find(actionName);
|
||||
if (nextIt == actionAnimations_.end()) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CharacterAnimation: action %s missing, keep %s", actionName.c_str(),
|
||||
currentActionTag_.c_str());
|
||||
return;
|
||||
if (parent_) {
|
||||
parent_->ReportFatalCharacterError("CharacterAnimation::SetAction",
|
||||
"requested animation tag is not loaded",
|
||||
std::string(), actionName);
|
||||
} else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CharacterAnimation: action %s missing and parent is null",
|
||||
actionName.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!currentActionTag_.empty()) {
|
||||
@@ -154,6 +173,7 @@ void CharacterAnimation::SetAction(const std::string& actionName) {
|
||||
}
|
||||
currentActionTag_ = actionName;
|
||||
SetDirection(direction_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CharacterAnimation::SetDirection(int direction) {
|
||||
|
||||
@@ -46,8 +46,8 @@ bool CharacterInputRouter::OnKeyDown(const KeyEvent& event) {
|
||||
}
|
||||
}
|
||||
|
||||
bool CharacterInputRouter::OnKeyUp(KeyCode keyCode) {
|
||||
switch (keyCode) {
|
||||
bool CharacterInputRouter::OnKeyUp(const KeyUpEvent& event) {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyCode::a:
|
||||
case KeyCode::Left:
|
||||
left_ = false;
|
||||
|
||||
@@ -1,9 +1,42 @@
|
||||
#include "character/CharacterObject.h"
|
||||
#include "character/CharacterObject.h"
|
||||
#include <SDL2/SDL.h>
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace frostbite2D {
|
||||
namespace {
|
||||
|
||||
const char* StateIdToString(CharacterStateId stateId) {
|
||||
switch (stateId) {
|
||||
case CharacterStateId::Idle:
|
||||
return "Idle";
|
||||
case CharacterStateId::Move:
|
||||
return "Move";
|
||||
case CharacterStateId::Jump:
|
||||
return "Jump";
|
||||
case CharacterStateId::Fall:
|
||||
return "Fall";
|
||||
case CharacterStateId::Landing:
|
||||
return "Landing";
|
||||
case CharacterStateId::Action:
|
||||
return "Action";
|
||||
case CharacterStateId::Hurt:
|
||||
return "Hurt";
|
||||
case CharacterStateId::Dead:
|
||||
return "Dead";
|
||||
default:
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
const char* NonEmptyOrPlaceholder(const std::string& value, const char* placeholder) {
|
||||
return value.empty() ? placeholder : value.c_str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CharacterObject::Construction(int jobId) {
|
||||
// Reset all runtime state before rebuilding the character from config.
|
||||
EnableEventReceive();
|
||||
RemoveAllChildren();
|
||||
animationManager_ = nullptr;
|
||||
@@ -16,6 +49,7 @@ bool CharacterObject::Construction(int jobId) {
|
||||
motor_ = CharacterMotor();
|
||||
status_ = CharacterStatus();
|
||||
lastDeltaTime_ = 0.0f;
|
||||
inputEnabled_ = true;
|
||||
|
||||
auto config = character::loadCharacterConfig(jobId);
|
||||
if (!config) {
|
||||
@@ -36,17 +70,19 @@ bool CharacterObject::Construction(int jobId) {
|
||||
|
||||
animationManager_ = MakePtr<CharacterAnimation>();
|
||||
if (!animationManager_->Init(this, *config_, equipmentManager_)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CharacterObject: no layered avatar animations available, character will stay empty");
|
||||
ReportFatalCharacterError("CharacterObject::Construction",
|
||||
"no usable animation tags were loaded");
|
||||
return false;
|
||||
}
|
||||
AddChild(animationManager_);
|
||||
|
||||
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);
|
||||
const CharacterActionDefinition* idleAction = RequireAction(
|
||||
"idle", "CharacterObject::Construction");
|
||||
if (!idleAction) {
|
||||
return false;
|
||||
}
|
||||
if (!PlayActionDefinition(*idleAction, "CharacterObject::Construction")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetWorldPosition({});
|
||||
@@ -55,10 +91,35 @@ bool CharacterObject::Construction(int jobId) {
|
||||
}
|
||||
|
||||
void CharacterObject::SetAction(const std::string& actionName) {
|
||||
currentAction_ = actionName;
|
||||
if (animationManager_) {
|
||||
animationManager_->SetAction(actionName);
|
||||
SetActionStrict(actionName, "CharacterObject::SetAction", std::string());
|
||||
}
|
||||
|
||||
bool CharacterObject::SetActionStrict(const std::string& actionName,
|
||||
const char* phase,
|
||||
const std::string& requestedActionId) {
|
||||
if (actionName.empty()) {
|
||||
ReportFatalCharacterError(phase, "requested animation tag is empty",
|
||||
requestedActionId, actionName);
|
||||
return false;
|
||||
}
|
||||
if (!animationManager_) {
|
||||
ReportFatalCharacterError(phase, "animation manager is not initialized",
|
||||
requestedActionId, actionName);
|
||||
return false;
|
||||
}
|
||||
if (!animationManager_->HasAction(actionName)) {
|
||||
ReportFatalCharacterError(phase, "requested animation tag is not loaded",
|
||||
requestedActionId, actionName);
|
||||
return false;
|
||||
}
|
||||
if (!animationManager_->SetAction(actionName)) {
|
||||
ReportFatalCharacterError(phase, "failed to activate requested animation tag",
|
||||
requestedActionId, actionName);
|
||||
return false;
|
||||
}
|
||||
|
||||
currentAction_ = actionName;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CharacterObject::SetDirection(int direction) {
|
||||
@@ -75,9 +136,7 @@ void CharacterObject::SetCharacterPosition(const Vec2& pos) {
|
||||
|
||||
void CharacterObject::SetWorldPosition(const CharacterWorldPosition& pos) {
|
||||
motor_.position = pos;
|
||||
// 逻辑世界坐标是 x/y/z,真正渲染时再投影到屏幕坐标。
|
||||
SetPosition(pos.ToScreenPosition());
|
||||
// 地面 y 继续决定同层对象前后遮挡顺序。
|
||||
SetZOrder(static_cast<int>(pos.y));
|
||||
}
|
||||
|
||||
@@ -96,9 +155,10 @@ void CharacterObject::ApplyHit(const HitContext& hit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CharacterActionDefinition* hurtAction = FindAction(hit.hurtAction);
|
||||
const CharacterActionDefinition* hurtAction = RequireAction(
|
||||
hit.hurtAction, "CharacterObject::ApplyHit");
|
||||
if (!hurtAction) {
|
||||
hurtAction = FindAction("hurt_light");
|
||||
return;
|
||||
}
|
||||
|
||||
motor_.verticalVelocity = hit.launchZVelocity;
|
||||
@@ -109,8 +169,7 @@ void CharacterObject::ApplyHit(const HitContext& hit) {
|
||||
}
|
||||
|
||||
void CharacterObject::OnUpdate(float deltaTime) {
|
||||
// 角色主循环顺序固定为:
|
||||
// 输入采样 -> 缓冲推进 -> 意图生成 -> 状态机决策 -> motor 推进 -> 投影到屏幕。
|
||||
// Fixed update order keeps input, state transitions, and motion deterministic.
|
||||
lastDeltaTime_ = deltaTime;
|
||||
commandBuffer_.Advance(deltaTime);
|
||||
inputRouter_.EmitCommands(commandBuffer_);
|
||||
@@ -121,29 +180,32 @@ void CharacterObject::OnUpdate(float deltaTime) {
|
||||
SetFacing(motor_.facing);
|
||||
}
|
||||
|
||||
bool CharacterObject::OnKeyDown(const KeyEvent& event) {
|
||||
if (event.isRepeat()) {
|
||||
bool CharacterObject::OnEvent(const Event& event) {
|
||||
if (!IsEventReceiveEnabled() || !inputEnabled_) {
|
||||
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));
|
||||
switch (event.getType()) {
|
||||
case EventType::KeyDown: {
|
||||
const auto& keyEvent = static_cast<const KeyEvent&>(event);
|
||||
if (keyEvent.isRepeat()) {
|
||||
return false;
|
||||
}
|
||||
return inputRouter_.OnKeyDown(keyEvent);
|
||||
}
|
||||
case EventType::KeyUp:
|
||||
return inputRouter_.OnKeyUp(static_cast<const KeyUpEvent&>(event));
|
||||
case EventType::JoystickAxis:
|
||||
return inputRouter_.OnJoystickAxis(static_cast<const JoystickAxisEvent&>(event));
|
||||
case EventType::JoystickButtonDown:
|
||||
return inputRouter_.OnJoystickButtonDown(
|
||||
static_cast<const JoystickButtonDownEvent&>(event));
|
||||
case EventType::JoystickButtonUp:
|
||||
return inputRouter_.OnJoystickButtonUp(
|
||||
static_cast<const JoystickButtonUpEvent&>(event));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterActionDefinition* CharacterObject::FindAction(
|
||||
@@ -151,24 +213,78 @@ const CharacterActionDefinition* CharacterObject::FindAction(
|
||||
return actionLibrary_.FindAction(actionId);
|
||||
}
|
||||
|
||||
std::string CharacterObject::ResolveAnimationTag(
|
||||
const std::string& preferred,
|
||||
const std::string& fallback) const {
|
||||
// 逻辑动作与资源动作是两层名字:优先选动作定义里的标签,不存在再回退。
|
||||
if (animationManager_ && animationManager_->HasAction(preferred)) {
|
||||
return preferred;
|
||||
const CharacterActionDefinition* CharacterObject::RequireAction(
|
||||
const std::string& actionId,
|
||||
const char* phase) const {
|
||||
const CharacterActionDefinition* action = FindAction(actionId);
|
||||
if (!action) {
|
||||
ReportFatalCharacterError(phase, "requested logical action does not exist", actionId);
|
||||
return nullptr;
|
||||
}
|
||||
if (animationManager_ && animationManager_->HasAction(fallback)) {
|
||||
return fallback;
|
||||
return action;
|
||||
}
|
||||
|
||||
bool CharacterObject::PlayActionDefinition(const CharacterActionDefinition& action,
|
||||
const char* phase) {
|
||||
if (action.animationTag.empty()) {
|
||||
ReportFatalCharacterError(phase, "logical action has empty animationTag",
|
||||
action.actionId, action.animationTag);
|
||||
return false;
|
||||
}
|
||||
if (config_ && !config_->animationPath.empty()) {
|
||||
return config_->animationPath.begin()->first;
|
||||
}
|
||||
return preferred;
|
||||
return SetActionStrict(action.animationTag, phase, action.actionId);
|
||||
}
|
||||
|
||||
void CharacterObject::PlayAnimationTag(const std::string& actionTag) {
|
||||
SetAction(actionTag);
|
||||
SetActionStrict(actionTag, "CharacterObject::PlayAnimationTag", std::string());
|
||||
}
|
||||
|
||||
std::string CharacterObject::DescribeConfiguredAnimationTags() const {
|
||||
if (!config_ || config_->animationPath.empty()) {
|
||||
return "<none>";
|
||||
}
|
||||
|
||||
std::ostringstream stream;
|
||||
bool first = true;
|
||||
for (const auto& [actionTag, path] : config_->animationPath) {
|
||||
(void)path;
|
||||
if (!first) {
|
||||
stream << ", ";
|
||||
}
|
||||
stream << actionTag;
|
||||
first = false;
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
void CharacterObject::ReportFatalCharacterError(
|
||||
const char* phase,
|
||||
const char* reason,
|
||||
const std::string& requestedActionId,
|
||||
const std::string& requestedAnimationTag) const {
|
||||
std::ostringstream stream;
|
||||
stream << "CharacterObject fatal error: "
|
||||
<< (reason && reason[0] != '\0' ? reason : "unknown error") << '\n'
|
||||
<< " phase: " << (phase && phase[0] != '\0' ? phase : "<unknown>") << '\n'
|
||||
<< " jobId: " << jobId_ << '\n'
|
||||
<< " jobTag: "
|
||||
<< (config_ ? NonEmptyOrPlaceholder(config_->jobTag, "<empty>") : "<config not loaded>")
|
||||
<< '\n'
|
||||
<< " state: " << StateIdToString(stateMachine_.GetState()) << '\n'
|
||||
<< " currentActionId: "
|
||||
<< NonEmptyOrPlaceholder(stateMachine_.GetCurrentActionId(), "<none>") << '\n'
|
||||
<< " currentAnimationTag: " << NonEmptyOrPlaceholder(currentAction_, "<none>") << '\n'
|
||||
<< " requestedActionId: "
|
||||
<< NonEmptyOrPlaceholder(requestedActionId, "<none>") << '\n'
|
||||
<< " requestedAnimationTag: "
|
||||
<< NonEmptyOrPlaceholder(requestedAnimationTag, "<empty>") << '\n'
|
||||
<< " loadedActionIds: " << actionLibrary_.DescribeActionIds() << '\n'
|
||||
<< " configuredAnimationTags: " << DescribeConfiguredAnimationTags() << '\n'
|
||||
<< " loadedAnimationTags: "
|
||||
<< (animationManager_ ? animationManager_->DescribeAvailableActions()
|
||||
: std::string("<animation manager not initialized>"));
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", stream.str().c_str());
|
||||
Application::get().quit();
|
||||
}
|
||||
|
||||
void CharacterObject::RunStateHook(const std::string& phase,
|
||||
@@ -177,7 +293,6 @@ void CharacterObject::RunStateHook(const std::string& phase,
|
||||
(void)phase;
|
||||
(void)stateId;
|
||||
(void)action;
|
||||
// 预留给后续脚本系统;当前默认由 C++ 状态节点接管生命周期逻辑。
|
||||
}
|
||||
|
||||
void CharacterObject::SetFacing(int direction) {
|
||||
|
||||
@@ -35,9 +35,15 @@ void CharacterStateMachine::Update(CharacterObject& owner,
|
||||
|
||||
void CharacterStateMachine::ForceHurt(CharacterObject& owner,
|
||||
const CharacterActionDefinition* hurtAction) {
|
||||
if (!hurtAction) {
|
||||
owner.ReportFatalCharacterError("CharacterStateMachine::ForceHurt",
|
||||
"hurt action definition is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const CharacterActionDefinition* previousAction = currentAction_;
|
||||
currentAction_ = hurtAction;
|
||||
currentActionId_ = hurtAction ? hurtAction->actionId : std::string("hurt_light");
|
||||
currentActionId_ = hurtAction->actionId;
|
||||
if (currentState_ == CharacterStateId::Hurt) {
|
||||
InvokeStateHook(owner, previousAction, "on_exit");
|
||||
stateTime_ = 0.0f;
|
||||
@@ -123,9 +129,15 @@ void CharacterStateMachine::ChangeState(CharacterObject& owner,
|
||||
|
||||
void CharacterStateMachine::EnterAction(CharacterObject& owner,
|
||||
const CharacterActionDefinition* action) {
|
||||
if (!action) {
|
||||
owner.ReportFatalCharacterError("CharacterStateMachine::EnterAction",
|
||||
"action definition is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const CharacterActionDefinition* previousAction = currentAction_;
|
||||
currentAction_ = action;
|
||||
currentActionId_ = action ? action->actionId : std::string();
|
||||
currentActionId_ = action->actionId;
|
||||
if (currentState_ == CharacterStateId::Action) {
|
||||
InvokeStateHook(owner, previousAction, "on_exit");
|
||||
stateTime_ = 0.0f;
|
||||
@@ -158,7 +170,8 @@ bool CharacterStateMachine::TryStartAction(CharacterObject& owner,
|
||||
return false;
|
||||
}
|
||||
|
||||
const CharacterActionDefinition* action = owner.FindAction(actionId);
|
||||
const CharacterActionDefinition* action = owner.RequireAction(
|
||||
actionId, "CharacterStateMachine::TryStartAction");
|
||||
commandBuffer.Consume(commandType);
|
||||
if (!action) {
|
||||
return false;
|
||||
|
||||
@@ -19,16 +19,12 @@ void CharacterStateBase::ChangeState(CharacterStateMachine& machine,
|
||||
}
|
||||
|
||||
void CharacterStateBase::PlayActionById(CharacterObject& owner,
|
||||
const std::string& actionId,
|
||||
const std::string& fallbackTag) const {
|
||||
if (const auto* action = owner.FindAction(actionId)) {
|
||||
if (!action->animationTag.empty()) {
|
||||
owner.PlayAnimationTag(action->animationTag);
|
||||
return;
|
||||
}
|
||||
const std::string& actionId) const {
|
||||
const auto* action = owner.RequireAction(actionId, "CharacterStateBase::PlayActionById");
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
owner.PlayAnimationTag(owner.ResolveAnimationTag(actionId, fallbackTag));
|
||||
owner.PlayActionDefinition(*action, "CharacterStateBase::PlayActionById");
|
||||
}
|
||||
|
||||
void CharacterStateBase::InvokeHook(CharacterStateMachine& machine,
|
||||
|
||||
@@ -30,13 +30,15 @@ void HurtState::OnEnter(CharacterStateMachine& machine,
|
||||
ActionFrame(machine) = 0;
|
||||
const CharacterActionDefinition* action = GetCurrentAction(machine);
|
||||
if (!action) {
|
||||
action = context.owner.FindAction("hurt_light");
|
||||
SetCurrentAction(machine, action, action ? action->actionId : "hurt_light");
|
||||
context.owner.ReportFatalCharacterError("HurtState::OnEnter",
|
||||
"hurt state entered without current action");
|
||||
return;
|
||||
}
|
||||
if (action) {
|
||||
context.owner.PlayAnimationTag(action->animationTag);
|
||||
InvokeHook(machine, context.owner, action, "on_enter");
|
||||
|
||||
if (!context.owner.PlayActionDefinition(*action, "HurtState::OnEnter")) {
|
||||
return;
|
||||
}
|
||||
InvokeHook(machine, context.owner, action, "on_enter");
|
||||
}
|
||||
|
||||
void HurtState::OnUpdate(CharacterStateMachine& machine,
|
||||
@@ -47,12 +49,8 @@ void HurtState::OnUpdate(CharacterStateMachine& machine,
|
||||
|
||||
const CharacterActionDefinition* action = GetCurrentAction(machine);
|
||||
if (!action) {
|
||||
action = owner.FindAction("hurt_light");
|
||||
SetCurrentAction(machine, action, action ? action->actionId : "hurt_light");
|
||||
}
|
||||
if (!action) {
|
||||
context.owner.SetFacing(motor.facing);
|
||||
ChangeState(machine, context, ResolveInterruptedState(context));
|
||||
owner.ReportFatalCharacterError("HurtState::OnUpdate",
|
||||
"hurt state updated without current action");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,10 +38,16 @@ void SwordmanAttackState::OnEnter(CharacterStateMachine& machine,
|
||||
ActionFrameProgress(machine) = 0.0f;
|
||||
ActionFrame(machine) = 0;
|
||||
const CharacterActionDefinition* action = GetCurrentAction(machine);
|
||||
if (action) {
|
||||
context.owner.PlayAnimationTag(action->animationTag);
|
||||
InvokeHook(machine, context.owner, action, "on_enter");
|
||||
if (!action) {
|
||||
context.owner.ReportFatalCharacterError("SwordmanAttackState::OnEnter",
|
||||
"action state entered without current action");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.owner.PlayActionDefinition(*action, "SwordmanAttackState::OnEnter")) {
|
||||
return;
|
||||
}
|
||||
InvokeHook(machine, context.owner, action, "on_enter");
|
||||
}
|
||||
|
||||
void SwordmanAttackState::OnUpdate(CharacterStateMachine& machine,
|
||||
|
||||
Reference in New Issue
Block a user