Files
Frostbite2D/Game/src/character/CharacterAnimation.cpp
Lenheart de522a1e64 refactor(animation): 重构动画方向处理逻辑
- 将翻转逻辑集中到Animation类中处理
- 添加spriteFrameOffsets_存储帧偏移量
- 改进角色动画方向切换时的表现
- 移除CharacterAnimation中的ApplyFlipRecursive方法
- 优化动画帧位置和旋转的计算方式
2026-04-03 09:13:50 +08:00

175 lines
5.0 KiB
C++

#include "character/CharacterAnimation.h"
#include "character/CharacterObject.h"
#include <SDL2/SDL.h>
#include <array>
#include <cstdio>
namespace frostbite2D {
namespace {
const std::array<const char*, 12> kAvatarParts = {
"weapon_avatar", "aurora_avatar", "hair_avatar", "hat_avatar",
"face_avatar", "breast_avatar", "coat_avatar", "skin_avatar",
"waist_avatar", "pants_avatar", "shoes_avatar", "weapon"};
std::string truncatePath(const std::string& path) {
size_t slashPos = path.find_last_of('/');
if (slashPos == std::string::npos) {
return {};
}
return path.substr(0, slashPos);
}
std::string actionTail(const std::string& path) {
size_t slashPos = path.find_last_of('/');
if (slashPos == std::string::npos) {
return path;
}
return path.substr(slashPos);
}
} // namespace
bool CharacterAnimation::Init(CharacterObject* parent,
const character::CharacterConfig& config,
const CharacterEquipmentManager& equipmentManager) {
parent_ = parent;
actionAnimations_.clear();
currentActionTag_.clear();
direction_ = 1;
for (const auto& [actionName, actionPath] : config.animationPath) {
for (const char* slotName : kAvatarParts) {
CreateAnimationBySlot(actionName, slotName, actionPath, config, equipmentManager);
}
}
if (actionAnimations_.empty()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"CharacterAnimation: no usable action animations for job %d",
config.jobId);
return false;
}
if (actionAnimations_.count("rest") > 0) {
SetAction("rest");
} else {
SetAction(actionAnimations_.begin()->first);
}
return true;
}
std::string CharacterAnimation::FormatImgPath(std::string path, Animation::ReplaceData data) {
size_t pos = path.find("%04d");
if (pos != std::string::npos) {
path.replace(pos, 4, "%02d%02d");
}
char buffer[512] = {};
std::snprintf(buffer, sizeof(buffer), path.c_str(), data.param1, data.param2);
return std::string(buffer);
}
void CharacterAnimation::CreateAnimationBySlot(
const std::string& actionName,
const std::string& slotName,
const std::string& actionPath,
const character::CharacterConfig& config,
const CharacterEquipmentManager& equipmentManager) {
if (slotName == std::string("skin_avatar")) {
Animation::ReplaceData replaceData(0, 0);
if (const auto* equip = equipmentManager.GetEquip(slotName)) {
auto it = equip->jobAnimations.find(config.jobId);
if (it != equip->jobAnimations.end() && !it->second.empty()) {
replaceData.param1 = it->second.front().imgFormat[0];
replaceData.param2 = it->second.front().imgFormat[1];
}
}
auto animation = MakePtr<Animation>(actionPath, FormatImgPath, replaceData);
if (!animation || !animation->IsUsable()) {
return;
}
animation->SetVisible(false);
AddChild(animation);
actionAnimations_[actionName].push_back(animation);
return;
}
const auto* equip = equipmentManager.GetEquip(slotName);
if (!equip) {
return;
}
auto jobAniIt = equip->jobAnimations.find(config.jobId);
if (jobAniIt == equip->jobAnimations.end()) {
return;
}
std::string equipDir = truncatePath(equip->path);
std::string actionPathTail = actionTail(actionPath);
for (const auto& variation : jobAniIt->second) {
if (variation.animationGroup.empty()) {
continue;
}
std::string aniPath = equipDir + "/" + variation.animationGroup + actionPathTail;
auto animation = MakePtr<Animation>(
aniPath, FormatImgPath,
Animation::ReplaceData(variation.imgFormat[0], variation.imgFormat[1]));
if (!animation || !animation->IsUsable()) {
continue;
}
animation->SetVisible(false);
animation->SetZOrder(variation.layer);
AddChild(animation);
actionAnimations_[actionName].push_back(animation);
}
}
void 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 (!currentActionTag_.empty()) {
auto currentIt = actionAnimations_.find(currentActionTag_);
if (currentIt != actionAnimations_.end()) {
for (auto& animation : currentIt->second) {
animation->Reset();
animation->SetVisible(false);
}
}
}
for (auto& animation : nextIt->second) {
animation->Reset();
animation->SetVisible(true);
}
currentActionTag_ = actionName;
SetDirection(direction_);
}
void CharacterAnimation::SetDirection(int direction) {
direction_ = direction >= 0 ? 1 : -1;
auto currentIt = actionAnimations_.find(currentActionTag_);
if (currentIt == actionAnimations_.end()) {
return;
}
for (auto& animation : currentIt->second) {
if (animation) {
animation->SetDirection(direction_);
}
}
}
} // namespace frostbite2D