Extra2D/include/animation/animation_clip.h

185 lines
6.0 KiB
C
Raw Normal View History

2026-02-11 19:40:26 +08:00
#pragma once
#include <cassert>
#include <animation/animation_frame.h>
#include <animation/sprite_frame_cache.h>
2026-02-11 19:40:26 +08:00
#include <string>
#include <vector>
namespace extra2d {
// ============================================================================
// AnimationClip - 动画片段(纯数据,可复用)
// 借鉴 Cocos一份 AnimationClip 可被多个 AnimationNode 同时使用
// ============================================================================
class AnimationClip {
public:
AnimationClip() = default;
explicit AnimationClip(const std::string &name) : name_(name) {}
// ------ 帧管理 ------
void addFrame(const AnimationFrame &frame) { frames_.push_back(frame); }
void addFrame(AnimationFrame &&frame) { frames_.push_back(std::move(frame)); }
void insertFrame(size_t index, const AnimationFrame &frame) {
assert(index <= frames_.size());
frames_.insert(frames_.begin() + static_cast<ptrdiff_t>(index), frame);
}
void removeFrame(size_t index) {
assert(index < frames_.size());
frames_.erase(frames_.begin() + static_cast<ptrdiff_t>(index));
}
void clearFrames() { frames_.clear(); }
const AnimationFrame &getFrame(size_t index) const {
assert(index < frames_.size());
return frames_[index];
}
AnimationFrame &getFrame(size_t index) {
assert(index < frames_.size());
return frames_[index];
}
size_t getFrameCount() const { return frames_.size(); }
bool empty() const { return frames_.empty(); }
// ------ 全局属性(对应原始 AnimationFlag------
FramePropertySet &globalProperties() { return globalProperties_; }
const FramePropertySet &globalProperties() const { return globalProperties_; }
bool isLooping() const {
return globalProperties_.getOr<bool>(FramePropertyKey::Loop, false);
}
void setLooping(bool loop) { globalProperties_.withLoop(loop); }
// ------ 时间信息 ------
float getTotalDuration() const {
float total = 0.0f;
for (const auto &frame : frames_) {
total += frame.delay;
}
return total;
}
// ------ 预计算最大帧尺寸 ------
Size getMaxFrameSize() const {
Size maxSize;
for (const auto &frame : frames_) {
if (frame.spriteFrame && frame.spriteFrame->isValid()) {
const auto &rect = frame.spriteFrame->getRect();
if (rect.size.width > maxSize.width)
maxSize.width = rect.size.width;
if (rect.size.height > maxSize.height)
maxSize.height = rect.size.height;
}
}
return maxSize;
}
// ------ 元数据 ------
void setName(const std::string &name) { name_ = name; }
const std::string &getName() const { return name_; }
void setSourcePath(const std::string &path) { sourcePath_ = path; }
const std::string &getSourcePath() const { return sourcePath_; }
// ------ 静态工厂 ------
static Ptr<AnimationClip> create(const std::string &name = "") {
return makePtr<AnimationClip>(name);
}
/// 从精灵图网格创建(所有帧按顺序)
static Ptr<AnimationClip> createFromGrid(Ptr<Texture> texture, int frameWidth,
int frameHeight,
float frameDurationMs = 100.0f,
int frameCount = -1, int spacing = 0,
int margin = 0) {
if (!texture)
return nullptr;
int texW = texture->getWidth();
int texH = texture->getHeight();
int usableW = texW - 2 * margin;
int usableH = texH - 2 * margin;
int cols = (usableW + spacing) / (frameWidth + spacing);
int rows = (usableH + spacing) / (frameHeight + spacing);
int total = (frameCount > 0) ? frameCount : cols * rows;
auto clip = makePtr<AnimationClip>();
for (int i = 0; i < total; ++i) {
int col = i % cols;
int row = i / cols;
if (row >= rows)
break;
// 翻转行顺序精灵图第0行在顶部但OpenGL纹理V坐标从底部开始
// 所以将行索引翻转使第0行对应纹理底部V=1.0第3行对应纹理顶部V=0.0
int flippedRow = (rows - 1) - row;
Rect rect(
static_cast<float>(margin + col * (frameWidth + spacing)),
static_cast<float>(margin + flippedRow * (frameHeight + spacing)),
static_cast<float>(frameWidth), static_cast<float>(frameHeight));
auto sf = SpriteFrame::create(texture, rect);
AnimationFrame frame;
frame.spriteFrame = std::move(sf);
frame.delay = frameDurationMs;
clip->addFrame(std::move(frame));
}
return clip;
}
/// 从精灵图网格创建(指定帧索引列表)
static Ptr<AnimationClip>
createFromGridIndices(Ptr<Texture> texture, int frameWidth, int frameHeight,
const std::vector<int> &frameIndices,
float frameDurationMs = 100.0f, int spacing = 0,
int margin = 0) {
if (!texture)
return nullptr;
int texW = texture->getWidth();
int texH = texture->getHeight();
int usableW = texW - 2 * margin;
int usableH = texH - 2 * margin;
int cols = (usableW + spacing) / (frameWidth + spacing);
int rows = (usableH + spacing) / (frameHeight + spacing);
auto clip = makePtr<AnimationClip>();
for (int idx : frameIndices) {
int col = idx % cols;
int row = idx / cols;
// 翻转行顺序精灵图第0行在顶部但OpenGL纹理V坐标从底部开始
int flippedRow = (rows - 1) - row;
Rect rect(
static_cast<float>(margin + col * (frameWidth + spacing)),
static_cast<float>(margin + flippedRow * (frameHeight + spacing)),
static_cast<float>(frameWidth), static_cast<float>(frameHeight));
auto sf = SpriteFrame::create(texture, rect);
AnimationFrame frame;
frame.spriteFrame = std::move(sf);
frame.delay = frameDurationMs;
clip->addFrame(std::move(frame));
}
return clip;
}
private:
std::string name_;
std::string sourcePath_;
std::vector<AnimationFrame> frames_;
FramePropertySet globalProperties_;
};
} // namespace extra2d