refactor(animation): 重构动画方向处理逻辑
- 将翻转逻辑集中到Animation类中处理 - 添加spriteFrameOffsets_存储帧偏移量 - 改进角色动画方向切换时的表现 - 移除CharacterAnimation中的ApplyFlipRecursive方法 - 优化动画帧位置和旋转的计算方式
This commit is contained in:
@@ -40,6 +40,7 @@ public:
|
|||||||
void Reset();
|
void Reset();
|
||||||
animation::AniFrame GetCurrentFrameInfo();
|
animation::AniFrame GetCurrentFrameInfo();
|
||||||
void SetFrameIndex(int index);
|
void SetFrameIndex(int index);
|
||||||
|
void SetDirection(int direction);
|
||||||
void InterpolationLogic();
|
void InterpolationLogic();
|
||||||
|
|
||||||
Vec2 GetMaxSize() const;
|
Vec2 GetMaxSize() const;
|
||||||
@@ -65,6 +66,7 @@ public:
|
|||||||
|
|
||||||
std::vector<animation::AniFrame> frames_;
|
std::vector<animation::AniFrame> frames_;
|
||||||
std::vector<Ptr<Sprite>> spriteFrames_;
|
std::vector<Ptr<Sprite>> spriteFrames_;
|
||||||
|
std::vector<Vec2> spriteFrameOffsets_;
|
||||||
|
|
||||||
std::unordered_map<std::string, animation::AniFlag> animationFlag_;
|
std::unordered_map<std::string, animation::AniFlag> animationFlag_;
|
||||||
|
|
||||||
@@ -75,8 +77,15 @@ public:
|
|||||||
ReplaceData additionalOptionsData_;
|
ReplaceData additionalOptionsData_;
|
||||||
|
|
||||||
Vec2 maxSize_ = Vec2::Zero();
|
Vec2 maxSize_ = Vec2::Zero();
|
||||||
|
int direction_ = 1;
|
||||||
|
|
||||||
std::vector<animation::AniFrame> interpolationData_;
|
std::vector<animation::AniFrame> interpolationData_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ApplyFramePresentation(const Vec2& framePos,
|
||||||
|
const Vec2& imageRate,
|
||||||
|
float rotation,
|
||||||
|
BlendMode blendMode);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace frostbite2D
|
} // namespace frostbite2D
|
||||||
|
|||||||
@@ -5,10 +5,39 @@
|
|||||||
#include <frostbite2D/types/type_math.h>
|
#include <frostbite2D/types/type_math.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
using namespace frostbite2D::animation;
|
using namespace frostbite2D::animation;
|
||||||
|
|
||||||
namespace frostbite2D {
|
namespace frostbite2D {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Vec2 resolveFramePosition(const AniFrame& frameInfo, const Vec2& imageRate) {
|
||||||
|
return Vec2(frameInfo.imgPos.x * imageRate.x, frameInfo.imgPos.y * imageRate.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float resolveFrameRotation(const AniFrame& frameInfo) {
|
||||||
|
if (frameInfo.flag.count("IMAGE_ROTATE")) {
|
||||||
|
return std::get<float>(frameInfo.flag.at("IMAGE_ROTATE"));
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 resolveImageRate(const AniFrame& frameInfo) {
|
||||||
|
if (frameInfo.flag.count("IMAGE_RATE")) {
|
||||||
|
return std::get<Vec2>(frameInfo.flag.at("IMAGE_RATE"));
|
||||||
|
}
|
||||||
|
return Vec2::One();
|
||||||
|
}
|
||||||
|
|
||||||
|
BlendMode resolveBlendMode(const AniFrame& frameInfo) {
|
||||||
|
if (frameInfo.flag.count("GRAPHIC_EFFECT_LINEARDODGE")) {
|
||||||
|
return BlendMode::Additive;
|
||||||
|
}
|
||||||
|
return BlendMode::Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Animation::Animation() {
|
Animation::Animation() {
|
||||||
}
|
}
|
||||||
@@ -39,6 +68,8 @@ void Animation::Init(const std::string& aniPath) {
|
|||||||
aniPath_ = aniPath;
|
aniPath_ = aniPath;
|
||||||
animationFlag_ = info->flag;
|
animationFlag_ = info->flag;
|
||||||
frames_ = std::move(info->frames);
|
frames_ = std::move(info->frames);
|
||||||
|
spriteFrames_.clear();
|
||||||
|
spriteFrameOffsets_.clear();
|
||||||
|
|
||||||
if (animationFlag_.count("LOOP")) {
|
if (animationFlag_.count("LOOP")) {
|
||||||
isLooping_ = true;
|
isLooping_ = true;
|
||||||
@@ -69,6 +100,7 @@ void Animation::Init(const std::string& aniPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
spriteFrames_.push_back(spriteObj);
|
spriteFrames_.push_back(spriteObj);
|
||||||
|
spriteFrameOffsets_.push_back(spriteObj->GetOffset());
|
||||||
|
|
||||||
auto spriteSize = spriteObj->GetSize();
|
auto spriteSize = spriteObj->GetSize();
|
||||||
if (maxSize_.x < spriteSize.x) maxSize_.x = spriteSize.x;
|
if (maxSize_.x < spriteSize.x) maxSize_.x = spriteSize.x;
|
||||||
@@ -102,6 +134,7 @@ void Animation::Init(const std::string& aniPath) {
|
|||||||
if (ani.second.layer.size() >= 2) {
|
if (ani.second.layer.size() >= 2) {
|
||||||
subAni->SetZOrder(ani.second.layer[1]);
|
subAni->SetZOrder(ani.second.layer[1]);
|
||||||
}
|
}
|
||||||
|
subAni->SetDirection(direction_);
|
||||||
AddChild(subAni);
|
AddChild(subAni);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,20 +221,11 @@ void Animation::FlushFrame(int index) {
|
|||||||
if (flagBuf.count("PLAY_SOUND")) {
|
if (flagBuf.count("PLAY_SOUND")) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flagBuf.count("IMAGE_RATE")) {
|
Vec2 imageRate = resolveImageRate(frameInfo);
|
||||||
auto rate = std::get<Vec2>(flagBuf.at("IMAGE_RATE"));
|
float rotation = resolveFrameRotation(frameInfo);
|
||||||
currentFrame_->SetScale(rate);
|
BlendMode blendMode = resolveBlendMode(frameInfo);
|
||||||
currentFrame_->SetPosition(frameInfo.imgPos.x * rate.x, frameInfo.imgPos.y * rate.y);
|
Vec2 framePos = resolveFramePosition(frameInfo, imageRate);
|
||||||
}
|
ApplyFramePresentation(framePos, imageRate, rotation, blendMode);
|
||||||
|
|
||||||
if (flagBuf.count("GRAPHIC_EFFECT_LINEARDODGE")) {
|
|
||||||
currentFrame_->SetBlendMode(BlendMode::Additive);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flagBuf.count("IMAGE_ROTATE")) {
|
|
||||||
auto rotation = std::get<float>(flagBuf.at("IMAGE_ROTATE"));
|
|
||||||
currentFrame_->SetRotation(rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flagBuf.count("INTERPOLATION")) {
|
if (flagBuf.count("INTERPOLATION")) {
|
||||||
if (interpolationData_.empty()) {
|
if (interpolationData_.empty()) {
|
||||||
@@ -234,6 +258,27 @@ void Animation::SetFrameIndex(int index) {
|
|||||||
currentFrameTime_ = 0.0f;
|
currentFrameTime_ = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Animation::SetDirection(int direction) {
|
||||||
|
direction_ = direction >= 0 ? 1 : -1;
|
||||||
|
|
||||||
|
if (currentFrame_ && currentFrameIndex_ >= 0 &&
|
||||||
|
currentFrameIndex_ < static_cast<int>(frames_.size())) {
|
||||||
|
const AniFrame& frameInfo = frames_[currentFrameIndex_];
|
||||||
|
Vec2 imageRate = resolveImageRate(frameInfo);
|
||||||
|
float rotation = resolveFrameRotation(frameInfo);
|
||||||
|
BlendMode blendMode = resolveBlendMode(frameInfo);
|
||||||
|
Vec2 framePos = resolveFramePosition(frameInfo, imageRate);
|
||||||
|
ApplyFramePresentation(framePos, imageRate, rotation, blendMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& child : GetChildren()) {
|
||||||
|
auto* subAnimation = dynamic_cast<Animation*>(child.Get());
|
||||||
|
if (subAnimation) {
|
||||||
|
subAnimation->SetDirection(direction_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Animation::InterpolationLogic() {
|
void Animation::InterpolationLogic() {
|
||||||
if (interpolationData_.empty()) {
|
if (interpolationData_.empty()) {
|
||||||
return;
|
return;
|
||||||
@@ -268,11 +313,6 @@ void Animation::InterpolationLogic() {
|
|||||||
static_cast<float>(rgbaData[3]) / 255.0f);
|
static_cast<float>(rgbaData[3]) / 255.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
Vec2 posData = Vec2::lerp(oldData.imgPos, newData.imgPos, interRate);
|
|
||||||
currentFrame_->SetPosition(posData);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
Vec2 oldRateData = Vec2::One();
|
Vec2 oldRateData = Vec2::One();
|
||||||
Vec2 newRateData = Vec2::One();
|
Vec2 newRateData = Vec2::One();
|
||||||
@@ -285,10 +325,6 @@ void Animation::InterpolationLogic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vec2 rateData = Vec2::lerp(oldRateData, newRateData, interRate);
|
Vec2 rateData = Vec2::lerp(oldRateData, newRateData, interRate);
|
||||||
currentFrame_->SetScale(rateData);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
float oldAngleData = 0.0f;
|
float oldAngleData = 0.0f;
|
||||||
float newAngleData = 0.0f;
|
float newAngleData = 0.0f;
|
||||||
|
|
||||||
@@ -300,7 +336,9 @@ void Animation::InterpolationLogic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float angleData = math::lerp(oldAngleData, newAngleData, interRate);
|
float angleData = math::lerp(oldAngleData, newAngleData, interRate);
|
||||||
currentFrame_->SetRotation(angleData);
|
Vec2 posData = Vec2::lerp(oldData.imgPos, newData.imgPos, interRate);
|
||||||
|
Vec2 framePos(posData.x * rateData.x, posData.y * rateData.y);
|
||||||
|
ApplyFramePresentation(framePos, rateData, angleData, currentFrame_->GetBlendMode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,4 +346,33 @@ Vec2 Animation::GetMaxSize() const {
|
|||||||
return maxSize_;
|
return maxSize_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Animation::ApplyFramePresentation(const Vec2& framePos,
|
||||||
|
const Vec2& imageRate,
|
||||||
|
float rotation,
|
||||||
|
BlendMode blendMode) {
|
||||||
|
if (!currentFrame_ || currentFrameIndex_ < 0 ||
|
||||||
|
currentFrameIndex_ >= static_cast<int>(spriteFrameOffsets_.size())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFrame_->SetScale(imageRate);
|
||||||
|
currentFrame_->SetBlendMode(blendMode);
|
||||||
|
currentFrame_->SetFlippedX(direction_ < 0);
|
||||||
|
|
||||||
|
Vec2 offset = spriteFrameOffsets_[currentFrameIndex_];
|
||||||
|
Vec2 position = framePos;
|
||||||
|
float mirroredRotation = rotation;
|
||||||
|
if (direction_ < 0) {
|
||||||
|
// 朝左时要同时镜像帧位置和素材锚点偏移,避免多层时装各自翻转后错位。
|
||||||
|
float visualWidth = currentFrame_->GetSize().x * std::abs(imageRate.x);
|
||||||
|
position.x = -position.x;
|
||||||
|
offset.x = -offset.x - visualWidth;
|
||||||
|
mirroredRotation = -rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFrame_->SetOffset(offset);
|
||||||
|
currentFrame_->SetPosition(position);
|
||||||
|
currentFrame_->SetRotation(mirroredRotation);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace frostbite2D
|
} // namespace frostbite2D
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ private:
|
|||||||
const std::string& actionPath,
|
const std::string& actionPath,
|
||||||
const character::CharacterConfig& config,
|
const character::CharacterConfig& config,
|
||||||
const CharacterEquipmentManager& equipmentManager);
|
const CharacterEquipmentManager& equipmentManager);
|
||||||
void ApplyFlipRecursive(Actor* actor, bool flipped) const;
|
|
||||||
|
|
||||||
CharacterObject* parent_ = nullptr;
|
CharacterObject* parent_ = nullptr;
|
||||||
ActionAnimationList actionAnimations_;
|
ActionAnimationList actionAnimations_;
|
||||||
|
|||||||
@@ -50,14 +50,6 @@ std::string trim(const std::string& value) {
|
|||||||
return value.substr(begin, end - begin);
|
return value.substr(begin, end - begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string trimBackticks(const std::string& value) {
|
|
||||||
std::string result = trim(value);
|
|
||||||
if (result.size() >= 2 && result.front() == '`' && result.back() == '`') {
|
|
||||||
return result.substr(1, result.size() - 2);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string fileStem(const std::string& path) {
|
std::string fileStem(const std::string& path) {
|
||||||
size_t slashPos = path.find_last_of("/\\");
|
size_t slashPos = path.find_last_of("/\\");
|
||||||
size_t begin = slashPos == std::string::npos ? 0 : slashPos + 1;
|
size_t begin = slashPos == std::string::npos ? 0 : slashPos + 1;
|
||||||
@@ -108,10 +100,18 @@ private:
|
|||||||
// action_list.lst 同时兼容两种格式:
|
// action_list.lst 同时兼容两种格式:
|
||||||
// 1. 标准 PVF #PVF_File + 索引 + `path`
|
// 1. 标准 PVF #PVF_File + 索引 + `path`
|
||||||
// 2. 早期临时版的 actionId + path 成对写法
|
// 2. 早期临时版的 actionId + path 成对写法
|
||||||
|
bool isNewActionListPath(const std::string& path) {
|
||||||
|
return path.find("action_logic") != std::string::npos
|
||||||
|
&& path.size() >= 4
|
||||||
|
&& path.substr(path.size() - 4) == ".act";
|
||||||
|
}
|
||||||
|
|
||||||
|
// action_list.lst only accepts #PVF_File entries like `swordman/action_logic/idle.act`.
|
||||||
std::vector<std::pair<std::string, std::string>> parseActionListEntries(
|
std::vector<std::pair<std::string, std::string>> parseActionListEntries(
|
||||||
ScriptTokenStream& listStream) {
|
ScriptTokenStream& listStream) {
|
||||||
std::vector<std::pair<std::string, std::string>> entries;
|
std::vector<std::pair<std::string, std::string>> entries;
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
|
auto& pvf = PvfArchive::get();
|
||||||
while (!listStream.IsEnd()) {
|
while (!listStream.IsEnd()) {
|
||||||
std::string token = trim(listStream.Next());
|
std::string token = trim(listStream.Next());
|
||||||
if (!token.empty()) {
|
if (!token.empty()) {
|
||||||
@@ -119,23 +119,13 @@ std::vector<std::pair<std::string, std::string>> parseActionListEntries(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens.empty()) {
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokens.front() == "#PVF_File") {
|
|
||||||
for (size_t i = 1; i + 1 < tokens.size(); i += 2) {
|
|
||||||
std::string actionPath = trimBackticks(tokens[i + 1]);
|
|
||||||
if (actionPath.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
entries.push_back({toLower(fileStem(actionPath)), actionPath});
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i + 1 < tokens.size(); i += 2) {
|
for (size_t i = 0; i + 1 < tokens.size(); i += 2) {
|
||||||
entries.push_back({toLower(tokens[i]), trimBackticks(tokens[i + 1])});
|
std::string rawActionPath = trim(tokens[i + 1]);
|
||||||
|
std::string actionPath = pvf.normalizePath(rawActionPath);
|
||||||
|
if (actionPath.empty() || !isNewActionListPath(actionPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entries.push_back({toLower(fileStem(actionPath)), actionPath});
|
||||||
}
|
}
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
@@ -357,13 +347,20 @@ bool CharacterActionLibrary::TryLoadPvfActionScripts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto entries = parseActionListEntries(listStream);
|
auto entries = parseActionListEntries(listStream);
|
||||||
|
if (entries.empty()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterActionLibrary: action_list parsed 0 entries from %s",
|
||||||
|
listPath.c_str());
|
||||||
|
}
|
||||||
for (const auto& [actionId, relativePath] : entries) {
|
for (const auto& [actionId, relativePath] : entries) {
|
||||||
if (actionId.empty() || relativePath.empty()) {
|
if (actionId.empty() || relativePath.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptTokenStream actionStream(
|
std::string fullActPath = relativePath.rfind("character/", 0) == 0
|
||||||
"character/" + config.jobTag + "/action_logic/" + relativePath);
|
? relativePath
|
||||||
|
: "character/" + relativePath;
|
||||||
|
ScriptTokenStream actionStream(fullActPath);
|
||||||
if (!actionStream.IsValid()) {
|
if (!actionStream.IsValid()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -377,9 +374,9 @@ bool CharacterActionLibrary::TryLoadPvfActionScripts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token == "[action id]") {
|
if (token == "[action id]") {
|
||||||
definition.actionId = toLower(actionStream.Next());
|
definition.actionId = toLower(trim(actionStream.Next()));
|
||||||
} else if (token == "[animation tag]") {
|
} else if (token == "[animation tag]") {
|
||||||
definition.animationTag = actionStream.Next();
|
definition.animationTag = trim(actionStream.Next());
|
||||||
} else if (token == "[total frames]") {
|
} else if (token == "[total frames]") {
|
||||||
definition.totalFrames = toInt(actionStream.Next(), definition.totalFrames);
|
definition.totalFrames = toInt(actionStream.Next(), definition.totalFrames);
|
||||||
} else if (token == "[loop]") {
|
} else if (token == "[loop]") {
|
||||||
@@ -394,7 +391,7 @@ bool CharacterActionLibrary::TryLoadPvfActionScripts(
|
|||||||
definition.moveSpeedScale = toFloat(actionStream.Next(), definition.moveSpeedScale);
|
definition.moveSpeedScale = toFloat(actionStream.Next(), definition.moveSpeedScale);
|
||||||
} else if (token == "[cancel rule]") {
|
} else if (token == "[cancel rule]") {
|
||||||
CharacterCancelRuleDefinition rule;
|
CharacterCancelRuleDefinition rule;
|
||||||
rule.targetAction = toLower(actionStream.Next());
|
rule.targetAction = toLower(trim(actionStream.Next()));
|
||||||
rule.beginFrame = toInt(actionStream.Next(), 0);
|
rule.beginFrame = toInt(actionStream.Next(), 0);
|
||||||
rule.endFrame = toInt(actionStream.Next(), rule.beginFrame);
|
rule.endFrame = toInt(actionStream.Next(), rule.beginFrame);
|
||||||
rule.requireGrounded = isTrueToken(actionStream.Next());
|
rule.requireGrounded = isTrueToken(actionStream.Next());
|
||||||
@@ -403,14 +400,14 @@ bool CharacterActionLibrary::TryLoadPvfActionScripts(
|
|||||||
} else if (token == "[frame event]") {
|
} else if (token == "[frame event]") {
|
||||||
CharacterFrameEventDefinition frameEvent;
|
CharacterFrameEventDefinition frameEvent;
|
||||||
frameEvent.frame = toInt(actionStream.Next(), 0);
|
frameEvent.frame = toInt(actionStream.Next(), 0);
|
||||||
frameEvent.type = parseFrameEventType(actionStream.Next());
|
frameEvent.type = parseFrameEventType(trim(actionStream.Next()));
|
||||||
if (frameEvent.type == CharacterFrameEventType::SetVelocityXY) {
|
if (frameEvent.type == CharacterFrameEventType::SetVelocityXY) {
|
||||||
frameEvent.velocityXY.x = toFloat(actionStream.Next(), 0.0f);
|
frameEvent.velocityXY.x = toFloat(actionStream.Next(), 0.0f);
|
||||||
frameEvent.velocityXY.y = toFloat(actionStream.Next(), 0.0f);
|
frameEvent.velocityXY.y = toFloat(actionStream.Next(), 0.0f);
|
||||||
} else if (frameEvent.type == CharacterFrameEventType::SetVelocityZ) {
|
} else if (frameEvent.type == CharacterFrameEventType::SetVelocityZ) {
|
||||||
frameEvent.velocityZ = toFloat(actionStream.Next(), 0.0f);
|
frameEvent.velocityZ = toFloat(actionStream.Next(), 0.0f);
|
||||||
} else {
|
} else {
|
||||||
frameEvent.stringValue = actionStream.Next();
|
frameEvent.stringValue = trim(actionStream.Next());
|
||||||
}
|
}
|
||||||
definition.frameEvents.push_back(frameEvent);
|
definition.frameEvents.push_back(frameEvent);
|
||||||
}
|
}
|
||||||
@@ -425,8 +422,6 @@ bool CharacterActionLibrary::TryLoadPvfActionScripts(
|
|||||||
addOrReplaceAction(actions_, std::move(definition));
|
addOrReplaceAction(actions_, std::move(definition));
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Log("CharacterActionLibrary: loaded %d actions for job %d",
|
|
||||||
static_cast<int>(actions_.size()), config.jobId);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#include "character/CharacterAnimation.h"
|
#include "character/CharacterAnimation.h"
|
||||||
#include "character/CharacterObject.h"
|
#include "character/CharacterObject.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <frostbite2D/2d/sprite.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
@@ -160,20 +158,16 @@ void CharacterAnimation::SetAction(const std::string& actionName) {
|
|||||||
|
|
||||||
void CharacterAnimation::SetDirection(int direction) {
|
void CharacterAnimation::SetDirection(int direction) {
|
||||||
direction_ = direction >= 0 ? 1 : -1;
|
direction_ = direction >= 0 ? 1 : -1;
|
||||||
ApplyFlipRecursive(this, direction_ < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CharacterAnimation::ApplyFlipRecursive(Actor* actor, bool flipped) const {
|
auto currentIt = actionAnimations_.find(currentActionTag_);
|
||||||
if (!actor) {
|
if (currentIt == actionAnimations_.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* sprite = dynamic_cast<Sprite*>(actor)) {
|
for (auto& animation : currentIt->second) {
|
||||||
sprite->SetFlippedX(flipped);
|
if (animation) {
|
||||||
}
|
animation->SetDirection(direction_);
|
||||||
|
}
|
||||||
for (const auto& child : actor->GetChildren()) {
|
|
||||||
ApplyFlipRecursive(child.Get(), flipped);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ std::optional<CharacterConfig> loadCharacterConfig(int jobId) {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
characterConfigCache()[jobId] = config;
|
characterConfigCache()[jobId] = config;
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@@ -388,7 +389,6 @@ std::optional<EquipmentConfig> loadEquipmentConfig(int equipmentId) {
|
|||||||
EquipmentConfig config;
|
EquipmentConfig config;
|
||||||
config.id = equipmentId;
|
config.id = equipmentId;
|
||||||
config.path = indexIt->second;
|
config.path = indexIt->second;
|
||||||
SDL_Log("CharacterDataLoader: equipment %d -> %s", equipmentId, config.path.c_str());
|
|
||||||
|
|
||||||
while (!stream.isEnd()) {
|
while (!stream.isEnd()) {
|
||||||
std::string segment = stream.get();
|
std::string segment = stream.get();
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ void CharacterObject::ApplyHit(const HitContext& hit) {
|
|||||||
}
|
}
|
||||||
stateMachine_.ForceHurt(hurtAction);
|
stateMachine_.ForceHurt(hurtAction);
|
||||||
PlayAnimationTag(hurtAction ? hurtAction->animationTag
|
PlayAnimationTag(hurtAction ? hurtAction->animationTag
|
||||||
: ResolveAnimationTag("damage", "rest"));
|
: ResolveAnimationTag("hurt_light", "rest"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterObject::OnUpdate(float deltaTime) {
|
void CharacterObject::OnUpdate(float deltaTime) {
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ bool hasMoveIntent(const CharacterIntent& intent) {
|
|||||||
return std::abs(intent.moveX) > 0.01f || std::abs(intent.moveY) > 0.01f;
|
return std::abs(intent.moveX) > 0.01f || std::abs(intent.moveY) > 0.01f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void playActionById(CharacterObject& owner,
|
||||||
|
const std::string& actionId,
|
||||||
|
const std::string& fallbackTag = "rest") {
|
||||||
|
if (const auto* action = owner.FindAction(actionId)) {
|
||||||
|
if (!action->animationTag.empty()) {
|
||||||
|
owner.PlayAnimationTag(action->animationTag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
owner.PlayAnimationTag(owner.ResolveAnimationTag(actionId, fallbackTag));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void CharacterStateMachine::Reset() {
|
void CharacterStateMachine::Reset() {
|
||||||
@@ -100,18 +113,20 @@ void CharacterStateMachine::ChangeState(CharacterObject& owner,
|
|||||||
|
|
||||||
switch (nextState) {
|
switch (nextState) {
|
||||||
case CharacterStateId::Idle:
|
case CharacterStateId::Idle:
|
||||||
owner.PlayAnimationTag(owner.ResolveAnimationTag("rest", "rest"));
|
playActionById(owner, "idle");
|
||||||
break;
|
break;
|
||||||
case CharacterStateId::Move:
|
case CharacterStateId::Move:
|
||||||
owner.PlayAnimationTag(owner.ResolveAnimationTag("run", "walk"));
|
playActionById(owner, "move");
|
||||||
break;
|
break;
|
||||||
case CharacterStateId::Jump:
|
case CharacterStateId::Jump:
|
||||||
|
playActionById(owner, "jump");
|
||||||
|
break;
|
||||||
case CharacterStateId::Fall:
|
case CharacterStateId::Fall:
|
||||||
owner.PlayAnimationTag(owner.ResolveAnimationTag("jump", "rest"));
|
playActionById(owner, "fall");
|
||||||
break;
|
break;
|
||||||
case CharacterStateId::Landing:
|
case CharacterStateId::Landing:
|
||||||
landingTimer_ = 0.0f;
|
landingTimer_ = 0.0f;
|
||||||
owner.PlayAnimationTag(owner.ResolveAnimationTag("rest", "rest"));
|
playActionById(owner, "landing");
|
||||||
break;
|
break;
|
||||||
case CharacterStateId::Action:
|
case CharacterStateId::Action:
|
||||||
case CharacterStateId::Hurt:
|
case CharacterStateId::Hurt:
|
||||||
|
|||||||
Reference in New Issue
Block a user