- 将翻转逻辑集中到Animation类中处理 - 添加spriteFrameOffsets_存储帧偏移量 - 改进角色动画方向切换时的表现 - 移除CharacterAnimation中的ApplyFlipRecursive方法 - 优化动画帧位置和旋转的计算方式
181 lines
5.3 KiB
C++
181 lines
5.3 KiB
C++
#include "character/CharacterObject.h"
|
||
#include <SDL2/SDL.h>
|
||
|
||
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) {
|
||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||
"CharacterObject: failed to load job %d config", jobId);
|
||
return false;
|
||
}
|
||
|
||
jobId_ = jobId;
|
||
growType_ = -1;
|
||
direction_ = 1;
|
||
config_ = *config;
|
||
equipmentManager_.Init(config_->baseJobConfig);
|
||
if (auto actionLibrary = loadCharacterActionLibrary(*config_)) {
|
||
actionLibrary_ = *actionLibrary;
|
||
}
|
||
|
||
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");
|
||
}
|
||
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);
|
||
}
|
||
|
||
SetWorldPosition({});
|
||
SetFacing(1);
|
||
return true;
|
||
}
|
||
|
||
void CharacterObject::SetAction(const std::string& actionName) {
|
||
currentAction_ = actionName;
|
||
if (animationManager_) {
|
||
animationManager_->SetAction(actionName);
|
||
}
|
||
}
|
||
|
||
void CharacterObject::SetDirection(int direction) {
|
||
direction_ = direction >= 0 ? 1 : -1;
|
||
if (animationManager_) {
|
||
animationManager_->SetDirection(direction_);
|
||
}
|
||
}
|
||
|
||
void CharacterObject::SetCharacterPosition(const Vec2& 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("hurt_light", "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
|